Skip to content

Admission Controllers Deep Dive

What Are Admission Controllers?

Admission controllers are plugins that intercept requests to the Kubernetes API server after authentication and authorization, but before the object is persisted to etcd. They can validate, mutate, or reject requests.

CKS Exam Relevance

Admission controllers are a critical security mechanism. The CKS exam may ask you to:

  • Enable or disable specific admission controllers
  • Configure ImagePolicyWebhook
  • Create validating or mutating webhook configurations
  • Understand the admission chain order
  • Troubleshoot webhook failures

Admission Controller Chain

Key Ordering Rules

  1. Mutating webhooks run first -- they can modify the object
  2. Object schema validation runs next -- checks the object is valid
  3. Validating webhooks run last -- they can only accept or reject
  4. If any admission controller rejects, the entire request fails
  5. Mutating webhooks may run multiple times if they modify the object

Types of Admission Controllers

Built-in Admission Controllers

These are compiled into the API server binary:

ControllerTypePurpose
NamespaceLifecycleValidatingPrevents operations in terminating/non-existent namespaces
LimitRangerMutatingEnforces LimitRange constraints
ServiceAccountMutatingAutomates SA token mounting
DefaultStorageClassMutatingAssigns default storage class
ResourceQuotaValidatingEnforces ResourceQuota limits
PodSecurityValidatingEnforces Pod Security Standards
NodeRestrictionValidatingLimits kubelet's API permissions
AlwaysPullImagesMutatingForces imagePullPolicy: Always
ImagePolicyWebhookValidatingExternal image admission
EventRateLimitValidatingLimits API event rate
DenyServiceExternalIPsValidatingBlocks external IPs on services

Dynamic Admission Controllers (Webhooks)

These are user-configured and run as external services:

TypeCRDCan Modify Object?
MutatingAdmissionWebhookMutatingWebhookConfigurationYes
ValidatingAdmissionWebhookValidatingWebhookConfigurationNo (accept/reject only)

Viewing and Enabling Admission Controllers

Check Enabled Controllers

bash
# View current admission controllers on the API server
kubectl -n kube-system get pod kube-apiserver-controlplane -o yaml | \
  grep -A1 enable-admission

# Or check the API server manifest directly
cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep enable-admission

Enable Additional Controllers

Edit the API server static pod manifest:

yaml
# /etc/kubernetes/manifests/kube-apiserver.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-apiserver
    - --enable-admission-plugins=NodeRestriction,PodSecurity,ImagePolicyWebhook
    # ... other flags ...

Disable a Controller

yaml
    - --disable-admission-plugins=DefaultStorageClass

Important

After modifying /etc/kubernetes/manifests/kube-apiserver.yaml, the kubelet automatically restarts the API server. Wait for it to come back up:

bash
# Watch for API server restart
watch crictl ps | grep kube-apiserver
# Or
kubectl get nodes  # Will fail temporarily, then succeed

Security-Critical Built-in Controllers

NodeRestriction

Limits what kubelets can modify. Without this, a compromised node could modify any object in the cluster.

What it restricts:

  • Kubelets can only modify their own Node object
  • Kubelets can only modify pods bound to their node
  • Kubelets cannot modify labels with node-restriction.kubernetes.io/ prefix
  • Prevents compromised nodes from escalating privileges
yaml
# Always enable NodeRestriction
- --enable-admission-plugins=NodeRestriction

PodSecurity

Enforces Pod Security Standards (Baseline, Restricted, Privileged) at the namespace level. This is the replacement for the deprecated PodSecurityPolicy.

yaml
# Label a namespace to enforce restricted standards
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

AlwaysPullImages

Forces imagePullPolicy: Always on all containers. This prevents using cached images that might have been modified and ensures registry authentication is always checked.

yaml
- --enable-admission-plugins=AlwaysPullImages

Security Benefit

Without AlwaysPullImages, a pod could use a locally cached image that was pulled by another pod with different registry credentials. This could allow unauthorized access to private images.


ImagePolicyWebhook

ImagePolicyWebhook is an admission controller that consults an external service to decide whether to allow or deny an image. This is a common CKS exam topic.

Architecture

Step 1: Create the Admission Configuration

yaml
# /etc/kubernetes/admission/admission-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ImagePolicyWebhook
  configuration:
    imagePolicy:
      kubeConfigFile: /etc/kubernetes/admission/kubeconfig.yaml
      allowTTL: 50                # Cache allow decisions (seconds)
      denyTTL: 50                 # Cache deny decisions (seconds)
      retryBackoff: 500           # Retry interval (ms)
      defaultAllow: false         # DENY if webhook is unreachable

defaultAllow: false

Setting defaultAllow: false means that if the webhook is unreachable, all image pulls are denied. This is the secure default for production but can break the cluster if the webhook goes down. On the CKS exam, read the question carefully to determine which setting is required.

Step 2: Create the Kubeconfig for the Webhook

yaml
# /etc/kubernetes/admission/kubeconfig.yaml
apiVersion: v1
kind: Config
clusters:
- name: image-policy-webhook
  cluster:
    server: https://image-policy.example.com:8443/image-policy
    certificate-authority: /etc/kubernetes/admission/webhook-ca.crt
users:
- name: api-server
  user:
    client-certificate: /etc/kubernetes/admission/api-server-client.crt
    client-key: /etc/kubernetes/admission/api-server-client.key
current-context: image-policy
contexts:
- context:
    cluster: image-policy-webhook
    user: api-server
  name: image-policy

Step 3: Configure the API Server

