Pod 安全标准与准入 — 等级和模式是不同的维度
一句话总结
Pod Security Standards 规定的是禁止什么(privileged、baseline、restricted),Pod Security Admission 规定的是如何处理违规(enforce、audit、warn),这两者是不同的维度,所以可以在命名空间标签中自由组合。
为什么需要它
Pod 规格中有好几个能把宿主机整个打开的开关:hostNetwork、hostPID、privileged: true、hostPath 卷、任意添加的 capability。只要打开其中一个,容器隔离实际上就消失了。可是这些字段,又确实是正常的基础设施工作负载(CNI 代理、日志收集器、节点导出器)实际在使用的。这是一个不是“禁止”,而是必须确定“允许到什么程度”的问题,所以单纯的禁止列表解决不了。
PodSecurityPolicy 曾经承担过这个位置,但在 v1.25 中被移除了。PSP 看的不是用户,而是创建 Pod 的主体(通常是控制器的服务账号)的权限,而且当 Pod 上有多个 PSP 时,很难预测会适用哪一个。取而代之的是 Pod Security Admission。规则由项目确定并固定为三个等级,适用单位则简化为一个命名空间标签。
工作原理
维度一——等级(Pod Security Standards)。
| 等级 | 性质 |
|---|---|
privileged |
没有限制。用于系统和基础设施工作负载 |
baseline |
阻止已知的权限提升的最小限制。默认的 Pod 规格可以原样通过 |
restricted |
强烈要求遵循 Pod 加固的最佳实践 |
baseline 所阻止的,是共享宿主机命名空间、privileged、允许列表之外的 capability、hostPath 卷、宿主机端口,以及绕过 AppArmor、SELinux、seccomp 之类的做法。restricted 在此基础上又增加了限制卷类型、allowPrivilegeEscalation: false、runAsNonRoot: true、runAsUser 不设为 0、显式指定 seccomp 配置文件、drop 所有 capability 而只允许 NET_BIND_SERVICE。这里有一个重要的差别。baseline 只要“不使用奇怪的东西”就能通过,而 restricted 连普通的清单也通不过。因为什么都没写的容器,没有 seccomp 配置文件,也没有 drop capability。
维度二——模式(Pod Security Admission)。
| 模式 | 违规时 |
|---|---|
enforce |
拒绝 Pod |
audit |
在审计日志的事件中添加注解,并放行 |
warn |
向用户显示警告,并放行 |
两个维度通过标签相遇。每个模式有两个标签。
apiVersion: v1
kind: Namespace
metadata:
name: my-baseline-namespace
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: v1.30
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
这个组合本身就是引入流程。现在只强制 baseline,restricted 的违规用警告和审计来统计。一个命名空间可以设置全部三个模式,而且每个模式可以指定不同的等级,所以“提前打开下一个目标并观察”,只需两行标签就能做到。
漏掉 -version 后缀,升级那天部署就会停摆。版本标签的值是有效的 Kubernetes 次版本号或 latest,不写的话按 latest 运行。各个等级的内容会随版本变化——实际上 restricted 在 v1.25 中就发生了变化。如果设成 latest,升级集群的那天,明明什么清单都没改,昨天还能用的部署却被拦住了。反过来,如果固定了版本,即使等级变强,当天也什么都不会发生,而升级版本的时间表由人来定。这是把策略变更与集群升级分离开的机制。
准入是查看请求的机制。它无法拦住已经在运行的 Pod。贴上标签之后,违规的 Pod 仍然继续运行,只有当它重启或被重新调度时才会被拒绝。所以在切换之前,需要有提前找出违规的流程。用服务端 dry-run 运行标签命令,该命名空间中已有的 Pod 的违规会以警告形式出现。
kubectl label --dry-run=server --overwrite ns payments \
pod-security.kubernetes.io/enforce=restricted
文档中还写有一个容易忽略的事实。enforce 不适用于工作负载资源,只适用于由此产生的 Pod。audit 和 warn 也适用于 Deployment 这样的工作负载。所以带有违规模板的 Deployment,kubectl apply 会成功(会出现警告),而 Pod 没有生成这件事,要等几秒后在 ReplicaSet 的事件中才会暴露出来。这是“部署成功了,可是没有 Pod”这类咨询的常见原因。
豁免(exemption)不是写在标签上,而是写在准入控制器的配置文件中,共有三个维度——用户名、RuntimeClass 名称、命名空间。文档警告说,不要豁免控制器的服务账号。如果豁免了 replicaset-controller,所有能创建工作负载的人都会被隐式豁免。
在现场相遇的样子
第一,直接把 restricted 启用为 enforce 的团队。普通的清单全都被拦住。对于从来没写过 securityContext 的团队来说,这意味着“要把所有 Deployment 都改一遍”,所以标签通常一天之内就被撤下来。正确的顺序是:用 warn、audit 启用 restricted,而 enforce 停留在 baseline。
第二,在系统命名空间上设置了 restricted。CNI 代理或节点导出器一重启,就起不来了。集群陷入无法自行恢复的状态,而要知道原因是标签,得打开 Pod 事件才行。
第三,集群升级那天的故障。只有没有写 -version 就启用的命名空间,部署被拦住了。清单和策略都没变,所以很久才找到原因。固定版本不是偷懒,而是变更管理。
第四,用指标来看的方法。kube-apiserver 会输出 pod_security_evaluations_total、pod_security_errors_total、pod_security_exemptions_total。在以 audit 模式运行的期间,把这些值放到仪表板上,就能用数字回答“可以提升到 enforce 吗”。
参考文档
- Pod Security Admission: https://kubernetes.io/docs/concepts/security/pod-security-admission/
- Pod Security Standards: https://kubernetes.io/docs/concepts/security/pod-security-standards/
- 用命名空间标签应用标准:https://kubernetes.io/docs/tasks/configure-pod-container/enforce-standards-namespace-labels/
- 从 PodSecurityPolicy 迁移:https://kubernetes.io/docs/tasks/configure-pod-container/migrate-from-psp/
下一项实验要做什么
在 kwok 集群的命名空间上亲手贴标签,走完一遍切换流程。先启动违规 Pod,用 --dry-run=server 试用标签,确认已有的违规会以警告形式出现,在只开启 warn 和 audit 的状态下,看到同一个 Pod 能通过,然后提升到 enforce,确认它被拒绝。把固定了 -version 的命名空间和使用 latest 的命名空间并排放置,并亲手制造带有违规模板的 Deployment,apply 成功而 Pod 却没有生成的场景。