密钥管理与Vault实践
1. 反面案例
密钥怎么泄露的(真实案例):
- 提交 .env 到 git(grep 历史能挖到)
- 镜像 ENV DATABASE_PASSWORD=xxx(docker history 看到)
- 日志打印 token
- 客户端代码 console.log(secret)
- CI 配置 echo $SECRET 到日志
- Slack 群里贴密钥临时调试
密钥泄露后果:直接破窗 = 数据泄露 / 资金损失 / 信誉。
2. 分级管理
| 级别 | 例 | 存储 |
|---|---|---|
| 公开(不敏感) | API_URL、版本号 | git、config map |
| 内部(不能泄露) | 第三方 API key、内部 endpoint | CI Secrets / Vault |
| 高敏感 | DB 密码、签名私钥、root 凭证 | Vault + 审计 |
| 顶级 | 根 CA、KMS 主密钥 | HSM / 离线 |
3. 不能做的事
- ❌ 写 git 仓库(即使 private)
- ❌ 写 Docker 镜像 ENV / ARG
- ❌ 写日志 / 错误堆栈
- ❌ 命令行参数(
ps aux能看) - ❌ 公共聊天工具
- ❌ 邮件
- ❌ 客户端代码(前端 JS 任何用户都能看)
4. 推荐做法
4.1 环境变量注入(最低标准)
# K8s
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
容器内 process.env.DB_PASSWORD 取。
4.2 文件挂载
volumeMounts:
- name: secret
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secret
secret:
secretName: db-secret
应用读 /etc/secrets/password。挂载文件能动态更新(环境变量不行)。
4.3 应用主动拉
启动时调 Vault 拉密钥到内存。永远不落盘。
5. K8s Secret
K8s Secret 默认 base64(不是加密)。增强:
5.1 etcd 加密
apiserver 配置 --encryption-provider-config:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources: [secrets]
providers:
- aescbc:
keys:
- name: key1
secret: <32 字节 base64>
- identity: {}
云托管集群(ACK / EKS / GKE)控制台一键开。
5.2 sealed-secrets
加密后可以提交 git:
echo -n 'password' | kubectl create secret generic db --dry-run=client \
--from-file=password=/dev/stdin -o yaml | \
kubeseal --controller-namespace=kube-system -o yaml > db-sealed.yaml
db-sealed.yaml 是密文,安全提交。集群内 controller 解密成 Secret。
5.3 external-secrets-operator(推荐)
把外部 Vault / AWS Secrets Manager 同步到 K8s Secret:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault
spec:
provider:
vault:
server: "https://vault.example.com"
path: "kv"
auth:
kubernetes:
mountPath: kubernetes
role: my-app
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-secret
spec:
refreshInterval: 1h
secretStoreRef:
name: vault
kind: SecretStore
target:
name: db-secret
data:
- secretKey: password
remoteRef:
key: prod/db
property: password
6. HashiCorp Vault
企业级密钥管理。
6.1 部署
helm install vault hashicorp/vault \
--set "server.ha.enabled=true" \
--set "server.ha.replicas=3"
6.2 启用 KV 引擎
vault secrets enable -version=2 kv
vault kv put kv/prod/db password="xxx"
vault kv get kv/prod/db
6.3 K8s 认证
vault auth enable kubernetes
vault write auth/kubernetes/config \
kubernetes_host="https://kubernetes.default.svc"
vault policy write my-app - <<EOF
path "kv/data/prod/db" {
capabilities = ["read"]
}
EOF
vault write auth/kubernetes/role/my-app \
bound_service_account_names=my-app \
bound_service_account_namespaces=production \
policies=my-app \
ttl=1h
6.4 Vault Agent Injector
注解触发自动注入:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "my-app"
vault.hashicorp.com/agent-inject-secret-db: "kv/data/prod/db"
vault.hashicorp.com/agent-inject-template-db: |
}}- with secret "kv/data/prod/db" -}}
DB_PASSWORD=}} .Data.data.password }}
}}- end }}
容器启动时 sidecar 拉密钥写到 /vault/secrets/db。
6.5 动态密钥(高级)
# 启动 database 引擎
vault secrets enable database
vault write database/config/postgres \
plugin_name=postgresql-database-plugin \
connection_url="postgresql://}}username}}:}}password}}@db:5432/app" \
allowed_roles=readonly \
username="vault" \
password="vault-pass"
# 创建 role
vault write database/roles/readonly \
db_name=postgres \
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
每次 read 生成短期数据库账号,TTL 后自动撤销。杀手锏。
7. 云原生方案
| 平台 | 服务 |
|---|---|
| AWS | Secrets Manager / Parameter Store / KMS |
| GCP | Secret Manager |
| Azure | Key Vault |
| 阿里 | KMS |
集成 K8s 用 external-secrets-operator 同步。
8. Secret Rotation
定期轮换:
- DB 密码:30-90 天
- API key:90 天
- 证书:自动续期(Let's Encrypt 90 天)
- root key:年级别 + KMS 自动 rotation
应用代码必须支持热加载新密钥(不重启换密码)。
9. 审计
谁什么时候读了哪个密钥要记录:
# Vault audit log
vault audit enable file file_path=/vault/logs/audit.log
可疑访问立即告警(异常时间、异常来源)。
10. 常见反模式
.env提交 git:经典灾难- Secret 写 Docker ARG:history 泄露
- K8s Secret = 安全:base64 不是加密
- 同一密钥多年不换:泄露后破坏面巨大
- 生产 staging 共用密钥:staging 泄露 = 生产泄露
- 应用 log 打印 token:日志聚合到 ELK 后所有人能看
- Vault root token 长期保留:unseal 后立即吊销
- 不审计 secret 访问:被偷不知道