土法炼钢兴趣小组的算法知识备份

【网络工程】CDN 与 HTTPS:边缘 TLS、证书管理与安全

文章导航

分类入口
network
标签入口
#cdn#https#tls#certificate#edge-security

目录

HTTPS 已经是 Web 的默认传输方式。当流量经过 CDN 时,TLS 加密不再是”客户端到源站”的点对点连接,而是分成了两段——客户端到边缘、边缘到源站。这两段的 TLS 配置、证书管理和安全策略各有不同的工程挑战。

一、CDN HTTPS 架构模式

CDN 的 HTTPS 部署有三种基本模式,选择取决于安全需求和运维复杂度:

1.1 边缘终止(Edge Termination)

最常见的模式:TLS 在边缘 PoP 终止,边缘到源站可以使用 HTTP 或独立的 TLS 连接:

模式 A:边缘 HTTPS + 回源 HTTP(最简单,安全性最低)

客户端 ──[HTTPS]──→ CDN 边缘 ──[HTTP]──→ 源站
         加密             明文

模式 B:边缘 HTTPS + 回源 HTTPS(推荐)

客户端 ──[HTTPS]──→ CDN 边缘 ──[HTTPS]──→ 源站
         TLS 1            TLS 2
         CDN 证书          源站证书

模式 C:Full Strict(最严格)

客户端 ──[HTTPS]──→ CDN 边缘 ──[HTTPS + 证书验证]──→ 源站
         TLS 1            TLS 2 + 验证源站证书有效性

Cloudflare 的 SSL 模式对比:

模式 客户端→CDN CDN→源站 源站证书要求 安全级别
Off HTTP HTTP 无加密
Flexible HTTPS HTTP 半加密
Full HTTPS HTTPS 自签名即可 加密但不验证
Full (Strict) HTTPS HTTPS 有效 CA 签发 完全加密

工程建议:生产环境至少使用 Full 模式,推荐 Full (Strict)。Flexible 模式存在中间人攻击风险——CDN 到源站的明文传输可以被截获。

1.2 TLS 透传(TLS Passthrough)

CDN 不终止 TLS,将加密流量直接转发到源站:

TLS 透传模式:

客户端 ──[HTTPS]────────────────────→ 源站
            └── 经过 CDN 但不解密 ──┘

CDN 行为:
- 通过 SNI(Server Name Indication)确定目标域名
- 基于 SNI 路由到正确的源站
- 不解密流量 → 不能缓存 → 不能修改内容
- 不能注入安全头 → 不能做 WAF

透传模式的 Nginx 配置:

stream {
    # 基于 SNI 路由的 TLS 透传
    map $ssl_preread_server_name $backend {
        api.example.com     api_backend;
        www.example.com     web_backend;
        default             default_backend;
    }
    
    upstream api_backend {
        server origin-api.example.com:443;
    }
    
    upstream web_backend {
        server origin-web.example.com:443;
    }
    
    upstream default_backend {
        server origin-default.example.com:443;
    }
    
    server {
        listen 443;
        
        # 预读 SNI 但不终止 TLS
        ssl_preread on;
        
        proxy_pass $backend;
        proxy_connect_timeout 5s;
        
        # 透传模式下保留客户端 IP
        # 需要 PROXY Protocol 支持
        proxy_protocol on;
    }
}

1.3 双证书架构

CDN 使用自己的证书面向客户端,用另一套证书连接源站:

双证书架构:

客户端 ──[TLS: CDN 证书]──→ CDN 边缘 ──[TLS: 源站证书]──→ 源站

CDN 证书(前端):
- 由 CDN 托管或用户上传
- 域名: www.example.com
- 签发 CA: Let's Encrypt / DigiCert

源站证书(后端):
- 由源站部署
- 可以使用 CDN 签发的 Origin CA 证书
- 也可以使用公共 CA 证书

优势:
- 证书轮换互不影响
- CDN 可以优化前端 TLS 配置(新协议/密码套件)
- 源站证书可以使用 CDN 的 Origin CA(免费,15 年有效期)

Cloudflare Origin CA 证书的使用:

# 在源站部署 Cloudflare Origin CA 证书

