<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Kyverno Archives - Linuxcent</title>
	<atom:link href="https://linuxcent.com/tag/kyverno/feed/" rel="self" type="application/rss+xml" />
	<link>https://linuxcent.com/tag/kyverno/</link>
	<description>Infrastructure security, from the kernel up.</description>
	<lastBuildDate>Sat, 09 May 2026 18:40:45 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.3</generator>

<image>
	<url>https://linuxcent.com/wp-content/uploads/2026/04/favicon-512x512-1-150x150.png</url>
	<title>Kyverno Archives - Linuxcent</title>
	<link>https://linuxcent.com/tag/kyverno/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">211632295</site>	<item>
		<title>Admission Webhooks: Validating and Mutating Requests Before They Reach etcd</title>
		<link>https://linuxcent.com/kubernetes-admission-webhooks-explained/</link>
					<comments>https://linuxcent.com/kubernetes-admission-webhooks-explained/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Sat, 25 Apr 2026 18:17:00 +0000</pubDate>
				<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Admission Webhooks]]></category>
		<category><![CDATA[CRD]]></category>
		<category><![CDATA[Kyverno]]></category>
		<category><![CDATA[MutatingWebhookConfiguration]]></category>
		<category><![CDATA[OPA]]></category>
		<category><![CDATA[ValidatingWebhookConfiguration]]></category>
		<guid isPermaLink="false">https://linuxcent.com/kubernetes-admission-webhooks-explained/</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 6</span> <span class="rt-label rt-postfix">minutes</span></span>Understand Kubernetes admission webhooks — validating vs mutating, when to use each, how they intercept API requests before etcd, and how they pair with CRDs.</p>
<p>The post <a href="https://linuxcent.com/kubernetes-admission-webhooks-explained/">Admission Webhooks: Validating and Mutating Requests Before They Reach etcd</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 6</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><em>Kubernetes CRDs &amp; Operators: Extending the API, Episode 9</em><br />
<em><a href="/what-is-kubernetes-crd/">What Is a CRD?</a> · <a href="/kubernetes-custom-resources-examples/">CRDs You Already Use</a> · <a href="/kubernetes-crd-schema-explained/">CRD Anatomy</a> · <a href="/write-kubernetes-crd-yaml-walkthrough/">Write Your First CRD</a> · <a href="/kubernetes-crd-cel-validation/">CEL Validation</a> · <a href="/kubernetes-controller-reconcile-loop/">Controller Loop</a> · <a href="/build-kubernetes-operator-controller-runtime/">Build an Operator</a> · <a href="/kubernetes-crd-versioning-conversion-webhook/">CRD Versioning</a> · </em><em><a href="/kubernetes-admission-webhooks-explained/">Admission Webhooks</a></em><em> · <a href="/kubernetes-crd-production-finalizers-conditions-rbac/">CRDs in Production</a></em></p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>Kubernetes admission webhooks are HTTPS endpoints called by the API server synchronously on every create/update/delete — before the object reaches etcd<br />
  <em>(two types: mutating webhooks modify the object; validating webhooks approve or reject it — mutating runs first, then validating)</em></li>
<li>Use a <strong>validating webhook</strong> when you need to reject objects based on state you cannot express in CEL: checking if a referenced Secret exists, enforcing cross-resource quotas, consulting an external policy engine</li>
<li>Use a <strong>mutating webhook</strong> when you need to inject defaults or sidecar containers that depend on context you cannot express in the CRD schema (environment-specific defaults, sidecar injection)</li>
<li>Admission webhooks are an <strong>availability dependency</strong> — if your webhook is unreachable, the API requests it covers will fail. <code class="" data-line="">failurePolicy: Ignore</code> is the safety valve; use it only for non-critical webhooks</li>
<li>OPA/Gatekeeper and Kyverno are admission webhook platforms — they let you write policy as code (Rego, YAML) instead of writing Go webhook handlers</li>
<li>For CRD-specific validation that only depends on the object itself, prefer CEL (EP05) — webhooks are for rules that require external lookups or cross-resource checks</li>
</ul>
<hr />
<h2 id="the-big-picture">The Big Picture</h2>
<pre><code class="" data-line="">  KUBERNETES ADMISSION CHAIN (full picture)

  kubectl apply -f backuppolicy.yaml
        │
        ▼
  API Server: authentication + authorization
        │
        ▼
  1. Mutating admission webhooks
     ┌───────────────────────────────────────┐
     │ Receive object, return modified object │
     │ Examples: inject annotations,          │
     │ set defaults, add sidecars            │
     └───────────────────────────────────────┘
        │
        ▼
  2. Schema validation (OpenAPI + CEL)
        │
        ▼
  3. Validating admission webhooks
     ┌───────────────────────────────────────┐
     │ Receive object, return allow/deny     │
     │ Examples: quota checks, cross-        │
     │ resource validation, policy engines   │
     └───────────────────────────────────────┘
        │
        ▼ (allowed)
  etcd storage
