Skip to content

安全配置指南

安全是 Web 服务的重中之重。Caddy 提供了丰富的安全特性,本章将详细介绍如何配置各种安全措施。

🔒 HTTPS 安全

强制 HTTPS

caddyfile
example.com {
    # Caddy 默认强制 HTTPS,无需额外配置
    respond "Secure connection established"
}

# 如果需要禁用自动重定向
http://insecure.example.com {
    respond "HTTP only site"
}

HSTS 配置

caddyfile
example.com {
    header {
        # HTTP 严格传输安全
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    }
    
    root * /var/www/html
    file_server
}

TLS 配置强化

caddyfile
example.com {
    tls {
        # 仅允许 TLS 1.2 和 1.3
        protocols tls1.2 tls1.3
        
        # 强密码套件
        ciphers TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
        
        # 椭圆曲线
        curves x25519 secp384r1 secp256r1
        
        # ALPN 协议
        alpn h2 http/1.1
    }
    
    respond "Secure TLS configuration"
}

🛡️ 安全头配置

基础安全头

caddyfile
example.com {
    header {
        # 防止点击劫持
        X-Frame-Options DENY
        
        # 防止 MIME 类型嗅探
        X-Content-Type-Options nosniff
        
        # XSS 保护
        X-XSS-Protection "1; mode=block"
        
        # 引用策略
        Referrer-Policy strict-origin-when-cross-origin
        
        # 隐藏服务器信息
        -Server
        -X-Powered-By
    }
    
    root * /var/www/html
    file_server
}

内容安全策略 (CSP)

caddyfile
example.com {
    header {
        # 基础 CSP
        Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https:; connect-src 'self'; frame-ancestors 'none'"
        
        # 严格的 CSP
        # Content-Security-Policy "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self'; base-uri 'self'; form-action 'self'"
        
        # CSP 报告
        # Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-report"
    }
    
    root * /var/www/html
    file_server
}

权限策略 (Permissions Policy)

caddyfile
example.com {
    header {
        # 权限策略(原 Feature Policy)
        Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()"
        
        # 或者允许特定功能
        # Permissions-Policy "camera=(self), microphone=(self), geolocation=(self 'https://maps.example.com')"
    }
    
    root * /var/www/html
    file_server
}

🔐 认证和授权

基本认证

caddyfile
admin.example.com {
    basicauth {
        # 使用 bcrypt 哈希密码
        admin $2a$14$hgl486...
        user  $2a$14$xyz123...
        
        # 生成哈希密码的命令:
        # caddy hash-password --plaintext "your-password"
    }
    
    root * /var/www/admin
    file_server
}

条件认证

caddyfile
example.com {
    # 内网用户免认证
    @internal remote_ip 192.168.0.0/16 10.0.0.0/8
    
    # 管理路径需要认证
    @admin path /admin/*
    basicauth @admin {
        skip @internal
        admin $2a$14$hgl486...
    }
    
    root * /var/www/html
    file_server
}

JWT 认证(需要插件)

caddyfile
api.example.com {
    jwt {
        primary yes
        path /api
        redirect /login
        allow sub admin
        allow aud api
        secret {$JWT_SECRET}
    }
    
    reverse_proxy localhost:3000
}

🚫 访问控制

IP 白名单

caddyfile
admin.example.com {
    # 只允许特定 IP 访问
    @allowed remote_ip 192.168.1.100 10.0.0.0/8 203.0.113.0/24
    respond @allowed {
        root * /var/www/admin
        file_server
    }
    
    respond "Access denied" 403
}

IP 黑名单

caddyfile
example.com {
    # 阻止特定 IP
    @blocked remote_ip 192.168.1.100 203.0.113.50
    respond @blocked "Access denied" 403
    
    root * /var/www/html
    file_server
}

地理位置限制

caddyfile
example.com {
    # 基于国家代码限制(需要 GeoIP 数据)
    @blocked_countries header CF-IPCountry CN RU KP
    respond @blocked_countries "Service not available in your region" 403
    
    # 只允许特定国家
    @allowed_countries header CF-IPCountry US CA GB DE FR
    respond @allowed_countries {
        root * /var/www/html
        file_server
    }
    
    respond "Service not available" 403
}

🚦 限流和防护

基础限流

caddyfile
api.example.com {
    rate_limit {
        zone api {
            key {remote_host}
            events 100
            window 1m
        }
    }
    
    reverse_proxy localhost:3000
}

分层限流

caddyfile
api.example.com {
    # 全局限流
    rate_limit {
        zone global {
            key {remote_host}
            events 1000
            window 1h
        }
    }
    
    # API 限流
    @api path /api/*
    rate_limit @api {
        zone api {
            key {header.x-api-key}
            events 100
            window 1m
        }
    }
    
    # 上传限流
    @upload path /upload/*
    rate_limit @upload {
        zone upload {
            key {remote_host}
            events 10
            window 1m
        }
    }
    
    reverse_proxy localhost:3000
}

DDoS 防护

caddyfile
example.com {
    # 连接限制
    rate_limit {
        zone ddos {
            key {remote_host}
            events 50
            window 10s
        }
    }
    
    # 请求大小限制
    request_body {
        max_size 1MB
    }
    
    # 超时设置
    timeouts {
        read_timeout 10s
        read_header_timeout 5s
        write_timeout 10s
        idle_timeout 120s
    }
    
    root * /var/www/html
    file_server
}

🔍 输入验证和过滤

路径验证

caddyfile
example.com {
    # 阻止路径遍历攻击
    @path_traversal path *..* */.* */.*/*
    respond @path_traversal "Invalid path" 400
    
    # 阻止敏感文件访问
    @sensitive path *.env *.config *.log *.bak *~
    respond @sensitive "File not found" 404
    
    # 阻止脚本文件直接访问
    @scripts path *.php *.asp *.jsp *.cgi
    respond @scripts "Access denied" 403
    
    root * /var/www/html
    file_server
}

请求头验证

caddyfile
api.example.com {
    # 验证 Content-Type
    @invalid_content_type {
        method POST PUT PATCH
        not header Content-Type application/json*
    }
    respond @invalid_content_type "Invalid Content-Type" 400
    
    # 验证 User-Agent
    @no_user_agent not header User-Agent *
    respond @no_user_agent "User-Agent required" 400
    
    # 阻止恶意 User-Agent
    @malicious_ua header User-Agent *sqlmap* *nikto* *nmap* *masscan*
    respond @malicious_ua "Access denied" 403
    
    reverse_proxy localhost:3000
}

SQL 注入防护

caddyfile
api.example.com {
    # 检测 SQL 注入模式
    @sql_injection {
        query *union* *select* *insert* *update* *delete* *drop* *exec* *script*
        query *'* *"* *;* *--* */**/
    }
    respond @sql_injection "Invalid request" 400
    
    reverse_proxy localhost:3000
}

