LabHub
学习 学习路径 课程

CKS — Kubernetes 安全专家

三行标签取代 PSP 的原因

在 LabHub 中继续学习

一句话总结

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 会被阻止,所以应先启用 warnaudit, 观察哪些对象会受影响,再提升为 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: trueallowPrivilegeEscalation: falsecapabilities.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 文件。