起因
应用要数据库密码 / 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 灾备(多人持有)
- 学习曲线(团队培训几周)
踩过的坑
-
unseal key 丢:Vault crash 后没人能 unseal → 数据丢。
3 人各持 1 把 key + 异地存。 -
lease 不 renew:长跑应用 credential 1h 过期后失败。要么续
lease 要么处理 reauth。 -
policy 写错:太宽 → app 能读其它 app secret。policy review。
-
Vault 单点:3 节点 HA 但 quorum 挂 → 全停。raft snapshot 备份。
-
dev 模式记 prod:
vault server -dev数据 in-memory,重启丢。
生产绝不用 dev mode。
登录后参与评论。