</code></pre>
<p>Kubernetes admission webhooks are how tools like Istio inject sidecars, Kyverno enforces policies, and OPA/Gatekeeper applies organizational guardrails — all without modifying Kubernetes source code. Understanding them completes the picture of how Kubernetes is extended beyond CRDs.</p>
<hr />
<h2 id="validating-vs-mutating-when-to-use-each">Validating vs Mutating: When to Use Each</h2>
<pre><code class="" data-line="">  DECISION TREE: CEL vs Validating Webhook vs Mutating Webhook

  &quot;I need to validate a field value&quot;
      │
      ├── Depends only on the object being submitted?
      │   → Use CEL (x-kubernetes-validations) — EP05
      │
      └── Needs to look up another resource, quota, or external system?
          → Use Validating Admission Webhook

  &quot;I need to set default values or inject content&quot;
      │
      ├── Defaults depend only on other fields in the same object?
      │   → Use OpenAPI schema defaults or CEL
      │
      └── Defaults depend on environment, namespace labels, or external config?
          → Use Mutating Admission Webhook
</code></pre>
<p>Practical examples:</p>
<table>
<thead>
<tr>
<th>Rule</th>
<th>Right tool</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="" data-line="">retentionDays</code> must be ≤ 365</td>
<td>CEL</td>
</tr>
<tr>
<td><code class="" data-line="">if storageClass=premium then retentionDays ≤ 90</code></td>
<td>CEL</td>
</tr>
<tr>
<td>Referenced <code class="" data-line="">SecretStore</code> must exist in the same namespace</td>
<td>Validating webhook</td>
</tr>
<tr>
<td><code class="" data-line="">BackupPolicy</code> count per namespace must not exceed team quota</td>
<td>Validating webhook</td>
</tr>
<tr>
<td>Inject <code class="" data-line="">costCenter</code> annotation from namespace labels</td>
<td>Mutating webhook</td>
</tr>
<tr>
<td>Inject backup-agent sidecar into all Pods in labeled namespaces</td>
<td>Mutating webhook</td>
</tr>
<tr>
<td>Enforce that all BackupPolicies have a <code class="" data-line="">team</code> label</td>
<td>Kyverno or OPA policy</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="the-webhook-requestresponse-contract">The Webhook Request/Response Contract</h2>
<p>Both webhook types receive an <code class="" data-line="">AdmissionReview</code> object and return an <code class="" data-line="">AdmissionReview</code> response.</p>
<p>Request (from API server to webhook):</p>
<pre><code class="" data-line="">{
  &quot;apiVersion&quot;: &quot;admission.k8s.io/v1&quot;,
  &quot;kind&quot;: &quot;AdmissionReview&quot;,
  &quot;request&quot;: {
    &quot;uid&quot;: &quot;705ab4f5-6393-11e8-b7cc-42010a800002&quot;,
    &quot;kind&quot;: {&quot;group&quot;: &quot;storage.example.com&quot;, &quot;version&quot;: &quot;v1alpha1&quot;, &quot;kind&quot;: &quot;BackupPolicy&quot;},
    &quot;resource&quot;: {&quot;group&quot;: &quot;storage.example.com&quot;, &quot;version&quot;: &quot;v1alpha1&quot;, &quot;resource&quot;: &quot;backuppolicies&quot;},
    &quot;operation&quot;: &quot;CREATE&quot;,
    &quot;userInfo&quot;: {&quot;username&quot;: &quot;alice&quot;, &quot;groups&quot;: [&quot;system:authenticated&quot;]},
    &quot;object&quot;: { /* full BackupPolicy JSON */ },
    &quot;oldObject&quot;: null
  }
}
</code></pre>
<p>Response for a validating webhook (allow):</p>
<pre><code class="" data-line="">{
  &quot;apiVersion&quot;: &quot;admission.k8s.io/v1&quot;,
  &quot;kind&quot;: &quot;AdmissionReview&quot;,
  &quot;response&quot;: {
    &quot;uid&quot;: &quot;705ab4f5-6393-11e8-b7cc-42010a800002&quot;,
    &quot;allowed&quot;: true
  }
}
</code></pre>
<p>Response for a validating webhook (deny):</p>
<pre><code class="" data-line="">{
  &quot;response&quot;: {
    &quot;uid&quot;: &quot;...&quot;,
    &quot;allowed&quot;: false,
    &quot;status&quot;: {
      &quot;code&quot;: 422,
      &quot;message&quot;: &quot;referenced SecretStore &#039;aws-secrets-manager&#039; not found in namespace &#039;production&#039;&quot;
    }
  }
}
</code></pre>
<p>Response for a mutating webhook (allow + patch):</p>
<pre><code class="" data-line="">{
  &quot;response&quot;: {
    &quot;uid&quot;: &quot;...&quot;,
    &quot;allowed&quot;: true,
    &quot;patchType&quot;: &quot;JSONPatch&quot;,
    &quot;patch&quot;: &quot;W3sib3AiOiJhZGQiLCJwYXRoIjoiL21ldGFkYXRhL2Fubm90YXRpb25zL2Nvc3RDZW50ZXIiLCJ2YWx1ZSI6ImVuZ2luZWVyaW5nIn1d&quot;
    // base64-encoded JSON patch:
    // [{&quot;op&quot;:&quot;add&quot;,&quot;path&quot;:&quot;/metadata/annotations/costCenter&quot;,&quot;value&quot;:&quot;engineering&quot;}]
  }
}
</code></pre>
<hr />
<h2 id="writing-a-validating-webhook-with-kubebuilder">Writing a Validating Webhook with kubebuilder</h2>
<pre><code class="" data-line="">kubebuilder create webhook \
  --group storage \
  --version v1alpha1 \
  --kind BackupPolicy \
  --programmatic-validation
