把数据库密码直接写进YAML文件再提交到Git仓库,是不少团队踩过的坑。即使后来补删了文件,历史记录里依然留有痕迹,攻击者只需要翻一遍提交历史就能拿到生产环境的凭证。在集群环境中,这个问题被进一步放大:节点可能被攻破、etcd存储需要加密、密钥需要定期轮换。本文从Kubernetes原生方案的安全短板讲起,逐步展开与外部密钥系统集成的主流做法。

为什么原生的Secret不够安全
Kubernetes提供了Secret对象来存放敏感信息,很多团队认为用它就万事大吉了。但默认情况下,Secret只是一段经过Base64编码的数据,编码不等于加密,任何拿到内容的人执行一次base64 -d就能看到明文。Secret的存储依赖etcd,如果集群管理员没有为etcd配置静态加密,所有Secret在etcd中都是以明文落盘的。
此外,Secret的分发链路也存在暴露面。挂载到Pod的Secret会以文件形式出现在节点磁盘的临时目录中,任何能登录节点或部署特权容器的人都可以读到它。环境变量方式的注入则更容易被忽略,通过kubectl describe pod或者应用崩溃时转储的环境变量,密码就泄露出去了。
还有一个常被忽略的问题是轮换。原生Secret没有自动轮换机制,一个数据库密码用了两年没换过的情况在企业内部比比皆是。合规审计要求密钥定期更换时,运维往往只能手动改Secret再滚动重启所有应用,既繁琐又容易出错。这些短板共同指向一个结论:生产环境的密钥管理需要引入专门的外部系统。
外部密钥系统的两种典型模式
目前主流的外部密钥集成有两种思路。第一种是静态同步模式,以HashiCorp Vault的KV引擎加External Secrets Operator为代表。集群中不再直接存放密钥本体,而是部署一个Operator,它监听自定义资源ExternalSecret,发现新资源后从Vault拉取对应的密钥数据,并在集群内创建等价的原生Secret供应用消费。密钥的权威来源始终是Vault,集群里的Secret只是一个缓存副本。
第二种是动态密钥模式,也就是Vault的Dynamic Secrets。应用需要连接数据库时,不是使用长期固定的密码,而是向Vault发起请求,Vault实时在数据库里创建一个带TTL的临时账号。TTL到期后账号自动销毁,即使凭证泄露,攻击窗口也被限制在几分钟到几小时之内。这种方式对传统应用的改造成本较高,但对新建服务来说是安全性的质的提升。
# 使用Vault CLI创建动态数据库角色
vault write database/roles/app-role \
db_name=mysql-prod \
creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}'; \
GRANT SELECT ON appdb.* TO '{{name}}'@'%';" \
default_ttl=1h max_ttl=24h
# 获取临时凭证
vault read database/creds/app-role
# 输出的username和password在1小时后自动失效两种模式各有适用场景。静态同步适合存量系统迁移,应用代码几乎零改动;动态密钥适合对安全性要求极高的新建服务。实际项目中常见的做法是混合使用:普通配置用静态同步,核心数据库凭证走动态模式。
用External Secrets Operator落地集成
External Secrets Operator目前是社区事实上的标准方案,它支持Vault、AWS KMS、Azure Key Vault、阿里云KMS等几十种后端。部署好Operator后,首先需要定义一个SecretStore来声明后端连接信息。
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
namespace: prod
spec:
provider:
vault:
server: "https://vault.ipipp.com:8200"
path: "kv"
version: "v2"
auth:
kubernetes:
mountPath: kubernetes
role: eso-role
serviceAccountRef:
name: external-secrets-sa这里使用Kubernetes服务账号做认证,Vault侧需要为对应的角色配置策略,限定它只能读取特定路径下的密钥,遵循最小权限原则。接着定义ExternalSecret,声明要把Vault中的哪些数据同步到本地。
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: prod
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: db-credentials
data:
- secretKey: username
remoteRef:
key: prod/mysql
property: username
- secretKey: password
remoteRef:
key: prod/mysql
property: passwordrefreshInterval设为1小时意味着每小时会从Vault重新拉取一次数据,如果Vault侧发生了轮换,集群内的Secret会在一个小时内自动更新,配合应用的熱加载机制就能实现无重启轮换。需要注意,应用本身要支持配置文件或环境变量的动态感知,否则Secret更新了进程里还是旧值。
信封加密与密钥的密钥
还有一个容易被混淆的概念需要厘清:即使接入了外部密钥系统,etcd静态加密仍然建议开启。Kubernetes的encryption configuration允许指定使用KMS provider,工作原理是信封加密——数据密钥负责加密Secret内容,而数据密钥本身由外部KMS中的根密钥加密保护。这样etcd磁盘上既没有明文Secret,也没有裸的数据密钥。
信封加密的优势在于性能。如果每次读写Secret都要走一次外部KMS的网络调用,延迟会明显堆积。引入数据密钥后,日常加解密在本地完成,只有数据密钥的加解密才需要访问KMS,绝大部分操作都被加速了。这也是各大云厂商KMS服务的通用设计。
运维层面的注意事项
集成落地后还有几件事不能省。首先是网络可达性,集群到Vault或KMS之间的链路必须稳定,建议部署本地缓存或在Vault前加一层代理,避免外部系统故障导致应用Pod启动失败。其次是备份策略,Vault的密钥分片要妥善保管,否则Vault本身坏了所有密钥都无法恢复。最后是审计,Vault的audit log和KMS的访问日志要接入集中式日志平台,任何一次密钥读取都应可追溯到具体的工作负载。
密钥管理不是一次性工程,而是持续运营的过程。从ConfigMap硬编码到原生Secret,再到外部密钥系统集成和动态凭证,每前进一步,攻击者可利用的面就缩小一分。建议从最敏感的那批凭证开始迁移,用小范围验证跑通流程后再逐步推广到整个集群。