Secret 的安全风险:从明文到加密的必经之路
在讲服务网格集成 Secret 之前,我想先聊聊安全。嗯,这个话题绕不开。我自己在项目中踩过的坑,十有八九都跟 Secret 的存储方式有关。说白了,Secret 要是没管好,整个集群就像把家门钥匙挂在门外——你用起来方便,别人拿起来也方便。
这一节我们不搞花哨的,就理清楚四个核心风险点,以及我习惯怎么处理它们。你如果全部掌握,至少在 Secret 安全这块,能挡住 90% 的常见攻击。
1. 明文存储:最直接的漏洞
很多人把 Secret 当成加密工具,其实不是。Kubernetes Secret 默认只是 base64 编码,不是加密。编码和加密,这里我分得特别清楚——编码是给人看的,加密是给机器锁的。
我在项目中遇到过的真实案例:有一次,团队把数据库密码写进 YAML 文件里,直接提交到 Git。CI 跑了没问题,但三个月后有人发现 Git 历史里躺着一个 echo "cGFzc3dvcmQ=" | base64 -d…… 嗯,那周的改密会议,气氛挺沉重。
防止明文存储,我建议至少做到这几点:
- 永远不要将 Secret 的原始值写入 YAML,即使你用了 base64。
- 使用 External Secrets Operator 或 Sealed Secrets,让 YAML 中只保留加密后的密文。
- CI/CD 流水线中集成 Secret 扫描(比如 truffleHog),确保不会意外泄露。
小技巧:我个人习惯在 Git 仓库根目录放一个 .gitattributes,把包含 secret 字样的文件标记为 diff=secret,并用 git diff --check 钩子禁止提交。这样能防住第一次粗心。
2. Base64 编码误解:你以为你加密了?
这可能是误解最大的地方。Base64 不是加密算法,它只是把二进制数据转成 ASCII 字符,方便在文本协议里传输。我见过太多新手直接 echo password | base64 就塞进 YAML,然后问「为什么我的 Secret 不安全?」
为什么会这样?因为 kubectl 创建 Secret 时,默认只做 base64 编码,且 etcd 如果不开启加密,就是存成 base64 后的明文字符串。任何人只要拿到 etcd 的快照或者有 etcd 的读权限,就能直接看。
| 方案 | 安全性 | 适用场景 |
|---|---|---|
| 纯 base64 | ❌ 不安全 | 仅用于开发测试,千万别上生产 |
| base64 + RBAC | 只能防住集群内部分用户,防不住 etcd 管理员 | |
| etcd 加密 (AES-GCM) | ✅ 推荐 | 生产环境标配 |
| 外部加密 + 服务网格集成 | 更严格的合规场景,比如 PCI-DSS |
避坑指南:我曾经帮一家客户做安全审计,发现他们的 Secret 在 etcd 里是明文,但团队觉得「我们已经用了 RBAC,只能让指定 ServiceAccount 读取,所以没问题」。错!etcd 的备份文件一样可以读出全部 Secret。后来我们强制启用了 etcd 加密,并把备份文件也做了加密存储。
3. RBAC 权限控制:最小权限原则
RBAC 是 Kubernetes 控制谁做什么的核心机制,但在 Secret 上,很多人把它用成了「全有或全无」。我见过最夸张的配置是给了一个 namespace 所有 Pod 的 get 权限…… 嗯,等于没控制。
我个人习惯把 Secret 的访问权限控制到最小粒度的 ServiceAccount。步骤如下:
- 按业务拆分 ServiceAccount:每个微服务一个,绝不共用。
- 使用 Role 而不是 ClusterRole:除非 Secret 跨 namespace,否则永远用 Role。
- 只给
get和list:不需要create、update、delete权限给普通服务。 - 配合 Pod 安全策略:禁止 Pod 挂载宿主机的 Docker socket,防止绕开 RBAC。
# 一个典型的最小权限 Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
# 只允许读取,不允许写入
verbs: ["get", "list"]
---
# 绑定到 ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: production
name: secret-reader-binding
subjects:
- kind: ServiceAccount
name: payment-service
namespace: production
roleRef:
kind: Role
name: secret-reader
apiGroup: rbac.authorization.k8s.io
我个人经验:曾经有个审计场景,我们需要知道「哪些 Pod 实际访问了 Secret」,默认 RBAC 日志是不记录 get 操作的。后来我启用了 Kubernetes Audit Logging 并过滤出 responseStatus.code=200 且 verb=get + resource=secrets 的条目,才真正看到谁读了什么。建议在生产环境必开。——时间不用太多,但能救命。
4. etcd 加密最佳实践:最后一道防线
etcd 是 Kubernetes 的整个状态中心,包括所有 Secret。如果 etcd 被拖走,前面的一切防护都白费。所以,etcd 加密不是可选,是必需。
Kubernetes 从 1.13 开始支持 etcd 加密,有两种主流方式:
- AES-GCM:性能好,加密速度快,适合高吞吐环境。
- 使用外部 KMS 插件:比如 AWS KMS、GCP Cloud KMS、Azure Key Vault、HashiCorp Vault。密钥管理更安全,但会增加延迟。
我的建议:如果你们公司有合规要求(比如 SOC2、PCI),直接用 KMS;如果没有,AES-GCM 也足够,但密钥文件本身要妥善保管。
配置示例(AES-GCM):
# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: ZXhhbXBsZV9rZXlfdmFsdWU=
- identity: {} # 回退到明文
然后需要在 kube-apiserver 启动参数中加上 --encryption-provider-config=/etc/kubernetes/encryption-config.yaml。记得滚动重启所有 apiserver。
另一个常见陷阱:etcd 加密只对新写入的数据生效。已经存在的 Secret 需要删除重建才会被加密。我在某个生产环境就是忘了这步,以为配了加密就万事大吉,结果旧 Secret 还是明文。——所以记得 kubectl delete secret <name> --ignore-not-found 然后重新创建。
核心逻辑梳理:Secret 安全的层次防御
下面这张图是我自己总结的四层防御模型。你细看就会发现,每一层都堵住一个攻击面。
你看,这四层是从左到右、从代码到运行时依次递进的。实际落地时,我建议按顺序检查:没有源头管控,后面再加密也没用;没有 etcd 加密,RBAC 再怎么精细也无济于事。你想想看,攻击者只要能拿到 etcd 快照,所有 ServiceAccount 的权限都不管用——因为数据本身已经暴露了。
这就是我们常说的“纵深防御”。每一层都是独立的安全屏障,任何一层被突破,后面还有保底。嗯,这也是我在生产环境中坚持的底线。