尽管名为 Secret,Kubernetes 的机密数据管理却漏洞百出。
>
##
🎯 本文涵盖内容
- etcd 默认不加密,以明文 (Base64) 存储 Secret 的原因和风险
- 仅凭创建 Pod 的权限即可绕过 Secret RBAC 的完整控制的攻击路径
- Pod 每次读取 Secret 时,审计日志(Audit Log)实际上为空的结构性原因
- 针对各项漏洞的实际应对方法
📌 引言 — etcd 是 Kubernetes 的心脏和阿喀琉斯之踵
Kubernetes 集群的所有状态信息都存储在名为 etcd 的分布式键值存储中。Deployment 信息、ConfigMap,以及我们今天关注的 Secret,全部都在这里。
如果 etcd 被攻破,就相当于掌控了整个集群。然而,这个重要的存储库存在三个结构性漏洞。这些并非简单的配置错误,而是设计决策和操作便利性所导致的结构性问题。

🔍 漏洞 1 — etcd 加密,为何不是默认选项?
问题的核心
Kubernetes Secret 的值默认以 Base64 编码形式存储在 etcd 中,不进行加密。Base64 并非加密,它只是将数据转换为另一种形式。只需一行命令 `echo “bXlwYXNzd29yZA==” | base64 -d` 即可还原原文。
任何能够直接访问 etcd 的人都可以轻松解密并读取这些数据。这就是为什么必须启用静态加密的原因。
为何不是默认选项?
Kubernetes 默认不启用加密有其现实原因。
① 密钥管理的复杂性:加密密钥应该存储在哪里?如果使用本地密钥加密 Secret 数据,可以应对 etcd 被窃取的情况,但如果主机本身被入侵,则无法提供保护。这是因为加密密钥存储在 EncryptionConfiguration YAML 文件中,而有能力的攻击者可以访问该文件。
② 现有数据迁移:即使启用加密,已存储的 Secret 也不会自动加密。所有现有 Secret 都必须重新写入。
③ 性能开销:每次读写操作都会增加加密/解密过程。
当前提供的加密方式
Kubernetes 提供了多种加密提供程序。`identity` 是默认值,完全不进行加密。AES-CBC 由于填充预言攻击漏洞已不再安全,并且由于密钥存储在控制平面节点上,因此在主机被入侵时无能为力。AES-GCM 比 AES-CBC 有所改进,但同样存在密钥存储在主机上的局限性。
正确应对
# /etc/kubernetes/enc.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- kms: # ✅ 生产环境推荐:通过KMS集成进行外部密钥管理
name: myKmsPlugin
endpoint: unix:///tmp/socketfile.sock
apiVersion: v2
- identity: {} # fallback — 仅用于解密
添加 kube-apiserver 配置:
# /etc/kubernetes/manifests/kube-apiserver.yaml
spec:
containers:
- command:
- kube-apiserver
- --encryption-provider-config=/etc/kubernetes/enc.yaml # 添加此行
在生产环境中,与 AWS KMS、GCP KMS、Azure Key Vault 等外部 KMS 集成,将密钥本身在集群外部管理,才是正确的做法。
🔍 漏洞 2 — 通过创建 Pod 权限绕过 Secret RBAC
最隐蔽的漏洞
这是三个漏洞中最隐蔽的一个。即使某个用户没有 `secrets` 资源的 `get` 权限,但如果他能够创建 Pod,就可以访问 Secret。
RBAC 系统强制只有授权用户才能创建 Pod,但它不控制用户在 Pod 定义中放入什么内容。如果没有适当的准入控制(Admission Control),拥有 Pod 创建权限的用户可以创建具有任意镜像和任意权限的容器。
3 种攻击场景
场景 A:通过环境变量窃取 Secret
# 即使没有Secret的get权限,如果这样创建Pod...
apiVersion: v1
kind: Pod
metadata:
name: secret-stealer
spec:
containers:
- name: attacker
image: busybox
command: ["sh", "-c", "echo $DB_PASSWORD && sleep 3600"]
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials # 想要访问的Secret
key: password
Pod 日志中会直接输出 DB_PASSWORD 的值。
场景 B:窃取 Service Account 令牌
攻击者可以创建一个 Pod,使用恶意镜像,该镜像旨在窃取具有高权限的 Service Account 的令牌。攻击者可以利用此令牌执行 API 操作并提升权限。
# 在Pod内部检查挂载的令牌
cat /var/run/secrets/kubernetes.io/serviceaccount/token
# 使用此令牌向API服务器发出请求
curl -H "Authorization: Bearer $(cat /var/run/secrets/...)" https://k8s-api/api/v1/secrets
场景 C:通过 hostPath 挂载直接访问 etcd
如果能够使用节点选择器将 Pod 调度到控制平面节点并挂载主机文件系统,就可以在该节点上 chroot,获取 root 权限,并直接读取 etcd 数据库。
应对方法
# 使用准入控制器阻止特权Pod
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
# 或者使用Pod安全准入
---
# 将PSA标签应用于命名空间
kubectl label namespace production
pod-security.kubernetes.io/enforce=restricted
pod-security.kubernetes.io/enforce-version=latest
核心原则:
- 默认设置 automountServiceAccountToken: false
- 禁止 hostPath, hostPID, hostNetwork 挂载策略
- 通过 Admission Controller (OPA Gatekeeper, Kyverno) 验证镜像来源和权限
🔍 漏洞 3 — Secret 访问审计日志的结构性空白
这是核心问题。Pod 每次读取挂载的 Secret 时,都不会生成审计日志。为什么?
Kubernetes 审计日志的工作方式
Kubernetes 审计日志始于 kube-apiserver 组件内部。对 API 服务器的每个请求(request)都会生成一个审计事件。
核心问题就在这里。审计日志只记录通过 API 服务器发出的请求。
Secret 传递给 Pod 的方式
[파드 생성 시]
API Server → kubelet → 파드 볼륨 마운트
↑
이 시점에 1번만 Secret GET 요청 발생 → 로그 기록됨
[파드 실행 중]
파드 컨테이너 → /var/run/secrets/... 파일 직접 읽기
↑
이건 그냥 파일시스템 read() 시스템 콜
→ API 서버를 거치지 않음 → 감사 로그 없음
当 Secret 作为环境变量注入时,情况也相同。容器运行时在 Pod 启动时一次性获取值并设置为环境变量,之后的所有访问都发生在进程级别。API 服务器与此无关。
Kubernetes 默认不提供 Secret 访问的详细审计日志。这使得难以监控谁何时访问了 Secret。
为何“放任”不管?
从设计角度看,这更像是一种有意为之的权衡:
- 性能:将 Secret 作为文件挂载,即使容器读取数百次,API 服务器也不会有负载。如果每次都通过 API 调用处理,整个集群的性能将急剧下降。
- 卷挂载的架构限制:kubelet 将 Secret 挂载到 tmpfs。之后的访问是 OS 内核级别的文件 I/O。Kubernetes 无法拦截。
- 审计日志默认禁用:默认情况下,Kubernetes 集群的审计日志并未启用,必须创建审计策略并在 kube-apiserver 配置中明确指定才能启用。
审计日志配置 + 认识局限性
# /etc/kubernetes/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived"
rules:
# Secret API请求以Metadata级别记录(防止值泄露)
- level: Metadata
resources:
- group: ""
resources: ["secrets"]
# 跟踪Pod的创建/删除
- level: RequestResponse
verbs: ["create", "delete", "patch"]
resources:
- group: ""
resources: ["pods"]
⚠️ 注意:如果以 RequestResponse 级别记录 Secret,Secret 值本身会记录在审计日志中。审计日志将成为另一个泄露途径。Metadata 级别是现实的折衷方案。
真正的解决方案 — 运行时审计
要检测 Pod 对 Secret 文件的访问,需要 OS 级别的工具。
# Falco规则示例 — 检测对 /var/run/secrets 的访问
- rule: Read Kubernetes Service Account Token
desc: Detect any read of a Kubernetes service account token
condition: >
open_read
and fd.name startswith /var/run/secrets/kubernetes.io/serviceaccount
and not proc.name in (known_sa_readers)
output: >
"Service account token read (user=%user.name command=%proc.cmdline
file=%fd.name container=%container.name)"
priority: WARNING
Falco、Tetragon、基于 eBPF 的运行时安全工具可以弥补这一空白。
⚠️ 注意事项 / 常见错误
- AES-CBC 已不再安全 — 务必使用 KMS 或 AES-GCM
- 禁止在审计日志中设置 Secret RequestResponse 级别 — Secret 值会直接记录在日志中
- pods/exec 权限也是 Secret 泄露途径 — 通过 exec 进入容器可以访问挂载的 Secret 文件。此路径直接使用 Kubelet API 绕过 API 服务器,因此甚至不会留下审计日志。
- 如果误删加密密钥,集群将无法恢复。请将密钥管理委托给 Vault 或云 KMS。
✅ 总结 — 三大漏洞应对摘要
| 漏洞 | 核心问题 | 应对 |
| 1. 未应用加密 | 仅应用 Base64,无加密 | KMS 集成 + EncryptionConfiguration |
| 2. 通过创建 Pod 绕过 | RBAC 无法控制 Pod 内容 | PSA Restricted + OPA Gatekeeper |
| 3. 审计空白 | 绕过 API 服务器的文件访问未记录 | Falco/Tetragon eBPF 运行时监控 |
这三个漏洞都不是“功能缺陷”,而是源于性能、灵活性与安全性之间的权衡。理解这一点才能采取正确的应对措施。
下一步,建议考虑通过 HashiCorp Vault 的 Dynamic Secret 或 Kubernetes Secrets Store CSI Driver 将 Secret 完全从 etcd 中分离的架构。

发表回复