mcpField
S
ssl-tls-management

ssl-tls-management

github.com/bagelhole安全1.2k426 次安装23 周前更新
代码仓库

关于 ssl-tls-management

面向管理 Web 服务器、Kubernetes 集群和内部服务 TLS 的操作指南,涵盖证书签发、续期、配置与监控。适合负责 HTTPS 和服务间安全通信的运维人员。

主要功能

  1. 01使用 Certbot 签发和续期证书
  2. 02在 Kubernetes 部署 cert-manager
  3. 03生成密钥并检查证书链
  4. 04配置 Nginx 与 Apache TLS
  5. 05监控证书到期并设置告警

适用场景

  1. 01为新 Web 应用启用 HTTPS
  2. 02自动续期 Let's Encrypt 证书
  3. 03为 Kubernetes 服务部署证书
  4. 04排查即将到期或受损证书

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/bagelhole/devops-security-agent-skills --skill ssl-tls-management

安装后在 Claude Code 中输入/ssl-tls-management确认已加载。

详细介绍

SSL/TLS 管理

管理 Web 服务器、Kubernetes 集群和内部服务中的证书与安全通信。

适用场景

在以下情况下使用此技能:

  • 为新的 Web 应用设置 HTTPS
  • 使用 Let's Encrypt 自动续期证书
  • 在 Kubernetes 中部署 cert-manager
  • 配置内部服务之间通信的 TLS
  • 审计密码套件和 TLS 版本是否符合规范
  • 处理即将过期或已遭泄露的证书

前提条件

  • 用于申请公共证书且可控制 DNS 的域名
  • Web 服务器上的 root/sudo 访问权限
  • 已安装用于 Let's Encrypt 的 certbot
  • 可用的 openssl CLI(大多数 Linux 发行版默认已安装)
  • 用于部署 cert-manager 的 Kubernetes 集群和 Helm
  • 了解 X.509 证书信任链

使用 Certbot 配置 Let's Encrypt

安装和签发证书
# Install certbot (Ubuntu/Debian)
apt update && apt install -y certbot python3-certbot-nginx

# Obtain certificate for nginx (interactive)
certbot --nginx -d example.com -d www.example.com

# Non-interactive mode for automation
certbot certonly --nginx \
  -d example.com \
  -d www.example.com \
  --non-interactive \
  --agree-tos \
  --email [email protected]

# Standalone mode (when no web server is running)
certbot certonly --standalone \
  -d example.com \
  --preferred-challenges http

# DNS challenge (for wildcard certs)
certbot certonly --manual \
  --preferred-challenges dns \
  -d "*.example.com" \
  -d example.com

# Using DNS plugin for automation (Cloudflare example)
pip install certbot-dns-cloudflare
cat > /etc/letsencrypt/cloudflare.ini << 'EOF'
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
EOF
chmod 600 /etc/letsencrypt/cloudflare.ini

certbot certonly --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  -d "*.example.com" \
  -d example.com
自动续期
# Test renewal
certbot renew --dry-run

# Systemd timer (preferred over cron)
cat > /etc/systemd/system/certbot-renewal.service << 'EOF'
[Unit]
Description=Certbot certificate renewal
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"
EOF

cat > /etc/systemd/system/certbot-renewal.timer << 'EOF'
[Unit]
Description=Run certbot renewal twice daily

