Admission Webhooks
Kubernetes admission webhooks intercept API requests before resources are persisted. Starkite makes it simple to build validating and mutating webhooks with k8s.webhook().
Quick Start¶
Validating Webhook¶
Reject deployments with too many replicas:
#!/usr/bin/env kitecloud
def validate(obj):
if obj.spec.replicas > 10:
return {"allowed": False, "message": "max 10 replicas allowed"}
return {"allowed": True}
k8s.webhook("/validate",
validate = validate,
port = 9443,
tls_cert = "/certs/tls.crt",
tls_key = "/certs/tls.key",
)
Mutating Webhook¶
Inject labels into every deployment:
#!/usr/bin/env kitecloud
def mutate(obj):
obj["metadata"]["labels"]["managed-by"] = "starkite"
obj["metadata"]["annotations"]["mutated-at"] = time.format(time.now(), time.RFC3339)
return obj
k8s.webhook("/mutate",
mutate = mutate,
port = 9443,
tls_cert = "/certs/tls.crt",
tls_key = "/certs/tls.key",
)
How It Works¶
k8s.webhook() starts an HTTPS server that:
- Receives
AdmissionReviewrequests from the Kubernetes API server - Extracts the resource object and converts it to an AttrDict (dot-access + bracket-access)
- Calls your handler function with the object
- For validation: returns
allowed: true/falsebased on your handler's return value - For mutation: diffs the original and modified objects to generate an RFC 6902 JSON patch
- Returns the
AdmissionReviewresponse to the API server
The function blocks until terminated (like http.serve() and k8s.control()).
Object Access¶
Objects are passed as mutable AttrDicts. Use dot-notation for reading and bracket-notation for writing:
def mutate(obj):
# Read with dot-access
name = obj.metadata.name
ns = obj.metadata.namespace
replicas = obj.spec.replicas
# Read nested values
labels = obj.metadata.labels
image = obj.spec.containers[0].image # list items are also AttrDicts
# Write with bracket-access
obj["metadata"]["labels"]["env"] = "production"
obj["metadata"]["annotations"]["version"] = "v2"
# Dot-access returns a nested AttrDict — bracket writes on it propagate
labels = obj.metadata.labels
labels["team"] = "platform" # this modifies obj.metadata.labels
return obj
Handler Contracts¶
Validate Handler¶
def validate(obj):
# obj: AttrDict — the resource being admitted
# Return one of:
# {"allowed": True}
# {"allowed": False, "message": "reason for rejection"}
If the handler raises an error, the webhook returns allowed: false with the error message.
Mutate Handler¶
def mutate(obj):
# obj: AttrDict — the resource being admitted (mutable)
# Modify the object in place using bracket notation
# Return the modified object
# Changes are automatically converted to an RFC 6902 JSON patch
If the handler raises an error, the webhook returns allowed: false with the error message.
Combined Validation and Mutation¶
When both validate and mutate are provided, validation runs first. If validation rejects the request, mutation is skipped:
def validate(obj):
if not obj.metadata.labels.get("team"):
return {"allowed": False, "message": "team label required"}
return {"allowed": True}
def mutate(obj):
obj["metadata"]["labels"]["validated"] = "true"
return obj
k8s.webhook("/webhook",
validate = validate,
mutate = mutate,
port = 9443,
tls_cert = "/certs/tls.crt",
tls_key = "/certs/tls.key",
)
TLS Certificates¶
Webhooks require TLS. For local testing, generate a self-signed certificate:
openssl req -x509 -newkey rsa:2048 \
-keyout /tmp/key.pem -out /tmp/cert.pem \
-days 1 -nodes -subj '/CN=localhost'
kitecloud run webhook.star \
--var tls_cert=/tmp/cert.pem \
--var tls_key=/tmp/key.pem
For production, use cert-manager to manage certificates automatically.
Deploying to Kubernetes¶
A webhook deployment requires:
- Deployment — runs
kitecloud run webhook.starwith TLS certs mounted - Service — exposes the webhook pod (port 443 → 9443)
- TLS Secret — certificate and private key
- WebhookConfiguration — tells the API server which resources to intercept
Generate Deployment Artifacts¶
Use kitecloud kube gen-webhook-artifacts to generate all manifests:
# Generate YAML manifests
kitecloud kube gen-webhook-artifacts \
--webhook webhook.star \
--name myapp-webhook \
--image myregistry/myapp-webhook:v1 \
--namespace myapp-system \
--rule "group=apps resource=deployments operations=CREATE,UPDATE" > deploy.yaml
# Or generate a Starlark deployment script
kitecloud kube gen-webhook-artifacts \
--webhook webhook.star \
--name myapp-webhook \
--image myregistry/myapp-webhook:v1 \
--namespace myapp-system \
--rule "group=apps resource=deployments operations=CREATE,UPDATE" \
--output script > deploy-webhook.star
The --rule Flag¶
Specifies which resources the webhook intercepts, using key=value pairs:
# Deployments in the apps group
--rule "group=apps resource=deployments operations=CREATE,UPDATE"
# Pods in the core group (omit group)
--rule "resource=pods operations=CREATE"
# Multiple rules
--rule "group=apps resource=deployments operations=CREATE,UPDATE" \
--rule "resource=pods operations=CREATE"
Keys:
- group — API group (apps, batch, omit for core, * for all)
- version — API version (v1, omit for all)
- resource — resource type (deployments, pods)
- operations — comma-separated: CREATE, UPDATE, DELETE, CONNECT, *
Apply to Cluster¶
# Build and push image
docker build -t myregistry/myapp-webhook:v1 .
docker push myregistry/myapp-webhook:v1
# Apply manifests
kubectl apply -f deploy.yaml
# Update TLS secret with real certificates
kubectl create secret tls myapp-webhook-tls \
--cert=tls.crt --key=tls.key \
-n myapp-system --dry-run=client -o yaml | kubectl apply -f -
Examples¶
See examples/cloud/webhook/ for complete working examples:
validate-replicas.star— reject deployments with too many replicasmutate-labels.star— inject default labels into deployments