从 PodSecurityPolicy 迁移到内置 Pod 安全准入控制器
本页面描述了从 PodSecurityPolicy 迁移到内置的 PodSecurity Admission 控制器的过程。通过结合使用 dry-run、audit
和 warn
模式,可以有效地完成此操作,尽管在使用可变 PSP 时会变得更困难。
准备工作
你的 Kubernetes 服务器版本必须不低于 v1.22。
要查看版本,输入 kubectl version
。
如果你当前运行的 Kubernetes 版本不是 1.33,你可能需要切换到查看与你实际运行的 Kubernetes 版本对应的文档页面。
本页假设你已熟悉基本的 Pod 安全性准入 概念。
总体方法
你可以采用多种策略从 PodSecurityPolicy 迁移到 Pod 安全性准入。以下步骤是其中一种可能的迁移路径,目标是最大限度地降低生产中断和安全漏洞的风险。
- 决定 Pod 安全性准入是否适合你的用例。
- 检查 Namespace 权限
- 简化和标准化 PodSecurityPolicy
- 更新 Namespace
- 确定合适的 Pod 安全级别
- 验证 Pod 安全级别
- 强制执行 Pod 安全级别
- 绕过 PodSecurityPolicy
- 检查 Namespace 创建过程
- 禁用 PodSecurityPolicy
0. 决定 Pod 安全性准入是否适合你
Pod 安全性准入旨在开箱即用地满足最常见的安全需求,并在不同集群中提供一套标准的安全性级别。然而,它不如 PodSecurityPolicy 灵活。值得注意的是,PodSecurityPolicy 支持而 Pod 安全性准入不支持以下功能:
- 设置默认安全约束 - Pod 安全性准入是一个非可变的准入控制器,这意味着它在验证 Pod 之前不会修改它们。如果你依赖 PSP 的这一特性,你将需要修改工作负载以满足 Pod 安全约束,或者使用 可变准入 Webhook 进行这些更改。有关更多详细信息,请参阅下面的简化和标准化 PodSecurityPolicy。
- 策略定义的细粒度控制 - Pod 安全性准入仅支持 3 个标准级别。如果你需要更精细地控制特定约束,那么你将需要使用 可验证准入 Webhook 来强制执行这些策略。
- 子 Namespace 策略粒度 - 即使在单个 Namespace 中,PodSecurityPolicy 也允许你将不同的策略绑定到不同的 ServiceAccount 或用户。这种方法存在许多陷阱,不推荐使用,但如果你无论如何都需要此功能,则需要改用第三方 Webhook。例外情况是,如果你只需要完全豁免特定用户或 RuntimeClass,在这种情况下,Pod 安全性准入确实提供了一些静态配置用于豁免。
即使 Pod 安全性准入不能满足你的所有需求,它被设计为与其他策略强制机制**互补**,并且可以作为与其他准入 Webhook 并行的有用回退方案。
1. 检查 Namespace 权限
Pod 安全性准入由 Namespace 上的标签控制。这意味着任何可以更新(或修补或创建)Namespace 的人也可以修改该 Namespace 的 Pod 安全级别,这可能被用来绕过更严格的策略。在继续之前,请确保只有受信任的特权用户拥有这些 Namespace 权限。不建议将这些高权限授予不应具有提升权限的用户,但如果你必须这样做,则需要使用准入 Webhook 对 Namespace 对象上 Pod 安全标签的设置施加额外限制。
2. 简化和标准化 PodSecurityPolicy
在本节中,你将减少可变的 PodSecurityPolicy 并删除超出 Pod 安全标准范围的选项。你应该对要修改的原始 PodSecurityPolicy 的脱机副本进行此处建议的更改。克隆的 PSP 应该具有与原始 PSP 不同且在字母顺序上排在其之前的名称(例如,在其前面加上一个 0
)。不要在 Kubernetes 中创建新的策略 - 这将在下面的发布更新后的策略部分中介绍。
2.a. 消除纯粹可变的字段
如果 PodSecurityPolicy 正在修改 Pod,那么当你最终关闭 PodSecurityPolicy 时,可能会出现不符合 Pod 安全级别要求的 Pod。为了避免这种情况,你应该在切换之前消除所有 PSP 修改。不幸的是,PSP 没有明确区分可变和可验证字段,因此这不是一个直接的迁移过程。
你可以从消除纯粹可变且对验证策略没有任何影响的字段开始。这些字段(也在将 PodSecurityPolicy 映射到 Pod 安全标准参考中列出)是:
.spec.defaultAllowPrivilegeEscalation
.spec.runtimeClass.defaultRuntimeClassName
.metadata.annotations['seccomp.security.alpha.kubernetes.io/defaultProfileName']
.metadata.annotations['apparmor.security.beta.kubernetes.io/defaultProfileName']
.spec.defaultAddCapabilities
- 尽管从技术上讲这是一个可变和可验证的字段,但这些应该合并到.spec.allowedCapabilities
中,后者执行相同的验证但不进行修改。
注意
移除这些字段可能导致工作负载缺少必要的配置,并引发问题。关于如何安全地发布这些变更,请参阅下面的发布更新后的策略。2.b. 消除 Pod 安全标准未涵盖的选项
PodSecurityPolicy 中有几个字段未被 Pod 安全标准涵盖。如果你必须强制执行这些选项,则需要使用准入 Webhook 来补充 Pod 安全性准入,这超出了本指南的范围。
首先,你可以删除 Pod 安全标准未涵盖的纯粹可验证字段。这些字段(也在将 PodSecurityPolicy 映射到 Pod 安全标准参考中标记为“无意见”)是:
.spec.allowedHostPaths
.spec.allowedFlexVolumes
.spec.allowedCSIDrivers
.spec.forbiddenSysctls
.spec.runtimeClass
你还可以删除以下与 POSIX / UNIX 组控制相关的字段。
.spec.runAsGroup
.spec.supplementalGroups
.spec.fsGroup
其余的可变字段是正确支持 Pod 安全标准所必需的,需要在之后逐个处理:
.spec.requiredDropCapabilities
- 限制配置文件需要删除ALL
。.spec.seLinux
- (仅在使用MustRunAs
规则时可变)强制执行基本和限制配置文件对 SELinux 的要求。.spec.runAsUser
- (在使用RunAsAny
规则时不可变)强制执行限制配置文件的RunAsNonRoot
。.spec.allowPrivilegeEscalation
- (仅在设置为false
时可变)限制配置文件需要。
2.c. 发布更新后的 PSPs
接下来,你可以将更新后的策略发布到你的集群。你应该谨慎行事,因为移除可变选项可能导致工作负载缺少必要的配置。
对于每个更新的 PodSecurityPolicy:
- 确定在原始 PSP 下运行的 Pod。这可以通过
kubernetes.io/psp
注解完成。例如,使用 kubectl:PSP_NAME="original" # Set the name of the PSP you're checking for kubectl get pods --all-namespaces -o jsonpath="{range .items[?(@.metadata.annotations.kubernetes\.io\/psp=='$PSP_NAME')]}{.metadata.namespace} {.metadata.name}{'\n'}{end}"
- 将这些正在运行的 Pod 与原始 Pod 规约进行比较,以确定 PodSecurityPolicy 是否修改了 Pod。对于由工作负载资源创建的 Pod,你可以将 Pod 与控制器资源中的 PodTemplate 进行比较。如果发现任何变更,应使用所需的配置更新原始 Pod 或 PodTemplate。需要复查的字段有:
.metadata.annotations['container.apparmor.security.beta.kubernetes.io/*']
(将 * 替换为每个容器名称).spec.runtimeClassName
.spec.securityContext.fsGroup
.spec.securityContext.seccompProfile
.spec.securityContext.seLinuxOptions
.spec.securityContext.supplementalGroups
- 在容器上,在
.spec.containers[*]
和.spec.initContainers[*]
下:.securityContext.allowPrivilegeEscalation
.securityContext.capabilities.add
.securityContext.capabilities.drop
.securityContext.readOnlyRootFilesystem
.securityContext.runAsGroup
.securityContext.runAsNonRoot
.securityContext.runAsUser
.securityContext.seccompProfile
.securityContext.seLinuxOptions
- 创建新的 PodSecurityPolicy。如果任何 Role 或 ClusterRole 授予了对所有 PSP 的
use
权限,这可能导致使用新的 PSP 而不是与其对应的可变 PSP。 - 更新授权以授予对新 PSP 的访问权限。在 RBAC 中,这意味着更新任何授予对原始 PSP
use
权限的 Role 或 ClusterRole,使其也授予对更新后的 PSP 的该权限。 - 验证:经过一段时间的稳定运行后,重新运行步骤 1 中的命令,查看是否有任何 Pod 仍在使用原始 PSP。请注意,在发布新策略后,需要重新创建 Pod 才能完全验证。
- (可选)一旦你验证了原始 PSP 已不再使用,你可以删除它们。
3. 更新 Namespace
以下步骤需要在集群中的每个 Namespace 上执行。这些步骤中引用的命令使用 $NAMESPACE
变量来指代正在更新的 Namespace。
3.a. 确定合适的 Pod 安全级别
开始阅读Pod 安全标准并熟悉这 3 个不同的级别。
有几种方法可以选择 Namespace 的 Pod 安全级别:
- 根据 Namespace 的安全要求 - 如果你熟悉 Namespace 预期的访问级别,可以根据这些要求选择合适的级别,这类似于在新集群上的处理方法。
- 根据现有 PodSecurityPolicy - 使用将 PodSecurityPolicy 映射到 Pod 安全标准参考,你可以将每个 PSP 映射到一个 Pod 安全标准级别。如果你的 PSP 不是基于 Pod 安全标准的,你可能需要在选择至少与 PSP 一样宽松的级别和至少与 PSP 一样严格的级别之间做出决定。你可以使用此命令查看给定 Namespace 中哪些 PSP 被 Pod 使用:
kubectl get pods -n $NAMESPACE -o jsonpath="{.items[*].metadata.annotations.kubernetes\.io\/psp}" | tr " " "\n" | sort -u
- 根据现有 Pod - 使用验证 Pod 安全级别下的策略,你可以测试基本 (Baseline) 和限制 (Restricted) 级别,看看它们是否对现有工作负载足够宽松,并选择限制最少的有效级别。
注意
上述选项 2 和 3 基于**现有**的 Pod,可能会遗漏当前未运行的工作负载,例如 CronJob、缩放到零的工作负载或其他尚未发布的负载。3.b. 验证 Pod 安全级别
一旦你为 Namespace 选择了一个 Pod 安全级别(或者如果你正在尝试几个级别),最好先进行测试(如果使用特权 (Privileged) 级别可以跳过此步骤)。Pod 安全性包括几种工具来帮助测试和安全地发布配置文件。
首先,你可以进行策略的 dry-run(试运行),它会根据应用的策略评估当前在 Namespace 中运行的 Pod,而不会使新策略生效:
# $LEVEL is the level to dry-run, either "baseline" or "restricted".
kubectl label --dry-run=server --overwrite ns $NAMESPACE pod-security.kubernetes.io/enforce=$LEVEL
此命令将为任何在提议级别下无效的**现有** Pod 返回警告。
第二种方法更适合捕获当前未运行的工作负载:审计模式。在审计模式下运行时(与强制执行不同),违反策略级别的 Pod 会记录在审计日志中,可以在经过一段时间的稳定运行后进行复查,但不会被禁止。警告模式的工作方式类似,但会立即将警告返回给用户。你可以使用此命令在 Namespace 上设置审计级别:
kubectl label --overwrite ns $NAMESPACE pod-security.kubernetes.io/audit=$LEVEL
如果其中任何一种方法产生意外的违规,你将需要更新违规的工作负载以满足策略要求,或者放宽 Namespace 的 Pod 安全级别。
3.c. 强制执行 Pod 安全级别
当你确定所选级别可以安全地在 Namespace 上强制执行时,可以更新 Namespace 以强制执行所需的级别:
kubectl label --overwrite ns $NAMESPACE pod-security.kubernetes.io/enforce=$LEVEL
3.d. 绕过 PodSecurityPolicy
最后,你可以通过将完全特权 PSP 绑定到 Namespace 中的所有 ServiceAccount,从而有效地绕过 Namespace 级别的 PodSecurityPolicy。
# The following cluster-scoped commands are only needed once.
kubectl apply -f privileged-psp.yaml
kubectl create clusterrole privileged-psp --verb use --resource podsecuritypolicies.policy --resource-name privileged
# Per-namespace disable
kubectl create -n $NAMESPACE rolebinding disable-psp --clusterrole privileged-psp --group system:serviceaccounts:$NAMESPACE
由于特权 PSP 是不可变的,并且 PSP 准入控制器总是优先选择不可变的 PSP,这将确保该 Namespace 中的 Pod 不再被 PodSecurityPolicy 修改或限制。
像这样按 Namespace 禁用 PodSecurityPolicy 的优点是,如果出现问题,你可以通过删除 RoleBinding 轻松回滚变更。只需确保预先存在的 PodSecurityPolicy 仍在位即可!
# Undo PodSecurityPolicy disablement.
kubectl delete -n $NAMESPACE rolebinding disable-psp
4. 检查 Namespace 创建过程
现在现有 Namespace 已更新为强制执行 Pod 安全性准入,你应该确保更新创建新 Namespace 的过程和/或策略,以确保将适当的 Pod 安全配置文件应用于新 Namespace。
你还可以静态配置 Pod 安全准入控制器,为未标记的 Namespace 设置默认的强制、审计和/或警告级别。有关更多信息,请参阅配置准入控制器。
5. 禁用 PodSecurityPolicy
最后,你已准备好禁用 PodSecurityPolicy。为此,你需要修改 API 服务器的准入配置:如何关闭准入控制器?。
要验证 PodSecurityPolicy 准入控制器是否已禁用,你可以手动运行一个测试,模拟一个无权访问任何 PodSecurityPolicy 的用户(参见 PodSecurityPolicy 示例),或者通过检查 API 服务器日志。在启动时,API 服务器会输出日志行,列出已加载的准入控制器插件:
I0218 00:59:44.903329 13 plugins.go:158] Loaded 16 mutating admission controller(s) successfully in the following order: NamespaceLifecycle,LimitRanger,ServiceAccount,NodeRestriction,TaintNodesByCondition,Priority,DefaultTolerationSeconds,ExtendedResourceToleration,PersistentVolumeLabel,DefaultStorageClass,StorageObjectInUseProtection,RuntimeClass,DefaultIngressClass,MutatingAdmissionWebhook.
I0218 00:59:44.903350 13 plugins.go:161] Loaded 14 validating admission controller(s) successfully in the following order: LimitRanger,ServiceAccount,PodSecurity,Priority,PersistentVolumeClaimResize,RuntimeClass,CertificateApproval,CertificateSigning,CertificateSubjectRestriction,DenyServiceExternalIPs,ValidatingAdmissionWebhook,ResourceQuota.
你应该看到 PodSecurity
(在验证准入控制器列表中),并且两个列表都不应包含 PodSecurityPolicy
。
一旦你确定 PSP 准入控制器已禁用(并且经过足够的稳定运行时间以确信你无需回滚),你可以自由删除你的 PodSecurityPolicy 以及任何相关的 Role、ClusterRole、RoleBinding 和 ClusterRoleBinding(只需确保它们未授予任何其他不相关的权限)。