LabHub
学习 学习路径 课程

CKS — Kubernetes 安全专家

用 CEL 写准入策略

在 LabHub 中继续学习

目标

在不使用外部 Webhook 服务器的情况下,亲自创建在 API 服务器内通过 CEL 运行的 ValidatingAdmissionPolicy,使用绑定缩小应用范围,并同时编写传统的 ValidatingWebhookConfiguration 对象,亲手比较两种方式的差异。

为什么重要

RBAC 只能回答“是否可以创建 Pod”。该 Pod 是否为 privileged、镜像是否来自允许的 Registry,都无法用 RBAC 表达。Admission Policy 正是用来填补这一空白。

传统方式通过 ValidatingWebhookConfiguration 注册外部服务器,但这存在可用性陷阱。如果以 failurePolicy: Fail 注册的 Webhook 服务器宕机,所有目标资源的创建都会被拒绝,整个部署随之停止。为了遵守策略,反而让集群停摆。ValidatingAdmissionPolicy 在 API 服务器内部评估 CEL 表达式,消除了网络跳转和外部进程,也减少了这种失败模式。

策略与绑定分离同样是有意的设计。策略只定义“什么构成违规”,绑定则决定“应用到哪里、执行多严格”。同一策略可以在预发布环境中使用 Warn,在生产环境中使用 Deny。

步骤

  1. 创建命名空间 cks-admission,并添加标签 cks-policy=enforce
  2. 创建 ValidatingAdmissionPolicy cks-no-latest-tagspec.matchConstraints.resourceRules[0] 的 apiGroups 为 apps,apiVersions 为 v1, operations 为 CREATEUPDATE,resources 为 deployments。 (API 服务器会拒绝既没有 validations 也没有 auditAnnotations 的策略。本步骤先加入 expression: "true" 之类的占位验证表达式,并在第 3 步替换为真实表达式。)
  3. 在同一策略中添加 spec.validations[0]expression 是检查 Deployment 所有容器 镜像都不以 :latest 结尾的 CEL(必须包含 object.spec.template.spec.containersall),message 不得为空。
  4. 将同一策略的 spec.failurePolicy 设置为 Fail
  5. 创建 ValidatingAdmissionPolicyBinding cks-no-latest-tag-bindingpolicyNamecks-no-latest-tagvalidationActionsDenymatchResources.namespaceSelector.matchLabelscks-policy: enforce
  6. 创建 ValidatingAdmissionPolicy cks-no-privileged。matchConstraints 使用核心组 (apiGroups 为空字符串)、apiVersions v1、operations CREATE·UPDATE、resources pods; validations[0].expression 必须包含 privileged 一词和 object.spec.containers, 并且必须设置 message。
  7. 创建 ValidatingWebhookConfiguration cks-image-verify。配置一个 Webhook(nameverify.images.cks.local),failurePolicyIgnoresideEffectsNoneadmissionReviewVersionsv1clientConfig.service 指向命名空间 cks-admission 中的 Service image-verifier,rules 为针对核心组 v1podsCREATE
  8. cks-admission 中创建 Deployment checkout。replicas 为 2,选择器和 Pod 标签为 app=checkout,Pod 模板还要添加标签 app.kubernetes.io/name=checkout。 容器名称为 app,镜像使用 @sha256: 固定且不使用 :latest, 并在容器 securityContext 中显式设置 privileged: falseallowPrivilegeEscalation: false

参考

策略应用的命名空间

创建命名空间 cks-admission,并添加标签 cks-policy=enforce

绑定的 namespaceSelector 会根据该标签选择目标。请准确匹配标签名称和值。

ValidatingAdmissionPolicy 骨架

创建 ValidatingAdmissionPolicy cks-no-latest-tagspec.matchConstraints.resourceRules[0] 的 apiGroups 为 apps,apiVersions 为 v1, operations 为 CREATEUPDATE,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.containersall),message 不得为空。

object 是被检查的资源。对于 Deployment,容器位于 object.spec.template.spec.containers,可使用 CEL 的 all() 宏检查全部容器。

确定 failurePolicy

将同一策略的 spec.failurePolicy 设置为 Fail

该策略决定表达式评估失败时如何处理。对于安全策略,应当选择阻止,而不是放行。

通过绑定启用策略

创建 ValidatingAdmissionPolicyBinding cks-no-latest-tag-bindingpolicyNamecks-no-latest-tagvalidationActionsDenymatchResources.namespaceSelector.matchLabelscks-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(nameverify.images.cks.local),failurePolicyIgnoresideEffectsNoneadmissionReviewVersionsv1clientConfig.service 指向命名空间 cks-admission 中的 Service image-verifier,rules 为针对核心组 v1podsCREATE

sideEffectsadmissionReviewVersions 是必填字段,缺少时创建会被拒绝。并且当前没有 Webhook 服务器,错误选择失败策略会使集群停摆。

综合:通过策略的部署

cks-admission 中创建 Deployment checkout。replicas 为 2,选择器和 Pod 标签为 app=checkout,Pod 模板还要添加标签 app.kubernetes.io/name=checkout。 容器名称为 app,镜像使用 @sha256: 固定且不使用 :latest, 并在容器 securityContext 中显式设置 privileged: falseallowPrivilegeEscalation: false

必须同时满足前面创建的两项策略。使用 Digest 而非标签,并在容器 securityContext 中降低权限。