密码散落是最大的风险
很多团队的数据库密码写在 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
动态凭据带来的改变是结构性的:数据库里不再有"写死在代码里的密码",任何临时账号泄露都会在租约到期后自动失效,审计也能精确到"哪个应用在什么时间用过哪个临时账号"。唯一要注意的是别让应用的连接池把连接开太久——连接会在租约到期后失效。
密钥轮换与审计留痕
轮换不是"有空再说"的事,而是要有周期和流程:静态密钥至少每季度轮换一次,疑似泄露立即轮换;轮换要走"改密→验证→灰度生效→全量生效"的节奏,别一把梭。Vault 支持密钥版本化,轮换后旧版本仍可回溯,出问题时能查"当时用的是哪一版"。审计方面,Vault 的审计日志会记录每一次读取:谁、什么身份、哪条路径、什么时间,这些日志要接到集中日志系统长期保留。
# 查看 Vault 审计日志(示意)
{"time":"2026-10-26T10:00:00Z","auth":{"client_id":"role:app-backend"},
"request":{"path":"secret/data/db-prod","operation":"read"},
"response":{"data":{"version":3}}}
# 关键审计点:谁读了生产库凭据、读了几次、间隔多久
没有审计的密钥管理等于没有管理。哪天数据库被拖库,你至少要能回答:这个密码谁拿过、什么时候拿的、通过什么渠道——答不上来,复盘就无从谈起,攻击面也无法收口。
与 CI/CD 集成:取密不落盘
流水线里经常需要密钥:推镜像要仓库凭据、部署要集群 kubeconfig、跑测试要测试库密码。原则是取密在流水线运行时发生,不把密码写进仓库和制品。CI 平台(GitHub Actions、GitLab CI 等)都支持从 Vault 拉取密钥注入环境变量,日志里绝不允许打印。
# GitHub Actions 从 Vault 取密(示意)
jobs:
deploy:
steps:
- name: 读取 Vault 密钥
uses: hashicorp/vault-action@v3
with:
url: https://vault.example.com
token: ${{ secrets.VAULT_TOKEN }}
secrets: |
secret/data/ci/registry username | REGISTRY_USER ;
secret/data/ci/registry password | REGISTRY_PASS
- run: echo "$REGISTRY_PASS" | docker login registry.example.com -u "$REGISTRY_USER" --password-stdin
这里有个纪律:Vault Token 本身要短命,最好用 OIDC / JWT 短时令牌,流水线跑完即失效,而不是放一个长期 token 在 CI 配置里。取到的密钥只在当前 Job 的进程内存里存活,不写日志、不落磁盘、不进制品缓存。
一套最小密钥治理方案
不要一上来就追求全套 Vault 动态引擎。按这个最小路径逐步落地:
第一步:先止血
- 扫描并移除仓库/日志中的明文密码,全部轮换
- 生产密码从配置文件迁到 K8s Secret(文件挂载)
第二步:开启 etcd 加密 + 收紧 RBAC
- kube-apiserver 配置 encryption-provider-config
- Secret 读写按命名空间和角色最小授权
第三步:部署 Vault,接管静态密钥
- 数据库/API Key/证书全部收进 Vault
- 通过 Injector 让 Pod 按需取密
第四步:试点动态凭据
- 先从最敏感的数据库账号开始
- 观察连接池与租约,再推广
循序渐进,边治理边降低风险
密钥治理和流水线审计、权限最小化是同一套安全思路:先收敛、再集中、后自动化。就算团队很小,把第一步做扎实——清掉明文密码、配上基础加密——就已经把最大的风险堵住了。上面这些实践和 K8s 安全、流水线审计互为补充,可以一起看。