用 CEL 写准入策略
目标
在不使用外部 Webhook 服务器的情况下,亲自创建在 API 服务器内通过 CEL 运行的 ValidatingAdmissionPolicy,使用绑定缩小应用范围,并同时编写传统的 ValidatingWebhookConfiguration 对象,亲手比较两种方式的差异。
为什么重要
RBAC 只能回答“是否可以创建 Pod”。该 Pod 是否为 privileged、镜像是否来自允许的 Registry,都无法用 RBAC 表达。Admission Policy 正是用来填补这一空白。
传统方式通过 ValidatingWebhookConfiguration 注册外部服务器,但这存在可用性陷阱。如果以 failurePolicy: Fail 注册的 Webhook 服务器宕机,所有目标资源的创建都会被拒绝,整个部署随之停止。为了遵守策略,反而让集群停摆。ValidatingAdmissionPolicy 在 API 服务器内部评估 CEL 表达式,消除了网络跳转和外部进程,也减少了这种失败模式。
策略与绑定分离同样是有意的设计。策略只定义“什么构成违规”,绑定则决定“应用到哪里、执行多严格”。同一策略可以在预发布环境中使用 Warn,在生产环境中使用 Deny。
步骤
- 创建命名空间
cks-admission,并添加标签cks-policy=enforce。 - 创建 ValidatingAdmissionPolicy
cks-no-latest-tag。spec.matchConstraints.resourceRules[0]的 apiGroups 为apps,apiVersions 为v1, operations 为CREATE和UPDATE,resources 为deployments。 (API 服务器会拒绝既没有validations也没有auditAnnotations的策略。本步骤先加入expression: "true"之类的占位验证表达式,并在第 3 步替换为真实表达式。) - 在同一策略中添加
spec.validations[0]。expression是检查 Deployment 所有容器 镜像都不以:latest结尾的 CEL(必须包含object.spec.template.spec.containers和all),message不得为空。 - 将同一策略的
spec.failurePolicy设置为Fail。 - 创建 ValidatingAdmissionPolicyBinding
cks-no-latest-tag-binding。policyName为cks-no-latest-tag,validationActions为Deny,matchResources.namespaceSelector.matchLabels为cks-policy: enforce。 - 创建 ValidatingAdmissionPolicy
cks-no-privileged。matchConstraints 使用核心组 (apiGroups 为空字符串)、apiVersionsv1、operationsCREATE·UPDATE、resourcespods; validations[0].expression 必须包含privileged一词和object.spec.containers, 并且必须设置 message。 - 创建 ValidatingWebhookConfiguration
cks-image-verify。配置一个 Webhook(name为verify.images.cks.local),failurePolicy为Ignore,sideEffects为None,admissionReviewVersions为v1,clientConfig.service指向命名空间cks-admission中的 Serviceimage-verifier,rules 为针对核心组v1中pods的CREATE。 - 在
cks-admission中创建 Deploymentcheckout。replicas 为 2,选择器和 Pod 标签为app=checkout,Pod 模板还要添加标签app.kubernetes.io/name=checkout。 容器名称为app,镜像使用@sha256:固定且不使用:latest, 并在容器 securityContext 中显式设置privileged: false和allowPrivilegeEscalation: false。
参考
- 使用
kubectl explain validatingadmissionpolicy.spec.matchConstraints.resourceRules查看字段。 - CEL 示例:
object.spec.template.spec.containers.all(c, !c.image.endsWith(':latest')) - Pod 示例:
!object.spec.containers.exists(c, has(c.securityContext) && c.securityContext.privileged == true) - 常见错误 1:只创建策略,不创建绑定。没有绑定的策略不会检查任何请求。
- 常见错误 2:Webhook 中缺少
sideEffects或admissionReviewVersions时,API 服务器会直接拒绝创建对象。 - 常见错误 3:Webhook 服务器不存在时使用
failurePolicy: Fail,会导致目标资源完全无法创建。
策略应用的命名空间
创建命名空间 cks-admission,并添加标签 cks-policy=enforce。
绑定的 namespaceSelector 会根据该标签选择目标。请准确匹配标签名称和值。
ValidatingAdmissionPolicy 骨架
创建 ValidatingAdmissionPolicy cks-no-latest-tag。
spec.matchConstraints.resourceRules[0] 的 apiGroups 为 apps,apiVersions 为 v1,
operations 为 CREATE 和 UPDATE,resources 为 deployments。
(API 服务器会拒绝既没有 validations 也没有 auditAnnotations 的策略。本步骤先加入
expression: "true" 之类的占位验证表达式,并在第 3 步替换为真实表达式。)
matchConstraints.resourceRules 与 Admission Webhook 的 rules 结构相同。apiGroups、apiVersions、operations、resources 四项都必须填写。而且 API 服务器会直接拒绝既没有 validations 也没有 auditAnnotations 的策略,因此本步骤先加入一个占位验证表达式,再在下一步改为真实表达式。
使用 CEL 表达式编写验证规则
在同一策略中添加 spec.validations[0]。expression 是检查 Deployment 所有容器
镜像都不以 :latest 结尾的 CEL(必须包含 object.spec.template.spec.containers
和 all),message 不得为空。
object 是被检查的资源。对于 Deployment,容器位于 object.spec.template.spec.containers,可使用 CEL 的 all() 宏检查全部容器。
确定 failurePolicy
将同一策略的 spec.failurePolicy 设置为 Fail。
该策略决定表达式评估失败时如何处理。对于安全策略,应当选择阻止,而不是放行。
通过绑定启用策略
创建 ValidatingAdmissionPolicyBinding cks-no-latest-tag-binding。policyName 为
cks-no-latest-tag,validationActions 为 Deny,
matchResources.namespaceSelector.matchLabels 为 cks-policy: enforce。
没有绑定时,策略不会产生任何效果。validationActions 的取值决定请求会被拒绝还是仅收到警告。
添加禁止 privileged 的策略
创建 ValidatingAdmissionPolicy cks-no-privileged。matchConstraints 使用核心组
(apiGroups 为空字符串)、apiVersions v1、operations CREATE·UPDATE、resources pods;
validations[0].expression 必须包含 privileged 一词和 object.spec.containers,
并且必须设置 message。
目标为 Pod,因此容器路径与 Deployment 不同。某些容器可能没有 securityContext,请先在 CEL 中检查其是否存在。
编写 ValidatingWebhookConfiguration
创建 ValidatingWebhookConfiguration cks-image-verify。配置一个 Webhook(name 为
verify.images.cks.local),failurePolicy 为 Ignore,sideEffects 为 None,
admissionReviewVersions 为 v1,clientConfig.service 指向命名空间 cks-admission
中的 Service image-verifier,rules 为针对核心组 v1 中 pods 的 CREATE。
sideEffects 和 admissionReviewVersions 是必填字段,缺少时创建会被拒绝。并且当前没有 Webhook 服务器,错误选择失败策略会使集群停摆。
综合:通过策略的部署
在 cks-admission 中创建 Deployment checkout。replicas 为 2,选择器和 Pod 标签为
app=checkout,Pod 模板还要添加标签 app.kubernetes.io/name=checkout。
容器名称为 app,镜像使用 @sha256: 固定且不使用 :latest,
并在容器 securityContext 中显式设置 privileged: false 和 allowPrivilegeEscalation: false。
必须同时满足前面创建的两项策略。使用 Digest 而非标签,并在容器 securityContext 中降低权限。