密码散落是最大的风险

很多团队的数据库密码写在 application.yml 里、API Key 放在环境变量里、证书私钥躺在代码仓库里。平时没事,一旦哪份文件泄露——仓库被拖库、日志被公开、员工电脑被入侵——攻击者拿到的就是全套生产凭据,等于把门钥匙都配好送人。

密钥管理的本质是收敛风险面:把散落在各处的凭据集中到一个受控的地方,再对"谁能取、取了什么、什么时候取的"做审计。密码放哪里不是重点,重点是你不能回答"现在一共有多少个密码、在哪些地方、谁在用"——这才是一旦出事就全盘皆输的原因。

风险自查清单:
- 代码仓库里有没有 .env / config / key 文件?      → 立刻移除并轮换
- 日志里有没有打印过连接串或密码?                → 检查脱敏
- 每个环境(dev/staging/prod)的密码是否都不同?  → 应该不同
- 密码多久轮换一次?有没有人负责?                → 要有周期和责任人
本文核心知识点
  • K8s Secretbase64 不是加密,要配合 RBAC 与 etcd 加密。
  • Vault集中式密钥库,策略控制谁能取。
  • 动态凭据每次取都是新密码,到期自动失效。
  • 审计谁在什么时候取了什么密钥,全程留痕。
密钥与凭据管理配图:发光保险库大门与钥匙孔、两侧挂锁与盾牌、底部服务器机柜的蓝色扁平科技插画
把钥匙集中锁进保险库,比散落在每个抽屉里安全得多。

K8s Secret 的正确用法与局限

Kubernetes 有原生的 Secret 对象,很多人以为把密码放进去就安全了。但要注意:Secret 的 value 只是 base64 编码,不是加密,任何人只要有权读这个对象就能解出来。Secret 的价值是把凭据从 Pod 定义和镜像里拿出来、集中管理,真正的安全要靠 etcd 加密、RBAC 限权、以及"不落环境变量"的注入方式。

# 创建与引用 Secret
kubectl create secret generic db-cred \
  --from-literal=user=app --from-literal=pass=xxxx

# Pod 中以文件方式挂载(比 env 更安全、改后不重启也难同步)
# spec:
#   template:
#     spec:
#       containers:
#         - name: app
#           volumeMounts:
#             - name: cred
#               mountPath: /etc/cred
#       volumes:
#         - name: cred
#           secret:
#             secretName: db-cred

还有两个现实局限:一是静态 Secret 的密码不会自动轮换,改一次要重新创建对象并滚动重启;二是 Secret 的访问权限容易被忽略,默认 RBAC 放得太宽。所以 Secret 适合做第一层隔离,真正的治理还得靠专门的密钥管理系统。

集中式密钥管理:HashiCorp Vault

HashiCorp Vault 是目前最主流的开源密钥管理方案。它把所有静态密钥(数据库密码、API Key、证书)集中存储,支持版本化、策略控制和细粒度审计。Vault 本身的数据用主密钥加密,主密钥再被拆成多份共享密钥(unseal key)分散保管,服务重启后必须解封才能使用。

# Vault 静态密钥读写(示意)
vault kv put secret/db-prod user=app pass=xxxx
vault kv get secret/db-prod
vault kv metadata get secret/db-prod      # 看版本历史

# 策略:只允许 backend 角色读 db 相关路径
vault policy write backend - <<EOF
path "secret/data/db/*" {
  capabilities = ["read", "list"]
}
EOF

接入 K8s 时,官方提供 Vault Agent / Injector:给 Pod 加一个注解,init 容器自动从 Vault 取密、以文件形式挂载进业务容器,业务代码几乎不用改。这样密码不再出现在镜像、配置和 Secret 对象里,而是按需从保险库领取。

# K8s 注入示例:给 Deployment 加注解
# metadata:
#   annotations:
#     vault.hashicorp.com/agent-inject: "true"
#     vault.hashicorp.com/agent-inject-secret-db: "secret/data/db-prod"
#     vault.hashicorp.com/role: "app-backend"

动态凭据:让数据库密码自动轮换

静态密码最大的问题是要靠人记得轮换。Vault 的动态凭据(Dynamic Secrets)把这个难题自动化了:应用每次取密,Vault 都在数据库里现场创建一个临时账号,带一个租约(TTL);到期自动删除,密码根本不存在"长期有效"这回事。应用拿到的永远是刚生成的新密码,天然防泄露、防复用。

# 配置数据库动态引擎(示意)
vault secrets enable database
vault write database/config/mysql \
  plugin_name=mysql-database-plugin \
  connection_url="{{username}}:{{password}}@tcp(db:3306)/" \
  allowed_roles="app-readonly"
vault write database/roles/app-readonly \
  db_name=mysql \
  creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}';GRANT SELECT ON *.* TO '{{name}}'@'%';" \
  default_ttl="1h" max_ttl="24h"

# 应用取密:拿到一次性账号密码
vault read database/creds/app-readonly

动态凭据带来的改变是结构性的:数据库里不再有"写死在代码里的密码",任何临时账号泄露都会在租约到期后自动失效,审计也能精确到"哪个应用在什么时间用过哪个临时账号"。唯一要注意的是别让应用的连接池把连接开太久——连接会在租约到期后失效。