Webhooks are HTTP callbacks triggered by the Kubernetes API server during resource operations.
There are two main types
- Mutating Webhook: Modify or inject fields into a resource
- Validating Webook: Accept or reject a resource based upon logic
A validating webhook configuration
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: validate-pods.k8s.io
webhooks:
- name: podcheck.k8s.io
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
clientConfig:
service:
name: pod-validator
namespace: default
path: "/validate"
caBundle: <base64-ca>
admissionReviewVersions: ["v1"]
sideEffects: None
Essentially k8s web hooks give us the opportunity to intercept k8s API requests such as CREATE, UPDATE or DELETE. Using webhooks we can accept of reject requests without modifying the k8s object.
In the example YAML above, we’re going to intercept CREATE calls for pods. This is a validate-pods.k8s.io or validating web hook, which is non-mutating and can reject requests but not modify them. The name of the web hook is podcheck.k8s.io and then we have the rules, which we’ve already touched on. Then we have the clientConfig which will use our pod-validator service in the default namespace and the path /validate. For example this would mean a service is accessible via https://pod-validator.default.svc/validate. The sideEffects of None means this webhook doesn’t write to external systems, hence is safe for retries.
The webhook server must expose an HTTPS endpoint which access AdmissionReview requests and should return a response to denote whether the operation can proceed.
The AdmissionReview request will look similar to this for a pod CREATE
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"request": {
"uid": "1234abcd-5678-efgh-ijkl-9012mnopqrst",
"kind": {
"group": "",
"version": "v1",
"kind": "Pod"
},
"resource": {
"group": "",
"version": "v1",
"resource": "pods"
},
"requestKind": {
"group": "",
"version": "v1",
"kind": "Pod"
},
"requestResource": {
"group": "",
"version": "v1",
"resource": "pods"
},
"name": null,
"namespace": "default",
"operation": "CREATE",
"userInfo": {
"username": "system:serviceaccount:default:deployer",
"uid": "abc123",
"groups": [
"system:serviceaccounts",
"system:authenticated"
]
},
"object": {
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "example-pod",
"namespace": "default",
"labels": {
"app": "demo"
}
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:1.21",
"resources": {
"limits": {
"cpu": "500m",
"memory": "128Mi"
}
}
}
]
}
},
"oldObject": null,
"dryRun": false
}
}
A response will look something like this
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"response": {
"uid": "1234abcd-5678-efgh-ijkl-9012mnopqrst",
"allowed": true,
"status": {
"code": 200,
"message": "Pod validated successfully"
}
}
}
The allowed field can just be sent to false which minimal response like the one below
"allowed": false,
"status": {
"code": 400,
"message": "Missing required label: team"
}