</code></pre>
<p>Edit <code class="" data-line="">api/v1alpha1/backuppolicy_webhook.go</code>:</p>
<pre><code class="" data-line="">package v1alpha1

import (
    &quot;context&quot;
    &quot;fmt&quot;

    apierrors &quot;k8s.io/apimachinery/pkg/api/errors&quot;
    &quot;k8s.io/apimachinery/pkg/runtime&quot;
    &quot;k8s.io/apimachinery/pkg/types&quot;
    ctrl &quot;sigs.k8s.io/controller-runtime&quot;
    &quot;sigs.k8s.io/controller-runtime/pkg/client&quot;
    &quot;sigs.k8s.io/controller-runtime/pkg/webhook/admission&quot;
    esov1beta1 &quot;github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1&quot;
)

type BackupPolicyCustomValidator struct {
    Client client.Client
}

//+kubebuilder:webhook:path=/validate-storage-example-com-v1alpha1-backuppolicy,mutating=false,failurePolicy=fail,sideEffects=None,groups=storage.example.com,resources=backuppolicies,verbs=create;update,versions=v1alpha1,name=vbackuppolicy.kb.io,admissionReviewVersions=v1

func (v *BackupPolicyCustomValidator) SetupWebhookWithManager(mgr ctrl.Manager) error {
    v.Client = mgr.GetClient()
    return ctrl.NewWebhookManagedBy(mgr).
        For(&amp;BackupPolicy{}).
        WithValidator(v).
        Complete()
}

// ValidateCreate validates a new BackupPolicy.
func (v *BackupPolicyCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
    bp := obj.(*BackupPolicy)
    return nil, v.validateSecretStoreRef(ctx, bp)
}

// ValidateUpdate validates an updated BackupPolicy.
func (v *BackupPolicyCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
    bp := newObj.(*BackupPolicy)
    return nil, v.validateSecretStoreRef(ctx, bp)
}

