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

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

文章导航

分类入口
network
标签入口
#tls#certificate#pki#acme#letsencrypt#cert-manager#x509

目录

2020 年 2 月,Let’s Encrypt 因为一个合规性 bug 被迫在 5 天内撤销 300 万张证书。2021 年 9 月,Let’s Encrypt 的根证书 DST Root CA X3 过期,导致大量老设备和系统无法验证证书。证书看起来只是一个文件,但它背后的 PKI(Public Key Infrastructure,公钥基础设施)体系是整个 HTTPS 安全的信任根基。

证书过期导致的宕机是最常见也最可避免的生产事故。本文从工程视角,系统性地讲解证书的整个生命周期——从结构、签发、部署到监控和轮换。

一、X.509 证书结构

1.1 证书的组成

X.509 是 ITU-T 定义的公钥证书标准,目前广泛使用的是 v3 版本。一张 X.509 证书包含以下核心字段:

X.509 v3 证书结构:
┌─────────────────────────────────────────┐
│ Version: v3                             │
├─────────────────────────────────────────┤
│ Serial Number: 唯一标识(CA 内唯一)    │
├─────────────────────────────────────────┤
│ Signature Algorithm: sha256WithRSAEncryption │
├─────────────────────────────────────────┤
│ Issuer: 签发者(CA)的 DN               │
│   C=US, O=Let's Encrypt, CN=R3          │
├─────────────────────────────────────────┤
│ Validity:                               │
│   Not Before: 2025-01-01T00:00:00Z      │
│   Not After:  2025-03-31T23:59:59Z      │
├─────────────────────────────────────────┤
│ Subject: 证书持有者的 DN                │
│   CN=example.com                        │
├─────────────────────────────────────────┤
│ Subject Public Key Info:                │
│   Algorithm: RSA / ECDSA               │
│   Public Key: (2048/4096 bit RSA 或     │
│                256/384 bit ECDSA)        │
├─────────────────────────────────────────┤
│ Extensions (v3):                        │
│   Subject Alternative Name (SAN):      │
│     DNS: example.com                    │
│     DNS: www.example.com                │
│     DNS: *.example.com                  │
│   Key Usage: Digital Signature          │
│   Extended Key Usage: serverAuth        │
│   Basic Constraints: CA:FALSE           │
│   Authority Key Identifier: ...         │
│   Subject Key Identifier: ...           │
│   CRL Distribution Points: ...         │
│   Authority Information Access:         │
│     OCSP: http://ocsp.example.com       │
│     CA Issuers: http://...              │
│   Certificate Policies: ...             │
│   SCT (Signed Certificate Timestamp):   │
│     log1.ct.example.com, ...            │
├─────────────────────────────────────────┤
│ Signature: CA 用私钥对以上内容的签名    │
└─────────────────────────────────────────┘

1.2 用 OpenSSL 查看证书

# 查看远程服务器证书
echo | openssl s_client -connect example.com:443 \
  -servername example.com 2>/dev/null | \
  openssl x509 -noout -text

# 查看本地证书文件
openssl x509 -in cert.pem -noout -text

# 只看关键信息
openssl x509 -in cert.pem -noout \
  -subject -issuer -dates -serial \
  -ext subjectAltName

# 输出示例:
# subject=CN = example.com
# issuer=C = US, O = Let's Encrypt, CN = R3
# notBefore=Jan  1 00:00:00 2025 GMT
# notAfter=Mar 31 23:59:59 2025 GMT
# serial=0A:1B:2C:3D:4E:5F:6A:7B:8C:9D
# X509v3 Subject Alternative Name:
#     DNS:example.com, DNS:www.example.com

# 查看证书指纹(用于证书固定)
openssl x509 -in cert.pem -noout -fingerprint -sha256
# SHA256 Fingerprint=AB:CD:EF:...

# 验证证书链
openssl verify -CAfile chain.pem cert.pem
# cert.pem: OK

1.3 证书编码格式

证书文件格式:

PEM (Privacy Enhanced Mail):
  - Base64 编码的文本格式
  - 以 -----BEGIN CERTIFICATE----- 开头
  - 最常用的格式
  - 文件扩展名: .pem, .crt, .cer

