> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 2025 Updates Lab Solution Validating and Mutating Admission Controllers

> Walkthrough to create and test Kubernetes mutating and validating admission webhooks that enforce non root containers, configure TLS and webhook resources, and validate pod security behaviors

This lab walks through creating and testing a sample mutating admission webhook that enforces non-root containers by default, and shows how validating admission checks interact with those mutations. You'll:

* Create a webhook namespace and TLS secret.
* Deploy a webhook server and service.
* Apply a MutatingWebhookConfiguration that injects `runAsNonRoot: true` and `runAsUser: 1234` when a Pod has no security context.
* Verify behavior across three Pod test cases: default (mutated), explicit override, and conflicting settings (rejected).

Keywords: Kubernetes admission controllers, mutating webhook, validating webhook, MutatingWebhookConfiguration, runAsNonRoot, TLS secret.

1. Identify which admission controllers are mutating vs validating

* NamespaceAutoProvision is a mutating admission controller (it creates or modifies resources, e.g., auto-creating a namespace).
* namespace-exists (or similar) is a validating admission controller (it checks whether a namespace exists and rejects requests if validation fails).

Mutating webhooks are invoked first, and any changes they make are visible to subsequent validating webhooks. This ordering matters: a mutating webhook can add defaults that a validating webhook then evaluates.

<Callout icon="lightbulb" color="#1CB2FE">
  Mutating admission webhooks run before validating admission webhooks. Any mutation applied by the mutating webhook is what validating webhooks will evaluate.
</Callout>

2. Create the webhook namespace

Create the namespace `webhook-demo`:

```bash theme={null}
root@controlplane ~ ➜ kubectl create ns webhook-demo
namespace/webhook-demo created
```

Verify the new namespace:

```bash theme={null}
root@controlplane ~ ➜ kubectl get ns
NAME                 STATUS   AGE
default              Active   37m
kube-node-lease      Active   37m
kube-public          Active   37m
kube-system          Active   37m
webhook-demo         Active   3s
```

3. Create a TLS secret for the webhook server

A certificate and key were pre-generated. Create a TLS secret in `webhook-demo` using those files:

```bash theme={null}
root@controlplane ~ ➜ kubectl -n webhook-demo create secret tls webhook-server-tls \
  --cert "/root/keys/webhook-server-tls.crt" \
  --key  "/root/keys/webhook-server-tls.key"
secret/webhook-server-tls created
```

4. Deploy the webhook server Deployment and Service

Apply the provided Deployment and Service manifests:

```bash theme={null}
root@controlplane ~ ➜ kubectl apply -f webhook-deployment.yaml
deployment.apps/webhook-server created

root@controlplane ~ ➜ kubectl apply -f webhook-service.yaml
service/webhook-server created
```

Confirm the repository manifests are present:

```bash theme={null}
root@controlplane ~ ➜ ls
keys                pod-with-defaults.yaml  webhook-configuration.yaml  webhook-service.yaml
pod-with-conflict.yaml  pod-with-override.yaml  webhook-deployment.yaml
```

5. Inspect and apply the provided MutatingWebhookConfiguration

The sample MutatingWebhookConfiguration targets pod `CREATE` operations and routes to the `webhook-server` service at `/mutate`. The important rule and clientConfig look like:

```yaml theme={null}
webhooks:
  - name: webhook-server.webhook-demo.svc
    clientConfig:
      service:
        name: webhook-server
        namespace: webhook-demo
        path: "/mutate"
      caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
    rules:
      - operations: ["CREATE"]
        apiGroups: [""]
        apiVersions: ["v1"]
        resources: ["pods"]
```

Apply the configuration:

```bash theme={null}
root@controlplane ~ ➜ kubectl apply -f webhook-configuration.yaml
Warning: admissionregistration.k8s.io/v1beta1 MutatingWebhookConfiguration is deprecated in v1.16+, unavailable in v1.22+; use admissionregistration.k8s.io/v1 MutatingWebhookConfiguration
mutatingwebhookconfiguration.admissionregistration.k8s.io/demo-webhook created
```

<Callout icon="warning" color="#FF6B6B">
  The example manifest may use `admissionregistration.k8s.io/v1beta1`. On modern Kubernetes clusters, update your manifests to `admissionregistration.k8s.io/v1` to avoid deprecation and compatibility issues. See the Kubernetes admission webhook docs for details.
</Callout>

6. What the webhook does (behavior summary)

The demo mutating webhook enforces non-root defaults and applies validation rules:

* If a Pod is created without `securityContext` settings (`runAsNonRoot` / `runAsUser`), the webhook mutates the Pod spec to set:
  * `runAsNonRoot: true`
  * `runAsUser: 1234`
