Skip to content

필사 모드: ingress-nginx Security Hardening — WAF, mTLS, Blocking Annotation Risks

English
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

Introduction

The ingress controller is the gateway for all external traffic entering the cluster. If the gateway falls, every service behind it is at risk, so the security configuration of ingress-nginx is not an add-on but the front line of cluster security.

In particular, a series of CVEs reported in 2025 (the so-called IngressNightmare family) showed that abusing annotations and the admission webhook handling path enables code execution with controller privileges. Since the controller can usually read cluster-wide Secrets (including TLS certificates), compromising the controller can lead straight to compromising the cluster.

This article gathers, in one place, the hardening techniques to run ingress-nginx safely. From blocking dangerous annotations to WAF integration, mTLS, external auth, TLS policy, multi-tenant isolation, CVE response, and an operations checklist.

Disabling Snippet Annotations and RBAC

The first thing to lock down is snippet annotations. Annotations like configuration-snippet and server-snippet can inject arbitrary nginx config into the controller's nginx.conf, effectively providing a channel to execute arbitrary code with controller privileges.

apiVersion: v1

kind: ConfigMap

metadata:

name: ingress-nginx-controller

namespace: ingress-nginx

data:

allow-snippet-annotations: "false"

annotations-risk-level: "Critical"

strict-validate-path-type: "true"

In recent versions the default of allow-snippet-annotations is false. annotations-risk-level constrains the allowed scope of high-risk annotations; setting it to Critical blocks the most dangerous class.

The next key is RBAC. The permission to create an ingress is the permission to change routing, so it must be restricted to trusted subjects.

apiVersion: rbac.authorization.k8s.io/v1

kind: Role

metadata:

name: ingress-editor

namespace: team-a

rules:

- apiGroups: ["networking.k8s.io"]

resources: ["ingresses"]

verbs: ["get", "list", "watch", "create", "update", "patch"]

Grant ingress permissions per namespace, and govern special cases that need snippets through a separate review process and a policy engine (e.g., OPA Gatekeeper, Kyverno).

ModSecurity / Coraza WAF Integration

A Web Application Firewall (WAF) blocks attacks such as SQL injection, XSS, and path traversal at the request stage. ingress-nginx supports a WAF via ModSecurity. Note that ModSecurity is gradually migrating to its successor engine, Coraza, but the operational concepts are similar.

apiVersion: v1

kind: ConfigMap

metadata:

name: ingress-nginx-controller

namespace: ingress-nginx

data:

enable-modsecurity: "true"

enable-owasp-modsecurity-crs: "true"

modsecurity-snippet: |

SecRuleEngine On

SecRequestBodyAccess On

Enabling the OWASP Core Rule Set (CRS) alongside it blocks common attack patterns broadly. Early in adoption it is safer to run in detection-only mode (DetectionOnly) instead of blocking (On) to filter out false positives. Once false positives are caught, add exceptions for specific rules and gradually switch to blocking mode.

Client-Certificate mTLS

For routes where the caller must be strongly restricted — internal services or partner APIs — apply client-certificate-based mutual TLS (mTLS). Register the trusted CA certificate as a Secret and enable verification with auth-tls annotations.

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

name: secure-api

annotations:

nginx.ingress.kubernetes.io/auth-tls-verify-client: "on"

nginx.ingress.kubernetes.io/auth-tls-secret: "default/client-ca"

nginx.ingress.kubernetes.io/auth-tls-verify-depth: "1"

nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "true"

spec:

ingressClassName: nginx

tls:

- hosts:

- api.example.com

secretName: api-tls

rules:

- host: api.example.com

http:

paths:

- path: /

pathType: Prefix

backend:

service:

name: api-svc

port:

number: 443

Setting auth-tls-verify-client to on rejects requests without a valid client certificate. Turning on pass-certificate-to-upstream lets the backend receive certificate info via headers to use for additional authorization.

external auth and oauth2-proxy

If you want to delegate authentication to an auth service outside the controller, use external auth. The endpoint specified by auth-url validates every request, and based on the result it allows or blocks. Combined with oauth2-proxy, you can enforce OIDC-based SSO at the ingress level.