# 1. 通过 Cloudflare API 生成 Origin CA 证书
curl -X POST "https://api.cloudflare.com/client/v4/certificates" \
  -H "X-Auth-Key: $CF_API_KEY" \
  -H "X-Auth-Email: admin@example.com" \
  -H "Content-Type: application/json" \
  -d '{
    "hostnames": ["example.com", "*.example.com"],
    "requested_validity": 5475,
    "request_type": "origin-rsa",
    "csr": "'"$(cat server.csr)"'"
  }'

# 2. 保存证书
# 将返回的 certificate 保存为 origin-cert.pem

# 3. 源站 Nginx 配置
cat > /etc/nginx/conf.d/origin-tls.conf << 'CONF'
server {
    listen 443 ssl;
    server_name example.com;
    
    # Cloudflare Origin CA 证书
    ssl_certificate     /etc/nginx/ssl/origin-cert.pem;
    ssl_certificate_key /etc/nginx/ssl/origin-key.pem;
    
    # 只允许 Cloudflare IP 访问(安全加固)
    # https://www.cloudflare.com/ips/
    allow 173.245.48.0/20;
    allow 103.21.244.0/22;
    allow 103.22.200.0/22;
    allow 103.31.4.0/22;
    allow 141.101.64.0/18;
    allow 108.162.192.0/18;
    allow 190.93.240.0/20;
    allow 188.114.96.0/20;
    allow 197.234.240.0/22;
    allow 198.41.128.0/17;
    deny all;
    
    location / {
        proxy_pass http://localhost:8080;
    }
}
CONF

二、证书管理工程

CDN 环境下的证书管理比单服务器复杂得多——需要在全球数百个边缘节点上同步部署证书。

2.1 证书类型选择

CDN 证书类型对比:

┌──────────────┬───────────────┬──────────────┬─────────────┐
│              │ 共享证书       │ 专用证书      │ 自定义证书   │
├──────────────┼───────────────┼──────────────┼─────────────┤
│ 域名         │ CDN 子域名     │ 自有域名      │ 自有域名     │
│ 示例         │ xxx.cdn.com   │ www.example.com│ www.example.com│
│ 谁签发       │ CDN 提供商     │ CDN 代签      │ 用户自行签发 │
│ 费用         │ 免费          │ 免费/付费     │ 用户自费     │
│ SAN 共享     │ 和其他客户共享 │ 独占          │ 独占         │
│ 管理复杂度   │ 无            │ 低            │ 高           │
│ 私钥控制     │ CDN 持有      │ CDN 持有      │ 用户上传     │
│ 适用场景     │ 测试/原型     │ 大多数生产    │ 合规要求高   │
└──────────────┴───────────────┴──────────────┴─────────────┘

2.2 自动化证书管理

使用 ACME 协议实现 CDN 证书的自动签发和续期:

# cdn_cert_manager.py — CDN 证书自动化管理
import json
import time
import subprocess
from datetime import datetime, timedelta