* If a Pod explicitly sets `runAsNonRoot: false` (allow root), the webhook respects that explicit setting and does not override it.
* If a Pod has conflicting settings (e.g., `runAsNonRoot: true` and `runAsUser: 0`), the webhook rejects the request because the configuration is logically inconsistent.

7. Test 1 — Pod with no securityContext (mutating webhook applies defaults)

Pod manifest `pod-with-defaults.yaml` (no securityContext):

```yaml theme={null}
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-defaults
  labels:
    app: pod-with-defaults
spec:
  restartPolicy: OnFailure
  containers:
    - name: busybox
      image: busybox
      command: ["sh", "-c", "echo I am running as user $(id -u)"]
```

Apply it:

```bash theme={null}
root@controlplane ~ ➜ kubectl apply -f pod-with-defaults.yaml
pod/pod-with-defaults created
```

Inspect the pod to see the mutation applied:

```bash theme={null}
root@controlplane ~ ➜ kubectl get pod pod-with-defaults -o yaml
# Look for the effective securityContext in the pod (mutated by the webhook), e.g.:
# securityContext:
#   runAsNonRoot: true
#   runAsUser: 1234
```

Expected effective values:

* `runAsNonRoot: true`
* `runAsUser: 1234`

8. Test 2 — Pod with explicit override allowing root

Pod manifest `pod-with-override.yaml` explicitly allows root:

```yaml theme={null}
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-override
  labels:
    app: pod-with-override
spec:
  restartPolicy: OnFailure
  securityContext:
    runAsNonRoot: false
  containers:
    - name: busybox
      image: busybox
      command: ["sh", "-c", "echo I am running as user $(id -u)"]
```

Apply it:

```bash theme={null}
root@controlplane ~ ➜ kubectl apply -f pod-with-override.yaml
pod/pod-with-override created
```

Because the Pod explicitly sets `runAsNonRoot: false`, the mutating webhook does not inject defaults—explicit configuration wins. Behavior is the same with or without the webhook for this case.

9. Test 3 — Pod with conflicting settings (rejected by validation)

Pod manifest `pod-with-conflict.yaml` intentionally conflicts:

```yaml theme={null}
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-conflict
  labels:
    app: pod-with-conflict
spec:
  restartPolicy: OnFailure
  securityContext:
    runAsNonRoot: true
    runAsUser: 0
  containers:
    - name: busybox
      image: busybox
      command: ["sh", "-c", "echo I am running as user $(id -u)"]
```

Apply it; the webhook should reject this because `runAsNonRoot: true` conflicts with `runAsUser: 0`:

```bash theme={null}
root@controlplane ~ ➜ kubectl apply -f pod-with-conflict.yaml
Error from server: error when creating "pod-with-conflict.yaml": admission webhook "webhook-server.webhook-demo.svc" denied the request: runAsNonRoot specified, but runAsUser set to 0 (the root user)

root@controlplane ~ ❌
```

The webhook correctly validated and denied the request due to conflicting security settings.

Tests and expected outcomes

| Test case | Manifest | Expected behavior |
| - | - | - |
| Default (no securityContext) | `pod-with-defaults.yaml` | Mutating webhook injects `runAsNonRoot: true`, `runAsUser: 1234`. Pod is created with non-root UID. |
| Explicit override (allow root) | `pod-with-override.yaml` | Mutating webhook does not override `runAsNonRoot: false`. Pod runs as root (explicit config preserved). |
| Conflicting settings (invalid) | `pod-with-conflict.yaml` | Webhook rejects creation because `runAsNonRoot: true` conflicts with `runAsUser: 0`. |

Summary

* Mutating admission webhooks can inject secure defaults (e.g., non-root UID and `runAsNonRoot: true`) to resources that omit them.
* Validating admission webhooks (or validation logic within the webhook) detect and reject logically inconsistent or insecure configurations.
* Always use `admissionregistration.k8s.io/v1` for admission webhook configuration manifests on modern Kubernetes clusters to avoid deprecation issues.

Links and references

* [Kubernetes admission controllers overview](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/)
* [MutatingAdmissionWebhook and ValidatingAdmissionWebhook docs](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/)

<Callout icon="lightbulb" color="#1CB2FE">
  When developing or deploying admission webhooks, remember to:

  * Keep TLS certificates current and load the CA bundle into your webhook configuration.
  * Test webhook ordering: mutating first, validating second.
  * Use `admissionregistration.k8s.io/v1` manifests for compatibility with recent Kubernetes versions.
</Callout>

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/cka-certification-course-certified-kubernetes-administrator/module/cd124bdf-9911-4cc1-8177-f2d8b6dfd2a0/lesson/4e981330-0036-4e90-b32d-379759ea3910" />
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.