实战 K8s 准入控制:编写 Validating Webhook 封杀非官方镜像源
在生产环境中,随意从公共镜像仓库(如 Docker Hub、未知的三方镜像源)拉取镜像,会带来巨大的安全风险和不确定性。为了规范镜像来源,我们通常要求所有 Pod 只能从公司内部的私有仓库(如 Harbor)拉取镜像。
Kubernetes 提供了 Admission Controllers(准入控制器) 机制,其中的 ValidatingAdmissionWebhook 允许我们在资源持久化到 ETCD 之前,拦截 API 请求并根据自定义逻辑进行校验。本文将手把手教你如何实现一个简单的 Webhook 来限制镜像来源。
1. 核心原理
当 API Server 接收到创建 Pod 的请求时,如果配置了 Validating Webhook,API Server 会将请求体(AdmissionReview)以 JSON 格式发送到我们自定义的服务后端。
后端逻辑:
- 解析
AdmissionReview对象。 - 遍历 Pod 定义中的所有容器(包括
containers和initContainers)。 - 检查
image字段是否以允许的域名开头(如registry.example.com/)。 - 返回
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/Gatekeeper 或 Kyverno。它们允许通过声明式策略(Rego 或 YAML)实现同样的逻辑,无需维护复杂的后端代码。
总结
通过 Validating Webhook,我们可以在基础设施层强制执行安全合规策略。虽然手写 Webhook 具有最高的灵活性,但在大规模场景下,结合成熟的策略引擎(如 Kyverno)通常是更具维护性的选择。无论使用哪种方式,封杀不可信镜像源都是构建零信任集群的第一步。