class CDNCertManager:
    """管理 CDN 边缘节点的证书生命周期"""
    
    def __init__(self, cdn_api_url, api_token):
        self.cdn_api_url = cdn_api_url
        self.api_token = api_token
        self.cert_store = {}  # domain -> cert_info
    
    def check_expiry(self):
        """检查所有证书的到期时间"""
        expiring_soon = []
        
        for domain, info in self.cert_store.items():
            days_left = (info['not_after'] - datetime.utcnow()).days
            
            if days_left <= 30:
                expiring_soon.append({
                    'domain': domain,
                    'days_left': days_left,
                    'serial': info['serial'],
                    'issuer': info['issuer'],
                })
                
                if days_left <= 0:
                    print(f"[CRITICAL] {domain} 证书已过期!")
                elif days_left <= 7:
                    print(f"[WARNING]  {domain} 证书将在 {days_left} 天后过期")
                else:
                    print(f"[INFO]     {domain} 证书将在 {days_left} 天后过期")
        
        return expiring_soon
    
    def renew_certificate(self, domain):
        """使用 ACME 续期证书"""
        print(f"Renewing certificate for {domain}...")
        
        # 使用 certbot 签发证书(DNS-01 验证)
        result = subprocess.run([
            'certbot', 'certonly',
            '--dns-cloudflare',
            '--dns-cloudflare-credentials', '/etc/letsencrypt/cloudflare.ini',
            '-d', domain,
            '-d', f'*.{domain}',
            '--preferred-challenges', 'dns-01',
            '--non-interactive',
            '--agree-tos',
        ], capture_output=True, text=True)
        
        if result.returncode != 0:
            raise Exception(f"Certificate renewal failed: {result.stderr}")
        
        # 读取新证书
        cert_path = f'/etc/letsencrypt/live/{domain}/fullchain.pem'
        key_path = f'/etc/letsencrypt/live/{domain}/privkey.pem'
        
        with open(cert_path) as f:
            cert_pem = f.read()
        with open(key_path) as f:
            key_pem = f.read()
        
        return cert_pem, key_pem
    
    def deploy_to_cdn(self, domain, cert_pem, key_pem):
        """将证书部署到 CDN 边缘节点"""
        import requests
        
        response = requests.put(
            f"{self.cdn_api_url}/certificates/{domain}",
            headers={
                'Authorization': f'Bearer {self.api_token}',
                'Content-Type': 'application/json',
            },
            json={
                'certificate': cert_pem,
                'private_key': key_pem,
            }
        )
        
        if response.status_code != 200:
            raise Exception(f"CDN deployment failed: {response.text}")
        
        result = response.json()
        print(f"Certificate deployed to {result['edge_nodes_count']} edge nodes")
        print(f"Propagation time: ~{result['estimated_propagation_seconds']}s")
        
        return result
    
    def verify_deployment(self, domain, expected_serial):
        """验证证书在边缘节点是否生效"""
        import ssl
        import socket
        
        # 测试多个 PoP 节点
        test_resolvers = {
            'US-East': '1.1.1.1',
            'EU-West': '1.0.0.1',
            'AP-East': '8.8.8.8',
        }
        
        results = {}
        for region, resolver in test_resolvers.items():
            try:
                context = ssl.create_default_context()
                with socket.create_connection((domain, 443), timeout=10) as sock:
                    with context.wrap_socket(sock, server_hostname=domain) as ssock:
                        cert = ssock.getpeercert()
                        serial = cert.get('serialNumber', '')
                        not_after = cert.get('notAfter', '')
                        
                        results[region] = {
                            'serial_match': serial == expected_serial,
                            'not_after': not_after,
                            'version': ssock.version(),
                        }
            except Exception as e:
                results[region] = {'error': str(e)}
        
        return results

2.3 通配符证书与多域名证书

通配符证书 vs SAN 证书的选择:

通配符证书(Wildcard):
  *.example.com
  ├── 覆盖: api.example.com, www.example.com, cdn.example.com
  ├── 不覆盖: example.com(裸域名需要单独列入 SAN)
  ├── 不覆盖: sub.api.example.com(不支持多级子域名)
  └── 适用: 子域名数量多且动态变化

SAN 证书(Subject Alternative Name):
  SAN: example.com, www.example.com, api.example.com
  ├── 精确列出所有域名
  ├── 可以包含不同根域名
  ├── Let's Encrypt 限制: 每张证书最多 100 个 SAN
  └── 适用: 域名固定且数量有限

CDN 大规模场景:
  通常使用通配符 + SAN 组合:
  CN: example.com
  SAN: example.com, *.example.com, *.cdn.example.com

2.4 证书监控与告警

#!/bin/bash
# cert-monitor.sh — 批量检查 CDN 证书到期时间

DOMAINS=(
    "www.example.com"
    "api.example.com"
    "cdn.example.com"
    "static.example.com"
)

ALERT_DAYS=30
CRITICAL_DAYS=7

