Production-ready Kubernetes admission webhook that validates pods BEFORE they enter the cluster. Enforces 9 security policies out-of-the-box to prevent insecure workloads from running.
- Overview
- Architecture
- 9 Security Rules
- Endpoints & Monitoring
- Project Structure
- Quick Start
- Deployment Options
- Development & Local Build
- Testing Policies
- Troubleshooting
- ArgoCD Access & Port-Forward (PowerShell)
webhooklite is a lightweight, zero-dependency admission controller that intercepts pod creation and update requests at the Kubernetes API server level. Unlike static vulnerability scanners or runtime agents, webhooklite acts as an active gatekeeper that rejects non-compliant pods before scheduling.
- Active Admission Control - Blocks insecure pods at admission time, preventing misconfigured resources from touching etcd.
- Zero Runtime Overhead - No sidecars, kernel modules, or node daemons required.
- Built-in Observability - Prometheus metrics on admission decisions and dedicated
/healthzendpoints. - Graceful Shutdown & Resilience - Proper POSIX signal handling with timeout context for zero-downtime rollouts.
- Flexible Deployment - Ready-to-use Manifests, Helm Chart, and ArgoCD GitOps configuration.
flowchart LR
User["User / CI/CD"] -->|"kubectl apply"| API["kube-apiserver"]
API -->|"Mutating Webhooks"| M["Mutating Phase"]
M -->|"Validating Webhooks"| API
API -->|"HTTPS /validate"| WL["webhooklite Pod"]
subgraph Engine ["webhooklite Validation Engine"]
WL --> R1["Rule Checks 1-9"]
R1 --> Metrics[("Prometheus Metrics")]
end
WL -->|"Allow - 200 OK"| API
WL -.->|"Deny - 403 Forbidden"| API
API -->|"Persist"| ETCD[("etcd")]
API -.->|"Reject Request"| User
- A client submits a Pod manifest via
kubectlor CI/CD. - The
kube-apiserversends anAdmissionReviewJSON payload over HTTPS to webhooklite (/validate). - webhooklite evaluates all 9 security policies and increments Prometheus metrics.
- If compliant, an admission response with
allowed: trueis returned and etcd persists the Pod. - If any rule fails, the request is rejected with HTTP
403 Forbiddendetailing all rule violations.
| # | Rule | Blocks | Why |
|---|---|---|---|
| 1 | No Privileged Containers | securityContext.privileged: true |
Prevents container escapes and root access to the host kernel. |
| 2 | No Untagged / Latest Images | image: *:latest or images without tag |
Ensures version pinning and immutable, reproducible deployments. |
| 3 | Resource Limits Required | Missing resources.limits |
Prevents "noisy neighbor" resource starvation and DoS attacks. |
| 4 | runAsNonRoot Required | Missing or runAsNonRoot: false |
Minimizes privilege escalation attack surface inside the container. |
| 5 | No Privilege Escalation | allowPrivilegeEscalation: true |
Prevents gaining more privileges than the parent process (setuid binaries). |
| 6 | No Host Access | hostNetwork: true / hostPID: true |
Prevents snooping host network interfaces and host process namespaces. |
| 7 | Allowed Registries Only | Registries not in [docker.io, registry.k8s.io, gcr.io, ghcr.io] |
Protects cluster from unvetted images and supply chain attacks. |
| 8 | No Docker Socket Mounting | hostPath: /var/run/docker.sock |
Blocks container breakout into the host daemon. |
| 9 | Require app Label |
Missing metadata.labels.app |
Enforces resource tagging, traceability, and observability standards. |
webhooklite listens on port :8443 (HTTPS) and exposes the following endpoints:
| Path | Method | Purpose |
|---|---|---|
/validate |
POST |
Kubernetes admission webhook handler (receives AdmissionReview). |
/healthz |
GET |
Liveness and readiness probe endpoint (returns 200 ok). |
/metrics |
GET |
Prometheus metrics endpoint. |
/ |
GET |
Service overview & list of available endpoints. |
webhooklite exports admission telemetry using the standard Prometheus format:
# HELP webhooklite_requests_total Total number of admission review requests processed by webhooklite.
# TYPE webhooklite_requests_total counter
webhooklite_requests_total{result="allowed"} 42
webhooklite_requests_total{result="denied"} 7
webhooklite/
├── cmd/
│ └── webhooklite/
│ ├── main.go # Webhook HTTP server & 9 validation rules
│ └── main_test.go # Unit tests for admission rules
├── deploy/
│ ├── deployment.yaml # Deployment & Service manifests
│ ├── webhook-config.yaml # ValidatingWebhookConfiguration
│ └── good-pod.yaml # Compliant pod example
├── helm/
│ └── webhooklite/ # Production Helm 3 chart
│ ├── Chart.yaml
│ ├── values.yaml
│ └── templates/
├── argocd/
│ └── argocd-app.yaml # ArgoCD Application CRD for GitOps
├── scripts/
│ ├── generate-certs.sh # Linux/macOS certificate generator
│ ├── generate-certs.ps1 # Windows PowerShell certificate generator
│ └── deploy.ps1 # Automated PowerShell deployment script
├── Dockerfile # Multi-stage rootless container build
├── go.mod # Go module dependencies
└── README.md
git clone https://github.com/cooler-SAI/webhooklite.git
cd webhookliteKubernetes requires admission webhooks to communicate exclusively over TLS with a valid CA bundle:
# On Linux / macOS:
./scripts/generate-certs.sh
# On Windows (PowerShell):
.\scripts\generate-certs.ps1kubectl apply -f deploy/Deploy directly using raw manifests:
# 1. Apply webhook deployment and service
kubectl apply -f deploy/deployment.yaml
# 2. Register ValidatingWebhookConfiguration
kubectl apply -f deploy/webhook-config.yamlInstall via Helm with custom certificates:
helm install webhooklite ./helm/webhooklite \
--namespace webhook-system \
--create-namespace \
--set-file secret.tls.crt=certs/tls.crt \
--set-file secret.tls.key=certs/tls.key \
--set-file validatingWebhook.clientConfig.caBundle=certs/ca.crtDeploy declaratively via ArgoCD:
kubectl apply -f argocd/argocd-app.yaml- Go 1.22+
- Docker
kubectlconnected to a test cluster (e.g., Kind, Minikube, or k3s)
# Run all unit tests
go test -v ./...
# Run tests with race detection and coverage
go test -race -cover ./...go build -o bin/webhooklite ./cmd/webhooklitedocker build -t webhooklite:latest .kubectl run bad-priv --image=nginx:1.21 --privileged=truekubectl run bad-latest --image=nginx:latestkubectl run bad-nolimits --image=nginx:1.21kubectl run bad-root --image=nginx:1.21 --overrides='{"spec":{"securityContext":{"runAsNonRoot":false}}}'kubectl run bad-escalation --image=nginx:1.21 --overrides='{"spec":{"containers":[{"name":"nginx","image":"nginx:1.21","securityContext":{"allowPrivilegeEscalation":true}}]}}'kubectl run bad-hostnet --image=nginx:1.21 --overrides='{"spec":{"hostNetwork":true}}'kubectl run bad-registry --image=evilcorp.io/malware:1.0kubectl run bad-dockersock --image=nginx:1.21 --overrides='{
"spec": {
"securityContext": {"runAsNonRoot": true},
"containers": [{
"name": "nginx",
"image": "nginx:1.21",
"resources": {"limits": {"cpu": "100m", "memory": "128Mi"}},
"volumeMounts": [{"mountPath": "/var/run/docker.sock", "name": "dockersock"}]
}],
"volumes": [{"name": "dockersock", "hostPath": {"path": "/var/run/docker.sock"}}]
}
}'kubectl run bad-label --image=nginx:1.21 --overrides='{
"spec": {
"securityContext": {"runAsNonRoot": true},
"containers": [{
"name": "nginx",
"image": "nginx:1.21",
"resources": {"limits": {"cpu": "100m", "memory": "128Mi"}}
}]
}
}'kubectl apply -f deploy/good-pod.yamlOr via kubectl run :
kubectl run good-pod --image=nginx:1.21 --overrides='{
"metadata": {
"labels": {"app": "good-pod"}
},
"spec": {
"securityContext": {
"runAsNonRoot": true
},
"containers": [{
"name": "nginx",
"image": "nginx:1.21",
"resources": {
"limits": {"cpu": "100m", "memory": "128Mi"}
},
"securityContext": {
"allowPrivilegeEscalation": false
}
}]
}
}'kubectl logs -l app=webhooklite -n default -fkubectl get validatingwebhookconfigurations -o wide
kubectl describe validatingwebhookconfiguration webhooklite-validationx509: certificate signed by unknown authority: EnsurecaBundlein yourValidatingWebhookConfigurationmatches base64-encodedcerts/ca.crtorcerts/tls.crt.- System Namespaces Blocked: Ensure your
ValidatingWebhookConfigurationexcludes namespaces likekube-system,argocd, andwebhook-systemusingnamespaceSelector. - Timeout contacting webhook: Ensure webhook service port mapping (443 -> 8443) and firewall/CNI network policies permit traffic from the API server to the webhook pod.
kubectl port-forward svc/argocd-server -n argocd 8080:443ArgoCD Web UI will be available at: https://localhost:8080 (Username:
admin)
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String((kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath="{.data.password}")))