// ValidateDelete is a no-op here.
func (v *BackupPolicyCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
    return nil, nil
}

// validateSecretStoreRef checks that the referenced SecretStore exists in the same namespace.
func (v *BackupPolicyCustomValidator) validateSecretStoreRef(ctx context.Context, bp *BackupPolicy) error {
    ref := bp.Spec.SecretStoreRef
    if ref == &quot;&quot; {
        return nil  // optional field; CEL handles it if required
    }

    store := &amp;esov1beta1.SecretStore{}
    err := v.Client.Get(ctx, types.NamespacedName{Name: ref, Namespace: bp.Namespace}, store)
    if apierrors.IsNotFound(err) {
        return fmt.Errorf(&quot;referenced SecretStore %q not found in namespace %q&quot;,
            ref, bp.Namespace)
    }
    return err  // nil on found, real error on API failure
}
</code></pre>
<hr />
<h2 id="writing-a-mutating-webhook-cost-center-injection">Writing a Mutating Webhook: Cost Center Injection</h2>
<pre><code class="" data-line="">kubebuilder create webhook \
  --group storage \
  --version v1alpha1 \
  --kind BackupPolicy \
  --defaulting
</code></pre>
<p>Edit the defaulting webhook:</p>
<pre><code class="" data-line="">//+kubebuilder:webhook:path=/mutate-storage-example-com-v1alpha1-backuppolicy,mutating=true,failurePolicy=fail,sideEffects=None,groups=storage.example.com,resources=backuppolicies,verbs=create,versions=v1alpha1,name=mbackuppolicy.kb.io,admissionReviewVersions=v1

func (r *BackupPolicy) Default() {
    // Default is called by kubebuilder&#039;s webhook framework on admission.
    // The webhook handler calls this and patches the object.
    //
    // This runs AFTER API server schema defaults — use it for context-dependent defaults.
}

// For namespace-label-based injection, implement the full webhook handler instead:
type BackupPolicyMutator struct {
    Client client.Client
}

func (m *BackupPolicyMutator) Handle(ctx context.Context, req admission.Request) admission.Response {
    bp := &amp;BackupPolicy{}
    if err := json.Unmarshal(req.Object.Raw, bp); err != nil {
        return admission.Errored(http.StatusBadRequest, err)
    }

    // Fetch the namespace to read its labels
    ns := &amp;corev1.Namespace{}
    if err := m.Client.Get(ctx, types.NamespacedName{Name: bp.Namespace}, ns); err != nil {
        return admission.Errored(http.StatusInternalServerError, err)
    }

    // Inject costCenter annotation from namespace label
    if costCenter, ok := ns.Labels[&quot;billing/cost-center&quot;]; ok {
        if bp.Annotations == nil {
            bp.Annotations = make(map[string]string)
        }
        bp.Annotations[&quot;billing/cost-center&quot;] = costCenter
    }

    marshaled, err := json.Marshal(bp)
    if err != nil {
        return admission.Errored(http.StatusInternalServerError, err)
    }
    return admission.PatchResponseFromRaw(req.Object.Raw, marshaled)
}
</code></pre>
<hr />
<h2 id="the-webhookconfiguration-resource">The WebhookConfiguration Resource</h2>
<p>The <code class="" data-line="">ValidatingWebhookConfiguration</code> tells the API server which webhooks exist and which resources/operations they handle:</p>
<pre><code class="" data-line="">apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: backup-operator-validating-webhook
  annotations:
    cert-manager.io/inject-ca-from: backup-operator-system/backup-operator-serving-cert
webhooks:
  - name: vbackuppolicy.kb.io
    admissionReviewVersions: [&quot;v1&quot;]
    clientConfig:
      service:
        name: backup-operator-webhook-service
        namespace: backup-operator-system
        path: /validate-storage-example-com-v1alpha1-backuppolicy
    rules:
      - apiGroups:   [&quot;storage.example.com&quot;]
        apiVersions: [&quot;v1alpha1&quot;]
        operations:  [&quot;CREATE&quot;, &quot;UPDATE&quot;]
        resources:   [&quot;backuppolicies&quot;]
    failurePolicy: Fail          # Fail = reject request if webhook unreachable
    sideEffects: None
    timeoutSeconds: 10
    namespaceSelector:
      matchExpressions:
        - key: kubernetes.io/metadata.name
          operator: NotIn
          values: [&quot;kube-system&quot;]  # never webhook kube-system objects
