从 PodSecurityPolicy 迁移到内置的 PodSecurity 准入控制器

本页面描述从 PodSecurityPolicy 迁移到内置的 PodSecurity 准入控制器的过程。 这一迁移过程可以通过综合使用试运行、auditwarn 模式等来实现, 尽管在使用了变更式 PSP 时会变得有些困难。

准备开始

你的 Kubernetes 服务器版本必须不低于版本 v1.22. 要获知版本信息,请输入 kubectl version.

如果你目前运行的 Kubernetes 版本不是 {{ skew currentVersion }}, 你可能要切换本页面以查阅你实际所运行的 Kubernetes 版本文档。

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

方法概览

你可以采取多种策略来完成从 PodSecurityPolicy 到 Pod 安全性准入 (Pod Security Admission)的迁移。 下面是一种可能的迁移路径,其目标是尽可能降低生产环境不可用的风险, 以及安全性仍然不足的风险。

  1. 确定 Pod 安全性准入是否对于你的使用场景而言比较合适。
  2. 审查名字空间访问权限。
  3. 简化、标准化 PodSecurityPolicy。
  4. 更新名字空间:
    1. 确定合适的 Pod 安全性级别;
    2. 验证该 Pod 安全性级别可工作;
    3. 实施该 Pod 安全性级别;
    4. 绕过 PodSecurityPolicy。
  5. 审阅名字空间创建过程。
  6. 禁用 PodSecurityPolicy。

0. 确定是否 Pod 安全性准入适合你

Pod 安全性准入被设计用来直接满足最常见的安全性需求,并提供一组可用于多个集群的安全性级别。 不过,这一机制比 PodSecurityPolicy 的灵活度要低。 值得注意的是,PodSecurityPolicy 所支持的以下特性是 Pod 安全性准入所不支持的:

  • 设置默认的安全性约束 - Pod 安全性准入是一个非变更性质的准入控制器, 这就意味着它不会在对 Pod 进行合法性检查之前更改其配置。如果你之前依赖于 PSP 的这方面能力, 你或者需要更改你的负载以满足 Pod 安全性约束,或者需要使用一个 变更性质的准入 Webhook 来执行相应的变更。进一步的细节可参见后文的简化和标准化 PodSecurityPolicy

  • 对策略定义的细粒度控制 - Pod 安全性准入仅支持 三种标准级别。 如果你需要对特定的约束施加更多的控制,你就需要使用一个 验证性质的准入 Webhook 以实施这列策略。

  • 粒度小于名字空间的策略 - PodSecurityPolicy 允许你为不同的服务账户或用户绑定不同策略, 即使这些服务账户或用户隶属于同一个名字空间。这一方法有很多缺陷,不建议使用。 不过如果你的确需要这种功能,你就需要使用第三方的 Webhook。 唯一的例外是当你只需要完全针对某用户或者 RuntimeClasses 赋予豁免规则时, Pod 安全性准入的确也为豁免规则暴露一些 静态配置

即便 Pod 安全性准入无法满足你的所有需求,该机制也是设计用作其他策略实施机制的 补充,因此可以和其他准入 Webhook 一起运行,进而提供一种有用的兜底机制。

1. 审查名字空间访问权限

Pod 安全性准入是通过名字空间上的标签 来控制的。这也就是说,任何能够更新(或通过 patch 部分更新或创建) 名字空间的人都可以更改该名字空间的 Pod 安全性级别,而这可能会被利用来绕过约束性更强的策略。 在继续执行迁移操作之前,请确保只有被信任的、有特权的用户具有这类名字空间访问权限。 不建议将这类强大的访问权限授予不应获得权限提升的用户,不过如果你必须这样做, 你需要使用一个准入 Webhook 来针对为 Namespace 对象设置 Pod 安全性级别设置额外的约束。

2. 简化、标准化 PodSecurityPolicy

在本节中,你会削减变更性质的 PodSecurityPolicy,去掉 Pod 安全性标准范畴之外的选项。 针对要修改的、已存在的 PodSecurityPolicy,你应该将这里所建议的更改写入到其离线副本中。 所克隆的 PSP 应该与原来的副本名字不同,并且按字母序要排到原副本之前 (例如,可以向 PSP 名字前加一个 0)。 先不要在 Kubernetes 中创建新的策略 - 这类操作会在后文的推出更新的策略部分讨论。

2.a. 去掉纯粹变更性质的字段

如果某个 PodSecurityPolicy 能够变更字段,你可能会在关掉 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 安全性准入机制覆盖。如果你必须使用这些选项, 你需要在 Pod 安全性准入之外部署 准入 Webhook 以补充这一能力,而这类操作不在本指南范围。

