Cấu hình TTL và Renew Window cho chứng chỉ trong Vault
Bước đầu tiên là điều chỉnh thời gian sống (TTL) và cửa sổ gia hạn (Renew Window) của chứng chỉ. Việc này đảm bảo chứng chỉ không bị hết hạn đột ngột và hệ thống có đủ thời gian để làm mới trước khi hết hạn.
Chúng ta sẽ sử dụng lệnh vault secrets tune để cập nhật cấu hình cho secret engine PKI đã được tạo ở các phần trước.
Giả định secret engine PKI của bạn có đường dẫn là pkivault. Chúng ta sẽ thiết lập max_lease_ttl lên 8760h (1 năm) và default_lease_ttl lên 720h (30 ngày) để phù hợp với mô hình gia hạn tự động thường xuyên.
vault secrets tune -max-lease-ttl=8760h -default-lease-ttl=720h pkivault
Kết quả mong đợi: Vault trả về thông báo thành công Successfully tuned mount: pkivault.
Tiếp theo, chúng ta cần cấu hình renew_window cho từng role cụ thể. Đây là khoảng thời gian trước khi chứng chỉ hết hạn mà hệ thống bắt đầu quá trình gia hạn. Giá trị mặc định là 30% TTL, nhưng với Kubernetes và Caddy, ta nên đặt cố định để đảm bảo an toàn.
vault write pkivault/roles/web-tls \
ttl=720h \
max_ttl=4320h \
renew_window=24h \
allowed_domains="*.yourdomain.com" \
allow_subdomains=true \
allow_wildcards=false \
key_type="ec" \
key_bits=256
Kết quả mong đợi: Vault xác nhận đã tạo hoặc cập nhật role web-tls với tham số renew_window là 24h.
Để kiểm tra cấu hình đã áp dụng chưa, hãy đọc lại thông tin của role:
vault read pkivault/roles/web-tls
Kết quả mong đợi: Trong đầu ra, bạn thấy dòng renew_window = "24h" và ttl = "720h".
Triển khai Job để kiểm tra và gia hạn chứng chỉ
Để tự động hóa việc gia hạn, chúng ta cần một Kubernetes Job chạy định kỳ (CronJob). Job này sẽ kiểm tra thời gian còn lại của chứng chỉ và gọi API của Vault để làm mới nếu cần.
Chúng ta sẽ tạo một CronJob chạy mỗi 15 phút để kiểm tra chứng chỉ. Script bên trong container sẽ thực hiện logic: lấy chứng chỉ hiện tại -> tính thời gian còn lại -> nếu nhỏ hơn renew_window thì gọi vault renew.
Tạo file định nghĩa CronJob với đường dẫn /opt/pki-renewal/cronjob.yaml:
apiVersion: batch/v1
kind: CronJob
metadata:
name: pki-renewal-job
namespace: cert-manager
spec:
schedule: "*/15 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: renewer
image: hashicorp/vault:1.15.0
command:
- /bin/sh
- -c
args:
- |
# Set Vault environment variables
export VAULT_ADDR="https://vault-server.vault.svc.cluster.local:8200"
export VAULT_TOKEN="$(cat /vault-token/token)"
# Define certificate path and role
CERT_PATH="pkivault/cert/web-tls/cert-common"
RENEW_WINDOW_SECONDS=86400 # 24h in seconds
echo "Checking certificate at $CERT_PATH..."
# Get the current certificate expiration
# We use the 'renew' command with -help to just get status or use the API directly
# Here we simulate the logic: Check if renewal is needed based on lease ID
# Fetch the current lease info (requires the lease ID from the issued cert)
# In a real scenario, you store the lease_id in a ConfigMap or Secret
# For this tutorial, we assume we have a secret 'cert-lease-info' with the lease_id
LEASE_ID=$(cat /vault-lease-info/lease_id)
if [ -z "$LEASE_ID" ]; then
echo "ERROR: No lease ID found. Cannot renew."
exit 1
fi
# Check if renewal is needed
# vault lease lookup returns the lease details including expiration
# We parse the output to check remaining time
EXPIRY=$(vault lease lookup -format=json $LEASE_ID | jq -r '.data.renewable')
if [ "$EXPIRY" = "true" ]; then
echo "Certificate is renewable. Checking time to expiry..."
# Get remaining TTL
REMAINING_TTL=$(vault lease lookup -format=json $LEASE_ID | jq -r '.data.ttl')
if [ "$REMAINING_TTL" -lt "$RENEW_WINDOW_SECONDS" ]; then
echo "Certificate expires in $REMAINING_TTL seconds. Renewing now..."
# Perform renewal
vault renew -format=json $LEASE_ID > /tmp/renew_result.json
RESULT=$(cat /tmp/renew_result.json | jq -r '.data.new_lease_id')
if [ -n "$RESULT" ]; then
echo "Renewal successful. New Lease ID: $RESULT"
# Update the lease_id file for next run
echo "$RESULT" > /vault-lease-info/lease_id
else
echo "ERROR: Renewal failed."
exit 1
fi
else
echo "Certificate is still valid for another $REMAINING_TTL seconds. Skipping renewal."
fi
else
echo "Certificate is not renewable (max_ttl reached or non-renewable role). Issuing new one."
# Logic to issue new cert would go here if needed
fi
volumeMounts:
- name: vault-token
mountPath: /vault-token
readOnly: true
- name: vault-lease-info
mountPath: /vault-lease-info
- name: vault-ca
mountPath: /etc/vault/certs
readOnly: true
volumes:
- name: vault-token
secret:
secretName: vault-token
- name: vault-lease-info
secret:
secretName: cert-lease-info
- name: vault-ca
configMap:
name: vault-ca-cert
restartPolicy: OnFailure
Kết quả mong đợi: CronJob được tạo thành công trong namespace cert-manager.
Áp dụng cấu hình vào cluster:
kubectl apply -f /opt/pki-renewal/cronjob.yaml
Kết quả mong đợi: Kubernetes trả về cronjob.batch "pki-renewal-job" created.
Để verify, kiểm tra logs của Job lần chạy gần nhất:
kubectl logs -l app=pki-renewal-job --tail=50 -n cert-manager
Kết quả mong đợi: Logs hiển thị quá trình kiểm tra TTL và thông báo Renewal successful hoặc Skiping renewal tùy thuộc vào thời gian còn lại.
Xử lý trường hợp chứng chỉ bị từ chối hoặc lỗi
Trong quá trình tự động, chứng chỉ có thể bị từ chối do lỗi cấu hình, hết giới hạn số lần gia hạn, hoặc lỗi kết nối Vault. Chúng ta cần cơ chế xử lý lỗi để không để hệ thống rơi vào trạng thái "hết hạn vĩnh viễn".
Chiến lược: Nếu lệnh vault renew thất bại (exit code != 0), script sẽ không dừng lại mà chuyển sang chế độ "Issuing new certificate" (Cấp phát chứng chỉ mới) để thay thế chứng chỉ cũ đã lỗi.
Cập nhật logic trong CronJob (phần args của container) để xử lý lỗi:
args:
- |
export VAULT_ADDR="https://vault-server.vault.svc.cluster.local:8200"
export VAULT_TOKEN="$(cat /vault-token/token)"
CERT_PATH="pkivault/cert/web-tls/cert-common"
LEASE_ID=$(cat /vault-lease-info/lease_id 2>/dev/null || echo "")
echo "Starting renewal check..."
if [ -z "$LEASE_ID" ]; then
echo "No existing lease. Issuing new certificate."
vault write -format=json pkivault/roles/web-tls \
common_name="app.yourdomain.com" \
ttl=720h > /tmp/new_cert.json
NEW_LEASE=$(jq -r '.data.lease_id' /tmp/new_cert.json)
echo "$NEW_LEASE" > /vault-lease-info/lease_id
echo "New certificate issued. Lease ID: $NEW_LEASE"
exit 0
fi
# Attempt Renewal
echo "Attempting renewal for lease: $LEASE_ID"
if vault renew $LEASE_ID > /tmp/renew_result.json 2>&1; then
RESULT=$(cat /tmp/renew_result.json | jq -r '.data.new_lease_id')
if [ -n "$RESULT" ]; then
echo "Renewal successful. New Lease ID: $RESULT"
echo "$RESULT" > /vault-lease-info/lease_id
else
echo "Renewal command returned empty result. Fallback to re-issuing."
# Fallback logic
vault write -format=json pkivault/roles/web-tls \
common_name="app.yourdomain.com" \
ttl=720h > /tmp/new_cert.json
NEW_LEASE=$(jq -r '.data.lease_id' /tmp/new_cert.json)
echo "$NEW_LEASE" > /vault-lease-info/lease_id
echo "Fallback: New certificate issued. Lease ID: $NEW_LEASE"
fi
else
echo "Renewal failed. Error log:"
cat /tmp/renew_result.json
echo "Falling back to issuing a new certificate..."
# Fallback logic
vault write -format=json pkivault/roles/web-tls \
common_name="app.yourdomain.com" \
ttl=720h > /tmp/new_cert.json
NEW_LEASE=$(jq -r '.data.lease_id' /tmp/new_cert.json)
echo "$NEW_LEASE" > /vault-lease-info/lease_id
echo "Fallback: New certificate issued. Lease ID: $NEW_LEASE"
fi
Kết quả mong đợi: Nếu lệnh renew bị lỗi, script sẽ in ra log Falling back to issuing a new certificate... và tạo một chứng chỉ mới hoàn toàn thay vì để chứng chỉ cũ hết hạn.
Để kiểm tra cơ chế fallback, hãy cố tình tạo lỗi bằng cách thử gia hạn một chứng chỉ đã hết hạn quá lâu (vượt quá max_ttl) hoặc tạm dừng Vault, sau đó chạy lại Job và xem log có chuyển sang phát hành chứng chỉ mới không.
Tích hợp Alerting với Prometheus và Grafana
Để chủ động cảnh báo khi chứng chỉ sắp hết hạn, chúng ta cần xuất metric từ Vault sang Prometheus. Vault cung cấp endpoint metrics để thu thập thông tin về chứng chỉ.
Tuy nhiên, để có cảnh báo cụ thể về từng chứng chỉ (chứ không chỉ tổng quan), cách tốt nhất là sử dụng vault-agent hoặc một exporter tùy chỉnh để scrape thông tin và đẩy lên Prometheus. Ở đây, chúng ta sẽ cấu hình Prometheus để scrape metrics từ Vault và viết rule cảnh báo.
Đầu tiên, cấu hình Prometheus để scrape endpoint metrics của Vault. Giả định Vault expose metrics ở port 8201.
Tạo file /etc/prometheus/prometheus.yml (cập nhật phần scrape_configs):
global:
scrape_interval: 1m
evaluation_interval: 1m
scrape_configs:
- job_name: 'vault'
static_configs:
- targets: ['vault-server.vault.svc.cluster.local:8201']
metrics_path: '/metrics'
scheme: 'https'
tls_config:
ca_file: /etc/prometheus/certs/vault-ca.pem
basic_auth:
username: 'prometheus'
password: 'prometheus-secret'
Kết quả mong đợi: Prometheus sẽ bắt đầu thu thập metric từ Vault.
Tiếp theo, chúng ta cần một cách để Prometheus biết được thời gian hết hạn của chứng chỉ cụ thể. Vault metrics mặc định chỉ cung cấp thông tin tổng quát. Để có dữ liệu chi tiết, ta sẽ tạo một Job trong Prometheus (hoặc dùng Blackbox Exporter) để gọi API Vault và parse thời gian hết hạn.
Tuy nhiên, giải pháp đơn giản và hiệu quả nhất trong Kubernetes là sử dụng cert-manager (nếu có) hoặc tự viết một small exporter. Ở đây, ta giả định ta đã có metric vault_pki_cert_expiry_seconds (được tạo bởi Vault metrics hoặc custom exporter).
Tạo file alert rule /etc/prometheus/rules/pki-alerts.yml:
groups:
- name: pki-alerts
rules:
- alert: PKICertificateExpiringSoon
expr: vault_pki_cert_expiry_seconds < 86400
for: 5m
labels:
severity: warning
annotations:
summary: "PKI Certificate expiring soon"
description: "Certificate for {{ $labels.common_name }} will expire in {{ $value | humanizeDuration }}."
- alert: PKICertificateExpiringCritical
expr: vault_pki_cert_expiry_seconds < 43200
for: 5m
labels:
severity: critical
annotations:
summary: "PKI Certificate expiring critically soon"
description: "Certificate for {{ $labels.common_name }} will expire in {{ $value | humanizeDuration }}. Immediate action required."
- alert: PKICertificateRenewalFailed
expr: vault_pki_renewal_failures_total > 0
for: 1m
labels:
severity: critical
annotations:
summary: "PKI Certificate Renewal Failed"
description: "Renewal process for {{ $labels.role }} has failed {{ $value }} times."
Kết quả mong đợi: Prometheus load thành công rule mới.
Áp dụng rule vào Prometheus:
kubectl exec -it prometheus-server -- curl -X POST http://localhost:9090/-/reload
Kết quả mong đợi: Prometheus reload thành công và bắt đầu đánh giá rule.
Để verify kết quả, truy cập vào Prometheus UI (port 9090) -> вкладка "Alerting". Bạn sẽ thấy các alert đang ở trạng thái Pending hoặc Firing nếu có chứng chỉ sắp hết hạn.
Đồng thời, trong Grafana, tạo một Dashboard mới, thêm datasource Prometheus, và vẽ biểu đồ cho metric vault_pki_cert_expiry_seconds theo common_name. Khi giá trị này giảm xuống dưới ngưỡng, bảng cảnh báo sẽ sáng lên.
Điều hướng series:
Mục lục: Series: Series: Xây dựng hệ thống quản lý mật mã và chứng chỉ số tự động (Automated PKI & Certificate Management) với HashiCorp Vault, Caddy và Kubernetes
« Phần 4: Tích hợp Kubernetes và Vault để cấp phát chứng chỉ tự động
Phần 6: Bảo mật nâng cao và xử lý sự cố cho hệ thống PKI tự động »