工程实践 安全

HashiCorp Vault 密钥管理实践

从架构设计到生产部署,介绍 HashiCorp Vault 在高合规场景中的密钥管理、动态凭证与 PKI 自动化思路。

发布于 2026/03/20 更新于 2026/03/20 3 分钟

“在银行系统里,密钥泄露等同于资金泄露。“

前言

银行系统的密钥管理有三个硬要求:

  1. 永不硬编码:任何密钥都不能出现在代码、配置文件、环境变量(日志中)、Docker 镜像里
  2. 最小权限原则:每个应用只访问它需要的密钥,且有过期时间
  3. 审计全覆盖:谁在什么时间访问了什么密钥,所有操作均可追溯

HashiCorp Vault 是这一类问题的常见方案。本文按高合规场景整理一套较稳妥的做法,重点放在架构思路、配置方式和落地时容易踩到的坑。

1. Vault 架构:银行高可用部署

1.1 推荐架构:高性能存储 + HA

Vault 本身不存储数据,状态存储在后端。银行生产环境推荐:

┌─────────────────────────────────────────────────┐
│                   Load Balancer                 │
└───────────────────────┬─────────────────────────┘

         ┌──────────────┼──────────────┐
         ▼              ▼              ▼
    ┌─────────┐    ┌─────────┐    ┌─────────┐
    │ Vault   │    │ Vault   │    │ Vault   │
    │ Node 1  │    │ Node 2  │    │ Node 3  │
    └────┬────┘    └────┬────┘    └────┬────┘
         │              │              │
         └──────────────┼──────────────┘

              ┌─────────┴─────────┐
              │   Consul Cluster  │
              │  (HA Storage)     │
              │  5节点raft共识    │
              └───────────────────┘

关键配置:

# /etc/vault.d/vault.hcl
ui = true
cluster_addr = "https://vault-node-1.example.internal:8201"
api_addr     = "https://vault.example.internal:8200"

storage "consul" {
  address = "127.0.0.1:8500"
  path    = "vault/"
  consul.token = ""  # 从环境变量 VAULT_TOKEN 注入
}

# TLS 强制 HTTPS(银行必须)
listener "tcp" {
  address       = "0.0.0.0:8200"
  tls_cert_file = "/vault/tls/server.crt"
  tls_key_file  = "/vault/tls/server.key"
  tls_min_version = "tls12"
  tls_disable = false
}

# 审计日志落盘(合规要求,30天保留)
audit "file" {
  path = "/var/log/vault/audit.log"
  log_raw = true
}

# 多节点HA
ha_storage "consul" {
  address = "127.0.0.1:8500"
  path    = "vault/"
}

1.2 Vault Seal/Unseal 策略

Vault 启动时需要 Unseal 密钥才能解密数据。银行生产用 Shamir 密钥分割(5人持分,3人合一可解锁):

# 初始化 Vault(首次部署时执行一次)
vault operator init \
  -key-shares=5 \
  -key-threshold=3 \
  -pgp-keys=keybase:admin1.asc,keybase:admin2.asc,keybase:admin3.asc,keybase:admin4.asc,keybase:admin5.asc

# 输出示例:
# Unseal Key 1: xxx
# Unseal Key 2: xxx
# ...
# Initial Root Token: xxx

每次重启节点都需要 Unseal,用 Vault Auto-Unseal 配合 AWS KMS/GCP CKMS 更适合银行自动化场景:

# 启用自动 Unseal(不需要人工干预)
seal "awskms" {
  region     = "eu-west-1"
  kms_key_id = "alias/vault-production-key"
}

2. Secret Engines:按场景选对引擎

Vault 支持多种 Secret Engine,银行常用的是这三种:

Engine适用场景例子
KV Secrets Engine v2静态密钥(应用配置)数据库密码、API Key、证书
Database Secrets Engine动态数据库凭证MySQL/Oracle 连接密码
PKI Secrets Engine自动证书管理mTLS 证书、API Gateway 证书

2.1 KV Engine v2:静态密钥存储

# 启用 KV v2 引擎
vault secrets enable -path=secret -version=2 kv

# 存储数据库密码
vault kv put secret/payment-service/db \
  host=db-prod.internal \
  port=5432 \
  username=payment_app \
  password=super-secret-password

# 查看
vault kv get secret/payment-service/db

# 版本历史(v2 支持,重要!密码泄露后可回滚)
vault kv history secret/payment-service/db
vault kv delete -versions=3 secret/payment-service/db  # 删除泄露版本

2.2 Database Secrets Engine:动态凭证(最常用)

数据库密码永不过期是银行安全大忌。Database Engine 让 Vault 按需生成短期密码,用过即销毁:

# 启用 MySQL Database Engine
vault secrets enable -path=database database