check_cert() {
    local domain=$1
    
    # 获取证书信息
    cert_info=$(echo | openssl s_client -servername "$domain" \
        -connect "$domain:443" 2>/dev/null | \
        openssl x509 -noout -dates -subject -issuer -serial 2>/dev/null)
    
    if [ $? -ne 0 ]; then
        echo "[ERROR] $domain: 无法获取证书"
        return 1
    fi
    
    # 解析到期时间
    not_after=$(echo "$cert_info" | grep "notAfter" | cut -d= -f2)
    expiry_epoch=$(date -d "$not_after" +%s 2>/dev/null)
    now_epoch=$(date +%s)
    days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
    
    # 解析证书信息
    issuer=$(echo "$cert_info" | grep "issuer" | sed 's/issuer=//')
    serial=$(echo "$cert_info" | grep "serial" | cut -d= -f2)
    
    # 检查 TLS 版本
    tls_version=$(echo | openssl s_client -servername "$domain" \
        -connect "$domain:443" 2>/dev/null | grep "Protocol" | awk '{print $3}')
    
    # 输出结果
    if [ $days_left -le 0 ]; then
        echo "[CRITICAL] $domain: 证书已过期 ${days_left#-} 天"
    elif [ $days_left -le $CRITICAL_DAYS ]; then
        echo "[CRITICAL] $domain: 证书将在 $days_left 天后过期"
    elif [ $days_left -le $ALERT_DAYS ]; then
        echo "[WARNING]  $domain: 证书将在 $days_left 天后过期"
    else
        echo "[OK]       $domain: 证书有效期剩余 $days_left 天 (TLS $tls_version)"
    fi
    
    echo "           签发者: $issuer"
    echo "           到期日: $not_after"
    echo "           序列号: $serial"
}

echo "=== CDN Certificate Monitor ==="
echo "Date: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo ""

for domain in "${DOMAINS[@]}"; do
    check_cert "$domain"
    echo ""
done

Prometheus 证书到期监控:

# prometheus/rules/cert-alerts.yml
groups:
  - name: certificate_alerts
    rules:
      - alert: CertificateExpiringSoon
        expr: probe_ssl_earliest_cert_expiry - time() < 30 * 24 * 3600
        for: 1h
        labels:
          severity: warning
        annotations:
          summary: "证书即将过期: {{ $labels.instance }}"
          description: "{{ $labels.instance }} 的 TLS 证书将在 {{ $value | humanizeDuration }} 后过期"
      
      - alert: CertificateExpiryCritical
        expr: probe_ssl_earliest_cert_expiry - time() < 7 * 24 * 3600
        for: 10m
        labels:
          severity: critical
        annotations:
          summary: "证书即将过期(紧急): {{ $labels.instance }}"
          description: "{{ $labels.instance }} 的 TLS 证书将在 {{ $value | humanizeDuration }} 后过期,请立即续期"

三、边缘 TLS 优化

CDN 边缘节点的 TLS 配置直接影响握手延迟和安全性。

3.1 TLS 版本与密码套件

# CDN 边缘节点 TLS 配置(Nginx)

ssl_protocols TLSv1.2 TLSv1.3;

# TLS 1.3 密码套件(优先使用)
# 注意:TLS 1.3 密码套件不通过 ssl_ciphers 配置
# Nginx 自动使用 TLS 1.3 的三个密码套件:
#   TLS_AES_256_GCM_SHA384
#   TLS_CHACHA20_POLY1305_SHA256
#   TLS_AES_128_GCM_SHA256

# TLS 1.2 密码套件(按优先级排列)
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';

# 服务端优先选择密码套件
ssl_prefer_server_ciphers on;

# ECDHE 曲线配置
ssl_ecdh_curve X25519:secp384r1:secp256r1;

密码套件选择的工程依据:

性能对比(TLS 1.2 握手,单次):

密码套件                         | 握手耗时 | 安全等级
─────────────────────────────────────────────────────
ECDHE-ECDSA-AES128-GCM-SHA256   | 0.8ms   | 高
ECDHE-RSA-AES128-GCM-SHA256     | 1.2ms   | 高
ECDHE-ECDSA-CHACHA20-POLY1305   | 0.9ms   | 高(移动端优)
ECDHE-RSA-AES256-GCM-SHA384     | 1.5ms   | 很高
DHE-RSA-AES256-GCM-SHA384       | 15ms    | 高(但太慢)

