三行标签取代 PSP 的原因
一句话总结
Pod Security Admission 通过 namespace 的几行 label 设定 workload 的安全下限。 而 Secret 只是经过 base64 编码,并未加密,因此需要额外控制。
为什么需要了解这一点
PodSecurityPolicy 在 1.21 中被标记为 deprecated,并在 1.25 中移除。原因有两个。 使用 PSP 时,需要向 workload 的 ServiceAccount 授予“可以 use 此 PSP”的 RBAC 权限, 而这种结构本身会形成提权路径。并且很难预测哪个 Pod 会应用哪个 PSP, 因为从多个 PSP 中选择一个的规则十分复杂。
替代方案 PSA 则恰恰相反,非常简单。只需给 namespace 添加 label。
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: v1.30
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
enforce 会拒绝违规 Pod,audit 只在 audit log 中记录,warn 则只向 kubectl 用户
显示警告。三种模式可以分别设置,这是迁移的关键。如果直接在现有 cluster 上应用
enforce: restricted,大量 workload 会被阻止,所以应先启用 warn 与 audit,
观察哪些对象会受影响,再提升为 enforce。
-version label 同样重要。profile 的定义会随 Kubernetes 版本逐步收紧。
固定版本,可以防止 cluster 升级同时强化策略,导致 Pod 突然被拒绝。
工作原理
profile 分为三个级别。
| Profile | 核心限制 |
|---|---|
| privileged | 无限制。用于 CNI、storage driver 等 system workload |
| baseline | 禁止 hostNetwork/hostPID/hostIPC,禁止 privileged、hostPath 和危险 capability |
| restricted | baseline + 必须设置 runAsNonRoot、seccompProfile、capabilities drop ALL、allowPrivilegeEscalation false |
通过 restricted 的 Pod 必须具备四个字段:runAsNonRoot: true、
allowPrivilegeEscalation: false、capabilities.drop: ["ALL"]、
seccompProfile.type: RuntimeDefault。缺少其中任何一个都会被拒绝,错误消息会原样指出
哪个字段因为什么原因不符合要求。
第二个维度是 Secret,也是实际工作者最常误解的部分。Secret manifest 中的值采用
base64,看起来仿佛经过加密,但 base64 是编码而非加密。它没有 key,只需一条命令即可还原。
默认配置下,Secret 以明文存储在 etcd 中,因此获得 etcd backup 文件或 disk image 的人
可以读取所有 Secret。要启用静态加密,需要创建 EncryptionConfiguration 文件,并通过
kube-apiserver 的 --encryption-provider-config 指定。此时,identity provider 必须位于
列表最后。如果放在前面,就会退回明文存储。即使修改配置,现有 Secret 在重新写入前仍保持明文,
因此必须统一更新一次。
第三是注入方式。通过环境变量注入后,可以从 /proc/<PID>/environ 读取,还会被子进程继承,
并可能完整出现在 crash report 或 debug page 中。volume mount 更安全,惯例是使用
defaultMode: 0400 仅允许 owner 读取。
实际工作中的表现
直接查看 etcd 就能消除误解。未启用加密时,执行
etcdctl get /registry/secrets/default/app-db | hexdump -C,可以原样看到 key path 与 value。
启用加密后,同一位置会出现 k8s:enc:kms:v2: 之类的 provider prefix,正文则无法读取。
亲眼看过这种差异的人,设计方式会与未看过的人不同。
RBAC 方面也经常存在误解。在某个 namespace 拥有 get secrets 权限的人,可以读取该 namespace 的
全部 Secret。为了方便而授予开发人员的 edit 权限,实际上常常等同于读取 production credential 的权限。
只需一行 kubectl auth can-i get secrets --namespace payments --as dev@example.com 就能确认,
但真正检查过的团队很少。Role 可以通过 resourceNames 仅允许特定 Secret,这一点同样鲜为人知。
从 multi-tenancy 角度看,同样的问题还会重复出现。基于 namespace 的隔离共享 API server,
因此仍存在 CRD 安装冲突与 noisy neighbor 问题。vCluster 等为每个 tenant 提供独立 API server 的方式
可以解决这些问题,但 workload Pod 最终仍会以
<vcluster>-x-<이름>-x-<네임스페이스> 的形式实体化在 host cluster 的 namespace 中。
也就是说,host 侧依然需要保持 PSS 与 NetworkPolicy 的良好卫生。增加一层抽象,并不会免除下层控制。
下个练习将做什么
首先分别为两个 namespace 应用 baseline 与 restricted,创建能够通过 restricted 的 Pod,
再确认违规 Pod 确实会被拒绝。随后在 Secret 练习中介绍按类型创建、环境变量注入与 volume mount 的差异、
defaultMode、immutable Secret、通过 resourceNames 收紧的 RBAC,以及编写 EncryptionConfiguration 文件。