RAPID INCIDENT TRIAGE & ACTION PLAN•Security
01. Error Signature
EKS ALB Controller Webhook Cert Renewer
02. Root Cause Vector

Expired webhook TLS certificate on AWS Load Balancer Controller blocks all Ingress/Service reconciliation in EKS, causing immediate ingre...

03. Rapid Prescription

Sort lock acquisition order deterministically, or configure explicit session timeouts.

Zero-Trust Client IDE/ Security

EKS ALB Controller Webhook Cert Renewer

Expired webhook TLS certificate on AWS Load Balancer Controller blocks all Ingress/Service reconciliation in EKS, causing immediate ingress provisioning failures cluster-wide.

SYSTEM STATUS
CREDITS: 0 / 3
source.yaml(82L)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
apiVersion: v1
kind: ConfigMap
metadata:
  name: legacy-webhook-cert-rotator
  namespace: kube-system
  labels:
    app.kubernetes.io/part-of: pci-secure-zone
    # TODO: Clean up this legacy label after migration to new infra
    # legacy-region: us-east-1-deprecated
    # VULNERABILITY: This cron schedule is too infrequent; certificate expires before renewal triggers.
data:
  # This is the cron schedule for the cert renewal job (runs every 2 years, cert valid for 1 year)
  cron_schedule: "0 0 1 1 */24"
  # Old hardcoded AWS region from the previous deployment
  aws_region: "us-west-2"
  # Mixed syntax alert: this is a JSON string embedded in YAML for 'historical reasons'
  secret_env_vars: '{"AWS_ACCESS_KEY_ID": "AKIAIOSFODNN7EXAMPLE", "AWS_SECRET_ACCESS_KEY": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "STRIPE_API_KEY": "sk_live_4eC39HqLyjWDarjtT1zdp7dc", "DB_URI": "postgresql://user:Passw0rd!@legacy-db.internal:5432/audit_logs?sslmode=disable"}'
  # This script is sourced by the renewal job; note the hardcoded cert path that may not exist
  renewal_script: |
    #!/bin/bash
    # TODO: This is a temporary workaround for the EKS 1.28 upgrade; remove after Q4
    # Actually, we might need to keep it due to compliance...
    CERT_PATH="/etc/webhook/certs/tls.crt"
    # The controller expects the cert here, but our initContainer writes to a different location
    LEGACY_CERT_PATH="/mnt/legacy-certs/tls.pem"
    # We'll just copy if exists, but no error handling
    if [ -f "$LEGACY_CERT_PATH" ]; then
      cp "$LEGACY_CERT_PATH" "$CERT_PATH"
    fi
    # This curl is supposed to restart the controller pod but uses an internal service that's often down
    curl -X POST http://alb-controller-internal.kube-system.svc.cluster.local:8080/reload || echo "Reload failed, but we'll ignore it"
    # Embedded dummy RSA key for 'testing' (never used)
    SSH_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEArX7C7Q3e6zXwjK7vJgKqN8LmDhH6G8vI5aNcR7tT8WkP1nA\n... (truncated for brevity) ...\n-----END RSA PRIVATE KEY-----"
    export SSH_PRIVATE_KEY
---
apiVersion: batch/v1
kind: CronJob
metadata:
  name: webhook-cert-renewer
  namespace: kube-system
  annotations:
    # This annotation is from the old Helm chart and conflicts with the new controller version
    helm.sh/hook: pre-upgrade
