🔓 Kubernetes etcd 的三大致命弱点 — 你的 Secret 真的安全吗?

尽管名为 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。

为何“放任”不管?

从设计角度看,这更像是一种有意为之的权衡:

  1. 性能:将 Secret 作为文件挂载,即使容器读取数百次,API 服务器也不会有负载。如果每次都通过 API 调用处理,整个集群的性能将急剧下降。
  2. 卷挂载的架构限制:kubelet 将 Secret 挂载到 tmpfs。之后的访问是 OS 内核级别的文件 I/O。Kubernetes 无法拦截。
  3. 审计日志默认禁用:默认情况下,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

FalcoTetragon基于 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 SecretKubernetes Secrets Store CSI Driver 将 Secret 完全从 etcd 中分离的架构。


Comments

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注