- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며
- snippet 어노테이션 비활성화와 RBAC
- ModSecurity / Coraza WAF 연동
- 클라이언트 인증서 mTLS
- external auth와 oauth2-proxy
- TLS 정책 — 프로토콜과 cipher
- 헤더 보안
- rate limit으로 남용 방어
- CVE 대응 운영
- IP 접근 제어와 실제 클라이언트 IP
- 감사 로깅과 탐지
- 멀티테넌트 격리
- 운영 체크리스트
- Secret 관리와 인증서 자동화
- 공급망과 이미지 보안
- 네트워크 정책으로 격리
- Gateway API와의 관계
- 침해 의심 시 대응 절차
- 정기 보안 점검 루틴
- 마치며
- 참고 자료
들어가며
인그레스 컨트롤러는 클러스터로 들어오는 모든 외부 트래픽의 관문입니다. 관문이 뚫리면 그 뒤의 모든 서비스가 위험해지므로, ingress-nginx의 보안 설정은 단순한 부가 기능이 아니라 클러스터 보안의 최전선입니다.
특히 2025년에 보고된 일련의 CVE(이른바 IngressNightmare 계열)는 어노테이션과 admission webhook 처리 경로를 악용하면 컨트롤러 권한으로 코드 실행이 가능함을 보여줬습니다. 컨트롤러는 보통 클러스터 전체의 Secret(TLS 인증서 포함)을 읽을 수 있으므로, 컨트롤러 장악은 곧 클러스터 장악으로 이어질 수 있습니다.
이 글은 ingress-nginx를 안전하게 운영하기 위한 하드닝 기법을 한 번에 정리합니다. 위험한 어노테이션 차단부터 WAF 연동, mTLS, external auth, TLS 정책, 멀티테넌트 격리, CVE 대응, 그리고 운영 체크리스트까지 다룹니다.
snippet 어노테이션 비활성화와 RBAC
가장 먼저 잠가야 할 것은 snippet 어노테이션입니다. configuration-snippet, server-snippet 같은 어노테이션은 임의의 nginx 설정을 컨트롤러의 nginx.conf에 주입할 수 있어, 사실상 컨트롤러 권한으로 임의 코드를 실행할 수 있는 통로입니다.
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"
최신 버전에서는 allow-snippet-annotations의 기본값이 false입니다. annotations-risk-level은 위험도가 높은 어노테이션의 허용 범위를 제한하며, Critical로 두면 가장 위험한 부류를 막습니다.
그다음 핵심은 RBAC입니다. 인그레스를 만들 수 있는 권한은 곧 라우팅을 바꿀 수 있는 권한이므로, 신뢰할 수 있는 주체로만 제한해야 합니다.
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"]
네임스페이스 단위로 인그레스 권한을 부여하고, snippet이 필요한 특수 케이스는 별도의 검토 절차와 정책 엔진(예: OPA Gatekeeper, Kyverno)으로 통제하세요.
ModSecurity / Coraza WAF 연동
웹 애플리케이션 방화벽(WAF)은 SQL 인젝션, XSS, 경로 탐색 같은 공격을 요청 단계에서 차단합니다. ingress-nginx는 ModSecurity를 통한 WAF를 지원합니다. 참고로 ModSecurity는 점진적으로 후속 엔진인 Coraza로 이전되는 흐름이지만, 운영 개념은 유사합니다.
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
OWASP Core Rule Set(CRS)을 함께 활성화하면 일반적인 공격 패턴을 광범위하게 막을 수 있습니다. 도입 초기에는 차단(On) 대신 탐지 전용(DetectionOnly)으로 운영하며 오탐을 걸러내는 것이 안전합니다. 오탐이 잡히면 특정 룰을 예외 처리한 뒤 점진적으로 차단 모드로 전환하세요.
클라이언트 인증서 mTLS
내부 서비스나 파트너 API처럼 호출 주체를 강하게 제한해야 하는 경로에는 클라이언트 인증서 기반 상호 TLS(mTLS)를 적용합니다. 신뢰할 CA 인증서를 Secret으로 등록하고 auth-tls 어노테이션으로 검증을 켭니다.
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
auth-tls-verify-client을 on으로 두면 유효한 클라이언트 인증서가 없는 요청은 거부됩니다. pass-certificate-to-upstream을 켜면 백엔드가 인증서 정보를 헤더로 받아 추가 인가에 활용할 수 있습니다.
external auth와 oauth2-proxy
인증을 컨트롤러 밖의 인증 서비스에 위임하고 싶다면 external auth를 사용합니다. auth-url로 지정한 엔드포인트가 매 요청을 검증하고, 그 결과에 따라 통과/차단합니다. oauth2-proxy와 결합하면 OIDC 기반 SSO를 인그레스 레벨에서 강제할 수 있습니다.
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"
여기서 사용된 변수 표기(host, request_uri)는 nginx 변수이며, 반드시 코드 펜스 안에서만 써야 MDX 빌드가 깨지지 않습니다. external auth는 모든 요청에 추가 왕복을 만드므로, auth-cache 관련 옵션으로 캐싱을 고려하고, 인증 서비스의 가용성이 곧 전체 가용성이 된다는 점을 염두에 두세요.
TLS 정책 — 프로토콜과 cipher
TLS 설정은 컴플라이언스와 직결됩니다. 오래된 프로토콜(TLS 1.0/1.1)과 약한 cipher는 전역 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"
HSTS를 켜면 브라우저가 이후 접속을 항상 HTTPS로 강제합니다. 인증서 자체는 cert-manager로 자동 발급/갱신하여 만료 사고를 예방하세요.
헤더 보안
보안 관련 응답 헤더는 전역으로 추가할 수 있습니다. snippet을 끈 환경에서는 custom-http-errors나 별도의 헤더 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"
서버 버전 노출(Server, X-Powered-By)을 숨기면 공격자에게 주는 정보를 줄일 수 있습니다. clickjacking 방지를 위한 X-Frame-Options, MIME 스니핑 차단을 위한 X-Content-Type-Options는 기본으로 두는 것이 좋습니다.
rate limit으로 남용 방어
인증되지 않은 엔드포인트나 로그인 경로는 무차별 대입과 DoS의 표적입니다. 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"
내부망 대역은 whitelist로 제외하고, 외부 노출 경로에는 보수적인 한계를 두세요. 더 정교한 전역 제한이나 봇 차단이 필요하면 WAF 또는 별도 API Gateway와 조합합니다.
CVE 대응 운영
IngressNightmare 계열 CVE의 교훈은 명확합니다. 컨트롤러를 최신 패치 버전으로 유지하고, 공격 표면을 줄이며, 권한을 최소화하라는 것입니다.
[ CVE 대응 운영 흐름 ]
1. 버전 인벤토리 ── 현재 컨트롤러 버전/이미지 다이제스트 파악
2. 패치 모니터링 ── 보안 권고/릴리스 노트 구독
3. 빠른 업그레이드 ── 카나리/스테이징 후 신속 롤아웃
4. 표면 축소 ────── snippet 비활성, admission webhook 노출 제한
5. 권한 최소화 ──── 컨트롤러 RBAC/네트워크 정책으로 Secret 접근 통제
6. 탐지 ────────── 비정상 설정 변경/요청 모니터링
특히 admission webhook은 외부에서 직접 도달 가능하지 않도록 네트워크 정책으로 보호하고, 컨트롤러가 읽을 수 있는 Secret 범위를 가능한 한 좁혀야 합니다.
IP 접근 제어와 실제 클라이언트 IP
관리자 콘솔이나 사내 도구처럼 접근 주체가 제한적인 경로에는 소스 IP 기반 접근 제어가 효과적입니다.
metadata:
annotations:
nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8,192.168.0.0/16"
단, IP 제어가 제대로 동작하려면 컨트롤러가 실제 클라이언트 IP를 알아야 합니다. 클라우드 LB나 CDN 뒤에 있으면, 컨트롤러가 보는 소스 IP는 프록시의 IP일 수 있습니다. 이때는 신뢰할 프록시 대역을 등록하고 forwarded 헤더를 신뢰하도록 설정합니다.
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"
forwarded 헤더 신뢰는 양날의 검입니다. 신뢰 대역을 좁게 잡지 않으면 공격자가 X-Forwarded-For를 위조해 IP 화이트리스트를 우회할 수 있으므로, proxy-real-ip-cidr을 반드시 실제 프록시 대역으로 한정하세요. AWS NLB처럼 proxy protocol을 쓰는 경우에는 use-proxy-protocol을 켜서 원본 IP를 보존합니다.
감사 로깅과 탐지
보안은 차단뿐 아니라 탐지도 포함합니다. 접근 로그를 구조화하면 이상 징후를 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"}'
여기 사용된 변수 표기는 모두 nginx 변수이며 코드 펜스 안에서만 안전합니다. JSON 구조화 로그는 비정상적인 상태코드 급증, 특정 경로 스캐닝, 비정상 User-Agent 패턴을 탐지하는 데 유용합니다. WAF의 탐지 로그와 함께 모니터링하면 공격 시도를 조기에 포착할 수 있습니다.
멀티테넌트 격리
여러 팀이 한 클러스터를 공유한다면 인그레스 격리가 중요합니다. 핵심 도구는 IngressClass 분리, 네임스페이스 RBAC, 그리고 정책 엔진입니다.
| 격리 수단 | 목적 | 메커니즘 |
|---|---|---|
| IngressClass 분리 | 팀별 컨트롤러 분리 | ingressClassName 별 컨트롤러 |
| 네임스페이스 RBAC | 인그레스 생성 권한 제한 | Role/RoleBinding |
| 정책 엔진 | 위험 어노테이션 차단 | Kyverno/OPA Gatekeeper |
| 호스트 검증 | 호스트 도용 방지 | admission 정책으로 host 제한 |
정책 엔진으로 위험한 어노테이션(snippet, 임의 호스트 등)을 막는 예시는 다음과 같습니다.
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"
운영 체크리스트
마지막으로 하드닝 점검 항목을 체크리스트로 정리합니다.
- allow-snippet-annotations가 false이고 annotations-risk-level이 적절히 설정됨
- 인그레스 생성 권한이 RBAC로 최소화됨
- 정책 엔진으로 위험 어노테이션 차단 룰 적용됨
- WAF(ModSecurity/Coraza + CRS)가 적어도 탐지 모드로 동작 중
- 민감 경로에 mTLS 또는 external auth 적용됨
- TLS는 1.2/1.3만 허용, 약한 cipher 차단, HSTS 활성
- 보안 헤더 추가, 서버 버전 헤더 숨김
- 외부 노출 경로에 rate limit 적용됨
- 컨트롤러가 최신 패치 버전, admission webhook 네트워크 보호
- 멀티테넌트라면 IngressClass/네임스페이스 격리 적용됨
Secret 관리와 인증서 자동화
컨트롤러는 TLS 인증서를 Secret으로 읽습니다. 인증서가 만료되면 전체 서비스가 TLS 오류로 중단되므로, 자동화가 곧 안정성입니다. cert-manager로 발급과 갱신을 자동화하고, 만료 임박을 알람으로 감지하세요.
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
renewBefore를 충분히 두면 갱신 실패 시에도 재시도 여유가 생깁니다. 또한 컨트롤러가 읽을 수 있는 Secret을 최소화해야 합니다. 컨트롤러가 모든 네임스페이스의 Secret을 읽을 수 있다면, 컨트롤러 장악 시 클러스터 전체 인증서가 노출됩니다. 가능하면 인증서를 특정 네임스페이스로 한정하고, RBAC로 컨트롤러의 Secret 접근 범위를 좁히세요.
공급망과 이미지 보안
컨트롤러 자체의 무결성도 보안의 일부입니다. 신뢰할 수 있는 레지스트리에서 서명된 이미지를 사용하고, 다이제스트로 고정해 변조를 방지합니다.
# 이미지 다이제스트 고정 예시
image: registry.k8s.io/ingress-nginx/controller@sha256:abcdef...
이미지를 태그(latest 등)가 아니라 다이제스트로 고정하면, 동일 태그가 다른 이미지로 바뀌는 위험을 막을 수 있습니다. 정책 엔진으로 서명 검증(예: cosign/sigstore)을 강제하고, 컨트롤러 파드에 securityContext로 권한을 최소화하세요.
# 컨트롤러 파드 securityContext 예시
securityContext:
runAsNonRoot: true
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"]
readOnlyRootFilesystem와 capability drop은 컨테이너가 침해되더라도 공격자가 할 수 있는 일을 크게 줄입니다. NET_BIND_SERVICE만 추가하는 이유는 80/443 같은 특권 포트 바인딩 때문입니다.
네트워크 정책으로 격리
마지막 방어선은 네트워크 정책입니다. 컨트롤러로 들어오고 나가는 트래픽을 명시적으로 제한해, 침해 시 횡적 이동(lateral movement)을 막습니다.
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
admission webhook 포트는 외부에서 직접 도달할 수 없도록 ingress 규칙에서 제외하고, egress는 백엔드 서비스가 있는 네임스페이스로만 허용하는 식으로 좁히세요. 네트워크 정책은 CNI(Calico, Cilium 등)가 지원해야 적용됩니다.
Gateway API와의 관계
보안 관점에서도 2026년의 방향성은 중요합니다. ingress-nginx의 어노테이션/snippet 모델은 보안 표면이 넓고 표준화가 어렵다는 구조적 한계가 있습니다. Ingress API가 동결되고 ingress-nginx가 유지보수 모드로 전환된 지금, 후계 표준인 Gateway API는 보안 정책을 표준 CRD와 정책 리소스(예: BackendTLSPolicy, ReferenceGrant 등)로 표현해 역할 분리와 권한 통제를 더 명확하게 합니다. 기존 인그레스의 보안은 위 체크리스트로 유지하되, 신규 설계는 Gateway API의 정책 모델 위에서 하드닝하는 것이 장기적으로 안전합니다.
침해 의심 시 대응 절차
하드닝을 잘 해두어도 침해 가능성은 0이 아닙니다. 컨트롤러가 침해된 정황(비정상 설정 변경, 알 수 없는 snippet, 예기치 않은 외부 통신)이 보일 때를 대비한 절차를 미리 정해두세요.
[ 인그레스 컨트롤러 침해 대응 흐름 ]
1. 격리 ─────── 네트워크 정책으로 의심 파드의 egress 차단
2. 증거 보존 ── 파드 로그/nginx.conf/이벤트 스냅샷 확보
3. 영향 평가 ── 노출 가능한 Secret/인증서 범위 산정
4. 자격 회전 ── 컨트롤러가 접근하던 인증서/토큰 즉시 회전
5. 재배포 ───── 검증된 다이제스트 이미지로 컨트롤러 재생성
6. 사후 분석 ── 진입 경로 파악, 정책/RBAC 보강
가장 중요한 단계는 자격 회전입니다. 컨트롤러가 읽을 수 있던 TLS 인증서와 토큰은 이미 노출됐다고 가정하고 모두 회전해야 합니다. 그래서 앞서 "컨트롤러가 읽는 Secret 범위 최소화"가 단순한 모범 사례가 아니라 사고 시 피해 범위를 직접 줄이는 통제임을 알 수 있습니다.
정기 보안 점검 루틴
보안은 한 번 설정하고 끝나는 것이 아니라 지속적인 운영입니다. 다음을 정기 루틴으로 만드세요.
| 주기 | 점검 항목 |
|---|---|
| 상시 | 비정상 상태코드/요청 패턴 알람 |
| 주간 | 신규 CVE/보안 권고 확인 |
| 월간 | 컨트롤러 버전 최신화 검토 |
| 분기 | RBAC/네트워크 정책/인증서 범위 감사 |
이런 루틴을 자동화하면(예: 버전 드리프트 알람, 정책 위반 리포트) 사람의 주의력에 의존하지 않고도 보안 수준을 유지할 수 있습니다. 보안은 결국 일회성 설정이 아니라 운영 문화의 문제입니다.
마치며
ingress-nginx 보안 하드닝의 핵심은 공격 표면을 줄이고 권한을 최소화하는 것입니다. 위험한 snippet을 끄고 RBAC와 정책 엔진으로 통제하며, WAF와 mTLS, external auth로 방어 계층을 쌓고, TLS와 헤더 정책으로 기본기를 다지고, CVE에 신속히 대응하는 운영 루틴을 만드세요. 컨트롤러는 클러스터의 관문이며, 관문의 보안은 곧 전체의 보안입니다.
참고 자료
- ingress-nginx 보안 가이드: https://kubernetes.github.io/ingress-nginx/user-guide/security/
- ingress-nginx 어노테이션: 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 개념: https://kubernetes.io/docs/concepts/services-networking/ingress/