🔒 客户端证书认证

基础客户端证书

caddyfile
secure.example.com {
    tls {
        client_auth {
            mode require_and_verify
            trusted_ca_cert_file /etc/ssl/client-ca.pem
        }
    }
    
    # 传递客户端证书信息
    header {
        X-Client-Cert {tls_client_certificate}
        X-Client-Subject {tls_client_subject}
        X-Client-Issuer {tls_client_issuer}
    }
    
    reverse_proxy localhost:3000
}

可选客户端证书

caddyfile
api.example.com {
    tls {
        client_auth {
            mode request
            trusted_ca_cert_file /etc/ssl/client-ca.pem
        }
    }
    
    # 根据证书状态路由
    @authenticated header X-Client-Cert *
    handle @authenticated {
        header X-Auth-Method "certificate"
        reverse_proxy localhost:3001  # 高权限 API
    }
    
    handle {
        header X-Auth-Method "none"
        reverse_proxy localhost:3000  # 公开 API
    }
}

🛡️ 安全监控

安全日志

caddyfile
example.com {
    # 安全日志配置
    log {
        output file /var/log/caddy/security.log {
            roll_size 50mb
            roll_keep 10
        }
        format json
    }

    # 记录可疑活动
    @suspicious path *..* *.php *.asp
    handle @suspicious {
        respond "Not found" 404
    }

    file_server
}

入侵检测

caddyfile
example.com {
    # 检测常见攻击模式
    @attack_patterns {
        query *<script* *javascript:* *vbscript:*
        query *union+select* *1=1* *'or'1'='1*
        path */etc/passwd* */proc/self/environ*
        header User-Agent *sqlmap* *nikto* *w3af*
    }
    
    handle @attack_patterns {
        # 记录攻击尝试
        header X-Attack-Detected "true"
        header X-Attack-Type "pattern-match"
        header X-Client-IP {remote_host}
        
        # 返回假的 404 页面
        respond "Page not found" 404
    }
    
    root * /var/www/html
    file_server
}

🔧 安全配置模板

高安全性网站

caddyfile
secure.example.com {
    # TLS 配置
    tls {
        protocols tls1.3
        ciphers TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256
        curves x25519
    }
    
    # 安全头
    header {
        Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
        Content-Security-Policy "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self'; base-uri 'self'; form-action 'self'"
        X-Frame-Options DENY
        X-Content-Type-Options nosniff
        X-XSS-Protection "1; mode=block"
        Referrer-Policy no-referrer
        Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()"
        -Server
        -X-Powered-By
    }
    
    # 访问控制
    @admin path /admin/*
    basicauth @admin {
        admin $2a$14$hgl486...
    }
    
    # 限流
    rate_limit {
        zone secure {
            key {remote_host}
            events 50
            window 1m
        }
    }
    
    # 输入验证
    @invalid_paths path *..* *.php *.asp *.jsp
    respond @invalid_paths "Not found" 404
    
    root * /var/www/secure
    file_server {
        hide .* *.bak *.config
    }
}

API 安全配置

caddyfile
api.example.com {
    # CORS 配置
    @cors_preflight method OPTIONS
    handle @cors_preflight {
        header {
            Access-Control-Allow-Origin "https://app.example.com"
            Access-Control-Allow-Methods "GET, POST, PUT, DELETE"
            Access-Control-Allow-Headers "Content-Type, Authorization"
            Access-Control-Max-Age "86400"
        }
        respond "" 204
    }
    
    # API 安全头
    header {
        Access-Control-Allow-Origin "https://app.example.com"
        X-Content-Type-Options nosniff
        X-Frame-Options DENY
        Cache-Control "no-cache, no-store, must-revalidate"
        -Server
    }
    
    # API 密钥验证
    @no_api_key not header X-API-Key *
    respond @no_api_key "API key required" 401
    
    # 限流
    rate_limit {
        zone api {
            key {header.x-api-key}
            events 1000
            window 1h
        }
    }
    
    # 请求验证
    @invalid_json {
        method POST PUT PATCH
        not header Content-Type application/json*
    }
    respond @invalid_json "Invalid Content-Type" 400
    
    # 请求大小限制
    request_body {
        max_size 1MB
    }
    
    reverse_proxy localhost:3000 {
        header_up X-Forwarded-For {remote_host}
        header_up X-Real-IP {remote_host}
    }
}

通过这些安全配置,您可以构建一个安全可靠的 Web 服务。安全是一个持续的过程,需要定期更新和监控。 🛡️