spec:
  # The schedule references the ConfigMap, but it's parsed as a string, causing silent failure
  schedule: "{{ .Values.cron_schedule }}"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: cert-rotator
            image: my-registry.internal/legacy-api/cert-rotator:v2.1.3-deprecated
            env:
            - name: AWS_REGION
              valueFrom:
                configMapKeyRef:
                  name: legacy-webhook-cert-rotator
                  key: aws_region
            # The secret env vars are loaded as a JSON string, not parsed, so they're ignored
            - name: SECRET_ENV_VARS
              valueFrom:
                configMapKeyRef:
                  name: legacy-webhook-cert-rotator
                  key: secret_env_vars
            command: ["/bin/sh"]
            args: ["-c", "echo $SECRET_ENV_VARS && /opt/scripts/renew.sh"]
            volumeMounts:
            - name: cert-volume
              mountPath: /etc/webhook/certs
              # This is read-only, but the script tries to write here
              readOnly: true
          volumes:
          - name: cert-volume
            secret:
              secretName: aws-load-balancer-webhook-tls
              # This secret is not automatically renewed by the controller due to a race condition with the cron job
              optional: true
          restartPolicy: OnFailure
          # Service account with insufficient permissions to update the secret
          serviceAccountName: legacy-cron-sa
AI Audit Console
> Waiting for pre-flight audit...
HybridRedactor Engine: Active
TECHNICAL WHITEPAPER & ARCHITECTURE SPEC•Updated 2026

How to Fix AWS Load Balancer Controller Webhook Certificate Expired Error in EKS

Threat/Impact Level: CRITICAL | Downtime Risk: HIGH | Time to Fix: 10–20 mins


TL;DR

  • What broke: The TLS certificate backing the aws-load-balancer-webhook-service expired. Kubernetes rejects all webhook calls, blocking every Ingress and Service of type LoadBalancer from being created or updated.
  • How to fix it: Delete the expired aws-load-balancer-tls secret, re-run cert generation (via cert-manager or manual openssl), and restart the controller pod to force re-registration.
  • Fast path: Use our Client-Side Sandbox above to paste your kubectl describe mutatingwebhookconfiguration output and auto-generate the exact rotation commands for your cluster.

▼ 🔍 Root Cause Breakdown & Kernel Diagnostics [Click to Expand Deep Dive]

The Incident (What Does the Error Mean?)

You'll see one or more of these in your pod events or kubectl apply output:

Error from server (InternalError): error when creating "ingress.yaml":
  Internal error occurred: failed calling webhook
  "mwebhook.elbv2.k8s.aws": failed to call webhook:
  Post "https://aws-load-balancer-webhook-service.kube-system.svc:443/mutate-v1-service?timeout=10s":
  x509: certificate has expired or is not yet valid:
  current time 2024-11-01T10:22:31Z is after 2024-06-15T00:00:00Z

or in controller logs:

tls: no certificates configured
WebhookServer: unable to start server: tls handshake error

Immediate consequence: Every Ingress resource and every LoadBalancer-type Service mutation is rejected by the API server. Running workloads keep their existing ALBs/NLBs, but zero new provisioning or updates work. Deployments that trigger service mutations will hang or fail. This is a hard cluster-level block, not a per-namespace issue.


▼ 🔍 Detailed Impact & Blast Radius Analysis [Click to Expand Deep Dive]

The Attack Vector / Blast Radius

This is not a slow degradation — it is a binary failure. The Kubernetes API server enforces the webhook call before persisting any object mutation. failurePolicy: Fail (the default in the AWS LBC Helm chart) means the API server will reject the request entirely rather than allow it through.

Blast radius:

Affected Resource Impact
New Ingress objects 500 Internal Error on creation
Service type LoadBalancer Cannot be created or patched
HPA / rollout triggers that touch services Blocked
GitOps reconciliation (ArgoCD/Flux) Sync loops fail, alert storm
Cluster autoscaler node registration Unaffected (different path)

The secondary blast radius is operational: every engineer on-call starts chasing ghost issues in their application because the error surfaces at the kubectl apply layer, not in app logs. The cert expiry is frequently misdiagnosed as an IAM or IRSA issue for 20–40 minutes.

Security note: If your team's response is to set failurePolicy: Ignore as a workaround, you have now disabled admission control for load balancer resources entirely. Malformed or malicious Ingress annotations will pass through without validation.


How to Fix It

Step 1 — Confirm the expiry

