# image-policy-webhook **Repository Path**: xiaonqedu/image-policy-webhook ## Basic Information - **Project Name**: image-policy-webhook - **Description**: k8s webhook 准入控制:API Server 在保存资源对象之前,对提交的内容进行检查或修改 Admission Webhook:用自己编写的 HTTPS 服务,扩展这套检查和修改逻辑 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-04 - **Last Updated**: 2026-10-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Image Policy Webhook ## 01.Webhook ### 1、概述 > 本项目是一个独立的 Kubernetes 验证准入 Webhook:只允许实验命名空间中的 Pod 使用 `registry.qcraftai.com/` 开头的镜像。 > > 使用 Go 标准库实现,不依赖 Kubebuilder、CRD 或 Operator。 - **Webhook 程序**:运行中的 HTTPS 服务,负责检查镜像并返回允许或拒绝。 - **ValidatingWebhookConfiguration**:集群级资源对象,供 API Server 读取,定义哪些请求需要检查、调用哪个服务以及调用失败时如何处理。 - **Deployment、Service**:运行 Webhook 程序,提供访问入口。 - **TLS Secret**:保存 Webhook 服务使用的 HTTPS 证书和私钥。 **YAML 定义“何时调用、调用哪里”;`main.go` 定义“镜像是否符合要求”。** Webhook 不监听资源,不执行 `Reconcile`。API Server 在处理匹配的请求时,通过 HTTPS 主动调用它;如果没有准入配置注册它,程序即使已经运行,也不会自动参与准入校验。 ### 2、处理流程 ```text 用户或控制器提交 Pod 创建、更新请求 ↓ API Server 完成认证、鉴权等处理 ↓ 根据 ValidatingWebhookConfiguration 判断是否匹配 ├─ 不匹配 → 不调用这个 Webhook,继续其他处理 └─ 匹配 ↓ HTTPS POST,携带 AdmissionReview image-policy-webhook Service:443 ↓ Webhook Pod:8443 /validate-pods ↓ Go 程序检查请求中携带的 Pod 镜像 ├─ 不符合 → 返回 allowed: false 和原因 → API Server 拒绝 └─ 符合 → 返回 allowed: true ↓ API Server 继续处理,全部通过后保存到 etcd ↓ 新 Pod 由 Scheduler、kubelet 等继续完成部署 ``` - `AdmissionReview` 是请求和响应的数据格式,响应必须返回请求中的同一个 `uid`。 - 校验拒绝仍使用 HTTP 200,拒绝结果放在 `response.allowed` 和 `response.status.message` 中。 - HTTP 错误、证书错误、网络故障属于调用失败,按 YAML 中的 `failurePolicy` 处理。 ### 3、代码和文件对应关系 ```text image-policy-webhook/ ├─ main.go # HTTPS 服务和镜像校验 ├─ go.mod # Go 模块;当前要求 Go 1.25.3 ├─ Dockerfile # 将本地编译产物打包进 scratch 镜像 ├─ .dockerignore # 仅放行镜像构建所需文件 ├─ bin/ │ └─ webhook # 本地编译的 Linux 程序 └─ manifests/ ├─ server.yaml # Webhook Deployment、Service ├─ validating-webhook.yaml # 准入配置,caBundle 需匹配实际证书 └─ test-pod.yaml # 使用公司镜像的测试 Pod ``` #### ① `main()`:启动 HTTPS 服务 ```go // 注册校验接口和健康检查接口。 mux.HandleFunc("/validate-pods", validate) mux.HandleFunc("/healthz", /* 健康检查处理函数,见 main.go */) // 从 Secret 挂载目录读取证书,在 8443 端口接收请求。 server.ListenAndServeTLS("/certs/tls.crt", "/certs/tls.key") ``` 以上是逻辑摘录,不是需要额外执行或替换的完整代码。 #### ② `validate()`:处理 API Server 请求 ```text 解析 AdmissionReview ↓ 读取 request.uid 和 request.object ↓ 把 request.object 交给 checkImages() ↓ 返回相同 uid,以及 allowed 和拒绝原因 ``` Pod 配置已经包含在请求中,程序不需要再查询 API Server,因此也不需要 Kubernetes API 访问权限。本项目设置了 `automountServiceAccountToken: false`。 #### ③ `checkImages()`:执行具体规则 ```go // 检查业务容器、Init 容器和临时调试容器中的每个镜像。 if !strings.HasPrefix(c.Image, "registry.qcraftai.com/") { return fmt.Errorf( "容器 %s 的镜像 %q 不符合要求,必须以 registry.qcraftai.com/ 开头", c.Name, c.Image, ) } ``` 这里只检查仓库前缀,不检查镜像存在性、拉取权限或镜像内容安全性。 ## 02.实战 ### 0、准备环境 #### ① 环境说明 - 项目目录:`~/tmp/image-policy-webhook`。 - 集群:`k2` 对应的 `ack-ci-2`,执行前确认上下文。 - Webhook 服务命名空间:`image-policy-system`。 - 被校验的实验命名空间:`webhook-demo`。 - 节点标签:`deploy-type=prometheus2`;本例构建 `linux/amd64` 镜像。 **服务自身不在被校验的命名空间中,避免被自己的规则阻止创建。** 项目源码和 YAML 已经存在,使用本 README 不需要重新执行 `go mod init`,也不需要覆盖 `main.go`。 #### ② 检查工具、创建命名空间 ```bash cd ~/tmp/image-policy-webhook # 沿用已有的 Go 工具链;如果本地没有,首次使用会下载 export GOTOOLCHAIN=go1.25.3 go version docker version openssl version # 确认集群与节点架构 k2 config current-context k2 get nodes -l deploy-type=prometheus2 \ -L kubernetes.io/arch mkdir -p bin manifests # 创建或确认两个实验命名空间 k2 create namespace image-policy-system \ --dry-run=client -o yaml | k2 apply -f - k2 create namespace webhook-demo \ --dry-run=client -o yaml | k2 apply -f - ``` ### 1、构建并推送镜像 #### ① 本地编译、构建镜像 ```bash cd ~/tmp/image-policy-webhook # 检查 Go 代码 go vet ./... # 静态编译;Docker 构建时不需要下载 Go 基础镜像 CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -trimpath -o bin/webhook . # 与 manifests/server.yaml 中的镜像地址保持一致 export IMG=registry.qcraftai.com/qcraft/image-policy-webhook:v0.1.0 docker build -t "$IMG" . # 按提示输入仓库账号和密码;已有有效登录时可跳过 docker login registry.qcraftai.com docker push "$IMG" ``` 后续修改代码时使用新镜像标签,并同步修改 `manifests/server.yaml` 的 `image`,再构建、推送和部署,避免复用旧缓存。 #### ② 创建镜像拉取 Secret 本地 `docker login` 供本机推送镜像使用;节点拉取镜像使用各命名空间中的 Secret,两者需要分别配置。 ```bash # 先检查;已有同名且有效的 Secret 时,不需要重复创建 k2 get secret registry-pull-secret -n image-policy-system k2 get secret registry-pull-secret -n webhook-demo # 以下两个命令只在对应 Secret 不存在时执行 # 将账号和密码占位符替换为实际仓库凭据 k2 create secret docker-registry registry-pull-secret \ -n image-policy-system \ --docker-server=registry.qcraftai.com \ --docker-username='你的仓库用户名' \ --docker-password='你的仓库密码' k2 create secret docker-registry registry-pull-secret \ -n webhook-demo \ --docker-server=registry.qcraftai.com \ --docker-username='你的仓库用户名' \ --docker-password='你的仓库密码' ``` Secret 不能跨命名空间使用。两个 YAML 都通过 `imagePullSecrets` 引用各自命名空间中的 `registry-pull-secret`。 ### 2、准备 HTTPS 证书 #### ① 生成证书 - 服务域名:`image-policy-webhook.image-policy-system.svc`。 - 服务端证书和私钥交给 Webhook;CA 证书写入准入配置的 `caBundle`。 - 已有本实验对应的有效证书时,跳过生成步骤并复用原证书,避免重建 CA 后与已安装配置不一致。 ```bash # 证书放在项目外 mkdir -p ~/tmp/image-policy-certs chmod 700 ~/tmp/image-policy-certs # 首次安装:生成 CA 私钥和证书 openssl req -x509 -newkey rsa:2048 -nodes -sha256 \ -keyout ~/tmp/image-policy-certs/ca.key \ -out ~/tmp/image-policy-certs/ca.crt \ -days 365 \ -subj "/CN=image-policy-ca" \ -addext "basicConstraints=critical,CA:TRUE" \ -addext "keyUsage=critical,keyCertSign,cRLSign" # 生成服务端私钥和签名请求 openssl req -new -newkey rsa:2048 -nodes \ -keyout ~/tmp/image-policy-certs/tls.key \ -out ~/tmp/image-policy-certs/server.csr \ -subj "/CN=image-policy-webhook.image-policy-system.svc" # 定义证书用途和服务域名 cat > ~/tmp/image-policy-certs/server.ext <<'EOF' basicConstraints=critical,CA:FALSE keyUsage=critical,digitalSignature,keyEncipherment extendedKeyUsage=serverAuth subjectAltName=DNS:image-policy-webhook.image-policy-system.svc,DNS:image-policy-webhook.image-policy-system.svc.cluster.local EOF # 使用 CA 签发服务端证书 openssl x509 -req -sha256 \ -in ~/tmp/image-policy-certs/server.csr \ -CA ~/tmp/image-policy-certs/ca.crt \ -CAkey ~/tmp/image-policy-certs/ca.key \ -CAcreateserial \ -out ~/tmp/image-policy-certs/tls.crt \ -days 365 \ -extfile ~/tmp/image-policy-certs/server.ext chmod 600 ~/tmp/image-policy-certs/*.key # 验证证书,预期输出 OK openssl verify \ -CAfile ~/tmp/image-policy-certs/ca.crt \ ~/tmp/image-policy-certs/tls.crt ``` #### ② 创建证书 Secret ```bash # 将服务端证书和私钥保存为 Secret,供 Webhook Pod 挂载 k2 create secret tls image-policy-tls \ -n image-policy-system \ --cert="$HOME/tmp/image-policy-certs/tls.crt" \ --key="$HOME/tmp/image-policy-certs/tls.key" \ --dry-run=client -o yaml | k2 apply -f - ``` 这个命令只创建或更新指定 Secret,不修改集群证书,也不会启用准入校验。同名 Secret 已存在时会被更新。 本例证书有效期为一年;程序在启动时加载证书,更新证书 Secret 后需要重启 Webhook Pod。若 CA 也变化,还必须同步更新后面的 `caBundle`。 ### 3、部署 Webhook 服务 #### ① 应用现有 Deployment、Service `manifests/server.yaml` 已定义镜像、节点选择、拉取凭据、证书挂载以及 HTTPS 健康检查。 ```bash cd ~/tmp/image-policy-webhook # 启动 Go 服务;此步骤本身不会注册准入规则 k2 apply -f manifests/server.yaml # 服务就绪后,再继续注册 Webhook k2 rollout status \ deployment/image-policy-webhook \ -n image-policy-system \ --timeout=180s k2 get pods -n image-policy-system -o wide # 应当能看到 Service 关联的 Pod 后端地址 k2 get endpointslice \ -n image-policy-system \ -l kubernetes.io/service-name=image-policy-webhook ``` ```text image-policy-webhook Service:443 ↓ targetPort: https Webhook Pod:8443 ↓ /validate-pods:镜像校验 /healthz:健康检查 ``` ### 4、注册准入规则 #### ① 明确配置与程序的分工 - **`namespaceSelector` + `rules`**:哪些请求需要检查。 - **`clientConfig`**:调用哪个 Service、端口和路径,信任哪个 CA。 - **`failurePolicy`**:调用失败时如何处理;本例为 `Fail`,拒绝匹配的请求。 - **`main.go` 中的 `checkImages()`**:具体的公司镜像检查规则。 `ValidatingWebhookConfiguration` 自身是集群级对象,没有 `metadata.namespace`;实际拦截范围由下面的命名空间选择器限定。 #### ② 使用本次证书生成配置 已有文件中的 `caBundle` 不一定匹配新环境的证书,安装前使用对应 CA 生成。 ```bash cd ~/tmp/image-policy-webhook # CA 公共证书转为单行 Base64;不是私钥 CA_BUNDLE=$(base64 -w 0 "$HOME/tmp/image-policy-certs/ca.crt") # EOF 不加引号,让 CA_BUNDLE 展开为证书内容 cat > manifests/validating-webhook.yaml <