Stop untrusted images at the API with ImagePolicyWebhook
Scanning images in CI is necessary. It is not sufficient. Someone can still kubectl run an image the pipeline never saw. ImagePolicyWebhook is the admission plugin that asks an external service, at create time, whether those image names are allowed.
If the webhook is down and defaultAllow is false, the API server denies the pod. That is the secure default. It is also how you lock yourself out if the webhook is broken.
Enable the plugin
Edit the API server static pod:
sudo vim /etc/kubernetes/manifests/kube-apiserver.yaml
--enable-admission-plugins=NodeRestriction,ResourceQuota,ImagePolicyWebhook
--admission-control-config-file=/etc/kubernetes/admission-control.yaml
Admission config
# /etc/kubernetes/admission-control.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ImagePolicyWebhook
configuration:
imagePolicy:
kubeConfigFile: /etc/kubernetes/imagepolicy-webhook.kubeconfig
allowTTL: 30
denyTTL: 30
retryBackoff: 500
defaultAllow: false
defaultAllow: false means a timeout or a dead webhook is a deny. Set it true only to recover, not as policy.
Webhook kubeconfig
# /etc/kubernetes/imagepolicy-webhook.kubeconfig
apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority: /etc/kubernetes/pki/ca.crt
server: https://image-policy-webhook.example.com:8443/image-policy
name: image-policy-webhook
contexts:
- context:
cluster: image-policy-webhook
user: api-server
name: image-policy-webhook
current-context: image-policy-webhook
users:
- name: api-server
user:
client-certificate: /etc/kubernetes/pki/apiserver.crt
client-key: /etc/kubernetes/pki/apiserver.key
The API server is the client. The webhook must present TLS. Paths in this file have to exist on the control plane node, inside the API server pod’s filesystem.
What the webhook sees
The review object is imagepolicy.k8s.io/v1alpha1 ImageReview. The service looks at spec.containers[].image and returns status.allowed plus an optional reason. Policy applies to every container in the pod. One bad image fails the whole pod.
A typical allow-list is registries, not image names:
gcr.io/my-projectregistry.k8s.iodocker.io/library
Common extra rules: no :latest, no path that looks like an untrusted mirror.
Prove it
kubectl run test-pod --image=malicious-registry.com/bad-image:latest
kubectl run good-pod --image=gcr.io/my-company/app:v1.0
The first should fail at admission. The second should schedule.
When it does nothing
kubectl get pod -n kube-system kube-apiserver-master -o yaml | grep admission-plugins
sudo cat /etc/kubernetes/admission-control.yaml
kubectl logs -n kube-system kube-apiserver-master | grep ImagePolicyWebhook
kubectl get pods -n kube-system -l app=image-policy-webhook
Wrong kubeconfig path, expired webhook certs, and defaultAllow: true are the usual reasons a cluster looks like it has a policy and still accepts anything.
Files to remember:
/etc/kubernetes/manifests/kube-apiserver.yaml/etc/kubernetes/admission-control.yaml/etc/kubernetes/imagepolicy-webhook.kubeconfig