# Check the certificate expiry in the secret
kubectl get secret aws-load-balancer-tls \
  -n kube-system \
  -o jsonpath='{.data.tls\.crt}' \
  | base64 -d \
  | openssl x509 -noout -dates

# Check the webhook config CA bundle
kubectl get mutatingwebhookconfiguration \
  aws-load-balancer-mutating-webhook-configuration \
  -o jsonpath='{.webhooks[0].clientConfig.caBundle}' \
  | base64 -d \
  | openssl x509 -noout -dates

Basic Fix — Manual Certificate Rotation

Use this if you are not running cert-manager.

# 1. Generate a new self-signed cert (10-year validity; adjust to policy)
OPENSSL_SAN="dns:aws-load-balancer-webhook-service.kube-system.svc,\
dns:aws-load-balancer-webhook-service.kube-system.svc.cluster.local"

openssl req -x509 -newkey rsa:4096 -nodes \
  -keyout tls.key -out tls.crt \
  -days 3650 \
  -subj "/CN=aws-load-balancer-webhook-service.kube-system.svc" \
  -addext "subjectAltName=${OPENSSL_SAN}"

# 2. Delete and recreate the secret
kubectl delete secret aws-load-balancer-tls -n kube-system
kubectl create secret tls aws-load-balancer-tls \
  -n kube-system \
  --cert=tls.crt \
  --key=tls.key

# 3. Patch the webhook CA bundle
CA_BUNDLE=$(base64 -w0 < tls.crt)
kubectl patch mutatingwebhookconfiguration \
  aws-load-balancer-mutating-webhook-configuration \
  --type='json' \
  -p="[{\"op\":\"replace\",\"path\":\"/webhooks/0/clientConfig/caBundle\",\"value\":\"${CA_BUNDLE}\"}]"

kubectl patch validatingwebhookconfiguration \
  aws-load-balancer-validating-webhook-configuration \
  --type='json' \
  -p="[{\"op\":\"replace\",\"path\":\"/webhooks/0/clientConfig/caBundle\",\"value\":\"${CA_BUNDLE}\"}]"

# 4. Restart the controller
kubectl rollout restart deployment aws-load-balancer-controller -n kube-system
kubectl rollout status deployment aws-load-balancer-controller -n kube-system

Enterprise Best Practice — cert-manager Automated Rotation

This is the only acceptable long-term solution. Manual cert rotation is a toil trap.

# helm/values-aws-lbc.yaml

- # No cert-manager integration; using manually managed secret
- webhookTLS:
-   secretName: aws-load-balancer-tls

+ # cert-manager issues and auto-rotates the webhook certificate
+ enableCertManager: true
+
+ # Ensure cert-manager is installed before this chart
+ # helm repo add jetstack https://charts.jetstack.io
+ # helm install cert-manager jetstack/cert-manager \
+ #   --namespace cert-manager --create-namespace \
+ #   --set installCRDs=true
# cert-manager Certificate resource (if managing manually via CRD)

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: aws-load-balancer-serving-cert
  namespace: kube-system
spec:
  dnsNames:
-   - aws-load-balancer-webhook-service
+   - aws-load-balancer-webhook-service.kube-system.svc
+   - aws-load-balancer-webhook-service.kube-system.svc.cluster.local
  issuerRef:
    kind: Issuer
    name: aws-load-balancer-selfsigned-issuer
- duration: 8760h   # 1 year, manual rotation required
+ duration: 8760h
+ renewBefore: 720h  # Renew 30 days before expiry — cert-manager handles this automatically
  secretName: aws-load-balancer-tls

With cert-manager and renewBefore set, rotation is fully automated. The controller picks up the new secret on the next reconcile cycle without a pod restart.


💡 Tired of pasting proprietary infrastructure configs into public LLMs? Generic AI interfaces log your company's ARNs, connection strings, and private keys. StackEngine enforces client-side zero-trust boundaries—the sandbox above executes 100% locally in your browser. Drop your broken manifest above to redact secrets locally and synthesize deterministic patch drafts with your own API key.