首先,你可以去掉 Pod 安全性标准所未覆盖的那些验证性字段。这些字段(也列举于 将 PodSecurityPolicy 映射到 Pod 安全性标准参考中,标记为“无意见”)有:

  • .spec.allowedHostPaths
  • .spec.allowedFlexVolumes
  • .spec.allowedCSIDrivers
  • .spec.forbiddenSysctls
  • .spec.runtimeClass

你也可以去掉以下字段,这些字段与 POSIX/UNIX 用户组控制有关。

注意:

如果这些字段中存在使用 MustRunAs 策略的情况,则意味着对应字段是变更性质的。 去掉相应的字段可能导致负载无法设置所需的用户组,进而带来一些问题。 关于如何安全地将这类变更上线的相关建议,请参阅后文的推出更新的策略部分。

  • .spec.runAsGroup
  • .spec.supplementalGroups
  • .spec.fsGroup

剩下的变更性字段是为了适当支持 Pod 安全性标准所需要的,因而需要逐个处理:

  • .spec.requiredDropCapabilities - 需要此字段来为 Restricted 配置去掉 ALL 设置。
  • .spec.seLinux - (仅针对带有 MustRunAs 规则的变更性设置)需要此字段来满足 Baseline 和 Restricted 配置所需要的 SELinux 需求。
  • .spec.runAsUser - (仅针对带有 RunAsAny 规则的非变更性设置)需要此字段来为 Restricted 配置保证 RunAsNonRoot
  • .spec.allowPrivilegeEscalation - (如果设置为 false 则为变更性设置) 需要此字段来支持 Restricted 配置。

2.c. 推出更新的 PSP

接下来,你可以将更新后的策略推出到你的集群上。在继续操作时,你要非常小心, 因为去掉变更性质的选项可能导致有些工作负载缺少必需的配置。

针对更新后的每个 PodSecurityPolicy:

  1. 识别运行于原 PSP 之下的 Pod。可以通过 kubernetes.io/psp 注解来完成。 例如,使用 kubectl:

    1. PSP_NAME="original" # 设置你要检查的 PSP 的名称
    2. 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 而不是其变更性的副本。

  4. 更新你的鉴权配置,为访问新的 PSP 授权。在 RBAC 机制下,这意味着需要更新所有为原 PSP 授予 use 访问权限的 Role 或 ClusterRole 对象,使之也对更新后的 PSP 授权。

  5. 验证:经过一段时间后,重新执行步骤 1 中所给的命令,查看是否有 Pod 仍在使用原来的 PSP。 注意,在新的策略被推出到集群之后,Pod 需要被重新创建才可以执行全面验证。

  6. (可选)一旦你已经验证原来的 PSP 不再被使用,你就可以删除这些 PSP。

3. 更新名字空间

下面的步骤需要在集群中的所有名字空间上执行。所列步骤中的命令使用变量 $NAMESPACE 来引用所更新的名字空间。

3.a. 识别合适的 Pod 安全级别

首先请回顾 Pod 安全性标准内容, 并了解三个安全级别。

为你的名字空间选择 Pod 安全性级别有几种方法:

  1. 根据名字空间的安全性需求来确定 - 如果你熟悉某名字空间的预期访问级别, 你可以根据这类需求来选择合适的安全级别,就像大家在为新集群确定安全级别一样。

  2. 根据现有的 PodSecurityPolicy 来确定 - 基于 将 PodSecurityPolicy 映射到 Pod 安全性标准 参考资料,你可以将各个 PSP 映射到某个 Pod 安全性标准级别。如果你的 PSP 不是基于 Pod 安全性标准的,你可能或者需要选择一个至少与该 PSP 一样宽松的级别, 或者选择一个至少与其一样严格的级别。使用下面的命令你可以查看被 Pod 使用的 PSP 有哪些:

    1. kubectl get pods -n $NAMESPACE -o jsonpath="{.items[*].metadata.annotations.kubernetes\.io\/psp}" | tr " " "\n" | sort -u
  3. 根据现有 Pod 来确定 - 使用检查 Pod 安全性级别小节所述策略, 你可以测试 Baseline 和 Restricted 级别,检查它们是否对于现有负载而言足够宽松, 并选择二者之间特权级较低的合法级别。

注意:

上面的第二和第三种方案是基于 现有 Pod 的,因此可能错失那些当前未处于运行状态的 Pod,例如 CronJobs、缩容到零的负载,或者其他尚未全面铺开的负载。