# 配置 MySQL 连接
vault write database/config/payment-mysql \
  plugin_name=mysql-database-plugin \
  connection_url="{{username}}:{{password}}@tcp(db-prod.internal:3306)/" \
  username="vault_admin" \
  password="vault_admin_password"

# 创建角色:应用获取的密码有效期10分钟,最大使用100次
vault write database/roles/payment-app-role \
  db_name=payment-mysql \
  creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}'; GRANT SELECT ON payment_db.* TO '{{name}}'@'%';" \
  default_ttl="10m" \
  max_ttl="1h"

# 应用每次请求获取临时凭证
vault read database/creds/payment-app-role
# Output:
# Key                Value
# lease_id           database/creds/payment-app-role/xxx
# lease_duration     10m
# username           v-token-payment-app-ro-xxx
# password           A1b2C3d4E5f6G7h8

应用使用 Vault SDK 获取凭证,Vault 自动在 10 分钟后撤销:

// Spring Boot + Vault SDK
@Configuration
public class VaultConfig {

    @Bean
    public VaultTemplate vaultTemplate(VaultEndpoint vaultEndpoint,
                                        ClientAuthentication authentication) {
        return new VaultTemplate(vaultEndpoint,
            new VaultConfiguration(authentication, vaultEndpoint).createClientConfiguration());
    }
}

@Service
public class PaymentDbCredentialProvider {

    private final VaultTemplate vault;

    public DbCredentials getCredentials() {
        // lease_id 用于后续续约或主动撤销
        VaultResponse response = vault.read("database/creds/payment-app-role");
        return new DbCredentials(
            response.getData().get("username"),
            response.getData().get("password"),
            response.getLeaseId()
        );
    }

    // Vault 自动撤销,但应用应在 lease 即将过期时主动刷新
    public void closeLease(String leaseId) {
        vault.opsForLease().revoke(leaseId);
    }
}

这个模式的威力:数据库里永远不会有长期有效的应用密码——每个 Pod、每次部署拿到的都是新密码。即使代码泄露仓库,攻击者也拿不到真实凭证。

3. Kubernetes 集成:Vault Agent Injector

在 K8s 环境中,手动轮换凭证太繁琐。Vault Agent Injector 自动将凭证注入 Pod,无需应用改代码:

3.1 安装 Vault Agent(Helm)

helm install vault hashicorp/vault \
  --namespace vault \
  --set "injector.enabled=true" \
  --set "injector.leaderElecter.enabled=true"

3.2 注解驱动的自动注入

在 Deployment 中加几行注解,Agent 自动将 Vault 密钥注入文件或环境变量:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-service
spec:
  template:
    metadata:
      annotations:
        # 注入 KV 静态密钥到文件
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "payment-app"           # K8s 认证角色
        vault.hashicorp.com/tls-skip-verify: "false"

        # 注入数据库动态凭证
        vault.hashicorp.com/agent-inject-secret-db-creds: "database/creds/payment-app-role"
        vault.hashicorp.com/agent-inject-template-db-creds: |
          {{- with secret "database/creds/payment-app-role" -}}
          DB_HOST={{ .Data.data.host }}
          DB_PORT={{ .Data.data.port }}
          DB_USER={{ .Data.data.username }}
          DB_PASS={{ .Data.data.password }}
          {{- end }}

        # 注入静态配置
        vault.hashicorp.com/agent-inject-secret-db-config: "secret/data/payment-service/db"
        vault.hashicorp.com/agent-inject-template-db-config: |
          {{- with secret "secret/data/payment-service/db" -}}
          DB_PASSWORD={{ .Data.data.password }}
          {{- end }}

Pod 启动后自动有这些文件/环境变量:

/vault/secrets/db-creds      ← 数据库动态用户名密码(10分钟有效)
/vault/secrets/db-config     ← 静态数据库配置

应用读本地文件即可,完全不知道 Vault 存在——零代码改动

3.3 Vault K8s 认证:无需长期令牌

# 创建 K8s Service Account(每个命名空间一个)
kubectl create serviceaccount vault-auth -n payment
kubectl create clusterrolebinding vault-auth-binding \
  --clusterrole=system:auth-delegator \
  --serviceaccount=default:vault-auth

# Vault 配置 K8s 认证
vault auth enable kubernetes

vault write auth/kubernetes/config \
  token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
  kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
  kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt

# 创建 Vault Policy(最小权限)
cat <<'EOF' > payment-app-policy.hcl
path "secret/data/payment-service/*" {
  capabilities = ["read"]
}
path "database/creds/payment-app-role" {
  capabilities = ["read"]
}
EOF
vault policy write payment-app payment-app-policy.hcl

# 绑定 Policy 到 K8s ServiceAccount
vault write auth/kubernetes/role/payment-app \
  bound_service_account_names=vault-auth \
  bound_service_account_namespaces=payment \
  policies=payment-app \
  ttl=24h

4. PKI Engine:自动化 mTLS 证书管理