注意:
- ECDSA 比 RSA 快 40%+(密钥更短,签名更快)
- CHACHA20 在没有 AES-NI 的 ARM 设备上比 AES 快 3 倍
- 不应使用 DHE(慢且无额外安全收益)
- 不应使用 CBC 模式(有 padding oracle 攻击风险)

3.2 OCSP Stapling

OCSP(Online Certificate Status Protocol)用于检查证书是否被吊销。不使用 OCSP Stapling 时,浏览器需要额外请求 OCSP 服务器:

不使用 OCSP Stapling:

客户端 ──[TLS 握手]──→ CDN 边缘
客户端 ──[OCSP 查询]──→ OCSP 服务器(额外 RTT)
    等待 OCSP 响应... (50-300ms)
客户端 ──[继续握手]──→ CDN 边缘

使用 OCSP Stapling:

CDN 边缘预先获取 OCSP 响应并缓存
客户端 ──[TLS 握手]──→ CDN 边缘(附带 OCSP 响应)
    无需额外查询
    节省 50-300ms

Nginx OCSP Stapling 配置:

# 启用 OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;

# 信任链证书(用于验证 OCSP 响应)
ssl_trusted_certificate /etc/nginx/ssl/chain.pem;

# DNS 解析器(用于查询 OCSP 服务器)
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;

验证 OCSP Stapling 是否工作:

# 检查 OCSP Stapling 状态
echo | openssl s_client -servername www.example.com \
    -connect www.example.com:443 -status 2>/dev/null | \
    grep -A 5 "OCSP Response Status"

# 正常输出:
# OCSP Response Status: successful (0x0)
# OCSP Response Data:
#     OCSP Response Status: successful (0x0)
#     Cert Status: good
#     This Update: Jul 18 00:00:00 2025 GMT

# 如果输出 "OCSP response: no response sent"
# 说明 Stapling 未生效,需要检查:
# 1. ssl_trusted_certificate 路径是否正确
# 2. DNS 解析器是否可达
# 3. OCSP 服务器是否响应

3.3 会话恢复(Session Resumption)

减少 TLS 握手的 RTT 开销:

TLS 1.2 Session Ticket:

首次握手(Full Handshake):2 RTT
客户端 ──→ ClientHello
       ←── ServerHello + Certificate + ServerHelloDone
客户端 ──→ ClientKeyExchange + ChangeCipherSpec + Finished
       ←── NewSessionTicket + ChangeCipherSpec + Finished

后续握手(Abbreviated):1 RTT
客户端 ──→ ClientHello(含 Session Ticket)
       ←── ServerHello + ChangeCipherSpec + Finished
客户端 ──→ ChangeCipherSpec + Finished

TLS 1.3 PSK:

首次握手:1 RTT
后续握手(PSK):1 RTT(减少计算量)
0-RTT 恢复:0 RTT(但有重放风险)

CDN 环境下的 Session Ticket 挑战——多个边缘节点需要共享 Ticket Key:

# Session Ticket 配置
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;

# Session Ticket Key(所有边缘节点必须使用相同的 key)
ssl_session_ticket_key /etc/nginx/ssl/ticket-current.key;
ssl_session_ticket_key /etc/nginx/ssl/ticket-previous.key;

# Key 轮换脚本(每 12 小时轮换一次)
# 0 */12 * * * /etc/nginx/ssl/rotate-ticket-key.sh

Session Ticket Key 轮换脚本:

#!/bin/bash
# rotate-ticket-key.sh — TLS Session Ticket Key 轮换

KEY_DIR="/etc/nginx/ssl"
CURRENT_KEY="$KEY_DIR/ticket-current.key"
PREVIOUS_KEY="$KEY_DIR/ticket-previous.key"

# 保留旧 key 用于解密已有 ticket
cp "$CURRENT_KEY" "$PREVIOUS_KEY"

# 生成新 key(48 字节)
openssl rand 48 > "$CURRENT_KEY"
chmod 600 "$CURRENT_KEY" "$PREVIOUS_KEY"

# 分发到所有边缘节点
for node in $(cat /etc/nginx/edge-nodes.txt); do
    scp "$CURRENT_KEY" "$PREVIOUS_KEY" "$node:$KEY_DIR/"
    ssh "$node" "nginx -s reload"