3.b. 检查 Pod 安全性级别

一旦你已经为名字空间选择了 Pod 安全性级别(或者你正在尝试多个不同级别), 先进行测试是个不错的主意(如果使用 Privileged 级别,则可略过此步骤)。 Pod 安全性包含若干工具可用来测试和安全地推出安全性配置。

首先,你可以试运行新策略,这个过程可以针对所应用的策略评估当前在名字空间中运行的 Pod,但不会令新策略马上生效:

  1. # $LEVEL 是要试运行的级别,可以是 "baseline" 或 "restricted"
  2. kubectl label --dry-run=server --overwrite ns $NAMESPACE pod-security.kubernetes.io/enforce=$LEVEL

此命令会针对在所提议的级别下不再合法的所有 现存 Pod 返回警告信息。

第二种办法在抓取当前未运行的负载方面表现的更好:audit 模式。 运行于 audit 模式(而非 enforcing 模式)下时,违反策略级别的 Pod 会被记录到审计日志中, 经过一段时间后可以在日志中查看到,但这些 Pod 不会被拒绝。 warning 模式的工作方式与此类似,不过会立即向用户返回告警信息。 你可以使用下面的命令为名字空间设置 audit 模式的级别:

  1. kubectl label --overwrite ns $NAMESPACE pod-security.kubernetes.io/audit=$LEVEL

当以上两种方法输出意料之外的违例状况时,你就需要或者更新发生违例的负载以满足策略需求, 或者放宽名字空间上的 Pod 安全性级别。

3.c. 实施 Pod 安全性级别

当你对可以安全地在名字空间上实施的级别比较满意时,你可以更新名字空间来实施所期望的级别:

  1. kubectl label --overwrite ns $NAMESPACE pod-security.kubernetes.io/enforce=$LEVEL

3.d. 绕过 PodSecurityPolicy

最后,你可以通过将 完全特权的 PSP 绑定到某名字空间中所有服务账户上,在名字空间层面绕过所有 PodSecurityPolicy。

  1. # 下面集群范围的命令只需要执行一次
  2. kubectl apply -f privileged-psp.yaml
  3. kubectl create clusterrole privileged-psp --verb use --resource podsecuritypolicies.policy --resource-name privileged
  4. # 逐个名字空间地禁用
  5. kubectl create -n $NAMESPACE rolebinding disable-psp --clusterrole privileged-psp --group system:serviceaccounts:$NAMESPACE

由于特权 PSP 是非变更性的,PSP 准入控制器总是优选非变更性的 PSP, 上面的操作会确保对应名字空间中的所有 Pod 不再会被 PodSecurityPolicy 所更改或限制。

按上述操作逐个名字空间地禁用 PodSecurityPolicy 这种做法的好处是, 如果出现问题,你可以很方便地通过删除 RoleBinding 来回滚所作的更改。 你所要做的只是确保之前存在的 PodSecurityPolicy 还在。

  1. # 撤销 PodSecurityPolicy 的禁用
  2. kubectl delete -n $NAMESPACE rolebinding disable-psp

4. 审阅名字空间创建过程

现在,现有的名字空间都已被更新,强制实施 Pod 安全性准入, 你应该确保你用来管控新名字空间创建的流程与/或策略也被更新,这样合适的 Pod 安全性配置会被应用到新的名字空间上。

你也可以静态配置 Pod 安全性准入控制器,为尚未打标签的名字空间设置默认的 enforce、audit 与/或 warn 级别。 详细信息可参阅配置准入控制器页面。

5. 禁用 PodSecurityPolicy

最后,你已为禁用 PodSecurityPolicy 做好准备。要禁用 PodSecurityPolicy, 你需要更改 API 服务器上的准入配置: 我如何关闭某个准入控制器?

如果需要验证 PodSecurityPolicy 准入控制器不再被启用,你可以通过扮演某个无法访问任何 PodSecurityPolicy 的用户来执行测试(参见 PodSecurityPolicy 示例), 或者通过检查 API 服务器的日志来进行验证。在启动期间,API 服务器会输出日志行,列举所挂载的准入控制器插件。

  1. 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.
  2. 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(在 validating admission controllers 列表中), 并且两个列表中都不应该包含 PodSecurityPolicy

一旦你确定 PSP 准入控制器已被禁用(并且这种状况已经持续了一段时间, 这样你才会比较确定不需要回滚),你就可以放心地删除你的 PodSecurityPolicy 以及所关联的所有 Role、ClusterRole、RoleBinding、ClusterRoleBinding 等对象 (仅需要确保他们不再授予其他不相关的访问权限)。