WEBKT

实战 K8s 准入控制:编写 Validating Webhook 封杀非官方镜像源

38 0 0 0

在生产环境中,随意从公共镜像仓库(如 Docker Hub、未知的三方镜像源)拉取镜像,会带来巨大的安全风险和不确定性。为了规范镜像来源,我们通常要求所有 Pod 只能从公司内部的私有仓库(如 Harbor)拉取镜像。

Kubernetes 提供了 Admission Controllers(准入控制器) 机制,其中的 ValidatingAdmissionWebhook 允许我们在资源持久化到 ETCD 之前,拦截 API 请求并根据自定义逻辑进行校验。本文将手把手教你如何实现一个简单的 Webhook 来限制镜像来源。

1. 核心原理

当 API Server 接收到创建 Pod 的请求时,如果配置了 Validating Webhook,API Server 会将请求体(AdmissionReview)以 JSON 格式发送到我们自定义的服务后端。

后端逻辑:

  1. 解析 AdmissionReview 对象。
  2. 遍历 Pod 定义中的所有容器(包括 containersinitContainers)。
  3. 检查 image 字段是否以允许的域名开头(如 registry.example.com/)。
  4. 返回 AdmissionResponse,告知 API Server 是否允许该请求。

2. 后端逻辑实现(Go 示例)

我们使用简单的 Go HTTP 服务来处理请求。核心逻辑如下:

// 核心校验函数
func validatePodImages(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
    raw := ar.Request.Object.Raw
    pod := v1.Pod{}
    if err := json.Unmarshal(raw, &pod); err != nil {
        return &admissionv1.AdmissionResponse{
            Allowed: false,
            Result:  &metav1.Status{Message: "无法解析 Pod 对象"},
        }
    }

    // 定义允许的镜像前缀
    allowedPrefix := "registry.example.com/"

    // 检查所有容器
    containers := append(pod.Spec.Containers, pod.Spec.InitContainers...)
    for _, container := range containers {
        if !strings.HasPrefix(container.Image, allowedPrefix) {
            return &admissionv1.AdmissionResponse{
                Allowed: false,
                Result: &metav1.Status{
                    Code:    403,
                    Message: fmt.Sprintf("镜像来源非法: %s。仅允许来自 %s 的镜像。", container.Image, allowedPrefix),
                },
            }
        }
    }

    return &admissionv1.AdmissionResponse{Allowed: true}
}

3. 处理 TLS 证书

Kubernetes 要求 Webhook 服务必须通过 HTTPS 暴露。在集群内部,最简单的方案是使用 cert-manager 或手动生成由集群 CA 签名的证书。

关键点:ValidatingWebhookConfiguration 中的 caBundle 字段必须包含签发 Webhook 服务端证书的根证书 base64 编码。

4. 部署 Webhook 服务

我们需要将上述逻辑打包成容器镜像,并在 K8s 中部署为一个 Service。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: image-checker-webhook
spec:
  template:
    spec:
      containers:
        - name: webhook
          image: my-company/image-checker:v1
          ports:
            - containerPort: 443
          volumeMounts:
            - name: certs
              mountPath: /etc/webhook/certs
              readOnly: true
      volumes:
        - name: certs
          secret:
            secretName: webhook-server-tls

5. 配置准入规则

这是最后一步,告诉 K8s 哪些请求需要发送给我们的 Webhook。

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: image-validation-cfg
webhooks:
  - name: validate-image.example.com
    rules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["pods"]
        scope: "Namespaced"
    clientConfig:
      service:
        namespace: security-system
        name: image-checker-svc
        path: "/validate"
      caBundle: <BASE64_ENCODED_CA_CERT>
    admissionReviewVersions: ["v1"]
    sideEffects: None
    timeoutSeconds: 5
    # 关键配置:如果 Webhook 挂了,是允许还是禁止?
    # Production 建议用 Fail,测试环境可用 Ignore
    failurePolicy: Fail 

6. 进阶考虑与坑点

  • 排除系统级命名空间:通过 namespaceSelector 排除 kube-system 等命名空间,防止 Webhook 逻辑错误导致系统组件无法启动。
  • 处理 Ephemeral Containers:在 K8s 1.25+ 稳定版中,临时容器(调试用)也可能带入非法镜像,校验逻辑中应增加对 pod.Spec.EphemeralContainers 的遍历。
  • 性能影响:Webhook 会增加 Pod 创建的延迟。逻辑应尽量简单,且后端服务需具备高可用性。
  • 替代方案:如果你不想手写代码,可以考虑使用 OPA/GatekeeperKyverno。它们允许通过声明式策略(Rego 或 YAML)实现同样的逻辑,无需维护复杂的后端代码。

总结

通过 Validating Webhook,我们可以在基础设施层强制执行安全合规策略。虽然手写 Webhook 具有最高的灵活性,但在大规模场景下,结合成熟的策略引擎(如 Kyverno)通常是更具维护性的选择。无论使用哪种方式,封杀不可信镜像源都是构建零信任集群的第一步。

云原生架构师老张 Kubernetes安全审计容器镜像

评论点评