metadata:

annotations:

nginx.ingress.kubernetes.io/auth-url: "https://oauth2-proxy.example.com/oauth2/auth"

nginx.ingress.kubernetes.io/auth-signin: "https://oauth2-proxy.example.com/oauth2/start?rd=https://$host$request_uri"

The variable notation used here (host, request_uri) are nginx variables and must only be used inside code fences so the MDX build does not break. external auth adds an extra round trip to every request, so consider caching via auth-cache options, and keep in mind that the availability of the auth service becomes the availability of the whole.

TLS Policy — Protocols and Ciphers

TLS configuration is tied directly to compliance. Explicitly block old protocols (TLS 1.0/1.1) and weak ciphers in the global ConfigMap.

data:

ssl-protocols: "TLSv1.2 TLSv1.3"

ssl-ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384"

ssl-prefer-server-ciphers: "true"

hsts: "true"

hsts-max-age: "31536000"

hsts-include-subdomains: "true"

Enabling HSTS forces the browser to always connect over HTTPS thereafter. Automatically issue and renew the certificates themselves with cert-manager to prevent expiry incidents.

Header Security

Security-related response headers can be added globally. In environments where snippets are disabled, use custom-http-errors or a separate header ConfigMap.

data:

hide-headers: "Server,X-Powered-By"

add-headers: "ingress-nginx/custom-headers"

apiVersion: v1

kind: ConfigMap

metadata:

name: custom-headers

namespace: ingress-nginx

data:

X-Frame-Options: "SAMEORIGIN"

X-Content-Type-Options: "nosniff"

Referrer-Policy: "strict-origin-when-cross-origin"

Hiding server-version exposure (Server, X-Powered-By) reduces the information given to attackers. Keep X-Frame-Options to prevent clickjacking and X-Content-Type-Options to block MIME sniffing on by default.

Defending Against Abuse with Rate Limit

Unauthenticated endpoints and login routes are targets for brute-force and DoS. Set a baseline defense with rate limit.

metadata:

annotations:

nginx.ingress.kubernetes.io/limit-rps: "10"

nginx.ingress.kubernetes.io/limit-connections: "5"

nginx.ingress.kubernetes.io/limit-whitelist: "10.0.0.0/8"

Exclude internal network ranges via whitelist and place conservative limits on externally exposed routes. If you need more sophisticated global limiting or bot blocking, combine with a WAF or a separate API Gateway.

CVE Response Operations

The lesson of the IngressNightmare family of CVEs is clear: keep the controller on the latest patched version, reduce the attack surface, and minimize privileges.

[ CVE response operations flow ]

1. version inventory ── know current controller version/image digest

2. patch monitoring ── subscribe to security advisories/release notes

3. fast upgrade ────── roll out quickly after canary/staging

4. shrink surface ──── disable snippets, restrict admission webhook exposure

5. least privilege ── govern Secret access via controller RBAC/network policy

6. detection ──────── monitor abnormal config changes/requests

In particular, protect the admission webhook with network policy so it is not directly reachable from outside, and narrow the scope of Secrets the controller can read as much as possible.

IP Access Control and the Real Client IP

For routes with a limited set of callers — admin consoles or internal tools — source-IP-based access control is effective.

metadata:

annotations:

nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8,192.168.0.0/16"

But for IP control to work properly, the controller must know the real client IP. Behind a cloud LB or CDN, the source IP the controller sees may be the proxy's IP. In that case, register the trusted proxy ranges and configure the controller to trust forwarded headers.

apiVersion: v1

kind: ConfigMap

metadata:

name: ingress-nginx-controller

namespace: ingress-nginx

data:

use-forwarded-headers: "true"

proxy-real-ip-cidr: "10.0.0.0/8"

enable-real-ip: "true"

Trusting forwarded headers is a double-edged sword. If you do not scope the trusted ranges narrowly, an attacker can forge X-Forwarded-For to bypass the IP whitelist, so always limit proxy-real-ip-cidr to your actual proxy ranges. When using proxy protocol (as with AWS NLB), enable use-proxy-protocol to preserve the original IP.

