中文内容
Kubernetes v1.36:无法被删除的准入策略
如果你曾尝试在一组 Kubernetes 集群中强制执行安全策略,可能遇到过令人沮丧的“先有鸡还是先有蛋”问题。你的准入策略是 API 对象,这意味着它们只有在有人创建后才存在,并且任何拥有相应权限的人都可以删除它们。在集群引导期间,总会有一段时间策略尚未生效,而且无法阻止特权用户将其移除。
Kubernetes v1.36 引入了一个用于解决该问题的 alpha 特性:基于清单的准入控制。它允许你将准入 webhook 和基于 CEL 的策略定义为磁盘上的文件,由 API 服务器在启动时、开始处理任何请求之前加载。
我们正在弥合的缺口
如今大多数 Kubernetes 策略执行都通过 API 工作。你将 ValidatingAdmissionPolicy 或 webhook 配置作为 API 对象创建,准入控制器会接收它。这在稳定状态下运行良好,但存在一些根本限制。
在集群引导期间,从 API 服务器开始处理请求到你的策略被创建并生效之间存在一个空档。如果你正在从备份恢复,或从 etcd 故障中恢复,这个空档可能会很明显。
还存在一个自我保护问题。准入 webhook 和策略无法拦截对其自身配置资源的操作。Kubernetes 会跳过对 ValidatingWebhookConfiguration 等类型调用 webhook,以避免循环依赖。这意味着拥有足够权限的用户可以删除你的关键准入策略,而准入链中没有任何机制可以阻止他们。
我们——Kubernetes SIG API Machinery——希望有一种方式可以明确表示:“这些策略始终启用,没有例外。”
工作原理
你可以在已经通过 --admission-control-config-file 传递给 API 服务器的 AdmissionConfiguration 文件中添加 staticManifestsDir 字段。将它指向一个目录,把策略 YAML 文件放在那里,API 服务器就会在开始提供服务前加载它们。
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ValidatingAdmissionPolicy
configuration:
apiVersion: apiserver.config.k8s.io/v1
kind: ValidatingAdmissionPolicyConfiguration
staticManifestsDir: "/etc/kubernetes/admission/validating-policies/"
这些清单文件是标准 Kubernetes 资源定义。唯一要求是,这些清单定义的所有对象名称都必须以 .static.k8s.io 结尾。这个保留后缀可防止与基于 API 的配置发生冲突,并且在查看指标或审计日志时,便于判断某个准入决策来自哪里。
下面是一个完整示例,用于拒绝 kube-system 之外的特权容器:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "deny-privileged.static.k8s.io"
annotations:
kubernetes.io/description: "Deny launching privileged pods, anywhere this policy is applied"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
variables:
- name: allContainers
expression: >-
object.spec.containers +
(has(object.spec.initContainers) ? object.spec.initContainers : []) +
(has(object.spec.ephemeralContainers) ? object.spec.ephemeralContainers : [])
validations:
- expression: >-
!variables.allContainers.exists(c,
has(c.securityContext) && has(c.securityContext.privileged) &&
c.securityContext.privileged == true)
message: "Privileged containers are not allowed"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "deny-privileged-binding.static.k8s.io"
annotations:
kubernetes.io/description: "Bind deny-privileged policy to all namespaces except kube-system"
spec:
policyName: "deny-privileged.static.k8s.io"
validationActions:
- Deny
matchResources:
namespaceSelector:
matchExpressions:
- key: "kubernetes.io/metadata.name"
operator: NotIn
values: ["kube-system"]
保护过去无法保护的内容
我们最感到兴奋的部分,是能够拦截针对准入配置资源本身的操作。
使用基于 API 的准入时,webhook 和策略永远不会在 ValidatingAdmissionPolicy 或 ValidatingWebhookConfiguration 等类型上被调用。这个限制有充分理由:如果某个 webhook 可以拒绝对其自身配置的更改,你可能会被锁在外面,无法通过 API 修复问题。
基于清单的策略没有这个问题。如果某个有问题的策略阻止了不该阻止的内容,你可以修复磁盘上的文件,API 服务器会接收该更改。不存在循环依赖,因为恢复路径不经过 API。
这意味着你可以编写一个基于清单的策略,阻止关键的基于 API 的准入策略被删除。对于管理共享集群的平台团队来说,这是一个重要改进。现在你可以保证基线安全策略不会被集群管理员移除,无论是意外还是其他原因。
下面是实际效果。该策略会阻止对带有 platform.example.com/protected: "true" 标签的准入资源进行任何修改或删除:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "protect-policies.static.k8s.io"
annotations:
kubernetes.io/description: "Prevent modification or deletion of protected admission resources"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["admissionregistration.k8s.io"]
apiVersions: ["*"]
operations: ["DELETE", "UPDATE"]
resources:
- "validatingadmissionpolicies"
- "validatingadmissionpolicybindings"
- "validatingwebhookconfigurations"
- "mutatingwebhookconfigurations"
validations:
- expression: >-
!has(oldObject.metadata.labels) ||
!('platform.example.com/protected' in oldObject.metadata.labels) ||
oldObject.metadata.labels['platform.example.com/protected'] != 'true'
message: "Protected admission resources cannot be modified or deleted"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "protect-policies-binding.static.k8s.io"
annotations:
kubernetes.io/description: "Bind protect-policies policy to all admission resources"
spec:
policyName: "protect-policies.static.k8s.io"
validationActions:
- Deny
部署后,任何标记为 platform.example.com/protected: "true" 的基于 API 的准入策略或 webhook 配置都会受到防篡改保护。保护机制本身位于磁盘上,无法通过 API 移除。
需要了解的几点
基于清单的配置有意设计为自包含。它们不能引用 API 资源,这意味着策略不能使用 paramKind,准入 webhook 不能使用 Service 引用(而是仅支持 URL),并且绑定只能引用同一清单集中的策略。这些限制存在的原因是,这些配置需要在没有任何集群状态的情况下工作,包括启动时 etcd 尚不可用的阶段。
如果运行多个 API 服务器实例,每个实例都会独立加载自己的清单文件。系统没有内置跨服务器同步。这与其他基于文件的 API 服务器配置(如静态加密)采用相同模型。启用该特性后,Kubernetes 会在相关指标上以标签形式暴露配置哈希,因此你可以检测配置漂移。
运行时会监视文件变更,因此更新策略时不需要重启 API 服务器。如果更新清单文件,API 服务器会验证新配置并以原子方式切换。如果验证失败,它会保留上一个有效配置并记录错误。这意味着你可以使用标准配置管理工具(Ansible、Puppet,甚至挂载的 ConfigMaps)在整个集群群组中推出策略变更,而无需 API 服务器停机。
启动时的初始加载更加严格:如果任何清单无效,API 服务器将不会启动。这是有意设计的。启动时快速失败比在未加载预期策略的情况下运行更安全。
试用
要在 Kubernetes v1.36 中试用该特性:
- 为每个 kube-apiserver 启用 ManifestBasedAdmissionControlConfig 特性门控。
- 创建一个包含静态清单文件的目录。如果需要将其挂载到运行 API 服务器的 Pod 中,也请一并完成。只读挂载即可。
- 在 AdmissionConfiguration 中使用目录路径配置 staticManifestsDir。
- 启动 API 服务器,并让 --admission-control-config-file 指向你的 AdmissionConfiguration 文件。
完整文档位于 Manifest-Based Admission Control,你也可以关注 KEP-5793 以了解后续进展。
我们希望听到你的反馈。请在 Kubernetes Slack 的 #sig-api-machinery 频道联系我们(如需邀请,请访问 https://slack.k8s.io/ )。
如何参与
如果你有兴趣为该特性或其他 SIG API Machinery 项目做贡献,请在 Kubernetes Slack 上加入 #sig-api-machinery。也欢迎你参加每隔一个星期三举行的 SIG API Machinery 会议。
- ← 上一篇
- 下一篇 →