Secret 读取、Pod 创建与运行安全是不同的边界
一句话总结
读取 Secret API 的权限、创建 Pod 的权限、Pod 的运行安全设置,是三个不同的边界。即使是 Restricted Pod,也能通过卷读取同一命名空间中的 Secret。
为什么需要它
假设有如下场景:从部署服务账户中删除了 get secrets。kubectl auth can-i 也回答 no,于是报告称它无法访问秘密。但该账户仍保留 create pods。攻击者没有选择读取 Secret 的 API,而是选择创建一个挂载 Secret 卷的 Pod。kubelet 准备好卷后,容器通过普通文件读取就能取得值。直接访问被拒绝,并不等于间接访问已被阻止。
工作原理
连续提出三个问题
| 边界 | 提出的问题 | 本实验观察的内容 |
|---|---|---|
| 认证 | 请求者是谁? | 区分管理员证书与被模拟的 runner SA |
| 授权 | 是否允许这个动词、资源和范围? | 拒绝查询 Secret,允许创建 app 的 Pod |
| 准入 | 是否接受这个已获授权创建请求中的对象? | 检查 Pod 是否满足 Restricted 运行条件 |
Pod 创建请求的主体与 Pod 的 serviceAccountName 不是同一概念。管理员创建一个指定 runner 的 Pod,并不表示使用了 runner 的创建权限。下一项实验会使用 --as=system:serviceaccount:app:runner 发送真实创建请求。这是利用管理员 impersonate 权限进行的教学模拟请求,并非窃取或伪造 SA 令牌。
Restricted 阻止什么、保留什么
Pod Security Standards 的 Restricted 会限制 privilege escalation、root 运行等容器执行设置。从 baseline 起,就会限制 hostPath、hostPID、hostNetwork、privileged 等危险配置。但 Restricted 允许的卷中包含 secret 和 configMap。该策略不会判断某个用户在业务上是否可以使用特定 Secret。
即使同时设置 runAsNonRoot: true、capabilities drop ALL、allowPrivilegeEscalation: false、seccompProfile: RuntimeDefault,也不会禁止容器读取合法挂载的文件。automountServiceAccountToken: false 只是关闭 API 令牌自动挂载,不会阻止 Secret 卷。
因此,“create pods 总是等同于节点 root”这一说法也不正确。即使权限相同,可达范围仍取决于准入策略、可选择的 SA、命名空间范围和卷类型。pods/exec 受可以进入的现有容器的权限和数据影响。bind、escalate、impersonate 也必须检查允许的资源与主体范围,不能因为存在其中一个动词,就一概断定为 cluster-admin。
防御必须同时确定数据边界与工作负载边界
不互相信任的任务,不应与敏感 Secret 放在同一命名空间,这是起点。应最小化工作负载创建权限和可选择的服务账户;必要时通过独立准入策略限制允许引用的 Secret 和 SA 选择。即使给直接 get 权限加上 resourceNames,也不会自动限制通过创建 Pod 引用的卷。NetworkPolicy 控制网络连接,无法阻止读取已经挂载的本地文件。
PSA 在此基础上仍然必要。既要应用降低危险执行设置越过节点边界的控制,也要应用决定谁能使用命名空间内业务数据的控制。关键是不能因为一个边界打了勾,就省略另一个边界的验证。
静态存储加密也是另一条边界
Secret API 的 data 值只是 base64 表示,并非加密。未配置静态存储加密时,存储系统中的数据无法保证机密性。本实验的 k3s/kine 环境会在 SQLite 中序列化的 Secret 里确认合成标记。这不表示所有发行版都使用相同文件和序列化格式;托管服务的默认加密设置也必须另行确认。
EncryptionConfiguration 使用第一个 provider 写入新值,再通过后续 provider 读取旧值。把 identity 放在前面,新数据也不会加密。启用配置或更换密钥,并不会自动重新保存现有对象,还需要重写、验证和删除旧读取路径。正常授权的 API 请求会取得解密后的值,因此静态存储加密不会阻断上述挂载路径。如果密钥和备份一起暴露,加密效果也会丧失。
实际现场中的情况
示例检查顺序如下:先为特定 SA 指定命名空间,分别询问 can-i get secrets 和 can-i create pods;再结合准入策略与 SA 选择范围,检查它能创建怎样的 Pod;最后确认所创建工作负载挂载了哪些数据。can-i --list 只是起点,并不能完整解释所有外部授权和准入结果。必须同时查看实际请求与失败原因。
即使关闭服务账户的自动令牌挂载,Pod 中明确指定的设置仍可能优先。修改该选项不会立即改变所有正在运行的 Pod,应确认影响并通过新 Pod 应用。是否不需要令牌,与是否不需要 Secret,必须分别判断。
还要区分 401 与 403。允许匿名认证的路径中,无凭据请求可能作为 system:anonymous 用户和 system:unauthenticated 组处理。因此,匿名请求不一定总是 401;结果会随各路径的匿名许可、无效凭据与授权结论而变化。不能只看到 403 就断定已经通过了哪些准入控制,应同时确认响应原因和审计记录。
下一项实验要做什么
用合成 Secret 确认静态存储加密和重写,再在应用了 Restricted 的 app 中,比较 runner 创建的非 root Pod 所读取 Secret 文件的哈希。不要输出真实秘密值。把 API 直接查询被拒绝、Pod 创建成功、Secret 卷读取成功分别记录为独立证据,然后确认认证边界与不含正文的审计日志。该 VM 是教学用单节点环境,不能把结果扩大解释为其他租户或整个生产集群的安全保证。