憑證輪換概述
TLS 憑證是保護網路通訊安全的重要元件,但憑證具有有效期限,過期的憑證會導致服務中斷。憑證輪換(Certificate Rotation)是指在憑證到期前,以新憑證取代舊憑證的過程。
傳統手動輪換憑證的方式不僅耗時費力,還容易因人為疏忽導致服務中斷。因此,建立自動化的憑證輪換機制是現代基礎架構管理的重要課題。
為什麼需要自動化輪換
自動化憑證輪換帶來以下優勢:
- 降低人為錯誤:消除手動操作可能產生的疏漏
- 提升安全性:縮短憑證有效期,降低私鑰外洩風險
- 確保服務持續性:避免因憑證過期導致的服務中斷
- 符合合規要求:滿足企業安全政策對憑證管理的規範
- 減少維運負擔:釋放維運人員處理重複性工作的時間
輪換策略設計
設計憑證輪換策略時,需考慮以下要點:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 憑證輪換策略配置範例
rotation_policy:
# 在到期前多久進行輪換
renewal_threshold: 30d
# 輪換失敗時的重試間隔
retry_interval: 6h
# 最大重試次數
max_retries: 5
# 輪換完成後的驗證等待時間
validation_delay: 60s
# 是否保留舊憑證作為備份
keep_old_cert: true
backup_retention: 7d
|
Let’s Encrypt 自動續約
Let’s Encrypt 提供免費的 TLS 憑證,搭配 Certbot 可實現自動續約。
安裝 Certbot
1
2
3
4
5
6
| # Ubuntu/Debian
sudo apt update
sudo apt install certbot python3-certbot-nginx
# CentOS/RHEL
sudo dnf install certbot python3-certbot-nginx
|
設定自動續約
1
2
3
4
5
| # 測試續約流程
sudo certbot renew --dry-run
# 設定 cron job 自動執行續約
sudo crontab -e
|
1
2
| # 每天凌晨 2:30 執行續約檢查
30 2 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"
|
續約腳本範例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| #!/bin/bash
# /usr/local/bin/cert-renewal.sh
LOG_FILE="/var/log/certbot-renewal.log"
DOMAIN="example.com"
echo "[$(date)] Starting certificate renewal check" >> $LOG_FILE
certbot renew --cert-name $DOMAIN \
--deploy-hook "/usr/local/bin/post-renewal.sh" \
>> $LOG_FILE 2>&1
if [ $? -eq 0 ]; then
echo "[$(date)] Renewal check completed successfully" >> $LOG_FILE
else
echo "[$(date)] Renewal check failed" >> $LOG_FILE
# 發送告警通知
/usr/local/bin/send-alert.sh "Certificate renewal failed for $DOMAIN"
fi
|
Nginx 憑證熱重載
Nginx 支援不停機重載設定,可在更新憑證後立即生效。
1
2
3
4
5
6
7
8
| # 驗證 Nginx 設定
sudo nginx -t
# 熱重載 Nginx(不中斷現有連線)
sudo nginx -s reload
# 或使用 systemctl
sudo systemctl reload nginx
|
自動化熱重載腳本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| #!/bin/bash
# /usr/local/bin/post-renewal.sh
# 驗證新憑證
openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -dates
# 測試 Nginx 設定
nginx -t
if [ $? -ne 0 ]; then
echo "Nginx configuration test failed"
exit 1
fi
# 執行熱重載
nginx -s reload
echo "Nginx reloaded with new certificate"
|
Kubernetes cert-manager
cert-manager 是 Kubernetes 叢集中管理憑證的標準解決方案。
安裝 cert-manager
1
2
3
4
5
6
7
8
| # 使用 Helm 安裝
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set installCRDs=true
|
ClusterIssuer 設定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| 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 資源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| 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
commonName: example.com
dnsNames:
- example.com
- www.example.com
# 憑證輪換設定
renewBefore: 720h # 30 天前開始續約
duration: 2160h # 90 天有效期
|
HashiCorp Vault PKI
Vault PKI 引擎提供企業級的憑證管理能力。
啟用 PKI 引擎
1
2
3
4
5
6
7
8
9
10
| # 啟用 PKI secrets engine
vault secrets enable pki
# 設定最大 TTL
vault secrets tune -max-lease-ttl=87600h pki
# 產生根憑證
vault write pki/root/generate/internal \
common_name="Example Root CA" \
ttl=87600h
|
設定憑證角色
1
2
3
4
5
6
7
| vault write pki/roles/example-dot-com \
allowed_domains="example.com" \
allow_subdomains=true \
max_ttl="720h" \
ttl="72h" \
key_type="rsa" \
key_bits=2048
|
自動輪換設定
1
2
3
4
5
6
7
8
9
10
11
12
13
| # Vault Agent 設定
template {
source = "/etc/vault-agent/templates/cert.tpl"
destination = "/etc/ssl/certs/example.crt"
perms = 0644
command = "systemctl reload nginx"
}
template {
source = "/etc/vault-agent/templates/key.tpl"
destination = "/etc/ssl/private/example.key"
perms = 0600
}
|
監控與告警
建立完善的監控機制,確保及時發現憑證問題。
Prometheus 監控指標
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # Prometheus 告警規則
groups:
- name: certificate-alerts
rules:
- alert: CertificateExpiringSoon
expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 14
for: 1h
labels:
severity: warning
annotations:
summary: "憑證即將在 14 天內過期"
description: "{{ $labels.instance }} 的憑證將在 {{ $value | humanizeDuration }} 後過期"
- alert: CertificateExpired
expr: probe_ssl_earliest_cert_expiry - time() < 0
for: 0m
labels:
severity: critical
annotations:
summary: "憑證已過期"
description: "{{ $labels.instance }} 的憑證已經過期"
|
憑證到期檢查腳本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| #!/bin/bash
# /usr/local/bin/check-cert-expiry.sh
DOMAINS=("example.com" "api.example.com" "app.example.com")
WARN_DAYS=30
CRIT_DAYS=7
for domain in "${DOMAINS[@]}"; do
expiry_date=$(echo | openssl s_client -servername $domain -connect $domain:443 2>/dev/null | \
openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
expiry_epoch=$(date -d "$expiry_date" +%s)
current_epoch=$(date +%s)
days_left=$(( ($expiry_epoch - $current_epoch) / 86400 ))
if [ $days_left -lt $CRIT_DAYS ]; then
echo "CRITICAL: $domain certificate expires in $days_left days"
elif [ $days_left -lt $WARN_DAYS ]; then
echo "WARNING: $domain certificate expires in $days_left days"
else
echo "OK: $domain certificate expires in $days_left days"
fi
done
|
最佳實踐
- 縮短憑證有效期:建議使用 90 天或更短的有效期,降低私鑰洩漏風險
- 提前輪換:在到期前 30 天開始嘗試輪換,預留足夠的錯誤處理時間
- 多重驗證:輪換後執行憑證鏈驗證、連線測試等多項檢查
- 保留備份:保留舊憑證一段時間,以便必要時回滾
- 集中管理:使用 cert-manager 或 Vault 集中管理所有憑證
- 完善監控:建立多層次的監控告警機制
- 文件記錄:維護憑證清單,記錄各憑證的用途與負責人
- 定期演練:定期測試輪換流程,確保自動化機制正常運作
參考資料