HashiCorp Vault:动态 secret + 自动 rotation

起因

应用要数据库密码 / API key / cloud credential。
反模式:

  • secret 写 .env 文件 commit git(最差)
  • 环境变量(多 deploy 多份)
  • AWS Secrets Manager 静态(仍要手动 rotate)
  • K8s Secret base64(任何 cluster admin 看见)

Vault

  • 动态生成 secret(每应用 / 每会话独立 credential)
  • TTL + 自动 revoke
  • 审计 log 每次访问
  • 多 backend(DB / cloud / PKI / ...)

# dev 模式(生产不能用)
vault server -dev

# prod (单机)
vault server -config=vault.hcl
# vault.hcl
ui = true
storage "raft" {
    path = "/var/lib/vault"
    node_id = "node1"
}
listener "tcp" {
    address = "0.0.0.0:8200"
    tls_cert_file = "/etc/vault/cert.pem"
    tls_key_file = "/etc/vault/key.pem"
}

init + unseal:

vault operator init               # 显示 unseal key + root token
vault operator unseal <key1>
vault operator unseal <key2>
vault operator unseal <key3>

Shamir secret sharing:5 个 unseal key 中 3 个能解锁。

静态 secret (KV)

vault kv put secret/myapp/db password=secret123 username=admin
vault kv get secret/myapp/db

应用读:

import hvac
client = hvac.Client(url='https://vault:8200', token='...')
secret = client.secrets.kv.v2.read_secret_version(path='myapp/db')
db_pass = secret['data']['data']['password']

跟 AWS Secrets Manager 类似,但 KV 不是 Vault 杀手锏。

动态 secret (DB)

杀手 feature:vault 实时生成 DB credential。

# 配 PG backend
vault secrets enable database

vault write database/config/myapp-pg \
    plugin_name=postgresql-database-plugin \
    connection_url="postgresql://{{username}}:{{password}}@pg:5432/myapp" \
    allowed_roles="readonly,readwrite" \
    username="vault_admin" \
    password="..."

# 定义 role
vault write database/roles/readonly \
    db_name=myapp-pg \
    creation_statements="CREATE USER \"{{name}}\" WITH PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
        GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
    default_ttl="1h" \
    max_ttl="24h"

应用请求:

vault read database/creds/readonly
# username = v-token-readonly-xxx
# password = randompw
# lease_id = ...
# lease_duration = 3600

vault 实时在 PG 里 CREATE USER + GRANT,返回临时 credential。
1 小时后自动 DROP USER → credential 失效。

应用集成

import hvac
client = hvac.Client(url='https://vault:8200')
client.auth.kubernetes.login(role='myapp', jwt=open('/var/run/secrets/k8s/jwt').read())

creds = client.secrets.database.generate_credentials('readonly')
username = creds['data']['username']
password = creds['data']['password']

# 用 credential 连 PG
db = psycopg2.connect(host='pg', user=username, password=password)

每应用启动拿一份独立 credential。
应用挂了 / 重启 → 新 credential。
泄露 1 个 credential → 影响仅那 1 个,1 小时后自动废。

auth method

应用怎么登 Vault?

  • token:root token / 长期 token(不推荐)
  • AppRole:app + role-id + secret-id(机器登录)
  • Kubernetes:pod service account JWT 自动登
  • AWS / GCP / Azure:cloud IAM identity
  • OIDC:人通过 SSO

K8s 集成最常用:

# Pod 自动有 service account token
spec:
  serviceAccountName: myapp

Vault 配 K8s auth → JWT 验证后给 Vault token。

PKI (cert)

vault 当内部 CA:

vault secrets enable pki
vault write pki/root/generate/internal common_name="myorg" ttl=8760h

应用请求 cert:

vault write pki/issue/myapp common_name=myapp.example.com ttl=24h
# 返回 cert + key + ca chain

服务每天 rotate cert,过期自动失效。
比 Let's Encrypt 还自动化(内部 service 间 mTLS)。

audit log

vault audit enable file file_path=/var/log/vault/audit.log

每次 secret 访问 log:

{
  "time": "2025-03-14T...",
  "auth": {"display_name": "k8s-myapp-pod-xxx"},
  "request": {"path": "database/creds/readonly", "operation": "read"},
  "response": {...}
}

事后审计:谁什么时候拿了什么 secret。compliance 必要。

与 AWS Secrets Manager 对比

Vault AWS Secrets Manager
动态 secret ✅ (DB / cloud / SSH) 弱(仅 DB rotation)
多 cloud AWS only
PKI
自托管
价格 OSS 0 $0.40 / secret / month
复杂度

简单需求 → AWS Secrets Manager 省事。
多 cloud / 动态 secret 重 / 严格合规 → Vault。

sealed-secret(K8s 平民方案)

不想上 Vault 但又不想 git 存 secret:

# 生成 sealed-secret
echo -n "secret123" | kubectl create secret generic mysecret --dry-run=client --from-file=password=/dev/stdin -o yaml | kubeseal -o yaml > sealed.yaml

sealed.yaml 加密,commit git 安全。
集群里 sealed-secret-controller 解密 → 创建普通 K8s Secret。

简单 + GitOps friendly,但没动态 / rotation / audit。
小项目 OK,企业级要 Vault。

external-secrets operator

K8s + 外部 secret store(AWS SM / Vault / GCP SM 等):

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: db-pass
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: db-pass-secret
  data:
    - secretKey: password
      remoteRef:
        key: secret/myapp/db
        property: password

operator 从 Vault 同步到 K8s Secret,应用用 K8s Secret 不变。
适合:"想用 Vault 集中管 secret + 应用不用改"。

Vault as service mesh (Consul Connect)

Vault + Consul 一起做 mTLS service mesh。
某些场景替代 Istio。

真实部署

我们 prod:

  • Vault HA cluster(3 node Raft)
  • AWS RDS / Redis / Slack API / GitHub token 都 Vault 管
  • K8s auth method(pod 自动 login)
  • DB credential 1h TTL
  • audit log → Loki

效果:

  • 0 secret in git
  • credential leak 不再 catastrophic(1 小时失效)
  • audit 自动 → SOC2 / ISO 文档自动生成

挑战:

  • Vault 自身要 HA(挂了应用拿不到 secret → 失败)
  • unseal key 灾备(多人持有)
  • 学习曲线(团队培训几周)

踩过的坑

  1. unseal key 丢:Vault crash 后没人能 unseal → 数据丢。
    3 人各持 1 把 key + 异地存。

  2. lease 不 renew:长跑应用 credential 1h 过期后失败。要么续
    lease 要么处理 reauth。

  3. policy 写错:太宽 → app 能读其它 app secret。policy review。

  4. Vault 单点:3 节点 HA 但 quorum 挂 → 全停。raft snapshot 备份。

  5. dev 模式记 prodvault server -dev 数据 in-memory,重启丢。
    生产绝不用 dev mode。

精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。