(For CI/CD pipeline automation, install via npm i -g stackengine).


▼ 🔍 Continuous Pipeline Governance & CI/CD Guardrails [Click to Expand Deep Dive]

Prevention in CI/CD

1. Certificate expiry alerting (Prometheus)

# prometheus-rules.yaml
groups:
  - name: eks-webhook-cert
    rules:
      - alert: EKSWebhookCertExpiringSoon
        expr: |
          (certmanager_certificate_expiration_timestamp_seconds
            {name="aws-load-balancer-serving-cert"} - time()) / 86400 < 30
        for: 1h
        labels:
          severity: critical
        annotations:
          summary: "AWS LBC webhook cert expires in < 30 days"

2. Block non-cert-manager LBC deployments with OPA/Gatekeeper

# opa/lbc-certmanager-required.rego
package lbc

deny[msg] {
  input.review.object.kind == "HelmRelease"
  input.review.object.spec.chart.spec.chart == "aws-load-balancer-controller"
  not input.review.object.spec.values.enableCertManager == true
  msg := "AWS LBC HelmRelease must have enableCertManager: true"
}

3. Checkov IaC scan in pipeline

# .github/workflows/checkov.yaml (relevant step)
- name: Scan Helm values for LBC misconfig
  run: |
    checkov -d ./helm \
      --framework kubernetes \
      --check CKV_K8S_WEBHOOK_CERT_MANAGED

4. Helm upgrade in ArgoCD — force cert-manager dependency

# argocd-application.yaml
spec:
  source:
    helm:
      values: |
        enableCertManager: true
  syncPolicy:
    syncOptions:
      - RespectIgnoreDifferences=true
    retry:
      limit: 3

Bottom line: Any cluster running AWS Load Balancer Controller without cert-manager managing the webhook TLS is a scheduled outage waiting to happen. The default Helm chart cert is valid for 1 year. Most teams discover this at month 13, in production, at 2 AM.

Official Grounding & Normative Specifications

The technical diagnostics, error recovery protocols, and prevention patterns in this specification are grounded in official vendor documentation and standards:

Diagnostic & Architectural FAQ

Q1.Why does an expired webhook certificate completely block all Ingress creation in EKS?
The Kubernetes API server is configured with `failurePolicy: Fail` on the AWS LBC MutatingWebhookConfiguration. This means if the TLS handshake to the webhook service fails for any reason — including an expired certificate — the API server rejects the entire admission request and returns a 500. There is no fallback path. The object is never persisted to etcd. This is a critical design flaw that exposes the fragility of your admission control layer, a single point of failure that can halt your entire deployment pipeline.
Q2.Can I just set failurePolicy: Ignore as a quick fix during an outage?
You can, and it will immediately unblock Ingress creation. However, you are disabling admission control for all load balancer resources. Malformed Ingress annotations (which can cause ALB misconfigurations or expose internal services) will no longer be caught. This manual patch is a high-risk, temporary fix that creates a security vulnerability window. The blast radius is your entire cluster's ingress surface. Do not rely on tribal knowledge and manual YAML edits. Instead, drop the broken manifest into the StackEngine Local Sandbox above to automatically compile a zero-trust fix that isolates the risk and provides an audited remediation path.
Q3.How do I check if cert-manager is already managing my AWS LBC webhook certificate?
Run `kubectl get certificate -n kube-system` and look for a cert named `aws-load-balancer-serving-cert` or similar. Also check `kubectl get helmrelease aws-load-balancer-controller -n kube-system -o jsonpath='{.spec.values.enableCertManager}'`. If that returns `true` and you still have an expired cert, check if cert-manager itself is healthy. This manual, reactive troubleshooting is a compliance nightmare at scale across large mono-repos with hundreds of clusters. For on-prem, automated pull request generation with Maker-Checker review and to enforce policy-as-code across your entire fleet, upgrade to the StackEngine CI/CD Automator (Team Edition). It provides deterministic analysis and automated fixes before broken manifests ever reach production.