- Published on
EKS ALB Ingress WAF 403 차단 로그로 푸는 법
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 403을 돌려주면 보통 애플리케이션(인증/인가)부터 의심합니다. 그런데 EKS + AWS Load Balancer Controller(ALB Ingress) + AWS WAF 조합에서는, 애플리케이션까지 도달하기 전에 WAF에서 잘려서 403이 나는 경우가 흔합니다. 이때 핵심은 “어떤 룰이 왜 막았는지”를 WAF 로그에서 정확히 찾아 ALB/Ingress/앱 로그와 같은 요청으로 연결하는 것입니다.
이 글은 다음을 목표로 합니다.
- 403이 WAF 차단인지 ALB/Ingress/앱 문제인지 빠르게 구분
- WAF 로그에서 차단 룰(ruleId / terminatingRuleId) 을 특정
- 차단된 요청을 재현/검증하고, 최소 범위로 예외(allow) 또는 룰 튜닝 적용
- 운영에서 흔한 함정(헤더, 경로 정규화, 바디 검사, 레이트 리밋)을 함께 정리
관련해서 ALB Ingress 레벨의 인증(401) 이슈를 함께 점검해야 한다면: EKS ALB Ingress 401 반복 - OIDC·JWT·헤더 점검
1) 먼저: 이 403은 누가 만들었나? (WAF vs ALB vs 앱)
1-1. 클라이언트에서 403 응답 헤더로 1차 판별
WAF 차단일 때 응답은 ALB에서 내려오지만, 특징적인 헤더/바디 패턴이 보입니다.
Server: awselb/2.0가 보이고- 바디가 ALB 기본 에러 페이지 형태(HTML)이며
- 애플리케이션 로그에 해당 요청이 전혀 없다
이면 WAF/ALB 구간에서 잘렸을 확률이 큽니다.
다만 결정타는 WAF 로그입니다. “차단됐다”는 사실과 “어떤 룰이 막았다”는 증거는 WAF 로그에 있습니다.
1-2. ALB 액세스 로그로 ‘타겟까지 갔는지’ 확인
ALB 액세스 로그를 켜두면, 요청이 타겟 그룹까지 전달되었는지 간접적으로 볼 수 있습니다.
- WAF에서 차단되면 타겟 응답 코드 대신 ALB/WAF 단계에서
403이 찍히고, 타겟 관련 필드가 비정상/비어있을 수 있습니다. - 반대로 타겟까지 갔다면 target_status_code 등이 의미 있게 남습니다.
> 운영에서 로그가 누락/지연되어 원인 파악이 늦어지는 일이 많습니다. 수집 파이프라인 점검은 EKS에서 fluent-bit 로그 누락·지연 원인 9가지 도 같이 참고하세요.
2) WAF 로그를 “쓸모 있게” 켜는 방법 (필수)
WAF 로그는 CloudWatch Logs 또는 Kinesis Firehose(S3)로 보냅니다. 운영에서는 보통 S3 적재 + Athena가 가장 분석하기 편합니다.
2-1. WAF Logging Configuration 활성화
WAF Web ACL에 로깅을 켜고, 필요하면 필드 리덕션(redaction) 을 설정합니다(PII/토큰 보호).
httpRequest.headers중authorization,cookie등은 마스킹/제외 권장- 대신 디버깅에 필요한
host,user-agent,x-forwarded-for,x-amzn-trace-id등은 남깁니다
2-2. 룰을 바로 차단하지 말고 Count로 관찰하기
대규모 룰 변경은 장애를 부릅니다. 특히 AWS Managed Rules는 서비스 특성에 따라 오탐이 생길 수 있습니다.
- 신규 룰/룰 그룹은 먼저
Count액션으로 적용 - 로그에서 오탐 여부를 확인한 뒤
Block로 승격
3) WAF 403의 ‘진짜 원인’은 로그의 이 필드에 있다
WAF 로그에서 가장 중요한 필드는 아래입니다.
action:BLOCK/ALLOW/COUNTterminatingRuleId또는terminatingRuleTyperuleGroupList(Managed rule group이면 여기에서 매칭 룰 확인)httpRequest.clientIp,country,uri,args,headerslabels(AWS Managed Rules는 라벨로 분류 힌트를 줌)
3-1. CloudWatch Logs Insights 예시 쿼리
CloudWatch Logs로 보낸 경우, Insights로 “차단 Top 룰”부터 뽑습니다.
fields @timestamp, action, terminatingRuleId, httpRequest.uri, httpRequest.clientIp
| filter action = "BLOCK"
| stats count() as blocked by terminatingRuleId
| sort blocked desc
| limit 20
특정 URI만 막히는지 확인:
fields @timestamp, action, terminatingRuleId, httpRequest.uri
| filter action = "BLOCK" and httpRequest.uri like /\/api\//
| stats count() as blocked by httpRequest.uri, terminatingRuleId
| sort blocked desc
| limit 50
3-2. S3 + Athena로 장기 분석(운영 추천)
S3에 WAF 로그(JSON)를 쌓는다면 Athena 테이블을 만들어서 빠르게 집계할 수 있습니다.
SELECT
terminatingruleid,
COUNT(*) AS blocked
FROM waf_logs
WHERE action = 'BLOCK'
AND from_unixtime(timestamp/1000) > now() - interval '1' day
GROUP BY 1
ORDER BY blocked DESC
LIMIT 20;
> 포인트: “403이 났다”가 아니라 어떤 룰이 BLOCK 했는지(terminatingRuleId) 를 먼저 확정하세요. 그 다음에야 예외를 안전하게 설계할 수 있습니다.
4) ALB Ingress와 WAF 연결 구조를 이해해야 삽질이 줄어든다
EKS에서 ALB Ingress는 AWS Load Balancer Controller가 ALB와 리스너 룰을 만들고, WAF는 보통 ALB(또는 CloudFront) 에 Web ACL을 연결합니다.
- 클라이언트 → (CloudFront) → ALB(+WAF) → TargetGroup → Pod
- WAF가 막으면 Pod까지 요청이 안 감 → 앱 로그에 흔적 없음
또한 ALB Ingress는 경로 기반 라우팅을 하므로, WAF에서 보는 uri는 보통 클라이언트가 호출한 원본 경로입니다. 다만 앱이 내부적으로 리라이트를 한다면(다른 Ingress 컨트롤러처럼) 혼동이 생길 수 있는데, ALB Ingress는 NGINX처럼 강력한 리라이트를 지원하지 않으므로 대개 단순합니다.
5) 가장 흔한 WAF 403 패턴 6가지와 로그로 확인하는 법
5-1. AWSManagedRulesCommonRuleSet의 Body/QueryString 오탐
- 증상: 특정 POST 요청(JSON 바디)만 403
- 로그 힌트:
AWSManagedRulesCommonRuleSet하위 룰이 terminating
대응:
- 해당 엔드포인트에만 예외 적용(URI 스코프)
- 또는 바디 검사 크기/형식을 조정(가능하면 API 설계 변경)
5-2. SQLi/XSS 룰이 정상 파라미터를 공격으로 인식
- 증상: 검색 API에서
q=select ...같은 입력이 403 - 로그 힌트:
AWSManagedRulesSQLiRuleSet또는XSSRuleSet
대응:
- 해당 파라미터만 예외(예:
q파라미터) - 완전 disable은 지양
5-3. Bot Control / Anonymous IP / IP Reputation 차단
- 증상: 특정 ISP/국가/프록시에서만 403
- 로그 힌트:
clientIp,country,labels에 bot/anon 관련 라벨
대응:
- 사내 NAT/모니터링/서드파티 헬스체커 IP allowlist
- CloudFront를 쓴다면 원본 IP 전달 헤더 구성 점검
5-4. Rate-based rule(요청 폭주)로 403
- 증상: 트래픽 급증 시 특정 경로만 403이 튐
- 로그 힌트: rate 기반 룰이 terminating,
rateBasedRuleList
대응:
- 경로별 rate limit 분리
- 인증 사용자/토큰 기준으로 키를 잡을 수 있으면 더 안전
5-5. 헤더 크기/형식 문제(특히 Authorization, Cookie)
- 증상: 일부 사용자만 403, 대개 쿠키가 큰 브라우저 세션
- 로그 힌트: request headers에 비정상적으로 큰 값
대응:
- 세션 쿠키 정리, 토큰 길이 관리
- 불필요한 헤더 전달 최소화
5-6. 실제로는 WAF가 아니라 ALB Idle timeout/504와 혼동
403과 별개로, “차단”처럼 보이지만 사실은 타임아웃일 수 있습니다. 특히 스트리밍/롱폴링 API에서 자주 섞입니다.
- 504가 반복된다면: EKS ALB Ingress에서 504 Idle timeout만 반복될 때
6) 차단된 요청을 ‘같은 요청’으로 추적하는 실전 워크플로
6-1. 상관관계 ID를 하나로 통일
추천은 다음 중 하나를 표준으로 정하는 것입니다.
X-Request-Id를 클라이언트→서버까지 유지- 또는
traceparent(W3C Trace Context)
ALB는 기본적으로 애플리케이션에 요청 ID를 강제하지 않으므로, 앱/클라이언트에서 넣는 편이 낫습니다.
6-2. WAF 로그에서 특정 요청을 잡고, 같은 시간대 ALB 로그와 매칭
- WAF 로그에서
@timestamp,clientIp,uri,headers.user-agent확보 - ALB 액세스 로그에서 같은 조건으로 검색
- 앱 로그에는 없다면 WAF 차단 확정
6-3. 재현은 curl로 최소화
브라우저는 변수가 많습니다. 차단된 요청의 핵심 요소(메서드/경로/쿼리/바디/헤더)를 줄여가며 재현합니다.
curl -i 'https://api.example.com/search?q=select+1'
curl -i -X POST 'https://api.example.com/v1/items' \
-H 'Content-Type: application/json' \
-H 'X-Request-Id: debug-20260223-001' \
--data '{"name":"test","memo":"<script>alert(1)</script>"}'
재현이 되면, WAF 로그에서 X-Request-Id로 검색할 수 있게 헤더 로깅을 남겨두는 게 가장 빠릅니다(민감정보는 제외).
7) “예외 처리”를 가장 안전하게 하는 방법(룰 끄지 말기)
오탐이 확인되면 흔히 룰 그룹 전체를 Disable 하려 합니다. 하지만 운영 보안 관점에서 최악의 선택입니다. 대신 아래 우선순위를 권장합니다.
- 정확히 막는 terminatingRuleId 확인
- 해당 룰에 대해
- 특정 URI만 예외
- 특정 쿼리 파라미터만 예외
- 특정 헤더/바디 필드만 예외
- 예외 적용 후에도
Count로 잠시 관찰 → 문제 없으면 반영
7-1. Terraform 예시: Managed Rule Group + Rule Action Override
아래는 개념 예시입니다(리소스/필드명은 환경에 맞게 조정).
resource "aws_wafv2_web_acl" "api" {
name = "api-acl"
scope = "REGIONAL"
default_action { allow {} }
rule {
name = "AWSManagedRulesCommonRuleSet"
priority = 10
override_action { none {} }
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
# 특정 서브룰만 COUNT로 내려 오탐 관찰/완화
rule_action_override {
name = "SizeRestrictions_BODY"
action_to_use { count {} }
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "common"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "api-acl"
sampled_requests_enabled = true
}
}
7-2. URI 스코프 예외(정밀)
특정 엔드포인트에서만 오탐이 난다면, 스코프다운(scope-down)으로 범위를 줄입니다.
statement {
managed_rule_group_statement {
name = "AWSManagedRulesSQLiRuleSet"
vendor_name = "AWS"
scope_down_statement {
not_statement {
statement {
byte_match_statement {
search_string = "/health"
field_to_match { uri_path {} }
positional_constraint = "STARTS_WITH"
text_transformation {
priority = 0
type = "NONE"
}
}
}
}
}
}
}
핵심은 “/health는 검사에서 제외”처럼 정말 필요한 범위만 줄이는 것입니다.
8) 운영 체크리스트: 403 차단을 ‘로그로’ 끝내기
- WAF 로깅이 켜져 있고(CloudWatch/S3) 조회가 가능하다
-
action=BLOCK이벤트에서terminatingRuleId를 확인했다 - 같은 요청을 ALB 액세스 로그에서 찾았고, 타겟까지 안 갔음을 확인했다
- 재현 curl을 만들었고, 변경 후 재검증할 수 있다
- 예외는 룰 전체 disable이 아니라 URI/파라미터/서브룰 단위로 최소화했다
- 변경은
Count → Block단계로 롤아웃했다 - 로그 파이프라인 누락/지연이 없도록 모니터링한다
9) 마무리: 결론은 “403을 보지 말고 terminatingRuleId를 보라”
EKS에서 ALB Ingress 뒤 WAF 403은 증상이 아니라 결과입니다. 원인은 거의 항상 WAF 로그의 terminatingRuleId와 rule group 내부 매칭에 있습니다. 이 값을 기준으로 재현 요청을 만들고, 예외를 최소 범위로 적용하면 보안 수준을 유지하면서도 장애를 빠르게 끝낼 수 있습니다.
다음 단계로는,
- 차단 Top 룰을 대시보드화(CloudWatch Metrics/Athena 집계)
- 신규 릴리즈 시
Count기반 사전 관찰 - 요청 ID/트레이싱 표준화
를 추천합니다. 이렇게 해두면 “갑자기 403” 같은 운영 이슈가 로그만으로 정리되는 구간까지 갈 수 있습니다.