</code></pre>
<hr />
<h2 id="failurepolicy-fail-vs-ignore"><code class="" data-line="">failurePolicy</code>: Fail vs Ignore</h2>
<pre><code class="" data-line="">  failurePolicy: Fail (default)
  ──────────────────────────────
  If webhook is unreachable → API request fails with 500
  Use when: the validation is critical (quota enforcement, policy)
  Risk: your webhook becoming unavailable breaks all covered API operations

  failurePolicy: Ignore
  ──────────────────────────────
  If webhook is unreachable → API request proceeds as if webhook allowed it
  Use when: the webhook is advisory or can be bypassed safely
  Risk: policy is silently not enforced during webhook outage
</code></pre>
<p>For production operators, use <code class="" data-line="">failurePolicy: Fail</code> but ensure high availability:<br />
&#8211; Run at least 2 webhook pod replicas with <code class="" data-line="">PodDisruptionBudget</code><br />
&#8211; Use cert-manager for automatic TLS certificate rotation<br />
&#8211; Set <code class="" data-line="">timeoutSeconds</code> to a value that allows graceful degradation (5–10s)<br />
&#8211; Exclude system namespaces with <code class="" data-line="">namespaceSelector</code></p>
<hr />
<h2 id="opagatekeeper-and-kyverno-webhooks-as-policy-platforms">OPA/Gatekeeper and Kyverno: Webhooks as Policy Platforms</h2>
<p>Writing raw webhook handlers in Go is powerful but heavyweight for policy enforcement. OPA/Gatekeeper and Kyverno are webhook-based policy engines that let you express policies as code:</p>
<p><strong>Kyverno</strong> (YAML-based policies):</p>
<pre><code class="" data-line="">apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-backup-label
spec:
  rules:
    - name: require-team-label
      match:
        any:
          - resources:
              kinds: [&quot;BackupPolicy&quot;]
      validate:
        message: &quot;BackupPolicy must have a &#039;team&#039; label&quot;
        pattern:
          metadata:
            labels:
              team: &quot;?*&quot;
</code></pre>
<p><strong>OPA/Gatekeeper</strong> (Rego-based policies):</p>
<pre><code class="" data-line="">package backuppolicy

deny[msg] {
    input.request.kind.kind == &quot;BackupPolicy&quot;
    not input.request.object.metadata.labels[&quot;team&quot;]
    msg := &quot;BackupPolicy must have a &#039;team&#039; label&quot;
}
</code></pre>
<p>Both run as admission webhooks that the API server calls. The policy language sits on top of the webhook plumbing. For organizational policy enforcement across many resource types, these tools outperform custom Go webhook handlers.</p>
<hr />
<h2 id="common-mistakes"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Common Mistakes</h2>
<p><strong>Webhook covering <code class="" data-line="">*</code> resources or <code class="" data-line="">*</code> operations.</strong> A webhook covering all resources in the cluster is a reliability risk — a bug in the webhook or an outage breaks everything. Scope webhooks to exactly the resources and operations they need with <code class="" data-line="">rules[].resources</code> and <code class="" data-line="">rules[].operations</code>.</p>
<p><strong>No TLS certificate rotation.</strong> Webhook endpoints require a TLS certificate that the API server trusts. Certificates expire. Using cert-manager with the <code class="" data-line="">cert-manager.io/inject-ca-from</code> annotation automates this. Without it, expired certificates cause silent webhook outages (the API server rejects the TLS handshake, triggering <code class="" data-line="">failurePolicy</code> behavior).</p>
<p><strong>Not excluding system namespaces.</strong> If a validating webhook covers Pods and has <code class="" data-line="">failurePolicy: Fail</code>, and the webhook pod itself crashes, the API server cannot create a new webhook pod because the webhook rejects the creation. Use <code class="" data-line="">namespaceSelector</code> to exclude <code class="" data-line="">kube-system</code> and your operator&#8217;s own namespace.</p>
<p><strong>Treating webhook latency as free.</strong> Every API operation covered by a webhook adds a synchronous HTTP round-trip. On a busy cluster creating thousands of objects per minute, a 100ms webhook latency becomes significant. Set <code class="" data-line="">timeoutSeconds</code>, profile webhook performance, and scope rules narrowly.</p>
<hr />
<h2 id="quick-reference">Quick Reference</h2>
<pre><code class="" data-line=""># List all webhook configurations
kubectl get validatingwebhookconfigurations
kubectl get mutatingwebhookconfigurations