[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=3600
Persistent=true

[Install]
WantedBy=timers.target
EOF

systemctl enable --now certbot-renewal.timer

# Verify timer is active
systemctl list-timers certbot-renewal.timer

# Renewal hooks for post-renewal actions
mkdir -p /etc/letsencrypt/renewal-hooks/deploy
cat > /etc/letsencrypt/renewal-hooks/deploy/reload-services.sh << 'HOOK'
#!/bin/bash
systemctl reload nginx
# Also reload other services using the cert
systemctl reload haproxy 2>/dev/null || true
HOOK
chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-services.sh

用于 Kubernetes 的 cert-manager

安装
# Install with Helm
helm repo add jetstack https://charts.jetstack.io
helm repo update

helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.14.0 \
  --set installCRDs=true \
  --set prometheus.enabled=true

# Verify installation
kubectl get pods -n cert-manager
kubectl get crds | grep cert-manager
ClusterIssuer 配置
# letsencrypt-staging (use for testing first)
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-staging
    solvers:
      - http01:
          ingress:
            class: nginx
---
# letsencrypt-prod
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
      - http01:
          ingress:
            class: nginx
---
# DNS challenge solver (for wildcard certs with Cloudflare)
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod-dns
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-prod-dns
    solvers:
      - dns01:
          cloudflare:
            apiTokenSecretRef:
              name: cloudflare-api-token
              key: api-token
---
# Self-signed CA issuer for internal services
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned-ca
spec:
  selfSigned: {}
Certificate 资源
# Public-facing certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-cert
  namespace: default
spec:
  secretName: example-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - example.com
    - www.example.com
  duration: 2160h    # 90 days
  renewBefore: 720h  # 30 days before expiry
---
# Wildcard certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-cert
  namespace: default
spec:
  secretName: wildcard-tls
  issuerRef:
    name: letsencrypt-prod-dns
    kind: ClusterIssuer
  dnsNames:
    - "*.example.com"
    - example.com
---
# Ingress with automatic TLS
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - example.com
      secretName: example-tls
  rules:
    - host: example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

OpenSSL 命令参考

# Generate a private key
openssl genrsa -out server.key 4096

# Generate an ECDSA key (preferred for performance)
openssl ecparam -genkey -name prime256v1 -out server-ec.key

# Generate a CSR (Certificate Signing Request)
openssl req -new -key server.key -out server.csr \
  -subj "/C=US/ST=California/L=San Francisco/O=Acme Corp/CN=example.com"

# Generate CSR with SAN (Subject Alternative Names)
openssl req -new -key server.key -out server.csr -config <(cat <<EOF
[req]
default_bits = 4096
distinguished_name = dn
req_extensions = san
prompt = no

[dn]
CN = example.com
O = Acme Corp
C = US

[san]
subjectAltName = DNS:example.com,DNS:www.example.com,DNS:api.example.com
EOF
)

# Generate self-signed certificate (development/testing)
openssl req -x509 -nodes -days 365 -newkey rsa:4096 \
  -keyout selfsigned.key -out selfsigned.crt \
  -subj "/CN=localhost"

# View certificate details
openssl x509 -in cert.pem -noout -text

# Check certificate expiration date
openssl x509 -in cert.pem -noout -dates

# Check remote certificate
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
  openssl x509 -noout -dates -subject -issuer

# Verify certificate chain
openssl verify -CAfile ca-bundle.crt server.crt

# Check certificate chain from remote server
openssl s_client -connect example.com:443 -showcerts 2>/dev/null | \
  openssl x509 -noout -text

# Convert PEM to PKCS12
openssl pkcs12 -export -out cert.pfx -inkey server.key -in server.crt -certfile ca.crt

# Convert PKCS12 to PEM
openssl pkcs12 -in cert.pfx -out cert.pem -nodes

# Test TLS connection and cipher negotiation
openssl s_client -connect example.com:443 -tls1_3
openssl s_client -connect example.com:443 -cipher 'ECDHE-RSA-AES256-GCM-SHA384'

强 TLS 配置

Nginx
server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Protocol versions
    ssl_protocols TLSv1.2 TLSv1.3;

    # Cipher suites (TLS 1.2)
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    # Session settings
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    # Security headers
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Content-Type-Options nosniff always;
    add_header X-Frame-Options DENY always;

    # HTTP to HTTPS redirect (in separate server block)
}

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}
Apache
<VirtualHost *:443>
    ServerName example.com

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem

    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
    SSLHonorCipherOrder off

    SSLUseStapling on
    SSLStaplingCache shmcb:/tmp/stapling_cache(128000)

    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
</VirtualHost>

证书监控

#!/bin/bash
# cert-monitor.sh - Monitor certificate expiration across hosts

WARN_DAYS=30
CRIT_DAYS=7
HOSTS=(
  "example.com:443"
  "api.example.com:443"
  "admin.example.com:443"
)

for host in "${HOSTS[@]}"; do
  expiry=$(echo | openssl s_client -connect "$host" -servername "${host%%:*}" 2>/dev/null | \
    openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)

  if [ -z "$expiry" ]; then
    echo "ERROR: Cannot connect to $host"
    continue
  fi

  expiry_epoch=$(date -d "$expiry" +%s)
  now_epoch=$(date +%s)
  days_left=$(( (expiry_epoch - now_epoch) / 86400 ))

  if [ "$days_left" -le "$CRIT_DAYS" ]; then
    echo "CRITICAL: $host expires in $days_left days ($expiry)"
  elif [ "$days_left" -le "$WARN_DAYS" ]; then
    echo "WARNING: $host expires in $days_left days ($expiry)"
  else
    echo "OK: $host expires in $days_left days ($expiry)"
  fi
done
Prometheus cert-manager 指标
# Alert on expiring certificates in Kubernetes
groups:
  - name: cert-manager
    rules:
      - alert: CertificateExpiringSoon
        expr: certmanager_certificate_expiration_timestamp_seconds - time() < 7 * 24 * 3600
        for: 1h
        labels:
          severity: critical
        annotations:
          summary: "Certificate {{ $labels.name }} expires in less than 7 days"

      - alert: CertificateNotReady
        expr: certmanager_certificate_ready_status{condition="True"} == 0
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "Certificate {{ $labels.name }} is not ready"

故障排查

问题原因解决方案
Certbot 因“connection refused”失败防火墙阻止了端口 80开放端口 80,以通过 ACME HTTP-01 挑战
“Too many certificates already issued”达到 Let's Encrypt 速率限制测试时使用 staging 端点;等待速率限制重置
cert-manager 挑战一直处于 pending 状态Ingress 或 DNS 配置错误检查 kubectl describe challenge;验证 DNS 记录
混合内容警告HTTPS 页面中包含 HTTP 资源将所有资源 URL 更新为 HTTPS;使用 CSP 标头
OCSP 装订未生效未配置解析器在 nginx 中添加 resolver 指令;验证出站 DNS
缺少中间证书提供的证书链不完整使用 fullchain.pem 而非 cert.pem;使用 openssl s_client -showcerts 验证
TLS 握手失败客户端不支持所提供的密码套件添加 TLS 1.2 支持;检查密码套件兼容性

最佳实践

  • 使用 systemd 定时器或 cert-manager 自动续期
  • 通过告警监控证书到期日期(提前 30 天和 7 天告警)
  • 仅使用 TLS 1.2 和 TLS 1.3
  • 启用 HSTS,设置较长的 max-age 并包含子域名
  • 启用 OCSP 装订以提升握手性能
  • 尽可能使用 ECDSA 密钥以获得更好的性能
  • 使用 SSL Labs(ssllabs.com/ssltest)测试配置
  • 设置适当的文件权限(0600)以保护私钥
  • 在证书到期前轮换,而不是到期后
  • 维护所有服务的证书清单

相关技能

评论

支持 Markdown