很多团队一提到 Kubernetes Secret,就会下意识觉得“已经安全了”。但这其实是最常见的误区之一。Secret 只是 Kubernetes 里的敏感数据对象,不等于完整的加密系统,更不等于密钥管理平台。 如果一开始就把边界想错,后面的设计往往会一路偏掉::cite[162]。
本章你将学会
- 为什么说 Secret 不是加密系统,先要理解它的边界
- Secret 在存储、传输、访问各环节的风险点在哪里
- 如何结合 Encryption at Rest、KMS、External Secrets 等方案增强安全性
- 如何做敏感信息轮换、审计与日常治理
1. 先摆正认知:Secret 不是“自动安全”
Kubernetes Secret 的主要价值,是把敏感信息从镜像、代码、Pod 规范里拆出来,单独作为对象管理。这样做当然比“把密码直接写到 Deployment 里”要好,但它解决的只是组织和引用方式的一部分问题,不是从根上解决所有机密管理问题::cite[162]。
官方文档明确提醒:默认情况下,Secret 会以未加密的形式存储在 API Server 背后的 etcd 中;拥有 API 访问权限的人可以读取或修改 Secret,而能够在某个命名空间中创建 Pod 的主体,也往往能够通过挂载方式间接读取该命名空间里的 Secret::cite[162]。
换句话说,Secret 的边界至少包括这几层:
| 问题 | Secret 能否单独解决 |
|---|---|
| 不把密码写进镜像和代码 | 可以 |
| 对接 Kubernetes 工作负载引用 | 可以 |
| etcd 中默认强加密 | 不可以,需额外配置 |
| 外部密钥托管 | 不可以,需借助 KMS / 外部密钥系统 |
| 自动轮换所有凭据 | 不可以,需额外流程或控制器 |
| 全链路审计和泄露响应 | 不可以,需平台治理配合 |
所以更准确的说法应该是:Secret 是机密管理体系中的一个对象层,不是完整体系本身。
2. Secret 的风险面:存储、传输、访问都可能出问题
2.1 存储风险
Secret 默认保存在 etcd。如果你没有启用 Encryption at Rest,那么这些数据在底层存储里是明文可恢复的::cite[162][409]。
另外,Kubernetes 的 base64 编码只是编码,不是加密。下面这个清单:
apiVersion: v1
kind: Secret
metadata:
name: db-secret
namespace: demo
type: Opaque
data:
username: YWRtaW4=
password: c3VwZXItc2VjcmV0
password 看起来像“被处理过了”,其实只要 base64 -d 就能还原。不要把“看不懂”误以为“安全”。
2.2 传输风险
Secret 在 API 调用过程中依赖 TLS 保护,但你仍然要关注:
- 是否通过不安全日志打印了 Secret 内容
- 是否在 CI/CD 输出中暴露了
kubectl apply -f的原始内容 - 是否被开发脚本临时写入本地文件、Shell 历史或调试目录
2.3 访问风险
Secret 的高风险点不只是 get,list 和 watch 同样危险。官方最佳实践明确强调:授予对 Secret 的 list 或 watch 权限,等价于可以看到 Secret 内容,而不仅仅是对象名称::cite[512]。
此外,能够创建 Pod 的身份也很敏感,因为它可能通过挂载 Secret 的方式间接读取敏感数据::cite[162][512]。
3. 一个最基础但相对规范的 Secret 使用示例
3.1 完整 YAML
apiVersion: v1
kind: Namespace
metadata:
name: demo
---
apiVersion: v1
kind: Secret
metadata:
name: app-db-credentials
namespace: demo
type: Opaque
stringData:
username: app_user
password: S3cure-P@ssw0rd
---
apiVersion: v1
kind: Pod
metadata:
name: app-pod
namespace: demo
spec:
containers:
- name: app
image: nginx:1.27
volumeMounts:
- name: db-creds
mountPath: /etc/secrets/db
readOnly: true
volumes:
- name: db-creds
secret:
secretName: app-db-credentials
defaultMode: 0400
3.2 为什么推荐用卷挂载而不是环境变量优先
Secret 可以通过环境变量或卷挂载给容器,但从控制暴露面的角度看,卷挂载通常比环境变量更稳妥一些:
- 环境变量更容易被误打印
- 某些诊断工具会直接显示环境变量
- 变更后的 Secret 通常更适合通过文件方式被进程重新读取
当然,这不是绝对规则,但在大多数场景下,优先文件挂载、减少环境变量暴露会更容易做安全治理::cite[512]。
4. 先把底线补上:Encryption at Rest
Kubernetes 官方给出的第一条 Secret 安全建议,就是启用 Encryption at Rest。也就是让 API Server 在把 Secret 持久化到 etcd 前,先经过加密提供器处理::cite[162][512]。
4.1 EncryptionConfiguration 示例(aescbc)
下面是一个最基础的 EncryptionConfiguration 示例:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <BASE64_32_BYTE_KEY>
- identity: {}
几点注意:
identity代表不加密,通常放在后面作为兼容回退- 新写入的 Secret 会使用排在前面的 provider
- 已经写入的数据不会自动重加密,需要后续做 rewrite / migration::cite[409]
4.2 可选提供器怎么选
Kubernetes 文档列出了多种 provider,比如 identity、aescbc、secretbox、kms。其中:
| Provider | 说明 |
|---|---|
| identity | 不加密,仅用于兼容或回退 |
| aescbc | 常见的本地静态密钥方案 |
| secretbox | 另一种本地加密方案 |
| kms | 把数据密钥保护交给外部 KMS |
如果你只是先把 etcd 明文问题补掉,aescbc 是入门最快的方案;如果你希望密钥托管、轮换、审计都更专业,应该优先考虑 KMS::cite[409][410]。
5. 更进一步:在 v1.32 中优先考虑 KMS v2
Kubernetes 官方关于 KMS Provider 的说明里已经很明确:如果条件允许,应优先使用 KMS v2,因为 KMS v1 从 v1.28 起已被标记为 deprecated,并且从 v1.29 起默认关闭;KMS v2 在性能和实现上都更适合较新的 Kubernetes 集群::cite[410]。
这意味着在 Kubernetes v1.32 里,你做新集群 Secret 加密设计时,默认思路应该是:
能用 KMS v2,就不要再从 KMS v1 起步。
5.1 KMS v2 示例片段
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- kms:
apiVersion: v2
name: my-kms
endpoint: unix:///var/run/kmsplugin/socket.sock
cachesize: 1000
- identity: {}
这里的重点不是把 YAML 背下来,而是理解:
- kube-apiserver 不直接保管最终主密钥
- 它通过 KMS 插件与外部密钥管理服务协作
- 这样你就可以把主密钥轮换、访问控制、审计能力放到更成熟的密钥系统中去::cite[410]
6. External Secrets:把“真实秘密源”放在集群外
如果团队已经在云厂商 Secret Manager、Vault、企业密钥平台中维护密钥,最常见的做法不是把它们手工复制进 Kubernetes,而是使用 External Secrets Operator(ESO) 这类控制器,让 Kubernetes Secret 变成“同步结果”,而不是“唯一真相”::cite[483]。
ESO 的基本思路是:
- 定义
SecretStore或ClusterSecretStore,告诉控制器从哪里取值 - 定义
ExternalSecret,声明要同步哪些外部密钥 - 控制器拉取外部 API 数据,并生成 / 更新 Kubernetes Secret::cite[483]
6.1 ExternalSecret 示例
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: team-vault
namespace: demo
spec:
provider:
vault:
server: https://vault.example.com
path: kv
version: v2
auth:
kubernetes:
mountPath: kubernetes
role: demo-app
serviceAccountRef:
name: app-sa
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: app-db-secret
namespace: demo
spec:
refreshInterval: 1h
secretStoreRef:
name: team-vault
kind: SecretStore
target:
name: app-db-secret
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: prod/app/db
property: username
- secretKey: password
remoteRef:
key: prod/app/db
property: password
6.2 这种模式的好处
- 敏感源头仍在外部密钥系统
- Kubernetes 只保留运行时所需副本
- 支持周期性同步与集中轮换
- 凭据治理不再分散到一堆 Helm values、CI 变量和手工命令里::cite[483]
7. Secret 访问控制:真正高风险的是谁能读、谁能挂
官方 Secret 最佳实践强调两件很重要的事:
- 对 Secret 做最小权限 RBAC
- 对“能创建 Pod 的身份”同样保持警惕::cite[512]
7.1 一个合理的 Secret 只读角色
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-secret-reader
namespace: demo
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["app-db-credentials"]
verbs: ["get"]
这个角色比下面这种写法安全得多:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: dangerous-secret-reader
namespace: demo
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch"]
后者的问题是:
- 范围太大
- 可枚举命名空间里所有 Secret
- 易被调试脚本、运维脚本批量导出
8. 敏感信息轮换怎么做才像“生产方案”
很多团队第一次做 Secret 管理,只考虑“能不能用”,不考虑“怎么换”。但真正进入生产后,轮换能力比创建能力更重要。
8.1 建议把 Secret 分成两类
| 类型 | 示例 | 轮换方式 |
|---|---|---|
| 平台内部 Secret | 内部 Basic Auth、Webhook Token | 由平台统一定期轮换 |
| 外部依赖凭据 | 数据库密码、云访问令牌、第三方 API Key | 优先由上游系统轮换,再同步入集群 |
8.2 一个简单轮换流程
- 在外部系统或密码生成器中生成新值
- 更新 Secret 或更新外部秘密源
- 让应用通过 reload / restart 重新加载
- 验证新旧凭据切换成功
- 回收旧凭据
- 在审计系统中记录轮换时间、责任人、影响范围
8.3 支持热更新,不要只靠重启想象
并不是所有应用都会自动重新读取挂载文件或环境变量。你要提前确认:
- 应用是否支持 SIGHUP / reload
- 是否需要 sidecar 监控文件变化
- 是否必须重启 Pod 才能生效
否则“Secret 已更新”不等于“业务已切换”。
9. 审计实践:没有审计,Secret 管理就不完整
Secret 治理除了加密和轮换,还需要审计能力。建议至少关注下面几类事件:
- 谁读取了 Secret
- 谁修改了 Secret
- 谁在某 namespace 里获得了
secrets的 RBAC 权限 - 谁拥有创建 Pod 的能力
- 哪些 Pod 正在挂载哪些 Secret
结合 Kubernetes Audit、GitOps 代码审查、RBAC 审批流,你至少能回答这几个关键问题:
- 某个 Secret 最近一次是谁改的?
- 某个命名空间里谁有读取 Secret 的能力?
- 某个事故窗口内有哪些 Pod 可能暴露了敏感信息?
10. 常见误区
10.1 误区一:base64 就算加密
不是。base64 只是编码。
10.2 误区二:给运维一个 Secret 只读权限问题不大
如果这个“只读”里包含 list/watch,问题就已经不小了::cite[512]。
10.3 误区三:开启 Encryption at Rest 就万事大吉
它只能解决 etcd 持久化层的一部分风险,不能替代访问控制、外部密钥托管、凭据轮换和审计::cite[409][410]。
10.4 误区四:Kubernetes 里保存了 Secret,就不用外部密钥系统了
如果你的密钥生命周期、审批、跨系统复用、强审计要求都比较高,外部密钥系统通常仍然是更合适的“源头真相”。Kubernetes Secret 更适合做运行时分发层::cite[483]。
11. 一套适合 v1.32 的 Secret 安全基线
如果你的集群是 Kubernetes v1.32,我建议默认采用下面这套基线:
- 必须启用 Secret 的 Encryption at Rest
- 新设计优先 KMS v2,不从 KMS v1 起步::cite[410]
- 所有业务默认禁止
list/watch secrets - 能创建 Pod 的权限与 Secret 权限一起做审查
- 优先用文件挂载而不是环境变量暴露敏感值
- 高价值密钥优先放在外部密钥系统,通过 External Secrets / CSI 等机制下发::cite[483]
- 把轮换、审计、失效回收作为 Secret 生命周期的一部分,而不是事后补丁
12. 本章小结
这一章最核心的结论可以浓缩成一句话:
Secret 很重要,但 Secret 不是终点,它只是机密管理体系里的一个运行时对象层。
你需要记住:
- Secret 默认并不会自动解决 etcd 明文存储问题::cite[162]
get/list/watch secrets都是高风险权限::cite[512]- Kubernetes v1.32 场景下,Encryption at Rest 是底线,KMS v2 是更推荐的增强方案::cite[409][410]
- 如果你希望把“秘密源头”放在集群外,External Secrets 是非常常见的工程化方案::cite[483]
- 真正成熟的 Secret 管理,一定包含:最小权限、存储加密、外部托管、轮换机制、审计能力
下一章我们进入 Admission Controller,看看 Kubernetes 是如何在请求进入集群前统一执行安全与规范策略的。
📝 版权声明:本文为原创技术博客,转载请注明出处。
如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!