Kubernetes RBAC controls who can do what. OPA Gatekeeper controls what resources look like when they're created. The two are complementary, and production clusters need both. This guide focuses on Gatekeeper.
How Gatekeeper Works
Gatekeeper runs as a validating admission webhook. When you kubectl apply a resource, the API server sends a copy to Gatekeeper before writing it to etcd. Gatekeeper evaluates the resource against all applicable Constraint objects and rejects it if any policy fails.
The policy language is Rego (from OPA). You define a ConstraintTemplate (the Rego logic) and a Constraint (the parameters and scope):
ConstraintTemplate: "Containers must not run as root"
↓
Constraint: "Apply this rule to all pods in the production namespace"
↓
Admission request: rejected if runAsNonRoot is false
Essential Policies for Every Cluster
1. Require Non-Root Containers
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequirenonroot
spec:
crd:
spec:
names:
kind: K8sRequireNonRoot
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequirenonroot
violation[{"msg": msg}] {
c := input.review.object.spec.containers[_]
not c.securityContext.runAsNonRoot
msg := sprintf("Container %v must set runAsNonRoot: true", [c.name])
}
2. Block Privileged Containers
# Rego snippet
violation[{"msg": msg}] {
c := input.review.object.spec.containers[_]
c.securityContext.privileged
msg := sprintf("Container %v must not be privileged", [c.name])
}
3. Require Resource Limits
Pods without CPU/memory limits can consume unbounded resources and impact neighbors. Enforce limits at admission:
violation[{"msg": msg}] {
c := input.review.object.spec.containers[_]
not c.resources.limits.memory
msg := sprintf("Container %v must set resources.limits.memory", [c.name])
}
4. Restrict Allowed Registries
Only permit images from your internal registry:
violation[{"msg": msg}] {
c := input.review.object.spec.containers[_]
not startswith(c.image, "registry.company.com/")
msg := sprintf("Image %v must be from registry.company.com", [c.image])
}
Audit Mode: Discover Before Enforcing
Deploy policies in dryrun mode to discover violations without blocking anything:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequireNonRoot
metadata:
name: require-non-root
spec:
enforcementAction: dryrun # Change to "deny" when ready
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces: ["production"]
Query existing violations:
kubectl get k8srequirenonroot require-non-root -o jsonpath='{.status.violations}' | jq .
Run audit mode for at least one sprint before switching to deny. The violations output will surprise you — every cluster has old workloads that don't meet modern security standards.
Exemptions Without Compromising Security
Some system workloads (CoreDNS, CNI plugins) legitimately need elevated permissions. Exempt them by namespace rather than disabling policies:
spec:
match:
excludedNamespaces:
- kube-system
- kube-public
- cert-manager
Define a policy for which namespaces can be excluded and enforce it — otherwise exemptions accumulate until the policy is meaningless.