Audit Logging and Detection

Security includes detection, not just blocking. Structuring access logs lets you analyze anomalies in a SIEM.

data:

log-format-escape-json: "true"

log-format-upstream: '{"time":"$time_iso8601","remote_addr":"$remote_addr","status":"$status","request":"$request","upstream_status":"$upstream_status"}'

The variable notation used here are all nginx variables and are safe only inside code fences. JSON structured logs are useful for detecting abnormal status-code spikes, scanning of specific paths, and abnormal User-Agent patterns. Monitored together with WAF detection logs, they let you catch attack attempts early.

Multi-Tenant Isolation

If multiple teams share one cluster, ingress isolation matters. The key tools are IngressClass separation, namespace RBAC, and a policy engine.

| Isolation means | Purpose | Mechanism |

| --- | --- | --- |

| IngressClass separation | Separate per-team controllers | Controller per ingressClassName |

| Namespace RBAC | Restrict ingress-create permission | Role/RoleBinding |

| Policy engine | Block risky annotations | Kyverno/OPA Gatekeeper |

| Host validation | Prevent host hijacking | Restrict host via admission policy |

An example of blocking risky annotations (snippet, arbitrary hosts, etc.) with a policy engine follows.

apiVersion: kyverno.io/v1

kind: ClusterPolicy

metadata:

name: block-snippet-annotations

spec:

validationFailureAction: Enforce

rules:

- name: no-configuration-snippet

match:

any:

- resources:

kinds: ["Ingress"]

validate:

message: "configuration-snippet annotation is not allowed"

pattern:

metadata:

=(annotations):

X(nginx.ingress.kubernetes.io/configuration-snippet): "null"

Operations Checklist

Finally, here is a hardening checklist.

- allow-snippet-annotations is false and annotations-risk-level is set appropriately

- ingress-create permission is minimized via RBAC

- a policy engine enforces rules blocking risky annotations

- a WAF (ModSecurity/Coraza + CRS) runs at least in detection mode

- mTLS or external auth is applied on sensitive routes

- TLS allows only 1.2/1.3, weak ciphers blocked, HSTS enabled

- security headers added, server-version headers hidden

- rate limit applied on externally exposed routes

- controller on the latest patched version, admission webhook network-protected

- if multi-tenant, IngressClass/namespace isolation applied

Secret Management and Certificate Automation

The controller reads TLS certificates as Secrets. If a certificate expires, the whole service halts with TLS errors, so automation is stability. Automate issuance and renewal with cert-manager, and detect impending expiry via alerts.

apiVersion: cert-manager.io/v1

kind: Certificate

metadata:

name: api-tls

namespace: default

spec:

secretName: api-tls

duration: 2160h

renewBefore: 360h

issuerRef:

name: letsencrypt-prod

kind: ClusterIssuer

dnsNames:

- api.example.com

A sufficient renewBefore leaves room to retry even if a renewal fails. You should also minimize the Secrets the controller can read. If the controller can read Secrets in all namespaces, compromising it exposes cluster-wide certificates. Where possible, scope certificates to specific namespaces and narrow the controller's Secret access with RBAC.

Supply Chain and Image Security

The integrity of the controller itself is part of security. Use signed images from a trusted registry and pin by digest to prevent tampering.

example of pinning the image digest

image: registry.k8s.io/ingress-nginx/controller@sha256:abcdef...

Pinning the image by digest rather than a tag (such as latest) prevents the risk of the same tag changing to a different image. Enforce signature verification (e.g., cosign/sigstore) with a policy engine, and minimize privileges on the controller pod via securityContext.

example controller pod securityContext

securityContext:

runAsNonRoot: true

readOnlyRootFilesystem: true

allowPrivilegeEscalation: false

capabilities:

drop: ["ALL"]

add: ["NET_BIND_SERVICE"]

readOnlyRootFilesystem and capability drop greatly reduce what an attacker can do even if the container is breached. Only NET_BIND_SERVICE is added because of binding privileged ports like 80/443.

Isolation with Network Policy

The last line of defense is network policy. Explicitly restrict traffic into and out of the controller to block lateral movement upon a breach.