yaml
# /etc/kubernetes/manifests/kube-apiserver.yaml
spec:
  containers:
  - command:
    - kube-apiserver
    - --enable-admission-plugins=NodeRestriction,ImagePolicyWebhook
    - --admission-control-config-file=/etc/kubernetes/admission/admission-config.yaml
    volumeMounts:
    - name: admission
      mountPath: /etc/kubernetes/admission
      readOnly: true
  volumes:
  - name: admission
    hostPath:
      path: /etc/kubernetes/admission
      type: DirectoryOrCreate

Validating Webhook Configuration

A ValidatingWebhookConfiguration registers an external webhook as a validating admission controller.

yaml
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: pod-validator
webhooks:
- name: pod-validator.example.com
  admissionReviewVersions: ["v1"]
  sideEffects: None
  clientConfig:
    service:                        # In-cluster webhook service
      name: pod-validator
      namespace: webhook-system
      path: /validate-pods
      port: 443
    caBundle: <base64-encoded-CA-cert>
  rules:
  - apiGroups: [""]
    apiVersions: ["v1"]
    operations: ["CREATE", "UPDATE"]
    resources: ["pods"]
    scope: "Namespaced"
  failurePolicy: Fail              # Fail closed (reject if webhook errors)
  matchPolicy: Equivalent
  namespaceSelector:
    matchExpressions:
    - key: kubernetes.io/metadata.name
      operator: NotIn
      values: ["kube-system"]      # Skip kube-system
  timeoutSeconds: 10

Key Fields Explained

FieldPurposeRecommended
failurePolicyWhat to do if webhook is unavailableFail (fail closed)
sideEffectsWhether webhook has side effectsNone
timeoutSecondsMax time to wait for response10 seconds
namespaceSelectorWhich namespaces to apply toExclude system namespaces
rulesWhich resources/operations to interceptBe specific
caBundleCA cert to verify webhook's TLS certRequired

Mutating Webhook Configuration

Mutating webhooks can modify the request object. They run before validating webhooks.

yaml
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: pod-defaults
webhooks:
- name: pod-defaults.example.com
  admissionReviewVersions: ["v1"]
  sideEffects: None
  clientConfig:
    service:
      name: pod-defaults
      namespace: webhook-system
      path: /mutate-pods
      port: 443
    caBundle: <base64-encoded-CA-cert>
  rules:
  - apiGroups: [""]
    apiVersions: ["v1"]
    operations: ["CREATE"]
    resources: ["pods"]
  reinvocationPolicy: IfNeeded     # Re-run if other mutating webhooks change object
  failurePolicy: Fail
  namespaceSelector:
    matchExpressions:
    - key: kubernetes.io/metadata.name
      operator: NotIn
      values: ["kube-system"]
  timeoutSeconds: 10

Common Mutating Webhook Use Cases

Use CaseWhat It Does
Inject sidecar containersIstio sidecar injection
Add default labels/annotationsEnsure compliance metadata
Set default resource limitsPrevent pods without limits
Add imagePullSecretsInject registry credentials
Modify security contextsEnforce non-root by default

Failure Policies

PolicyValueBehaviorSecurity
Fail ClosedFailReject request if webhook is downMore secure, can cause outages
Fail OpenIgnoreAllow request if webhook is downLess secure, more available

Exam Tip

On the CKS exam, failurePolicy: Fail (fail closed) is generally the more secure choice. However, be aware that if applied to critical system namespaces, it can prevent the cluster from functioning if the webhook goes down.


Common CKS Exam Scenarios

Scenario 1: Enable an Admission Controller

bash
# Edit the API server manifest
sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml

# Add to --enable-admission-plugins
# --enable-admission-plugins=NodeRestriction,PodSecurity,ImagePolicyWebhook

# Wait for API server restart
kubectl get nodes  # Retry until it works

Scenario 2: Configure ImagePolicyWebhook

  1. Create the admission configuration file
  2. Create the kubeconfig file pointing to the webhook
  3. Add --admission-control-config-file to API server
  4. Add ImagePolicyWebhook to enabled plugins
  5. Mount the configuration directory
  6. Restart and verify

Scenario 3: Debug a Failing Webhook

bash
# Check webhook configurations
kubectl get validatingwebhookconfigurations
kubectl get mutatingwebhookconfigurations

# Check webhook endpoint health
kubectl get endpoints -n webhook-system

# Check webhook pod logs
kubectl logs -n webhook-system -l app=webhook

# Check for webhook-related events
kubectl get events --field-selector reason=FailedCreate

# Temporarily disable a webhook
kubectl delete validatingwebhookconfigurations <name>

Scenario 4: Exclude a Namespace from Webhooks

bash
# Add namespace selector to skip certain namespaces
# Most webhooks already exclude kube-system
# Add labels to namespaces you want to exclude
kubectl label namespace critical-ns skip-webhook=true

Then update the webhook configuration:

yaml
namespaceSelector:
  matchExpressions:
  - key: skip-webhook
    operator: DoesNotExist

Quick Reference

bash
# List all admission webhook configurations
kubectl get validatingwebhookconfigurations
kubectl get mutatingwebhookconfigurations

# Describe a webhook
kubectl describe validatingwebhookconfigurations <name>

# Check enabled admission plugins
ps aux | grep kube-apiserver | grep enable-admission

# View API server manifest
cat /etc/kubernetes/manifests/kube-apiserver.yaml

# Test if a resource would be admitted
kubectl apply -f resource.yaml --dry-run=server

# Check webhook endpoint connectivity
kubectl get endpoints -n <webhook-namespace>

# View admission-related events
kubectl get events -A --field-selector reason=FailedCreate

Released under the MIT License.