들어가며
어느 금요일 저녁, 보안팀에서 슬랙 메시지가 하나 날아왔습니다. "외부 스캐너가 우리 서비스 도메인에서 SQL Injection 시도를 1만 건 넘게 탐지했는데, 차단되고 있는 게 맞나요?" 저는 곧바로 클러스터의 Ingress 설정을 열어봤습니다. 그리고 깨달았습니다. 우리는 TLS 종단과 라우팅은 하고 있었지만, 애플리케이션 계층(L7)에서 들어오는 페이로드를 검사하는 장치가 전혀 없었습니다.
당시 우리 아키텍처는 흔한 모습이었습니다. 클라우드 로드밸런서 뒤에 ingress-nginx가 있고, 그 뒤로 수십 개의 마이크로서비스가 붙어 있었습니다. 각 서비스 팀은 자기 코드의 입력 검증에 신경 쓰긴 했지만, 전사적으로 일관된 공격 차단 정책은 없었습니다. 한 팀이 막아도 다른 팀이 뚫리면 결국 같은 클러스터, 같은 신뢰 경계 안에서 사고가 납니다.
이 글에서는 Ingress 단에 WAF(Web Application Firewall)를 적용하는 이야기를 다룹니다. 레거시 표준이었던 ModSecurity와 그 후속 엔진인 Coraza의 차이, ingress-nginx 통합 설정, OWASP Core Rule Set(CRS)을 적용하고 오탐을 다듬는 실전 과정, 탐지 모드와 차단 모드의 운영 전략, 로깅과 알림, 성능 영향, 그리고 전용 WAF와의 비교까지 운영자 관점에서 풀어보겠습니다.
한 가지 미리 짚고 넘어갈 점이 있습니다. 2026년 현재 쿠버네티스 Ingress API는 사실상 동결(frozen)되어 더 이상 새 기능이 추가되지 않으며, 후속 표준은 Gateway API입니다. 또한 ingress-nginx는 유지보수 모드에 들어갔고, 그 안에 내장되어 있던 ModSecurity 지원은 사실상 폐기(deprecated) 수순을 밟고 있습니다. 따라서 신규 구축이라면 Coraza 기반 경로를, 그리고 중장기적으로는 Gateway API로의 이전을 함께 고려해야 합니다. 이 부분은 본문에서 계속 짚겠습니다.
왜 Ingress 단에 WAF가 필요한가
WAF는 OSI 7계층, 즉 HTTP 요청의 내용 자체를 검사하는 방화벽입니다. 일반적인 네트워크 방화벽이나 시큐리티 그룹은 IP와 포트(L3/L4)를 보지만, WAF는 요청 본문, 헤더, 쿼리 파라미터, 쿠키 같은 애플리케이션 데이터를 들여다보고 공격 패턴을 찾아냅니다.
쿠버네티스 환경에서 Ingress는 외부 트래픽이 클러스터로 들어오는 단일 관문입니다. 바로 이 지점에 WAF를 배치하면 다음과 같은 이점이 있습니다.
- **단일 지점 적용**: 수십 개의 백엔드 서비스가 각자 보안 로직을 구현하지 않아도, 관문에서 일관된 정책을 강제할 수 있습니다.
- **가상 패치(virtual patching)**: 애플리케이션 코드를 당장 고치지 못하는 상황에서, 알려진 취약점(예: Log4Shell, 특정 CVE)을 노린 페이로드를 WAF 규칙으로 임시 차단할 수 있습니다.
- **방어 심화(defense in depth)**: 애플리케이션의 입력 검증이 완벽하다는 보장은 없습니다. WAF는 그 위에 한 겹을 더 두르는 안전망입니다.
- **가시성**: 어떤 공격이 얼마나 들어오는지 로그로 남길 수 있어, 보안 모니터링과 사고 대응의 기반이 됩니다.
다만 WAF는 만능이 아닙니다. 비즈니스 로직 취약점(예: 권한 우회, IDOR)은 패턴 매칭으로 잡기 어렵고, 암호화된 트래픽은 TLS 종단 이후에만 검사할 수 있으며, 잘못 튜닝하면 정상 트래픽을 막아 장애를 일으킬 수도 있습니다. 이 한계는 글 후반부에서 다시 다룹니다.
아래는 WAF가 어디에 위치하는지 보여주는 트래픽 흐름입니다.
인터넷
|
v
+----------------------+
| Cloud LoadBalancer | (L3/L4: IP, 포트)
+----------------------+
|
v
+----------------------+
| ingress-nginx Pod |
| +----------------+ |
| | WAF 엔진 | | (L7: HTTP 페이로드 검사)
| | ModSec/Coraza | | <-- OWASP CRS 규칙 평가
| +----------------+ |
+----------------------+
|
(검사 통과 시)
v
+----------------------+
| Backend Service |
| (마이크로서비스) |
+----------------------+
ModSecurity와 Coraza: 무엇이 어떻게 다른가
WAF "엔진"과 "규칙"은 분리해서 이해해야 합니다. 엔진은 규칙을 평가하는 실행기이고, 규칙(예: OWASP CRS)은 무엇을 막을지 정의하는 정책입니다. 같은 CRS 규칙을 ModSecurity로도, Coraza로도 돌릴 수 있습니다.
ModSecurity (레거시)
ModSecurity는 2002년부터 시작된 오픈소스 WAF 엔진으로, 오랫동안 사실상의 표준이었습니다. 처음에는 Apache 모듈로 출발해 이후 Nginx, IIS를 지원하는 libmodsecurity(v3)로 발전했습니다. ingress-nginx에는 이 ModSecurity가 컴파일되어 들어가 있었고, 어노테이션 한 줄로 켤 수 있었습니다.
문제는 ModSecurity의 핵심 스폰서였던 Trustwave가 2024년 7월 1일부로 ModSecurity 프로젝트 지원을 종료한다고 발표하면서 시작됐습니다. 이후 프로젝트는 OWASP로 이관되었지만, ingress-nginx 입장에서는 C로 작성된 무거운 의존성을 계속 안고 가기 부담스러운 상황이 되었습니다. 실제로 ingress-nginx 메인테이너들은 ModSecurity 통합을 deprecated로 표시하고, 향후 제거 방향을 명시했습니다.
Coraza (후속)
Coraza는 OWASP가 주관하는 Go로 작성된 WAF 엔진입니다. 핵심 특징은 ModSecurity의 규칙 문법(SecRule)과 거의 호환된다는 점입니다. 즉, 기존에 쓰던 SecLang 규칙과 OWASP CRS를 거의 그대로 가져다 쓸 수 있습니다. Go로 작성되어 메모리 안전성이 높고, Envoy나 Caddy, 그리고 coraza-proxy-wasm 형태로 다양한 프록시에 임베드할 수 있도록 설계되었습니다.
2026년 현재 클라우드 네이티브 환경에서 신규로 L7 WAF를 도입한다면, Coraza 계열이 사실상 표준 방향입니다. 특히 Envoy 기반 데이터 플레인(Gateway API 구현체 다수가 Envoy를 사용)에서는 coraza-proxy-wasm 필터로 WAF를 끼워 넣는 패턴이 자리잡고 있습니다.
비교표
| 항목 | ModSecurity (libmodsecurity v3) | Coraza |
| --- | --- | --- |
| 작성 언어 | C / C++ | Go |
| 출시 시점 | 2002년 (v3는 2017년) | 2021년 |
| 규칙 문법 | SecLang (SecRule) | SecLang 호환 |
| OWASP CRS | 지원 | 지원 |
| ingress-nginx 통합 | 내장(폐기 예정) | 외부/WASM 또는 Gateway API 경로 |
| Envoy 임베드 | 제한적 | proxy-wasm 으로 네이티브 지원 |
| 유지보수 상태 | OWASP 이관, 레거시 | 활발 |
| 메모리 안전성 | 수동 관리 | GC 기반 |
핵심만 기억하면 됩니다. 규칙(CRS)은 동일하게 재사용되고, 엔진만 ModSecurity에서 Coraza로 갈아탄다는 그림입니다.
ingress-nginx에 ModSecurity 적용하기 (기존 환경)
이미 ingress-nginx와 내장 ModSecurity를 쓰고 있는 기존 환경을 먼저 다룹니다. 신규 구축이라면 다음 섹션의 Coraza 경로를 보시기 바랍니다.
ingress-nginx에서 ModSecurity는 ConfigMap의 전역 설정과 Ingress 어노테이션의 두 층위로 제어됩니다. 먼저 컨트롤러 전역 ConfigMap입니다.
apiVersion: v1
kind: ConfigMap
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
data:
enable-modsecurity: "true"
enable-owasp-modsecurity-crs: "true"
전역 ModSecurity 설정 스니펫
modsecurity-snippet: |
SecRuleEngine DetectionOnly
SecRequestBodyAccess On
SecAuditEngine RelevantOnly
SecAuditLogParts ABIJDEFHZ
SecAuditLog /var/log/modsec/audit.log
SecAuditLogType Serial
위 설정에서 `enable-modsecurity`는 엔진 자체를 켜고, `enable-owasp-modsecurity-crs`는 컨트롤러 이미지에 내장된 OWASP CRS를 로드합니다. `SecRuleEngine DetectionOnly`는 처음 도입할 때 매우 중요합니다. 실제로 차단하지 않고 탐지만 하면서 로그를 쌓는 모드입니다.
다음은 특정 Ingress에만 ModSecurity를 적용하거나 세부 설정을 덮어쓰는 어노테이션 예시입니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop-ingress
namespace: shop
annotations:
nginx.ingress.kubernetes.io/enable-modsecurity: "true"
nginx.ingress.kubernetes.io/enable-owasp-core-rules: "true"
nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRuleEngine On
SecRequestBodyLimit 13107200
SecRule REQUEST_HEADERS:User-Agent "@contains badscanner" "id:1001,phase:1,deny,status:403,log,msg:'Blocked scanner'"
spec:
ingressClassName: nginx
rules:
- host: shop.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: shop-svc
port:
number: 80
여기서 어노테이션 단의 `SecRuleEngine On`은 해당 Ingress에 한해 차단 모드를 켜는 효과가 있습니다. 즉, 전역은 DetectionOnly로 두고 검증이 끝난 서비스만 하나씩 차단 모드로 승격시키는 점진적 롤아웃이 가능합니다.
ingress-nginx에 Coraza 적용하기 (권장 경로)
내장 ModSecurity가 폐기 수순인 만큼, Coraza를 외부에서 끼워 넣는 방식이 현대적 대안입니다. 대표적으로 두 가지 패턴이 있습니다.
첫째, ingress-nginx의 `server-snippet` 또는 `http-snippet`을 통해 OpenResty 기반 Coraza Lua 연동(coraza-nginx)을 호출하는 방식. 둘째, 데이터 플레인 자체를 Envoy 기반(예: Gateway API 구현체)으로 바꾸고 coraza-proxy-wasm 필터를 붙이는 방식입니다.
아래는 Envoy 기반 데이터 플레인에서 coraza-proxy-wasm 필터를 구성하는 개념적 예시입니다. EnvoyFilter나 게이트웨이 구현체별 확장 메커니즘을 통해 적용합니다.
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: coraza-waf
namespace: istio-system
spec:
configPatches:
- applyTo: HTTP_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: envoy.filters.network.http_connection_manager
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.wasm
typed_config:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
value:
config:
name: coraza-filter
vm_config:
runtime: envoy.wasm.runtime.v8
code:
local:
filename: /etc/envoy/coraza-proxy-wasm.wasm
configuration:
"@type": type.googleapis.com/google.protobuf.StringValue
value: |
{
"directives_map": {
"default": [
"SecRuleEngine On",
"Include @owasp_crs/REQUEST-942-APPLICATION-ATTACK-SQLI.conf"
]
},
"rules": ["default"]
}
Coraza의 디렉티브 문법이 ModSecurity와 동일하다는 점에 주목하세요. `SecRuleEngine On`, `Include` 지시문 모두 그대로입니다. 마이그레이션 시 규칙 자체는 거의 손대지 않아도 됩니다.
Coraza를 독립 데몬으로 두고 nginx의 `auth_request`나 sidecar로 검사를 위임하는 패턴도 있지만, 운영 복잡도와 지연(latency)을 고려하면 데이터 플레인에 직접 임베드하는 WASM 방식이 더 깔끔합니다.
OWASP Core Rule Set 적용과 튜닝
엔진을 켰다면, 이제 어떤 규칙으로 검사할지가 핵심입니다. OWASP CRS는 가장 널리 쓰이는 범용 룰셋으로, SQL Injection, XSS, RCE, LFI/RFI, 프로토콜 위반 등 OWASP Top 10에 대응하는 규칙들을 카테고리별로 묶어 제공합니다.
이상 점수 모드(Anomaly Scoring)
CRS의 동작 방식을 이해하는 것이 튜닝의 출발점입니다. CRS는 기본적으로 "이상 점수(anomaly scoring)" 모델로 동작합니다. 각 규칙이 매칭될 때마다 요청에 점수를 더하고, 그 누적 점수가 임계값(threshold)을 넘으면 차단합니다. 규칙 하나가 걸렸다고 바로 차단하는 게 아니라, 의심스러운 신호가 누적되어야 차단되는 구조입니다.
규칙에는 심각도에 따른 점수가 부여됩니다.
| 심각도 | 기본 점수 |
| --- | --- |
| CRITICAL | 5 |
| ERROR | 4 |
| WARNING | 3 |
| NOTICE | 2 |
기본 인바운드 임계값은 보통 5입니다. 즉 CRITICAL 규칙 하나만 걸려도 임계값에 도달합니다. 이 임계값과 점수 부여 방식은 CRS의 설정 파일에서 조정합니다.
crs-setup.conf 발췌 (개념 예시)
SecAction "id:900110,phase:1,pass,nolog,\
setvar:tx.inbound_anomaly_score_threshold=5,\
setvar:tx.outbound_anomaly_score_threshold=4"
파라노이아 레벨(Paranoia Level) 설정
SecAction "id:900000,phase:1,pass,nolog,\
setvar:tx.paranoia_level=1"
파라노이아 레벨(Paranoia Level)
CRS에는 PL1부터 PL4까지의 "파라노이아 레벨"이 있습니다. 레벨이 높을수록 더 공격적이고 엄격한 규칙이 활성화되어 탐지율은 올라가지만, 그만큼 오탐(false positive)도 급증합니다.
| 레벨 | 성격 | 권장 용도 |
| --- | --- | --- |
| PL1 | 기본, 오탐 거의 없음 | 대부분의 프로덕션 시작점 |
| PL2 | 다소 엄격 | 보안 민감 서비스, 튜닝 후 |
| PL3 | 매우 엄격 | 금융 등 고보안, 충분한 튜닝 필수 |
| PL4 | 극도로 엄격 | 특수 환경, 오탐 매우 많음 |
실전 권장은 명확합니다. **PL1에서 시작해 DetectionOnly로 충분히 로그를 쌓고, 오탐을 정리한 뒤에 차단 모드로 올리고, 필요하면 PL을 천천히 높입니다.** 처음부터 PL3에 차단 모드를 켜면 십중팔구 정상 서비스가 막혀 장애가 납니다.
오탐(False Positive) 관리
CRS를 도입하면 반드시 오탐을 만납니다. 예를 들어 JSON 본문에 SQL과 유사한 문자열이 들어가거나, 리치 텍스트 에디터가 HTML 태그를 그대로 전송하면 XSS 규칙에 걸립니다. 정상 트래픽을 막지 않으려면 예외 규칙을 작성해야 합니다.
오탐을 다루는 표준적인 방법은 특정 규칙 ID를 특정 경로나 파라미터에 대해서만 끄는 것입니다.
특정 규칙을 특정 경로에서만 비활성화
SecRule REQUEST_URI "@beginsWith /api/articles" \
"id:10001,phase:1,pass,nolog,\
ctl:ruleRemoveById=942100"
특정 파라미터에 대해서만 특정 규칙 비활성화
SecRule REQUEST_URI "@beginsWith /admin/editor" \
"id:10002,phase:1,pass,nolog,\
ctl:ruleRemoveTargetById=941100;ARGS:content"
여기서 `ruleRemoveById`는 규칙 전체를 끄고, `ruleRemoveTargetById`는 특정 입력 필드(여기서는 content 파라미터)에 대해서만 규칙 검사를 제외합니다. 가능하면 전체를 끄기보다 대상만 좁혀 제외하는 것이 안전합니다. 무작정 규칙 ID를 다 꺼버리면 WAF가 사실상 무력화됩니다.
오탐을 찾는 흐름은 다음과 같습니다.
[1] DetectionOnly 모드로 운영
|
v
[2] audit 로그에서 차단되었을 요청 수집
|
v
[3] 정상 트래픽인데 매칭된 규칙 ID 식별
|
v
[4] 경로/파라미터 단위 예외 규칙 작성
|
v
[5] 오탐이 충분히 줄면 차단(On) 모드로 승격
탐지 모드 vs 차단 모드
WAF 운영에서 가장 중요한 의사결정 중 하나가 "지금 차단할 것인가, 보기만 할 것인가"입니다.
| 모드 | SecRuleEngine 값 | 동작 | 용도 |
| --- | --- | --- | --- |
| 비활성 | Off | 규칙 평가 안 함 | 긴급 비활성화 |
| 탐지 | DetectionOnly | 매칭 로그만, 통과시킴 | 도입 초기, 튜닝 단계 |
| 차단 | On | 임계값 초과 시 차단 | 충분히 검증된 프로덕션 |
원칙은 항상 DetectionOnly로 시작하는 것입니다. 신규 서비스에 곧바로 차단 모드를 켜면, 그 서비스의 정상 트래픽 패턴을 모르는 상태이므로 오탐으로 인한 장애 위험이 큽니다. 최소 수일에서 수주간 탐지 모드로 운영하며 로그를 분석하고, 오탐 예외를 정리한 뒤에 서비스 단위로 하나씩 차단 모드로 올리는 것이 안전합니다.
긴급 상황에서 WAF가 정상 트래픽을 막고 있다고 판단되면, 즉시 DetectionOnly로 되돌릴 수 있도록 롤백 절차를 미리 문서화해 두는 것이 좋습니다. 어노테이션 한 줄 변경으로 즉시 적용되므로 빠른 완화가 가능합니다.
로깅과 알림
WAF의 가치는 절반이 가시성에서 나옵니다. 무엇을 막았고 무엇을 봤는지 기록이 없으면, 차단 정책을 다듬을 수도, 사고를 추적할 수도 없습니다.
ModSecurity/Coraza는 audit 로그를 남깁니다. 위에서 본 `SecAuditEngine RelevantOnly`는 이상 점수가 임계값을 넘었거나 명시적으로 로깅 대상이 된 요청만 기록한다는 의미입니다. 모든 요청을 다 기록하면(`On`) 로그량이 폭증하므로, 운영에서는 RelevantOnly가 일반적입니다.
audit 로그는 JSON 형태로 남기면 후속 수집과 분석이 편합니다.
SecAuditLogFormat JSON
SecAuditLogType Serial
SecAuditLog /var/log/modsec/audit.log
이렇게 남긴 로그는 사이드카나 노드 단위 로그 수집기(Fluent Bit, Vector 등)로 긁어 중앙 로그 시스템(Loki, Elasticsearch, OpenSearch)으로 보냅니다. 거기서 매칭된 규칙 ID, 클라이언트 IP, 공격 카테고리별로 대시보드를 만들고, 특정 임계치(예: 분당 차단 건수 급증)에 알림을 거는 것이 일반적인 구성입니다.
알림 설계에서 주의할 점은, 인터넷에 노출된 서비스는 상시로 스캐너 트래픽을 받기 때문에 "차단 1건당 알림"은 금세 소음이 됩니다. 추세(평소 대비 급증)나 특정 출발지의 집중 공격, 차단 모드 전환 직후의 오탐 급증 같은 의미 있는 신호에 알림을 걸어야 합니다.
성능 영향
WAF는 공짜가 아닙니다. 모든 요청의 본문과 헤더를 정규식 기반 규칙 수백 개로 평가하므로, CPU와 지연에 비용이 발생합니다.
- **CPU**: CRS 전체 규칙을 PL1로 돌릴 때, ingress 컨트롤러의 CPU 사용량이 눈에 띄게 증가합니다. 규칙 평가는 CPU 바운드 작업입니다.
- **지연(latency)**: 요청당 추가되는 지연은 보통 수 밀리초 수준이지만, 큰 요청 본문을 검사할 때나 파라노이아 레벨이 높을 때는 더 커질 수 있습니다.
- **요청 본문 버퍼링**: 본문 검사를 위해 WAF가 요청 본문을 버퍼링하므로, 대용량 업로드 경로에서는 `SecRequestBodyLimit`과 본문 검사 정책을 신중히 설정해야 합니다.
실무 권장 사항은 다음과 같습니다. 첫째, WAF를 켠 뒤 반드시 부하 테스트로 지연과 처리량 변화를 측정합니다. 둘째, ingress 컨트롤러의 리소스 요청/제한과 레플리카 수를 재산정합니다. 셋째, 대용량 파일 업로드처럼 본문 검사가 불필요하거나 위험한 경로는 별도 처리(예: 해당 경로만 본문 검사 제외)를 검토합니다.
대용량 업로드 경로의 본문 검사 제한 예시 (어노테이션 스니펫)
nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRule REQUEST_URI "@beginsWith /upload" \
"id:20001,phase:1,pass,nolog,ctl:requestBodyAccess=Off"
전용 WAF와의 비교
Ingress 내장 WAF가 모든 상황의 정답은 아닙니다. 클라우드 관리형 WAF(AWS WAF, Cloud Armor, Azure Front Door WAF 등)나 어플라이언스/CDN형 WAF(Cloudflare, Akamai 등)와 비교해 장단점을 따져야 합니다.
| 항목 | Ingress 내장(ModSec/Coraza) | 클라우드 관리형 WAF | CDN/어플라이언스형 |
| --- | --- | --- | --- |
| 위치 | 클러스터 내부 | 클라우드 엣지/LB 앞단 | 글로벌 엣지 |
| 비용 모델 | 컴퓨트 자원만 | 요청/규칙 단위 과금 | 트래픽/플랜 과금 |
| 운영 주체 | 직접 운영 | 관리형 | 관리형 |
| DDoS 방어 | 없음(별도 필요) | 일부 포함 | 강력 |
| 규칙 커스터마이징 | 매우 유연(SecLang) | 콘솔/제한적 | 제공자별 상이 |
| 지연 위치 | 백엔드 근처 | 엣지 | 엣지 |
| 클러스터 내부 트래픽 | 보호 가능 | 외부 트래픽만 | 외부 트래픽만 |
큰 그림은 이렇습니다. 대규모 DDoS와 글로벌 엣지 방어가 중요하면 CDN/클라우드 WAF가 강점이 있고, 세밀한 규칙 제어와 클러스터 내부 east-west 트래픽까지 검사하고 싶거나 벤더 종속을 피하고 싶으면 Ingress 내장(Coraza)이 유리합니다. 실무에서는 둘을 겹쳐 쓰는 다층 구성이 흔합니다. 엣지에서 대량 공격과 봇을 거르고, Ingress에서 정교한 애플리케이션 규칙을 적용하는 식입니다.
한계와 주의점
WAF를 도입할 때 반드시 인지해야 할 한계들입니다.
- **비즈니스 로직 공격은 못 막는다**: 권한 우회, IDOR, 가격 조작 같은 로직 취약점은 정상 형식의 요청이라 패턴 매칭으로 잡히지 않습니다. WAF는 인증/인가 설계를 대체하지 못합니다.
- **우회 기법이 존재한다**: 인코딩, 청크 분할, 파라미터 오염 등으로 시그니처를 우회하려는 시도가 끊임없이 있습니다. CRS는 정규화(normalization) 변환으로 대응하지만 완벽하지 않습니다.
- **TLS 종단 의존**: 암호화된 트래픽은 복호화 이후에만 검사 가능합니다. 종단 위치 설계가 중요합니다.
- **오탐으로 인한 장애**: 잘못된 튜닝은 그 자체로 서비스 장애입니다. 차단 모드 전환은 신중해야 합니다.
- **성능 부담**: 위에서 다룬 대로 CPU와 지연 비용이 있습니다.
- **유지보수**: 규칙(CRS)도 엔진도 지속적으로 업데이트해야 합니다. 방치된 WAF는 새로운 공격을 못 막습니다.
그리고 거듭 강조할 운영 컨텍스트가 있습니다. 2026년 현재 ingress-nginx는 유지보수 모드이고 내장 ModSecurity는 폐기 방향입니다. 새로 구축한다면 Coraza 기반 경로와 Gateway API로의 전환을 함께 설계해야 장기적으로 안전합니다. Gateway API 구현체 다수가 Envoy를 데이터 플레인으로 쓰므로, coraza-proxy-wasm을 붙이는 그림과 자연스럽게 맞물립니다.
도입 체크리스트
실무에서 Ingress WAF를 도입할 때 점검할 항목들을 정리합니다.
- [ ] 엔진 선택: 신규는 Coraza, 기존 ModSecurity는 마이그레이션 로드맵 수립
- [ ] DetectionOnly 모드로 먼저 배포했는가
- [ ] OWASP CRS는 PL1에서 시작했는가
- [ ] audit 로그를 중앙 로그 시스템으로 수집하고 있는가
- [ ] 오탐을 식별하는 분석 흐름이 있는가 (경로/파라미터 단위 예외)
- [ ] 차단 모드 승격은 서비스 단위 점진 롤아웃인가
- [ ] 긴급 롤백 절차(즉시 DetectionOnly 복귀)가 문서화되어 있는가
- [ ] 부하 테스트로 성능 영향을 측정했는가
- [ ] ingress 컨트롤러 리소스/레플리카를 재산정했는가
- [ ] 대용량 업로드 등 본문 검사 예외 경로를 정의했는가
- [ ] 알림은 단건이 아닌 의미 있는 추세에 걸려 있는가
- [ ] CRS와 엔진의 업데이트 주기를 정했는가
- [ ] 전용 WAF(엣지)와의 역할 분담을 정의했는가
- [ ] Gateway API 전환 시 WAF 적용 방식을 검토했는가
마치며
처음 슬랙 메시지를 받았던 그 금요일 저녁으로 돌아가 보면, 사실 우리에게 필요했던 건 단 하나의 마법 같은 차단 장치가 아니라 절차였습니다. Ingress 단에 WAF를 두는 것은 분명 강력한 방어선이지만, 그것을 켜는 순간 끝나는 일이 아니라 거기서부터 시작되는 운영입니다.
핵심을 정리하면 이렇습니다. 엔진은 ModSecurity에서 Coraza로 넘어가는 흐름이고, 규칙은 OWASP CRS를 PL1부터 신중히 적용하며, 운영은 DetectionOnly로 시작해 오탐을 정리한 뒤 점진적으로 차단으로 승격하는 것입니다. 그리고 WAF는 방어 심화의 한 겹일 뿐, 애플리케이션 보안과 인증/인가 설계를 대체하지 않습니다.
마지막으로, ingress-nginx가 유지보수 모드에 들어가고 Gateway API가 표준이 되어가는 2026년의 흐름 속에서, WAF 전략도 그 전환과 함께 설계하시길 권합니다. 엔진과 데이터 플레인은 바뀌어도, "탐지부터 시작해 신중히 차단으로"라는 운영 원칙은 변하지 않습니다.
참고 자료
- Kubernetes Ingress 공식 문서: https://kubernetes.io/docs/concepts/services-networking/ingress/
- ingress-nginx 공식 문서: https://kubernetes.github.io/ingress-nginx/
- ingress-nginx ModSecurity 사용자 가이드: https://kubernetes.github.io/ingress-nginx/user-guide/third-party-addons/modsecurity/
- Coraza WAF 공식 사이트: https://coraza.io/
- Coraza GitHub 저장소: https://github.com/corazawaf/coraza
- coraza-proxy-wasm GitHub: https://github.com/corazawaf/coraza-proxy-wasm
- OWASP ModSecurity Core Rule Set 프로젝트: https://owasp.org/www-project-modsecurity-core-rule-set/
- OWASP CRS 공식 사이트: https://coreruleset.org/
- OWASP CRS 문서(파라노이아 레벨): https://coreruleset.org/docs/concepts/paranoia_levels/
- Gateway API 공식 문서: https://gateway-api.sigs.k8s.io/
- Traefik 공식 문서: https://doc.traefik.io/traefik/
현재 단락 (1/259)
어느 금요일 저녁, 보안팀에서 슬랙 메시지가 하나 날아왔습니다. "외부 스캐너가 우리 서비스 도메인에서 SQL Injection 시도를 1만 건 넘게 탐지했는데, 차단되고 있는 게...