从 PodSecurityPolicy 迁移到内置 Pod 安全准入控制器

本页面描述了从 PodSecurityPolicy 迁移到内置的 PodSecurity Admission 控制器的过程。通过结合使用 dry-run、auditwarn 模式,可以有效地完成此操作,尽管在使用可变 PSP 时会变得更困难。

准备工作

你的 Kubernetes 服务器版本必须不低于 v1.22。

要查看版本,输入 kubectl version

如果你当前运行的 Kubernetes 版本不是 1.33,你可能需要切换到查看与你实际运行的 Kubernetes 版本对应的文档页面。

本页假设你已熟悉基本的 Pod 安全性准入 概念。

总体方法

你可以采用多种策略从 PodSecurityPolicy 迁移到 Pod 安全性准入。以下步骤是其中一种可能的迁移路径,目标是最大限度地降低生产中断和安全漏洞的风险。

  1. 决定 Pod 安全性准入是否适合你的用例。
  2. 检查 Namespace 权限
  3. 简化和标准化 PodSecurityPolicy
  4. 更新 Namespace
    1. 确定合适的 Pod 安全级别
    2. 验证 Pod 安全级别
    3. 强制执行 Pod 安全级别
    4. 绕过 PodSecurityPolicy
  5. 检查 Namespace 创建过程
  6. 禁用 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:

  1. 确定在原始 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}"
    
  2. 将这些正在运行的 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
  3. 创建新的 PodSecurityPolicy。如果任何 Role 或 ClusterRole 授予了对所有 PSP 的 use 权限,这可能导致使用新的 PSP 而不是与其对应的可变 PSP。
  4. 更新授权以授予对新 PSP 的访问权限。在 RBAC 中,这意味着更新任何授予对原始 PSP use 权限的 Role 或 ClusterRole,使其也授予对更新后的 PSP 的该权限。
  5. 验证:经过一段时间的稳定运行后,重新运行步骤 1 中的命令,查看是否有任何 Pod 仍在使用原始 PSP。请注意,在发布新策略后,需要重新创建 Pod 才能完全验证。
  6. (可选)一旦你验证了原始 PSP 已不再使用,你可以删除它们。

3. 更新 Namespace

以下步骤需要在集群中的每个 Namespace 上执行。这些步骤中引用的命令使用 $NAMESPACE 变量来指代正在更新的 Namespace。

3.a. 确定合适的 Pod 安全级别

开始阅读Pod 安全标准并熟悉这 3 个不同的级别。

有几种方法可以选择 Namespace 的 Pod 安全级别:

  1. 根据 Namespace 的安全要求 - 如果你熟悉 Namespace 预期的访问级别,可以根据这些要求选择合适的级别,这类似于在新集群上的处理方法。
  2. 根据现有 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
    
  3. 根据现有 Pod - 使用验证 Pod 安全级别下的策略,你可以测试基本 (Baseline) 和限制 (Restricted) 级别,看看它们是否对现有工作负载足够宽松,并选择限制最少的有效级别。

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(只需确保它们未授予任何其他不相关的权限)。

最后修改时间:2023 年 4 月 15 日下午 6:38 PST:修复 minor typo permision -> permission (5ed63def8a)