done

echo "Ticket key rotated at $(date)"

3.4 双证书部署(RSA + ECDSA)

为了兼容旧客户端(RSA)同时获得更好性能(ECDSA),可以在边缘部署双证书:

server {
    listen 443 ssl http2;
    server_name www.example.com;
    
    # RSA 证书(兼容性)
    ssl_certificate     /etc/nginx/ssl/rsa/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/rsa/privkey.pem;
    
    # ECDSA 证书(性能)
    ssl_certificate     /etc/nginx/ssl/ecdsa/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/ecdsa/privkey.pem;
    
    # Nginx 会根据客户端支持的密码套件自动选择证书:
    # 支持 ECDSA 的客户端 → 使用 ECDSA 证书
    # 不支持 ECDSA 的客户端 → 降级使用 RSA 证书
}

性能对比:

# RSA 2048 签名性能
$ openssl speed rsa2048
                  sign    verify    sign/s verify/s
rsa 2048 bits   0.000932s 0.000029s   1073.0  34892.2

# ECDSA P-256 签名性能
$ openssl speed ecdsap256
                              sign    verify    sign/s verify/s
256 bits ecdsa (nistp256)   0.000034s 0.000098s  29387.5  10255.0

# ECDSA 签名速度是 RSA 的 27 倍
# RSA 验证速度是 ECDSA 的 3.4 倍
# TLS 握手中服务端做签名(ECDSA 优势),客户端做验证(RSA 优势)
# 综合考虑:ECDSA 在 TLS 场景更优(签名是瓶颈)

四、回源 HTTPS 策略

CDN 到源站的 HTTPS 连接同样需要精心配置。

4.1 回源证书验证

回源验证级别:

Level 0: 不验证源站证书(不推荐)
  - CDN → 源站: HTTPS 但不验证证书
  - 风险: 中间人攻击可以冒充源站

Level 1: 验证证书有效性(推荐)
  - CDN → 源站: HTTPS + 验证证书链 + 验证域名
  - 源站需要有效的 CA 签发证书

Level 2: 证书钉扎(高安全场景)
  - CDN → 源站: HTTPS + 验证证书指纹
  - 即使 CA 被入侵也不受影响

Nginx 回源证书验证配置:

upstream origin {
    server origin.example.com:443;
}

server {
    location / {
        proxy_pass https://origin;
        proxy_ssl_server_name on;
        proxy_ssl_name origin.example.com;
        
        # 启用证书验证
        proxy_ssl_verify on;
        proxy_ssl_verify_depth 2;
        proxy_ssl_trusted_certificate /etc/nginx/ssl/ca-certificates.crt;
        
        # 指定回源使用的 TLS 版本
        proxy_ssl_protocols TLSv1.2 TLSv1.3;
        proxy_ssl_ciphers HIGH:!aNULL:!MD5;
        
        # 可选:客户端证书认证(mTLS 回源)
        # proxy_ssl_certificate     /etc/nginx/ssl/client.crt;
        # proxy_ssl_certificate_key /etc/nginx/ssl/client.key;
    }
}

4.2 回源 mTLS

在高安全场景下,CDN 到源站使用双向 TLS 认证:

mTLS 回源架构:

CDN 边缘 ──[mTLS]──→ 源站

CDN 出示:
  - CDN 客户端证书(证明请求来自 CDN,不是攻击者直连)

源站验证:
  - CDN 客户端证书的有效性
  - 证书中的 CN/SAN 是否匹配 CDN 提供商

配合 IP 白名单实现双重验证:
  1. 源站防火墙只放行 CDN IP 段
  2. 源站 TLS 只接受 CDN 的客户端证书
  → 攻击者无法绕过 CDN 直接访问源站

源站 Nginx 配置 mTLS:

server {
    listen 443 ssl;
    server_name origin.example.com;
    
    # 源站自身证书
    ssl_certificate     /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;
    
    # 要求客户端证书(mTLS)
    ssl_client_certificate /etc/nginx/ssl/cdn-ca.crt;  # CDN CA 证书
    ssl_verify_client on;
    ssl_verify_depth 2;
    
    # 只在证书验证通过后处理请求
    location / {
        # 将客户端证书信息传递给后端
        proxy_set_header X-SSL-Client-CN $ssl_client_s_dn_cn;
        proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
        
        proxy_pass http://localhost:8080;
    }
}

