HashiCorp Vault PKI Certificate Management
HashiCorp Vault 提供了強大的 PKI Secret Engine,可以動態產生 X.509 憑證,實現企業級的憑證管理。本文將介紹如何使用 Vault 建立完整的 PKI 架構,包含 Root CA、Intermediate CA 的設定,以及憑證的簽發、吊銷與自動輪換。
Vault PKI 概述
什麼是 Vault PKI?
Vault PKI Secret Engine 是 HashiCorp Vault 內建的憑證管理解決方案,它可以:
- 動態簽發憑證:根據需求即時產生短期憑證
- 自動化憑證管理:減少手動操作,降低人為錯誤
- 集中式管理:統一管理所有 CA 和憑證
- 細粒度權限控制:透過 Vault 政策管理憑證簽發權限
- 完整稽核記錄:追蹤所有憑證操作
PKI 架構設計
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| +------------------+
| Root CA | <-- 離線保存,僅用於簽發 Intermediate CA
| (Vault PKI) |
+--------+---------+
|
+--------v---------+
| Intermediate CA | <-- 線上運作,用於簽發終端憑證
| (Vault PKI) |
+--------+---------+
|
+--------v---------+
| End Entity | <-- 伺服器/用戶端憑證
| Certificates |
+------------------+
|
啟用 PKI Secret Engine
啟用 Root CA 的 PKI Engine
1
2
3
4
5
| # 啟用 PKI Secret Engine 作為 Root CA
vault secrets enable -path=pki pki
# 設定 Root CA 的最大 TTL(10 年)
vault secrets tune -max-lease-ttl=87600h pki
|
1
2
3
4
5
| # 啟用另一個 PKI Secret Engine 作為 Intermediate CA
vault secrets enable -path=pki_int pki
# 設定 Intermediate CA 的最大 TTL(5 年)
vault secrets tune -max-lease-ttl=43800h pki_int
|
設定 Root CA
產生 Root CA 憑證
1
2
3
4
5
6
7
8
| # 產生 Root CA 憑證(內部產生金鑰)
vault write pki/root/generate/internal \
common_name="Example Root CA" \
organization="Example Corp" \
country="TW" \
ttl=87600h \
key_bits=4096 \
exclude_cn_from_sans=true
|
設定 CRL 和 CA 端點
1
2
3
4
| # 設定 CRL 發布點和 CA 憑證下載位置
vault write pki/config/urls \
issuing_certificates="https://vault.example.com:8200/v1/pki/ca" \
crl_distribution_points="https://vault.example.com:8200/v1/pki/crl"
|
匯出 Root CA 憑證
1
2
3
4
5
| # 取得 Root CA 憑證(用於信任設定)
vault read -field=certificate pki/cert/ca > root_ca.crt
# 檢視 Root CA 憑證內容
openssl x509 -in root_ca.crt -text -noout
|
1
2
3
4
5
6
7
| # 產生 Intermediate CA 的 CSR
vault write -format=json pki_int/intermediate/generate/internal \
common_name="Example Intermediate CA" \
organization="Example Corp" \
country="TW" \
key_bits=4096 \
| jq -r '.data.csr' > intermediate.csr
|
1
2
3
4
5
6
| # 使用 Root CA 簽發 Intermediate CA 憑證
vault write -format=json pki/root/sign-intermediate \
csr=@intermediate.csr \
format=pem_bundle \
ttl=43800h \
| jq -r '.data.certificate' > intermediate.crt
|
1
2
3
| # 將簽發的憑證設定到 Intermediate CA
vault write pki_int/intermediate/set-signed \
certificate=@intermediate.crt
|
1
2
3
4
| # 設定 Intermediate CA 的 CRL 和憑證端點
vault write pki_int/config/urls \
issuing_certificates="https://vault.example.com:8200/v1/pki_int/ca" \
crl_distribution_points="https://vault.example.com:8200/v1/pki_int/crl"
|
建立 PKI Role
Role 定義了憑證簽發的規則和限制。
建立伺服器憑證 Role
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 建立用於簽發伺服器憑證的 Role
vault write pki_int/roles/server-cert \
allowed_domains="example.com" \
allow_subdomains=true \
allow_bare_domains=false \
allow_glob_domains=true \
allow_ip_sans=true \
server_flag=true \
client_flag=false \
code_signing_flag=false \
key_type="rsa" \
key_bits=2048 \
key_usage="DigitalSignature,KeyEncipherment" \
ext_key_usage="ServerAuth" \
max_ttl=720h \
ttl=168h
|
建立用戶端憑證 Role
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 建立用於簽發用戶端憑證的 Role
vault write pki_int/roles/client-cert \
allowed_domains="example.com" \
allow_subdomains=true \
allow_any_name=false \
server_flag=false \
client_flag=true \
key_type="rsa" \
key_bits=2048 \
key_usage="DigitalSignature" \
ext_key_usage="ClientAuth" \
max_ttl=168h \
ttl=24h
|
檢視已建立的 Role
1
2
3
4
5
| # 列出所有 Role
vault list pki_int/roles
# 檢視特定 Role 的設定
vault read pki_int/roles/server-cert
|
簽發憑證
簽發伺服器憑證
1
2
3
4
5
6
7
8
9
10
11
12
| # 簽發伺服器憑證
vault write -format=json pki_int/issue/server-cert \
common_name="www.example.com" \
alt_names="api.example.com,web.example.com" \
ip_sans="10.0.0.1,192.168.1.1" \
ttl=720h \
| tee server_cert.json
# 從 JSON 中提取憑證和金鑰
cat server_cert.json | jq -r '.data.certificate' > server.crt
cat server_cert.json | jq -r '.data.private_key' > server.key
cat server_cert.json | jq -r '.data.ca_chain[]' > ca_chain.crt
|
簽發用戶端憑證
1
2
3
4
5
6
7
8
9
| # 簽發用戶端憑證
vault write -format=json pki_int/issue/client-cert \
common_name="user@example.com" \
ttl=24h \
| tee client_cert.json
# 提取用戶端憑證
cat client_cert.json | jq -r '.data.certificate' > client.crt
cat client_cert.json | jq -r '.data.private_key' > client.key
|
驗證簽發的憑證
1
2
3
4
5
| # 檢視憑證資訊
openssl x509 -in server.crt -text -noout
# 驗證憑證鏈
openssl verify -CAfile root_ca.crt -untrusted intermediate.crt server.crt
|
憑證吊銷
吊銷特定憑證
1
2
3
| # 使用序號吊銷憑證
vault write pki_int/revoke \
serial_number="39:dd:2e:90:b7:23:1f:8d:d3:7d:31:c5:1b:da:84:d0:5b:65:31:58"
|
使用憑證內容吊銷
1
2
3
| # 使用憑證 PEM 內容吊銷
vault write pki_int/revoke \
certificate=@server.crt
|
產生和取得 CRL
1
2
3
4
5
6
7
8
| # 手動輪換 CRL
vault write pki_int/crl/rotate
# 取得 CRL
curl -s https://vault.example.com:8200/v1/pki_int/crl/pem > crl.pem
# 檢視 CRL 內容
openssl crl -in crl.pem -text -noout
|
清理已吊銷的憑證
1
2
3
4
5
| # 清理過期的已吊銷憑證(從 CRL 中移除)
vault write pki_int/tidy \
tidy_cert_store=true \
tidy_revoked_certs=true \
safety_buffer="72h"
|
自動輪換
設定自動 Tidy
1
2
3
4
5
6
7
| # 啟用自動 Tidy 功能
vault write pki_int/config/auto-tidy \
enabled=true \
interval_duration="24h" \
tidy_cert_store=true \
tidy_revoked_certs=true \
safety_buffer="72h"
|
設定 CRL 自動輪換
1
2
3
4
5
| # 設定 CRL 過期時間和自動重建
vault write pki_int/config/crl \
expiry="72h" \
auto_rebuild=true \
auto_rebuild_grace_period="12h"
|
監控憑證狀態
1
2
3
4
5
| # 列出所有已簽發的憑證
vault list pki_int/certs
# 檢視特定憑證的狀態
vault read pki_int/cert/<serial_number>
|
與 Kubernetes 整合
使用 Vault Agent Injector
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| # deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "web-app"
vault.hashicorp.com/agent-inject-secret-server.crt: "pki_int/issue/server-cert"
vault.hashicorp.com/agent-inject-template-server.crt: |
{{- with secret "pki_int/issue/server-cert" "common_name=web-app.default.svc" -}}
{{ .Data.certificate }}
{{ .Data.ca_chain | join "\n" }}
{{- end -}}
vault.hashicorp.com/agent-inject-secret-server.key: "pki_int/issue/server-cert"
vault.hashicorp.com/agent-inject-template-server.key: |
{{- with secret "pki_int/issue/server-cert" "common_name=web-app.default.svc" -}}
{{ .Data.private_key }}
{{- end -}}
spec:
serviceAccountName: web-app
containers:
- name: web-app
image: nginx:latest
|
使用 cert-manager 整合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| # vault-issuer.yaml
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: vault-issuer
namespace: default
spec:
vault:
path: pki_int/sign/server-cert
server: https://vault.example.com:8200
auth:
kubernetes:
role: cert-manager
mountPath: /v1/auth/kubernetes
serviceAccountRef:
name: cert-manager
---
# certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: web-app-cert
namespace: default
spec:
secretName: web-app-tls
duration: 720h
renewBefore: 168h
commonName: web-app.example.com
dnsNames:
- web-app.example.com
- web-app.default.svc
issuerRef:
name: vault-issuer
kind: Issuer
|
設定 Kubernetes Auth Method
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 啟用 Kubernetes 認證
vault auth enable kubernetes
# 設定 Kubernetes 認證
vault write auth/kubernetes/config \
kubernetes_host="https://kubernetes.default.svc:443" \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# 建立 Kubernetes 認證 Role
vault write auth/kubernetes/role/web-app \
bound_service_account_names=web-app \
bound_service_account_namespaces=default \
policies=pki-policy \
ttl=1h
|
建立 PKI Policy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 建立 PKI 存取政策
vault policy write pki-policy - <<EOF
# 允許簽發伺服器憑證
path "pki_int/issue/server-cert" {
capabilities = ["create", "update"]
}
# 允許讀取 CA 憑證
path "pki_int/cert/ca" {
capabilities = ["read"]
}
# 允許讀取 CRL
path "pki_int/crl" {
capabilities = ["read"]
}
EOF
|
最佳實踐
安全建議
- Root CA 離線保存:僅在需要簽發 Intermediate CA 時使用
- 使用短期憑證:減少憑證洩露的影響範圍
- 定期輪換 Intermediate CA:建議每 1-2 年輪換一次
- 啟用稽核日誌:追蹤所有憑證操作
- 實施最小權限原則:僅授予必要的憑證簽發權限
監控和告警
1
2
3
4
5
6
7
8
9
| # 檢查 PKI 引擎的健康狀態
vault read pki_int/config/crl
# 查看已簽發憑證的數量
vault list pki_int/certs | wc -l
# 檢查即將過期的憑證
vault read -format=json pki_int/cert/<serial> | \
jq -r '.data.expiration'
|
參考資料