银行内网服务间通信强制 mTLS,证书管理是个大麻烦——Vault PKI Engine 让这一切自动化:

# 启用 PKI Engine
vault secrets enable -path=pki_int pki

# 设置 CA 有效期
vault secrets tune -max-lease-ttl=87600h pki_int  # 10年

# 生成中间 CA(生产规范:根 CA 离线存储,中间 CA 在 Vault)
vault write pki_int/intermediate/generate/internal \
  common_name="Payment Intermediate CA" \
  ttl=8760h \
  --format=pem_bundle > intermediate.csr

vault issue pki_int/root/generate/sign \
  csr=@intermediate.csr \
  common_name="Payment Intermediate CA" \
  ttl=8760h > intermediate.cert.pem

vault write pki_int/intermediate/set-signed \
  certificate=@intermediate.cert.pem

# 创建角色:允许 *.hsbctech.internal 自动申请90天证书
vault write pki_int/roles/payment-services \
  allowed_domains="hsbctech.internal" \
  allow_subdomains=true \
  allow_wildcard_certificates=true \
  max_ttl="2160h" \      # 90天
  generate_lease=true

应用申请证书(自动化 via Spring Boot ACME 或 Cert-Manager):

// VaultClient 获取服务证书
public byte[] getServiceCertificate(String serviceName) {
    // 申请90天证书
    VaultResponse response = vault.write(
        "pki_int/issue/payment-services",
        Map.of(
            "common_name", serviceName + ".hsbctech.internal",
            "ttl", "2160h"
        )
    );
    return response.getData().get("certificate").getBytes();
}

5. 生产运营:监控与告警

Vault 是关键基础设施,任何异常都需要第一时间告警:

# Prometheus 抓取配置(Vault 内置 /metrics 端点)
- job_name: 'vault'
  consul_sd_configs:
    - services: ['vault']
  metrics_path: /v1/sys/metrics
  params:
    format: ['prometheus']
  relabel_configs:
    - source_labels: [__meta_consul_service_id]
      target_label: instance

关键告警规则(Prometheus AlertManager):

groups:
  - name: vault-alerts
    rules:
      # Vault Unseal 失败
      - alert: VaultNodeSealed
        expr: vault_core_unseal_progress > 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Vault 节点被 Seal,请立即处理"

      # 凭证即将过期(提前7天告警)
      - alert: VaultSecretExpiringSoon
        expr: vault_expiration_fetch_lease_count > 0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "有 {{ $value }} 个 Vault 凭证即将过期"

      # 异常访问模式(同一 ServiceAccount 频繁申请凭证)
      - alert: VaultAnomalousAccess
        expr: rate(vault_token_create_count[5m]) > 100
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "ServiceAccount 凭证申请频率异常"

6. 踩坑总结

坑1:Leases 续期与撤销链

Vault 的精髓在于有租约就有撤销链。但容易忘记:

# 续期(每次续到 max_ttl)
vault lease renew database/creds/payment-app-role/xxx

# 应用重启时忘记撤销旧凭证 → 凭证堆积
# 解决:Vault 在 max_ttl 自动撤销,但最好主动管理
vault lease revoke -prefix database/creds/payment-app-role

坑2:K8s 认证 Token 自动挂载的 TTL

K8s ServiceAccount Token 默认有效期是 永不过期(Kubernetes 1.23 之前)。Vault Role 的 TTL 无法撤销这些 Token:

# 检查 Token 类型
kubectl get sa vault-auth -n payment -o jsonpath='{.secrets[*].name}'
# 建议 Kubernetes 1.23+ 使用 TokenRequest API 生成有 TTL 的 Token

坑3:审计日志与 GDPR

Vault 审计日志会记录所有操作,包括密钥内容(log_raw=true)。在欧盟区的银行系统里,这可能与 GDPR 冲突:

# 审计日志脱敏(Vault 1.13+ 支持)
audit "file" {
  path = "/var/log/vault/audit.log"
 hmac_accessor = true        # 用 HMAC 替代真实 accessor ID
  log_raw = false             # 不记录明文敏感数据
}

7. 总结:银行 Vault 落地检查清单

步骤操作优先级
1部署 HA Vault + Consul 存储,启用 Auto-UnsealP0
2迁移所有硬编码密钥到 KV EngineP0
3Database Engine 替换静态 DB 密码P0
4Vault Agent Injector 集成 K8sP0
5K8s Auth Method 替换 Kubernetes TokenP1
6PKI Engine 自动化 mTLS 证书P1
7Prometheus 监控 + Unseal 告警P1
8审计日志 GDPR 合规审查P2

Vault 是银行 DevSecOps 实践的基石——把密钥管理做好,等于给整个基础设施上了一道最关键的锁。


相关阅读:Spring Cloud Gateway 银行网关实战 · Spring Security 与 OAuth2 银行级安全实战 · Kubernetes 完全指南