DER (Distinguished Encoding Rules):
  - 二进制格式
  - PEM 是 DER 的 Base64 编码
  - Java KeyStore 使用 DER
  - 文件扩展名: .der, .cer

PKCS#12 / PFX:
  - 包含证书 + 私钥的容器格式
  - 通常有密码保护
  - Windows / IIS 常用
  - 文件扩展名: .p12, .pfx
# 格式转换
# PEM → DER
openssl x509 -in cert.pem -outform DER -out cert.der

# DER → PEM
openssl x509 -in cert.der -inform DER -outform PEM -out cert.pem

# PEM 证书 + 私钥 → PKCS#12
openssl pkcs12 -export -out cert.p12 \
  -inkey key.pem -in cert.pem -certfile chain.pem

# PKCS#12 → PEM
openssl pkcs12 -in cert.p12 -out all.pem -nodes

二、PKI 信任链

2.1 信任链的结构

PKI 信任链 (Certificate Chain):

  ┌──────────────┐
  │   根 CA       │  ← 自签名,预装在操作系统/浏览器中
  │  (Root CA)    │     有效期: 20-30 年
  │  离线存储     │     全球约 150 个根 CA
  └──────┬───────┘
         │ 签发
         v
  ┌──────────────┐
  │  中间 CA      │  ← 由根 CA 签发
  │(Intermediate) │     有效期: 5-10 年
  │  在线运行     │     实际签发证书的实体
  └──────┬───────┘
         │ 签发
         v
  ┌──────────────┐
  │  叶子证书     │  ← 由中间 CA 签发
  │ (Leaf/End     │     有效期: 90 天 (Let's Encrypt)
  │  Entity)      │     或 398 天 (商业 CA)
  │  你的服务器   │
  └──────────────┘

2.2 证书验证流程

客户端验证服务器证书的完整流程:

1. 接收证书链
   服务器发送: [叶子证书] + [中间 CA 证书]
   (不发送根 CA 证书——客户端本地已有)

2. 构建信任链
   叶子证书.Issuer → 中间 CA.Subject ✓
   中间 CA.Issuer  → 根 CA.Subject   ✓ (本地查找)

3. 验证签名链
   用中间 CA 的公钥验证叶子证书的签名 ✓
   用根 CA 的公钥验证中间 CA 的签名    ✓

4. 检查有效期
   每张证书: notBefore ≤ 当前时间 ≤ notAfter ✓

5. 检查域名匹配
   请求的域名必须匹配证书的 SAN 或 CN
   *.example.com 匹配 www.example.com
   但不匹配 example.com 本身
   也不匹配 sub.www.example.com

6. 检查吊销状态
   CRL (Certificate Revocation List): 下载完整吊销列表
   OCSP (Online Certificate Status Protocol): 实时查询
   OCSP Stapling: 服务器预取 OCSP 响应

7. 检查证书用途
   Key Usage: digitalSignature
   Extended Key Usage: serverAuth (TLS 服务器证书)
   Basic Constraints: CA:FALSE (叶子证书)
# 验证证书链的完整性
openssl verify -show_chain \
  -CAfile /etc/ssl/certs/ca-certificates.crt \
  -untrusted intermediate.pem \
  server.pem

# 检查服务器返回的证书链
openssl s_client -connect example.com:443 \
  -servername example.com -showcerts 2>/dev/null | \
  grep -E "s:|i:|depth"
# depth=0: 叶子证书
# depth=1: 中间 CA
# depth=2: 根 CA (有时不包含)

# 常见错误: 服务器没有发送中间 CA 证书
# → 部分客户端(特别是移动端)无法验证证书
# 检查方法:
openssl s_client -connect example.com:443 \
  -servername example.com 2>&1 | \
  grep "Verify return code"
# 21 (unable to verify the first certificate)
# → 缺少中间 CA 证书

2.3 根 CA 信任存储

# 各平台的根 CA 信任存储位置

# Linux (Debian/Ubuntu)
ls /etc/ssl/certs/
# 或
ls /usr/share/ca-certificates/

# Linux (RHEL/CentOS)
ls /etc/pki/tls/certs/

# macOS
# 系统偏好设置 → 钥匙串访问 → 系统根证书

# 查看系统信任的根 CA 数量
awk -v cmd='openssl x509 -noout -subject' \
  '/BEGIN/{close(cmd)};{print | cmd}' \
  /etc/ssl/certs/ca-certificates.crt 2>/dev/null | \
  wc -l
# 约 130-150 个根 CA

# Java 的信任存储 (cacerts)
keytool -list -keystore "$JAVA_HOME/lib/security/cacerts" \
  -storepass changeit 2>/dev/null | grep -c "trustedCertEntry"

三、证书签发

3.1 CSR(Certificate Signing Request)

# 生成 RSA 密钥对和 CSR
openssl req -new -newkey rsa:2048 -nodes \
  -keyout server.key -out server.csr \
  -subj "/CN=example.com"

# 生成 ECDSA 密钥对和 CSR(推荐)
openssl ecparam -genkey -name prime256v1 -out server.key
openssl req -new -key server.key -out server.csr \
  -subj "/CN=example.com"

# 带 SAN 的 CSR(多域名证书)
openssl req -new -key server.key -out server.csr \
  -subj "/CN=example.com" \
  -addext "subjectAltName=DNS:example.com,DNS:www.example.com,DNS:api.example.com"

# 查看 CSR 内容
openssl req -in server.csr -noout -text

# CSR 包含:
#   - 公钥(从密钥对中提取)
#   - 请求的 Subject(域名等)
#   - 请求的扩展(SAN 等)
#   - 请求者的签名(证明持有私钥)
# CSR 不包含私钥——私钥永远不应离开服务器

3.2 证书类型

验证级别 缩写 验证内容 签发时间 价格 适用场景
域名验证 DV 域名所有权 分钟级 免费-$10 个人/小型网站
组织验证 OV 域名 + 组织信息 1-3 天 $50-$200 企业网站
扩展验证 EV 域名 + 组织 + 法律实体 1-2 周 $200-$1000 金融/政务
DV 证书的验证方式(三选一):

1. HTTP 验证 (http-01)
   CA 要求你在网站放置一个特定文件:
   http://example.com/.well-known/acme-challenge/<token>
   → 证明你控制了这个域名的 Web 服务器

2. DNS 验证 (dns-01)
   CA 要求你添加一个 DNS TXT 记录:
   _acme-challenge.example.com → <验证值>
   → 证明你控制了这个域名的 DNS
   → 支持通配符证书(*.example.com)

3. TLS-ALPN 验证 (tls-alpn-01)
   CA 通过 TLS 连接验证:
   在 443 端口提供包含验证令牌的自签名证书
   → 证明你控制了这个域名的 TLS 端口

四、ACME 协议与 Let’s Encrypt

4.1 ACME 协议概述

ACME(Automatic Certificate Management Environment,RFC 8555)是 Let’s Encrypt 设计的证书自动化管理协议。

ACME 协议流程:

  客户端 (certbot)                    ACME 服务器 (Let's Encrypt)
    │                                      │
    ├── POST /acme/new-account ───────────→│  1. 注册账号
    │←── 201 Created (account URL) ────────┤
    │                                      │
    ├── POST /acme/new-order ─────────────→│  2. 创建订单
    │   { identifiers: [{type:"dns",       │     (指定要签发的域名)
    │     value:"example.com"}] }          │
    │←── 201 Created (order URL +          │
    │     authorizations URLs) ────────────┤
    │                                      │
    ├── POST /acme/authz/xxx ─────────────→│  3. 获取验证要求
    │←── 200 (challenges: http-01,         │
    │     dns-01, tls-alpn-01) ────────────┤
    │                                      │
    │  [放置验证文件或 DNS 记录]            │
    │                                      │
    ├── POST /acme/challenge/xxx ─────────→│  4. 请求验证
    │←── 200 (status: processing) ─────────┤
    │                                      │
    │  [CA 访问验证 URL / DNS]             │
    │                                      │
    ├── POST /acme/authz/xxx ─────────────→│  5. 检查验证状态
    │←── 200 (status: valid) ──────────────┤
    │                                      │
    ├── POST /acme/order/xxx/finalize ────→│  6. 提交 CSR
    │   { csr: <Base64URL-encoded CSR> }   │
    │←── 200 (status: valid,               │
    │     certificate URL) ────────────────┤
    │                                      │
    ├── POST /acme/cert/xxx ──────────────→│  7. 下载证书
    │←── 200 (证书链 PEM) ────────────────┤

4.2 Certbot 部署实战

# 安装 Certbot
# Debian/Ubuntu
apt-get install certbot

# 方式一: Standalone 模式(临时启动 Web 服务器)
# 需要 80 端口空闲
certbot certonly --standalone \
  -d example.com -d www.example.com \
  --email admin@example.com --agree-tos

# 方式二: Webroot 模式(使用已有 Web 服务器)
certbot certonly --webroot \
  -w /var/www/html \
  -d example.com -d www.example.com

# 方式三: Nginx 插件(自动配置 Nginx)
certbot --nginx -d example.com

# 方式四: DNS 验证(通配符证书)
certbot certonly --manual \
  --preferred-challenges dns \
  -d "*.example.com" -d example.com

# 证书文件位置
ls /etc/letsencrypt/live/example.com/
# cert.pem       ← 服务器证书
# chain.pem      ← 中间 CA 证书
# fullchain.pem  ← cert.pem + chain.pem(Nginx 使用这个)
# privkey.pem    ← 私钥

# 查看证书信息
certbot certificates
# Certificate Name: example.com
# Domains: example.com www.example.com
# Expiry Date: 2025-04-01 (VALID: 89 days)
# Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem

# 手动续期
certbot renew --dry-run  # 测试续期
certbot renew            # 实际续期

# 自动续期(systemd timer 或 cron)
# certbot 安装时通常已配置自动续期
systemctl list-timers | grep certbot
# 或
cat /etc/cron.d/certbot

4.3 DNS 自动化验证

手动 DNS 验证不适合自动化。对于通配符证书或大规模部署,需要 DNS API 集成:

# 使用 Cloudflare DNS 插件自动验证
pip install certbot-dns-cloudflare

# 配置 Cloudflare API 凭证
cat > /etc/letsencrypt/cloudflare.ini << 'EOF'
dns_cloudflare_api_token = your-api-token-here
EOF
chmod 600 /etc/letsencrypt/cloudflare.ini

# 签发通配符证书
certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  -d "*.example.com" -d example.com

# 支持的 DNS 提供商插件:
# certbot-dns-cloudflare
# certbot-dns-route53 (AWS)
# certbot-dns-google (GCP)
# certbot-dns-digitalocean
# certbot-dns-dnspod (腾讯云)
# certbot-dns-aliyun (阿里云, 第三方)

五、私有 CA

5.1 为什么需要私有 CA

使用私有 CA 的场景:

1. 内部服务间通信(mTLS)
   - 微服务之间的双向认证
   - 不需要(也不应该)用公共 CA

2. 开发/测试环境
   - 本地开发需要 HTTPS
   - 不想为内部域名购买证书

3. IoT 设备
   - 设备出厂预装私有 CA 根证书
   - 设备连接使用私有 CA 签发的证书

4. 合规要求
   - 某些行业要求自建 PKI
   - 完全控制证书的签发和吊销

5.2 使用 OpenSSL 搭建私有 CA

# 创建 CA 目录结构
mkdir -p /opt/ca/{root,intermediate}/{certs,crl,newcerts,private,csr}
touch /opt/ca/root/index.txt /opt/ca/intermediate/index.txt
echo 1000 > /opt/ca/root/serial
echo 1000 > /opt/ca/intermediate/serial

# 1. 生成根 CA 密钥和证书
openssl genrsa -aes256 -out /opt/ca/root/private/ca.key 4096
# 输入密码保护私钥

openssl req -new -x509 -days 7300 \
  -key /opt/ca/root/private/ca.key \
  -out /opt/ca/root/certs/ca.crt \
  -subj "/C=CN/O=MyOrg/CN=MyOrg Root CA"

# 2. 生成中间 CA 密钥和 CSR
openssl genrsa -aes256 -out /opt/ca/intermediate/private/intermediate.key 4096

openssl req -new \
  -key /opt/ca/intermediate/private/intermediate.key \
  -out /opt/ca/intermediate/csr/intermediate.csr \
  -subj "/C=CN/O=MyOrg/CN=MyOrg Intermediate CA"

# 3. 用根 CA 签发中间 CA 证书
openssl x509 -req -days 3650 \
  -in /opt/ca/intermediate/csr/intermediate.csr \
  -CA /opt/ca/root/certs/ca.crt \
  -CAkey /opt/ca/root/private/ca.key \
  -CAcreateserial \
  -out /opt/ca/intermediate/certs/intermediate.crt \
  -extfile <(echo -e "basicConstraints=critical,CA:TRUE,pathlen:0\nkeyUsage=critical,digitalSignature,keyCertSign,cRLSign")

# 4. 签发服务器证书
openssl genrsa -out /opt/ca/intermediate/private/server.key 2048

openssl req -new \
  -key /opt/ca/intermediate/private/server.key \
  -out /opt/ca/intermediate/csr/server.csr \
  -subj "/CN=myapp.internal"

openssl x509 -req -days 365 \
  -in /opt/ca/intermediate/csr/server.csr \
  -CA /opt/ca/intermediate/certs/intermediate.crt \
  -CAkey /opt/ca/intermediate/private/intermediate.key \
  -CAcreateserial \
  -out /opt/ca/intermediate/certs/server.crt \
  -extfile <(echo -e "subjectAltName=DNS:myapp.internal,DNS:*.myapp.internal\nextendedKeyUsage=serverAuth\nbasicConstraints=CA:FALSE")

# 5. 构建完整证书链
cat /opt/ca/intermediate/certs/server.crt \
    /opt/ca/intermediate/certs/intermediate.crt \
    > /opt/ca/intermediate/certs/server-fullchain.crt

# 验证
openssl verify -CAfile /opt/ca/root/certs/ca.crt \
  -untrusted /opt/ca/intermediate/certs/intermediate.crt \
  /opt/ca/intermediate/certs/server.crt
# server.crt: OK

5.3 使用 step-ca 搭建生产级私有 CA

OpenSSL 手动管理 CA 适合学习,但生产环境推荐使用 step-ca(Smallstep)——它提供 ACME 服务器、自动续期、短期证书等现代特性。

# 安装 step CLI 和 step-ca
# https://smallstep.com/docs/step-ca/installation

# 初始化 CA
step ca init --name "MyOrg CA" \
  --dns ca.myorg.internal \
  --address :8443 \
  --provisioner admin

# 启动 CA 服务器
step-ca $(step path)/config/ca.json

# 签发证书
step ca certificate myapp.internal \
  server.crt server.key \
  --ca-url https://ca.myorg.internal:8443

# step-ca 支持 ACME 协议
# 内部服务可以用 certbot 对接私有 CA
certbot certonly --standalone \
  --server https://ca.myorg.internal:8443/acme/acme/directory \
  -d myapp.internal

六、Kubernetes 中的证书管理

6.1 cert-manager 架构

cert-manager 架构:

  ┌────────────────────────────────────────────────────┐
  │  Kubernetes Cluster                                 │
  │                                                     │
  │  ┌─────────────┐     ┌──────────────┐             │
  │  │ Certificate  │────→│ cert-manager │             │
  │  │   (CR)       │     │  controller  │             │
  │  └─────────────┘     └──────┬───────┘             │
  │                              │                      │
  │                     ┌────────┼────────┐            │
  │                     ↓        ↓        ↓            │
  │              ┌──────────┐ ┌──────┐ ┌──────┐       │
  │              │  Issuer   │ │ ACME │ │ Self │       │
  │              │ (CA/Vault)│ │      │ │Signed│       │
  │              └──────────┘ └──┬───┘ └──────┘       │
  │                               │                     │
  │                               │ ACME 协议           │
  └───────────────────────────────┼─────────────────────┘
                                  │
                                  ↓
                          ┌──────────────┐
                          │Let's Encrypt │
                          │  ACME Server │
                          └──────────────┘

6.2 cert-manager 部署

# 安装 cert-manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.0/cert-manager.yaml

# 等待 Pod 就绪
kubectl -n cert-manager get pods -w
# ClusterIssuer 配置 (Let's Encrypt)
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
    - http01:
        ingress:
          class: nginx
# Certificate 资源
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-com-tls
  namespace: default
spec:
  secretName: example-com-tls-secret
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
  - example.com
  - www.example.com
  duration: 2160h    # 90 天
  renewBefore: 720h  # 到期前 30 天自动续期
# Ingress 中使用证书
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  tls:
  - hosts:
    - example.com
    - www.example.com
    secretName: example-com-tls-secret
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web
            port:
              number: 80
# 查看证书状态
kubectl get certificates
# NAME               READY   SECRET                    AGE
# example-com-tls    True    example-com-tls-secret    5m

# 查看证书详情
kubectl describe certificate example-com-tls

# 查看 CertificateRequest
kubectl get certificaterequest

# 手动触发续期
kubectl cert-manager renew example-com-tls

七、证书监控与告警

7.1 证书过期监控脚本

#!/bin/bash
# cert_monitor.sh — 批量检查证书过期时间
# 用法: ./cert_monitor.sh domains.txt

WARN_DAYS=30
CRIT_DAYS=7
DOMAINS_FILE="${1:?Usage: $0 domains.txt}"

echo "=== 证书过期检查 $(date '+%Y-%m-%d %H:%M') ==="

while IFS= read -r domain; do
  [[ -z "$domain" || "$domain" == \#* ]] && continue
  
  # 获取证书过期时间
  expiry=$(echo | timeout 5 openssl s_client \
    -connect "$domain:443" -servername "$domain" \
    2>/dev/null | \
    openssl x509 -noout -enddate 2>/dev/null | \
    cut -d= -f2)
  
  if [[ -z "$expiry" ]]; then
    echo "ERROR  $domain — 无法获取证书"
    continue
  fi
  
  # 计算剩余天数
  expiry_epoch=$(date -d "$expiry" +%s 2>/dev/null)
  now_epoch=$(date +%s)
  days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
  
  # 状态判断
  if [[ $days_left -lt 0 ]]; then
    echo "EXPIRED $domain — 已过期 $((-days_left)) 天"
  elif [[ $days_left -lt $CRIT_DAYS ]]; then
    echo "CRITICAL $domain — 还剩 ${days_left} 天 (到期: $expiry)"
  elif [[ $days_left -lt $WARN_DAYS ]]; then
    echo "WARNING  $domain — 还剩 ${days_left} 天 (到期: $expiry)"
  else
    echo "OK       $domain — 还剩 ${days_left} 天"
  fi
done < "$DOMAINS_FILE"

7.2 Prometheus 证书监控

# Blackbox Exporter 配置
# /etc/blackbox_exporter/config.yml
modules:
  tls_check:
    prober: tcp
    timeout: 5s
    tcp:
      tls: true
      tls_config:
        insecure_skip_verify: false
# Prometheus 采集配置
# prometheus.yml
scrape_configs:
  - job_name: 'ssl_expiry'
    metrics_path: /probe
    params:
      module: [tls_check]
    static_configs:
      - targets:
        - example.com:443
        - api.example.com:443
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: blackbox-exporter:9115
# Prometheus 告警规则
# 证书 30 天内过期 → Warning
# 证书 7 天内过期 → Critical

groups:
- name: ssl_expiry
  rules:
  - alert: SSLCertExpiringSoon
    expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 30
    for: 1h
    labels:
      severity: warning
    annotations:
      summary: "证书即将过期: {{ $labels.instance }}"
      description: "剩余 {{ $value | humanizeDuration }}"

  - alert: SSLCertExpiringCritical
    expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 7
    for: 10m
    labels:
      severity: critical
    annotations:
      summary: "证书即将过期: {{ $labels.instance }}"
      description: "剩余 {{ $value | humanizeDuration }},立即续期!"

八、RSA vs ECDSA 证书选型

特性 RSA 2048 RSA 4096 ECDSA P-256 ECDSA P-384
等效安全强度 112 bit 128+ bit 128 bit 192 bit
公钥大小 256 bytes 512 bytes 64 bytes 96 bytes
签名大小 256 bytes 512 bytes 64 bytes 96 bytes
TLS 握手性能 基准 慢 2-3 倍 快 2-5 倍 快 1-3 倍
兼容性 最广 广 良好 良好
推荐 兼容性优先 非必要 新部署首选 高安全场景
# 生成 ECDSA 密钥对(推荐 P-256)
openssl ecparam -genkey -name prime256v1 | \
  openssl ec -out ecdsa.key

# 对比 RSA 和 ECDSA 的签名性能
openssl speed rsa2048 ecdsap256
# rsa 2048 bits:  sign: 1200/s  verify: 42000/s
# ecdsa P-256:    sign: 35000/s verify: 12000/s
# → ECDSA 签名快 29 倍(TLS 握手中服务器签名)
# → RSA 验证快 3.5 倍(TLS 握手中客户端验证)
# → 综合考虑,ECDSA 在握手中更高效

# Let's Encrypt 支持 ECDSA 证书
certbot certonly --standalone \
  -d example.com \
  --key-type ecdsa --elliptic-curve secp256r1

九、证书轮换的工程实践

9.1 自动续期的可靠性保障

# 续期失败的常见原因和排查

# 1. 80 端口被占用(Standalone 模式)
ss -tlnp | grep :80
# → 需要临时停止 Web 服务器或使用 Webroot 模式

# 2. DNS 解析问题(DNS 验证模式)
dig _acme-challenge.example.com TXT
# → 检查 DNS API 权限和网络连通性

# 3. 防火墙阻断
# Let's Encrypt 的验证服务器需要访问你的 80/443 端口
# 检查是否有 IP 白名单限制

# 4. 续期后未重载服务
# certbot 支持 deploy hook
certbot renew --deploy-hook "systemctl reload nginx"

# 5. 配置 Cron 定时续期
# 每天凌晨 2 点检查并续期
0 2 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx"

# 验证续期流程
certbot renew --dry-run --verbose

9.2 零停机证书轮换

# Nginx 零停机证书轮换
# 1. 更新证书文件(certbot renew 已完成)
# 2. 热重载 Nginx(不中断现有连接)
nginx -t && nginx -s reload

# 或使用 systemctl
nginx -t && systemctl reload nginx

# 验证新证书已生效
echo | openssl s_client -connect localhost:443 \
  -servername example.com 2>/dev/null | \
  openssl x509 -noout -dates
# 确认 notAfter 是新的日期

十、总结

证书管理看起来简单,但它是整个 HTTPS 安全链中最容易出错的环节:

  1. 自动化是唯一正确的答案。 手动管理证书必然导致过期——这不是”会不会”的问题,是”什么时候”。ACME 协议 + certbot/cert-manager 让证书的签发和续期完全自动化。如果你的证书还是手动管理的,这是最优先需要改进的事项。

  2. 证书链必须完整。 服务器必须发送叶子证书和中间 CA 证书。缺少中间 CA 是最常见的部署错误之一——桌面浏览器可能通过 AIA(Authority Information Access)自动下载中间 CA,但移动设备和命令行工具通常不会。

  3. ECDSA 证书应成为新部署的默认选择。 P-256 ECDSA 证书的公钥只有 64 字节(RSA-2048 是 256 字节),签名速度快 29 倍。更小的证书意味着更少的网络传输,更快的签名意味着更低的 CPU 开销。

  4. 监控先于一切。 在证书过期之前 30 天就应该触发告警。用 Blackbox Exporter + Prometheus 对所有外部端点做证书监控。用脚本定期检查内部服务的证书。

  5. 私有 CA 用 step-ca,不要手动 OpenSSL。 手动用 OpenSSL 管理 CA 适合理解原理,但生产环境需要 ACME 支持、自动续期、审计日志——step-ca 提供了这些开箱即用的能力。

下一篇我们进入 mTLS 工程实践——服务间双向认证的证书分发挑战、SPIFFE/SPIRE 的身份模型,以及 Service Mesh 中 mTLS 的落地方案。


上一篇:TLS 1.3 工程实践:1-RTT 与 0-RTT 的安全权衡

下一篇:mTLS 工程实践:服务间双向认证

同主题继续阅读

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

2025-08-05 · network

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

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

2025-07-31 · network

【网络工程】加密 DNS:DoH、DoT 与 DoQ 的工程部署

DNS 明文传输让运营商、WiFi 热点运营者和网络中间人能够窃听和篡改 DNS 查询。DoH、DoT、DoQ 三种加密 DNS 协议各自解决了不同场景的需求。本文对比三种协议的技术细节、性能差异和部署方式,分析企业环境中加密 DNS 的选型决策与落地实践。


By .