18.3 Admission Control and Policy Enforcement¶
Scanning container images, signing artifacts, and maintaining secure configurations provide security visibility, but visibility alone does not prevent insecure workloads from reaching production. The gap between knowing about a vulnerability and preventing its deployment requires enforcement mechanisms that automatically block non-compliant resources before they can run. In Kubernetes environments, admission control serves as this enforcement layer—a gatekeeper that evaluates every resource creation or modification request against organizational policies.
Without admission control, security becomes advisory. Developers might receive alerts about unsigned images or critical vulnerabilities, but nothing stops them from deploying anyway under deadline pressure. Admission control transforms security requirements from suggestions into guardrails, ensuring that policy violations are caught automatically rather than depending on human vigilance.
Kubernetes Admission Controller Architecture¶
When you run kubectl apply or a CI/CD pipeline deploys a resource, the request passes through a series of stages before the resource is persisted to etcd and acted upon by controllers. Admission controllers intercept requests after authentication and authorization but before persistence:
Request → Authentication → Authorization → Admission Control → Persistence → Controllers
↓
┌──────────────────────┐
│ Mutating Webhooks │
│ (modify resources) │
└──────────┬───────────┘
↓
┌──────────────────────┐
│ Validating Webhooks │
│ (accept/reject) │
└──────────────────────┘
Kubernetes provides two types of admission webhooks:
Mutating admission webhooks can modify incoming resources. They might inject sidecar containers, add default labels, or modify security contexts. Mutating webhooks run first, allowing subsequent validating webhooks to evaluate the final, mutated resource.
Validating admission webhooks evaluate resources against policies and return accept or reject decisions. They cannot modify resources—only approve or deny them. Multiple validating webhooks can evaluate the same resource, and all must approve for the request to proceed.
This architecture enables powerful policy enforcement without modifying Kubernetes core components. Organizations deploy webhook servers that receive admission review requests, evaluate them against policies, and return decisions. The challenge lies in making policy authoring, testing, and management practical at scale.
Open Policy Agent and Gatekeeper¶
Open Policy Agent (OPA) is a general-purpose policy engine that evaluates policies written in Rego, a declarative query language designed for policy decisions. OPA can enforce policies across many systems—Kubernetes, API gateways, service meshes, and more—using a consistent policy language.
Gatekeeper is the Kubernetes-native implementation of OPA, providing Custom Resource Definitions (CRDs) for defining and managing policies within Kubernetes itself. Gatekeeper operates as a validating admission webhook, intercepting resource requests and evaluating them against OPA policies.
Installing Gatekeeper:
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.14.0/deploy/gatekeeper.yaml
Note: Version numbers shown are examples. Always check the official documentation for the current stable release before deployment.
Gatekeeper uses a two-resource model: ConstraintTemplates define the policy logic in Rego, while Constraints apply that template to specific resources with parameters.
A ConstraintTemplate requiring container images from approved registries:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sallowedrepos
spec:
crd:
spec:
names:
kind: K8sAllowedRepos
validation:
openAPIV3Schema:
type: object
properties:
repos:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedrepos
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not strings.any_prefix_match(container.image, input.parameters.repos)
msg := sprintf("Container '%s' uses image '%s' from disallowed registry",
[container.name, container.image])
}
A Constraint applying the template:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
name: require-approved-registries
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
repos:
- "myregistry.io/"
- "gcr.io/my-project/"
Gatekeeper's strengths include its policy language expressiveness (Rego can implement complex logic), cross-platform applicability (the same policies work outside Kubernetes), and mature ecosystem. Its primary challenge is Rego's learning curve—the language, while powerful, requires investment to master.
Kyverno: Kubernetes-Native Policy Engine¶
Kyverno takes a different approach, using Kubernetes-native YAML for policy definitions rather than a specialized language. Policies look like other Kubernetes resources, reducing the learning curve for platform engineers already fluent in Kubernetes.
Installing Kyverno:
Note: Version numbers shown are examples. Always check the official documentation for the current stable release before deployment.
Kyverno policies combine matching rules with validation, mutation, or generation logic:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-approved-registries
spec:
validationFailureAction: Enforce
rules:
- name: check-registry
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Images must come from approved registries"
pattern:
spec:
containers:
- image: "myregistry.io/* | gcr.io/my-project/*"
Kyverno also excels at image verification, integrating directly with Sigstore:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signatures
spec:
validationFailureAction: Enforce
rules:
- name: verify-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "myregistry.io/*"
attestors:
- entries:
- keyless:
issuer: "https://token.actions.githubusercontent.com"
subject: "https://github.com/myorg/*"
Comparing the two engines:
| Aspect | Gatekeeper/OPA | Kyverno |
|---|---|---|
| Policy language | Rego (learning curve) | Kubernetes YAML (familiar) |
| Image verification | Requires external integration | Built-in Sigstore support |
| Mutation support | Supported (via webhooks) | Native, extensive |
| Resource generation | Not supported | Supported |
| Cross-platform | OPA works everywhere | Kubernetes-specific |
| Community size | Larger, more mature | Growing rapidly |
We recommend Kyverno for teams primarily focused on Kubernetes without existing OPA investment, and Gatekeeper for organizations using OPA across multiple systems or requiring complex policy logic that benefits from Rego's expressiveness.
Policy Categories and Examples¶
Effective admission control addresses multiple security domains. We organize policies into categories based on what they protect:
Image source policies restrict which registries and repositories can supply container images:
# Kyverno: Block images from public Docker Hub
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: block-public-dockerhub
spec:
validationFailureAction: Enforce
rules:
- name: block-dockerhub
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Images from public Docker Hub are not allowed"
pattern:
spec:
containers:
- image: "!docker.io/* & !library/*"
Signature verification policies require cryptographic proof of artifact authenticity (covered in Section 18.2).
Vulnerability policies block images with known vulnerabilities above a threshold. These typically require integration with scanning systems:
# Kyverno: Check vulnerability scan results
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-vulnerability-scan
spec:
validationFailureAction: Enforce
rules:
- name: check-scan-annotation
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Images must have passing vulnerability scan"
pattern:
metadata:
annotations:
scan.myorg.io/status: "pass"
Configuration policies enforce security best practices for workload specifications:
# Kyverno: Require non-root containers
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-non-root
spec:
validationFailureAction: Enforce
rules:
- name: check-non-root
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Containers must run as non-root"
pattern:
spec:
securityContext:
runAsNonRoot: true
containers:
- securityContext:
runAsNonRoot: true
Resource limit policies ensure workloads specify CPU and memory bounds, preventing resource exhaustion attacks:
# Kyverno: Require resource limits
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-resource-limits
spec:
validationFailureAction: Enforce
rules:
- name: check-limits
match:
any:
- resources:
kinds:
- Pod
validate:
message: "CPU and memory limits are required"
pattern:
spec:
containers:
- resources:
limits:
memory: "?*"
cpu: "?*"
Policy Development and Testing Workflows¶
Policies are code and deserve the same development rigor as application code. Testing policies before deployment prevents outages caused by overly restrictive or incorrectly written policies.
Unit testing validates policy logic against sample resources. Kyverno provides a CLI for policy testing:
# test/require-non-root/test.yaml
policies:
- ../policies/require-non-root.yaml
resources:
- resources/pod-non-root.yaml
- resources/pod-root.yaml
results:
- policy: require-non-root
rule: check-non-root
resource: pod-non-root
result: pass
- policy: require-non-root
rule: check-non-root
resource: pod-root
result: fail
For Gatekeeper, the gator CLI (Gatekeeper's testing tool, installable via go install github.com/open-policy-agent/gatekeeper/cmd/gator@latest) provides similar capabilities:
Integration testing deploys policies to a test cluster and validates expected behavior:
# CI/CD pipeline step
- name: Test policies
run: |
kind create cluster
kubectl apply -f policies/
# Should succeed
kubectl apply -f test-resources/valid-pod.yaml
# Should fail
! kubectl apply -f test-resources/invalid-pod.yaml
Policy linting catches common mistakes before deployment. Kyverno policies can be validated with:
We recommend storing policies in Git, reviewing changes through pull requests, and running automated tests in CI before merging.
Graduated Enforcement: Audit Mode to Blocking¶
Deploying policies in blocking mode without testing against existing workloads risks widespread outages. A graduated rollout strategy minimizes disruption:
-
Audit mode: Policies evaluate resources but only log violations without blocking. This reveals what would fail without impacting operations.
-
Warn mode: Policies emit warnings visible to users but still allow resources to be created. This educates developers about upcoming requirements.
-
Enforce mode: Policies block non-compliant resources. Only enable after confirming audit results are acceptable.
Kyverno supports all three modes via validationFailureAction:
Gatekeeper uses enforcementAction:
We recommend running new policies in audit mode for at least one week, reviewing violations to identify legitimate workloads that need modification or exemption, then proceeding through warn mode before enabling enforcement.
Exception Handling Processes¶
No policy covers every legitimate use case. Some workloads require elevated privileges, specific images, or other policy exceptions. Effective admission control includes structured exception handling:
Namespace-based exclusions exempt specific namespaces from policies:
# Kyverno: Exclude kube-system
spec:
rules:
- name: check-non-root
exclude:
any:
- resources:
namespaces:
- kube-system
Label-based exceptions allow workloads to opt out with explicit approval:
spec:
rules:
- name: check-non-root
exclude:
any:
- resources:
selector:
matchLabels:
security.myorg.io/exception-approved: "SEC-1234"
Time-bound exceptions require periodic review:
# Kyverno PolicyException (v1.9+)
apiVersion: kyverno.io/v2beta1
kind: PolicyException
metadata:
name: allow-privileged-monitoring
annotations:
# Track expiration manually; use CleanupPolicy for automatic removal
exception.myorg.io/expires: "2024-04-01"
exception.myorg.io/ticket: "SEC-1234"
spec:
exceptions:
- policyName: disallow-privileged
ruleNames:
- check-privileged
match:
any:
- resources:
kinds:
- Pod
namespaces:
- monitoring
Note: For automatic expiration of PolicyExceptions, combine this with a ClusterCleanupPolicy that uses Kyverno's
time_add()andtime_to_cron()JMESPath functions introduced in version 1.9.
We recommend requiring documented justification for exceptions, limiting their scope as narrowly as possible, and implementing regular reviews to remove stale exceptions.
Balancing Security with Operational Flexibility¶
Overly restrictive policies create pressure to bypass security controls entirely. The goal is policies strict enough to catch real security issues while flexible enough to accommodate legitimate operational needs.
Practical strategies include:
- Start with high-confidence policies: Block images without signatures or from unknown registries before tackling configuration nuances
- Provide escape hatches: Structured exceptions are better than frustrated developers disabling admission control
- Communicate early: Announce policy changes before enforcement, giving teams time to adapt
- Measure and iterate: Track exception requests to identify policies that are too strict or need refinement
Effective admission control should feel like guardrails on a mountain road—present to prevent catastrophe, not to slow normal travel. Security controls that work seamlessly encourage compliance; those that create friction encourage workarounds.