原生Secret仅Base64编码且etcd默认明文存储,须组合使用文件创建Secret、卷挂载替代环境变量、启用etcd静态加密(aescbc/secretbox)、轮换密钥,并在高敏场景接入External Secrets Operator对接Vault实现凭据动态拉取与自动轮转。

原生 Secret 无法保障 MySQL 凭据安全——它只是 Base64 编码,不是加密,etcd 中默认明文存储,RBAC 一松动就全暴露。真要管住 MySQL 密码,得绕过 Secret 的“假安全”表象,用组合策略堵住每个漏洞。
别用 kubectl create secret generic 直接写密码
命令行里敲 password='P@ss!2026' 看似方便,但密码会进 shell 历史、kubectl 日志、审计日志,甚至被终端录屏捕获。更糟的是,含 $、*、! 的密码可能被 shell 提前解析。
- 必须用单引号包裹值:
kubectl create secret generic mysql-cred --from-literal=password='P@ss!2026' - 更推荐从文件创建,彻底规避终端残留:
echo -n 'P@ss!2026' > password.txt,再执行kubectl create secret generic mysql-cred --from-file=password.txt - 绝对不要手动生成 Base64 后写进 YAML——那等于把明文密码和编码结果一起留在 Git 里
Pod 里别用环境变量注入 MYSQL_PASSWORD
环境变量太容易泄露:任何能 exec 进容器的用户都能用 ps aux 或读 /proc/1/environ 拿到密码;日志框架若没做字段过滤,也会把密码打出来。
- 改用 volume 挂载:
volumes引用 Secret,volumeMounts挂到/etc/mysql/secret/下 - 挂载时设
readOnly: true和defaultMode: 0400,确保只有 root 可读 - 应用代码改从文件读取,例如 Python 用
open('/etc/mysql/secret/password', 'r').read().strip(),而非os.getenv('MYSQL_PASSWORD')
生产环境必须关掉 etcd 明文存储
没开 etcd 静态加密,等于把所有 Secret 的 Base64 值裸放在磁盘上。攻击者拿到 etcd 快照或备份,三秒就能 base64 -d 还原出 MySQL 密码。
- 确认集群是否启用加密:
kubectl get secrets -A | head -n5看不到报错 ≠ 已加密;真正要看ps aux | grep encryption-config或检查 kube-apiserver 启动参数是否含--encryption-provider-config - 加密配置必须包含
aescbc或secretboxprovider,禁用identity(明文兜底)用于生产 - 密钥必须轮换:每 90 天更新一次 EncryptionConfiguration 中的
keys列表,并触发重加密(kubectl apply -f encryption-config.yaml && kubectl rollout restart deploy/kube-apiserver)
高敏场景直接跳过原生 Secret,用 External Secrets Operator
当 MySQL 密码涉及金融、支付或合规审计时,靠 RBAC + etcd 加密仍不够——只要 Pod 能访问 API Server,就有机会调用 /api/v1/namespaces/*/secrets 接口拉取凭据。
- 部署
ExternalSecretsCRD 和 ESO 控制器,让 Kubernetes 从 Vault/Azure Key Vault 按需拉取密码,不落地、不缓存 - 定义
ExternalSecret对象,指向 Vault 中路径kv/mysql/prod/root-pass,ESO 自动同步为本地Secret并注入 Pod - 配合 Vault 的 TTL 和轮转策略,MySQL 密码可自动更新,关联 Deployment 会滚动重启,零人工干预
最常被忽略的一点:Secret 的生命周期管理比创建难十倍。没人会记得半年前创建的 mysql-prod-legacy Secret 是否还在被某个灰度 Pod 引用,而它可能连着一个已下线但未清理的数据库实例——这种“幽灵凭据”才是渗透测试最爱的入口。











