HashiCorp Vault PKI 憑證管理

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

啟用 Intermediate CA 的 PKI Engine

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

設定 Intermediate CA

產生 Intermediate CA CSR

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

使用 Root CA 簽發 Intermediate CA 憑證

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

設定 Intermediate CA 憑證

1
2
3
# 將簽發的憑證設定到 Intermediate CA
vault write pki_int/intermediate/set-signed \
    certificate=@intermediate.crt

設定 Intermediate CA 的 URL

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

最佳實踐

安全建議

  1. Root CA 離線保存:僅在需要簽發 Intermediate CA 時使用
  2. 使用短期憑證:減少憑證洩露的影響範圍
  3. 定期輪換 Intermediate CA:建議每 1-2 年輪換一次
  4. 啟用稽核日誌:追蹤所有憑證操作
  5. 實施最小權限原則:僅授予必要的憑證簽發權限

監控和告警

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'

參考資料

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy