SELinux 卷标签变更进入 GA 阶段(以及 v1.37 中可能的影响)
如果你在 Linux 上运行带有 SELinux 强制模式的 Kubernetes,
请提前规划:未来某个版本(预计为 v1.37)预计将默认启用 SELinuxMount
特性门控。
这会使大多数工作负载的卷设置更快,但它可能会破坏以微妙方式仍依赖于旧版递归重新标记模型的应用程序
(例如,在同一节点上的特权和非特权 Pod 之间共享一个卷)。
Kubernetes v1.36 是审计集群、修复或选择退出此更改的正确版本。
如果你的节点不使用 SELinux,则没有任何变化: 当 SELinux 在 Linux 内核中不可用或被禁用时, kubelet 会跳过整个 SELinux 逻辑。你可以完全跳过本文。
本文建立在
Kubernetes 1.27:高效的 SELinux 重新标记(Beta)
一文中描述的早期工作基础上,该文介绍了 SELinuxMountReadWriteOncePod 特性门控。
需要解决的问题保持不变,但是本文将相同的方法扩展到所有卷。
问题描述
启用了安全增强 Linux(SELinux)的 Linux 系统使用附加到对象(例如文件和网络套接字)
上的标签来进行访问控制决策。
历史上,容器运行时将 SELinux 标签应用到 Pod 及其所有卷。
Kubernetes 仅将 Pod 的 securityContext 字段中的 SELinux 标签传递给容器运行时。
然后容器运行时递归地更改对 Pod 容器可见的所有文件上的 SELinux 标签。 如果卷上有许多文件,这可能非常耗时,尤其是当卷位于远程文件系统上时。
注意:
如果容器使用卷的 subPath,则只重新标记整个卷的该 subPath。
这允许具有两个不同 SELinux 标签的两个 Pod 使用同一个卷,只要它们使用不同的 subpath。
如果 Pod 在 Kubernetes API 中没有分配任何 SELinux 标签, 容器运行时会分配一个唯一的随机标签, 这样可能逃逸容器边界的进程无法访问主机上任何其他容器中的数据。 容器运行时仍会用此随机 SELinux 标签递归地重新标记所有 Pod 卷。
Kubernetes 的改进
在堆栈支持的情况下,kubelet 可以使用 -o context=<label> 挂载卷,
以便内核为该挂载上的所有 inode 应用正确的标签,而无需递归遍历 inode。
该路径受特性门控限制,并且需要(除其他外)Pod 暴露足够的 SELinux
标签(例如 spec.securityContext.seLinuxOptions.level),
并且卷驱动程序选择加入(对于 CSI,设置 CSIDriver 字段 spec.seLinuxMount: true)。
项目分阶段推出此功能:
- ReadWriteOncePod 卷在
SELinuxMountReadWriteOncePod特性门控下处理, 自 v1.28 起默认启用,在 v1.36 中达到 GA。 - 更广泛的覆盖范围在
SELinuxMount标志下处理,与 Pod 上的spec.securityContext.seLinuxChangePolicy字段配合使用。
如果 Pod 及其卷满足所有以下条件,Kubernetes 将直接以正确的 SELinux 标签挂载卷。 这种挂载将在恒定时间内完成,容器运行时无需递归地重新标记其上的任何文件。 要进行此类挂载,需要满足以下条件:
- 操作系统必须支持 SELinux。在未检测到 SELinux 支持的情况下, kubelet 和容器运行时不会对 SELinux 执行任何操作。
- 特性门控
SELinuxMountReadWriteOncePod必须启用。 如果你运行的是 Kubernetes v1.36,则该特性无条件启用。
Pod 必须使用具有适用
accessModes的 PersistentVolumeClaim:- 要么卷具有
accessModes: ["ReadWriteOncePod"] - 要么卷可以使用任何其他访问模式,但需要满足以下条件:
特性门控
SELinuxChangePolicy和SELinuxMount都已启用, 并且 Pod 的spec.securityContext.seLinuxChangePolicy设置为 nil(默认)或MountOption。
特性门控
SELinuxMount在 Kubernetes 1.36 中为 Beta 阶段且默认禁用。 所有其他 SELinux 相关的特性门控现在都已达到正式发布(GA)。如果其中任何特性门控被禁用,SELinux 标签将始终通过递归遍历卷(或其
subPath)由容器运行时应用。- 要么卷具有
Pod 必须在其 安全上下文 中至少分配了
seLinuxOptions.level,或者该 Pod 中的所有容器都必须在容器级别的安全上下文中设置它。 Kubernetes 将从操作系统默认值(通常为system_u、system_r和container_t) 中读取默认的user、role和type。如果 Kubernetes 不知道 SELinux 的“级别”,容器运行时将在卷挂载后分配一个随机级别。 在这种情况下,容器运行时仍会递归地重新标记卷。
负责该卷的卷插件或 CSI 驱动程序支持使用 SELinux 挂载选项进行挂载。
这些树内卷插件支持使用 SELinux 挂载选项进行挂载:
fc和iscsi。支持使用 SELinux 挂载选项进行挂载的 CSI 驱动程序必须通过设置
seLinuxMount字段在其 CSIDriver 实例中声明此功能。由其他卷插件或未设置
seLinuxMount: true的 CSI 驱动程序管理的卷将由容器运行时递归地重新标记。
破坏性变更
SELinuxMount 特性门控以微妙的方式改变了多个 Pod 之间可以共享的卷。
以下两种情况在递归重新标记下都能正常工作:
- 具有不同 SELinux 标签的两个 Pod 共享同一个卷,
但它们各自使用不同的
subPath访问该卷。
- 特权 Pod 和非特权 Pod 共享同一个卷。
当 SELinux 处于活动状态时,上述场景将不适用于 Kubernetes 挂载的现代目标行为。
相反,其中一个 Pod 将停留在 ContainerCreating 状态,直到另一个 Pod 被终止。
第一种情况非常小众,在实践中从未见过。
虽然第二种情况仍然相当罕见,但已在应用程序中观察到这种设置。
Kubernetes v1.36 提供了指标和事件来识别这些 Pod,并允许集群管理员通过
Pod 字段 spec.securityContext.seLinuxChangePolicy 选择退出挂载选项。
seLinuxChangePolicy
新的 Pod 字段 spec.securityContext.seLinuxChangePolicy
指定如何将 SELinux 标签应用到所有 Pod 卷。
在 Kubernetes v1.36 中,此字段是稳定 Pod API 的一部分。
有三个可用的选项:
- 字段未设置(默认)
- 在 Kubernetes v1.36 中,行为取决于
SELinuxMount特性门控是否启用。 默认情况下,该特性门控未启用,SELinux 标签递归应用。 如果你在集群中启用了该特性门控,并且满足所有其他条件, 则将使用挂载选项应用标签。
Recursive- SELinux 标签递归应用。这选择退出使用挂载选项。
MountOption- 如果满足所有其他条件,则使用挂载选项应用 SELinux 标签。
此选项仅在
SELinuxMount特性门控启用时可用。
SELinux 警告控制器(可选)
Kubernetes v1.36 在控制平面中提供了一个新的控制器 selinux-warning-controller。
此控制器在 kube-controller-manager 控制器内运行。
要使用它,你需要在 kube-controller-manager 命令行上传递 --controllers=*,selinux-warning-controller;
你还不能明确地将 SELinuxChangePolicy 特性门控覆盖为禁用。
控制器监视集群中的所有 Pod,当发现两个 Pod 以与 SELinuxMount
特性门控不兼容的方式共享同一卷时会发出事件。
所有此类冲突的 Pod 将收到一个事件,例如:
SELinuxLabel "system_u:system_r:container_t:s0:c98,c99" conflicts with pod my-other-pod that uses the same volume as this pod with SELinuxLabel "system_u:system_r:container_t:s0:c0,c1". If both pods land on the same node, only one of them may access the volume.
当冲突的 Pod 运行在不同名字空间时,实际的 Pod 名称可能会被隐藏, 以防止跨名字空间边界泄露信息。
即使这些 Pod 不在同一节点上运行,控制器也会报告此类事件, 以确保所有 Pod 都能正常工作,而不考虑 Kubernetes 调度器的决定。 它们下次可能会在同一节点上运行。
此外,控制器发出指标 selinux_warning_controller_selinux_volume_conflict,
列出 Pod 之间所有当前的冲突。
该指标具有用于识别冲突 Pod 及其 SELinux 标签的标签,例如:
selinux_warning_controller_selinux_volume_conflict{pod1_name="my-other-pod",pod1_namespace="default",pod1_value="system_u:object_r:container_file_t:s0:c0,c1",pod2_name="my-pod",pod2_namespace="default",pod2_value="system_u:object_r:container_file_t:s0:c0,c2",property="SELinuxLabel"} 1
启用此选择性加入控制器存在安全后果:它可能泄露名字空间名称,这些名称始终存在于指标中。 Kubernetes 项目假定只有集群管理员可以访问 kube-controller-manager 指标。
建议的升级路径
为确保从 v1.36 平滑升级到启用 SELinuxMount 的版本(预计为 v1.37),
我们建议你按照以下步骤操作:
- 在 kube-controller-manager 中启用 selinux-warning-controller。
- 检查
selinux_warning_controller_selinux_volume_conflict指标。 它显示 Pod 之间的所有潜在冲突。 对于每个冲突的 Pod(Deployment、StatefulSet 等), 要么应用选择退出(设置 Pod 的spec.securityContext.seLinuxChangePolicy: Recursive), 要么重新设计应用程序以消除此类冲突。例如,你的 Pod 真的需要以特权身份运行吗?
- 检查
volume_manager_selinux_volume_context_mismatch_warnings_total指标。 当实际启动在SELinuxMount被禁用时运行的 Pod 时,此指标由 kubelet 发出, 但此类 Pod 在SELinuxMount启用时将无法启动。 此指标列出了将经历真正冲突的 Pod 数量。不幸的是,此指标不会将确切的 Pod 名称公开为标签。 完整的 Pod 名称仅在selinux_warning_controller_selinux_volume_conflict指标中可用。
- 一旦两个指标都被考虑在内,升级到启用了
SELinuxMount的 Kubernetes 版本。
考虑使用 MutatingAdmissionPolicy、 变更性 Webhook 或策略引擎如 Kyverno 或 Gatekeeper 来将选择退出应用到名字空间或整个集群中的所有 Pod。
当 SELinuxMount 启用时,kubelet 将发出指标
volume_manager_selinux_volume_context_mismatch_errors_total,
其中包含因 SELinux 标签与使用同一卷的现有 Pod 冲突而无法启动的 Pod 数量。
如果 selinux-warning-controller 被启用,确切的 Pod 名称仍应在
selinux_warning_controller_selinux_volume_conflict 指标中可用。
进一步阅读
- KEP:使用挂载加速 SELinux 卷重新标记
- SELinux 卷重新标记特性门控
- 故事 3:集群升级
- 为 Pod 配置安全上下文 — 高效的 SELinux 卷重新标记和 selinux-warning-controller
致谢
如果你遇到问题、有反馈或想要贡献,请在 Kubernetes Slack 的
#sig-node 和 #sig-storage 中找到我们,或加入
SIG Node 或
SIG Storage 会议。