五、CDN HTTPS 安全最佳实践

5.1 安全头部配置

CDN 边缘可以统一注入安全头部,避免在每个源站重复配置:

# CDN 边缘统一注入安全头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "0" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

# Content-Security-Policy(需要根据实际情况调整)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: cdn.example.com;" always;

5.2 HSTS Preload

将域名加入浏览器的 HSTS Preload List,确保从第一次访问就使用 HTTPS:

HSTS Preload 的要求:
1. 提供有效的 TLS 证书
2. 同一 IP 上 HTTP 重定向到 HTTPS
3. 所有子域名都支持 HTTPS
4. HSTS 头包含:
   - max-age >= 31536000(1 年)
   - includeSubDomains
   - preload

注意事项:
- 加入 Preload List 后很难撤回(需要等浏览器更新周期)
- 确保所有子域名(包括内部系统)都支持 HTTPS
- 建议先用短 max-age 测试,确认无问题后再提交

5.3 证书透明度(CT)

Certificate Transparency 帮助检测未授权的证书签发:

# 查询 CT 日志中你的域名的所有证书
# 使用 crt.sh(CT 日志聚合器)
curl -s "https://crt.sh/?q=example.com&output=json" | \
    python3 -c "
import json, sys
certs = json.load(sys.stdin)
for c in sorted(certs, key=lambda x: x['not_before'], reverse=True)[:10]:
    print(f\"签发日期: {c['not_before']}  到期: {c['not_after']}\")
    print(f\"签发者: {c['issuer_name']}\")
    print(f\"域名: {c['common_name']}\")
    print()
"

# 监控是否有未授权的证书签发
# 如果发现不是你签发的证书 → 可能是 CA 被入侵或 DNS 劫持

5.4 ECH(Encrypted Client Hello)

TLS 握手中的 SNI(Server Name Indication)以明文传输,暴露了用户访问的域名。ECH 对 ClientHello 中的 SNI 加密:

传统 TLS 握手(SNI 明文):

ClientHello:
  SNI: www.example.com  ← 网络中间设备可见
  Supported Versions: TLS 1.3
  Key Share: X25519

ECH(加密 ClientHello):

ClientHello:
  ECH: <加密的内部 ClientHello>  ← SNI 被加密
  Outer SNI: cloudflare-ech.com   ← 只显示 CDN 域名
  
CDN 边缘解密后:
  Inner ClientHello:
    SNI: www.example.com  ← 只有 CDN 能看到

CDN 是 ECH 的天然部署平台——所有使用同一 CDN 的域名共享外层 SNI,使得网络审查者无法区分不同域名的流量。

六、故障排查

6.1 常见 HTTPS 问题诊断

# 1. 证书链不完整
$ curl -v https://www.example.com 2>&1 | grep -E "(SSL|certificate)"
# 错误: SSL certificate problem: unable to get local issuer certificate
# 原因: CDN 未部署中间证书
# 修复: 上传 fullchain.pem(包含中间证书)

# 2. 证书域名不匹配
$ echo | openssl s_client -servername www.example.com \
    -connect www.example.com:443 2>/dev/null | \
    openssl x509 -noout -text | grep -A1 "Subject Alternative Name"
# 如果 SAN 中没有 www.example.com → 证书域名不匹配

# 3. 混合内容(Mixed Content)
# HTTPS 页面加载 HTTP 资源 → 浏览器阻止
# 排查方法:
curl -s https://www.example.com | grep -i "http://"

# 4. HSTS 导致无法回退 HTTP
# 如果错误配置了 HSTS,用户无法通过 HTTP 访问
# 浏览器清除方法:
# Chrome: chrome://net-internals/#hsts → Delete domain
# Firefox: 清除历史记录 → 站点设置

# 5. TLS 版本不兼容
$ openssl s_client -servername www.example.com \
    -connect www.example.com:443 \
    -tls1_2 2>&1 | head -5
# 如果失败 → CDN 可能禁用了 TLS 1.2
# 影响: 旧设备(Android 4.x, IE 10)无法访问

6.2 端到端 TLS 诊断脚本

#!/bin/bash
# cdn-tls-audit.sh — CDN HTTPS 完整诊断

DOMAIN="${1:-www.example.com}"

echo "=== CDN TLS Audit: $DOMAIN ==="
echo ""

# 1. 证书信息
echo "--- Certificate Info ---"
echo | openssl s_client -servername "$DOMAIN" \
    -connect "$DOMAIN:443" 2>/dev/null | \
    openssl x509 -noout -subject -issuer -dates -serial

echo ""

# 2. TLS 版本
echo "--- TLS Version ---"
for ver in tls1 tls1_1 tls1_2 tls1_3; do
    result=$(echo | openssl s_client -servername "$DOMAIN" \
        -connect "$DOMAIN:443" -"$ver" 2>&1 | grep "Protocol")
    if [ $? -eq 0 ]; then
        echo "  $ver: SUPPORTED ($result)"
    else
        echo "  $ver: NOT SUPPORTED"
    fi
done

echo ""

# 3. OCSP Stapling
echo "--- OCSP Stapling ---"
echo | openssl s_client -servername "$DOMAIN" \
    -connect "$DOMAIN:443" -status 2>/dev/null | \
    grep "OCSP Response Status"

echo ""

# 4. 安全头部
echo "--- Security Headers ---"
headers=$(curl -sI "https://$DOMAIN" 2>/dev/null)
for header in "Strict-Transport-Security" "X-Content-Type-Options" \
    "X-Frame-Options" "Content-Security-Policy" "Referrer-Policy"; do
    value=$(echo "$headers" | grep -i "$header" | head -1)
    if [ -n "$value" ]; then
        echo "  ✓ $value"
    else
        echo "  ✗ $header: MISSING"
    fi
done

echo ""

# 5. 证书链验证
echo "--- Certificate Chain ---"
echo | openssl s_client -servername "$DOMAIN" \
    -connect "$DOMAIN:443" -showcerts 2>/dev/null | \
    grep -E "s:|i:" | head -10

echo ""
echo "Audit complete."

七、总结

CDN HTTPS 工程的核心要点:

  1. 架构选择:生产环境使用 Full (Strict) 模式——边缘 TLS 终止 + 回源 HTTPS + 证书验证
  2. 证书管理:自动化是关键——ACME 自动签发 + 到期监控 + 告警
  3. TLS 优化:ECDSA 双证书、OCSP Stapling、Session Resumption——每一项都能减少几十毫秒
  4. 回源安全:IP 白名单 + mTLS 双重验证,防止绕过 CDN 直连源站
  5. 安全头部:在边缘统一注入 HSTS、CSP 等安全头,确保一致性

上一篇: 动态加速:TCP 优化、路由优化与边缘计算 下一篇: CDN 故障调试:缓存命中率、回源异常与头分析

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-22 · network

网络工程索引

汇总本站网络工程系列文章,覆盖分层模型、以太网、IP、TCP、DNS、TLS、HTTP/2/3、CDN、BGP 与故障诊断。

2025-08-04 · network

【网络工程】证书工程:PKI 体系、ACME 与自动化管理

证书过期宕机是最常见也最可避免的生产事故。本文从工程角度剖析 X.509 证书结构、PKI 信任链的工作原理、ACME 协议与 Let's Encrypt 的自动化部署、私有 CA 的搭建实践,以及 cert-manager 在 Kubernetes 中的证书管理方案。覆盖证书监控、轮换策略与过期告警的完整工程体系。

2025-08-05 · network

【网络工程】mTLS 工程实践:服务间双向认证

mTLS(双向 TLS)在微服务架构中实现服务间的身份认证和通信加密。本文从工程角度剖析 mTLS 的握手流程差异、证书分发的三种模式、SPIFFE/SPIRE 的标准化身份框架,以及 Istio 和 Linkerd 中 mTLS 的实现原理。覆盖性能开销测量、调试方法和大规模部署的证书轮换策略。


By .