apiVersion: networking.k8s.io/v1

kind: NetworkPolicy

metadata:

name: ingress-nginx-restrict

namespace: ingress-nginx

spec:

podSelector:

matchLabels:

app.kubernetes.io/name: ingress-nginx

policyTypes:

- Ingress

- Egress

ingress:

- from:

- ipBlock:

cidr: 0.0.0.0/0

ports:

- protocol: TCP

port: 443

egress:

- to:

- namespaceSelector: {}

ports:

- protocol: TCP

port: 8080

Exclude the admission webhook port from the ingress rules so it is not directly reachable from outside, and narrow egress to allow only the namespaces where backend services live. Network policy applies only if your CNI (Calico, Cilium, etc.) supports it.

Relationship with Gateway API

From a security angle too, the 2026 direction matters. The annotation/snippet model of ingress-nginx has structural limits — a wide security surface and difficult standardization. Now that the Ingress API is frozen and ingress-nginx is in maintenance mode, the successor standard Gateway API expresses security policy via standard CRDs and policy resources (e.g., BackendTLSPolicy, ReferenceGrant), making role separation and access governance clearer. Maintain the security of existing ingresses with the checklist above, but for new designs, hardening on top of Gateway API's policy model is safer long term.

Response Procedure When a Breach Is Suspected

Even with good hardening, the probability of a breach is not zero. Define a procedure in advance for when you see signs the controller is compromised (abnormal config changes, unknown snippets, unexpected outbound traffic).

[ ingress controller breach response flow ]

1. isolate ────── block the suspect pod's egress with network policy

2. preserve evidence ── capture pod logs/nginx.conf/event snapshots

3. assess impact ── estimate the scope of exposable Secrets/certificates

4. rotate credentials ── immediately rotate certificates/tokens the controller accessed

5. redeploy ───── recreate the controller from a verified digest image

6. post-analysis ── identify the entry path, strengthen policy/RBAC

The most important step is credential rotation. Assume the TLS certificates and tokens the controller could read are already exposed and rotate them all. This is why "minimizing the Secrets the controller reads" earlier is not just a best practice but a control that directly reduces blast radius during an incident.

A Regular Security Review Routine

Security is not a set-and-forget configuration but ongoing operations. Make the following a regular routine.

| Cadence | Check item |

| --- | --- |

| Always | Alerts on abnormal status codes/request patterns |

| Weekly | Review new CVEs/security advisories |

| Monthly | Review controller version updates |

| Quarterly | Audit RBAC/network policy/certificate scope |

Automating such a routine (e.g., version-drift alerts, policy-violation reports) lets you maintain the security level without relying on human attention. Security is ultimately a matter of operational culture, not a one-time setting.

Conclusion

The essence of ingress-nginx security hardening is to reduce the attack surface and minimize privileges. Disable dangerous snippets and govern them with RBAC and a policy engine, stack defensive layers with a WAF, mTLS, and external auth, build the fundamentals with TLS and header policy, and create an operations routine that responds to CVEs quickly. The controller is the gateway to the cluster, and the gateway's security is the security of the whole.

References

- ingress-nginx security guide: https://kubernetes.github.io/ingress-nginx/user-guide/security/

- ingress-nginx annotations: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/

- ingress-nginx ModSecurity: https://kubernetes.github.io/ingress-nginx/user-guide/third-party-addons/modsecurity/

- Kubernetes RBAC: https://kubernetes.io/docs/reference/access-authn-authz/rbac/

- OWASP Core Rule Set: https://owasp.org/www-project-modsecurity-core-rule-set/

- Kyverno: https://kyverno.io/docs/

- cert-manager: https://cert-manager.io/docs/

- Gateway API: https://gateway-api.sigs.k8s.io/

- oauth2-proxy: https://oauth2-proxy.github.io/oauth2-proxy/

- Kubernetes Ingress concept: https://kubernetes.io/docs/concepts/services-networking/ingress/

현재 단락 (1/251)

The ingress controller is the gateway for all external traffic entering the cluster. If the gateway ...

작성 글자: 0원문 글자: 15,237작성 단락: 0/251