# Inspect webhook rules and failure policy
kubectl describe validatingwebhookconfiguration backup-operator-validating-webhook

# Temporarily disable a webhook for debugging (dangerous in production)
kubectl delete validatingwebhookconfiguration backup-operator-validating-webhook

# Check webhook endpoint certificate
kubectl get secret backup-operator-webhook-server-cert \
  -n backup-operator-system \
  -o jsonpath=&#039;{.data.tls\.crt}&#039; | base64 -d | openssl x509 -noout -dates

# Test webhook is reachable from a cluster node
kubectl run webhook-test --image=curlimages/curl --rm -it --restart=Never -- \
  curl -k https://backup-operator-webhook-service.backup-operator-system.svc:443/healthz
</code></pre>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>Mutating webhooks modify objects at admission; validating webhooks approve or reject them — mutating runs before validating</li>
<li>Use CEL for rules that depend only on the submitted object; use webhooks when you need external lookups or cross-resource checks</li>
<li><code class="" data-line="">failurePolicy: Fail</code> blocks API requests if the webhook is unreachable — ensure high availability before using it</li>
<li>Always exclude system namespaces and scope rules to specific resource types to minimize the blast radius of webhook failures</li>
<li>OPA/Gatekeeper and Kyverno are admission webhook platforms for policy-as-code — prefer them over custom Go handlers for organizational policy enforcement</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p><a href="/kubernetes-crd-production-finalizers-conditions-rbac/">EP10: Kubernetes CRDs in Production</a> ties the full series together — finalizer design patterns, status condition conventions, owner references, RBAC for multi-tenant CRD usage, and the production failure modes that catch teams off guard.</p>
<p>Get EP10 in your inbox when it publishes → <a href="https://linuxcent.com">subscribe at linuxcent.com</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-admission-webhooks-explained%2F&amp;linkname=Admission%20Webhooks%3A%20Validating%20and%20Mutating%20Requests%20Before%20They%20Reach%20etcd" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-admission-webhooks-explained%2F&amp;linkname=Admission%20Webhooks%3A%20Validating%20and%20Mutating%20Requests%20Before%20They%20Reach%20etcd" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-admission-webhooks-explained%2F&amp;linkname=Admission%20Webhooks%3A%20Validating%20and%20Mutating%20Requests%20Before%20They%20Reach%20etcd" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-admission-webhooks-explained%2F&amp;linkname=Admission%20Webhooks%3A%20Validating%20and%20Mutating%20Requests%20Before%20They%20Reach%20etcd" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-admission-webhooks-explained%2F&amp;linkname=Admission%20Webhooks%3A%20Validating%20and%20Mutating%20Requests%20Before%20They%20Reach%20etcd" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-admission-webhooks-explained%2F&amp;linkname=Admission%20Webhooks%3A%20Validating%20and%20Mutating%20Requests%20Before%20They%20Reach%20etcd" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-admission-webhooks-explained%2F&amp;linkname=Admission%20Webhooks%3A%20Validating%20and%20Mutating%20Requests%20Before%20They%20Reach%20etcd" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fkubernetes-admission-webhooks-explained%2F&#038;title=Admission%20Webhooks%3A%20Validating%20and%20Mutating%20Requests%20Before%20They%20Reach%20etcd" data-a2a-url="https://linuxcent.com/kubernetes-admission-webhooks-explained/" data-a2a-title="Admission Webhooks: Validating and Mutating Requests Before They Reach etcd"></a></p><p>The post <a href="https://linuxcent.com/kubernetes-admission-webhooks-explained/">Admission Webhooks: Validating and Mutating Requests Before They Reach etcd</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/kubernetes-admission-webhooks-explained/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1699</post-id>	</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: linuxcent.com @ 2026-08-12 20:16:18 by W3 Total Cache
-->