Published on

EKS ALB Ingress 403, WAF 아닌 원인 7가지

Authors

서버가 403을 뱉으면 많은 팀이 먼저 WAF를 의심합니다. 그런데 EKS에서 AWS Load Balancer Controller(ALB Ingress) 를 쓰는 환경에서는 WAF가 없어도 403이 충분히 발생합니다. 더 까다로운 점은 403이 ALB 자체가 낸 것인지, Ingress 액션(인증/고정 응답)이 낸 것인지, 백엔드 앱이 낸 것인지가 섞여 보인다는 겁니다.

이 글은 “WAF가 아닌데 403”을 전제로, 원인 7가지를 우선순위와 함께 정리하고, 각각을 어떤 로그/명령으로 확인할지와 어떻게 고칠지까지 연결합니다.

> 참고: 401/502와 증상이 엮여 보일 때가 많습니다. 인증 이슈는 EKS ALB Ingress 401 반복 - OIDC·JWT·헤더 점검, 백엔드 연결/헬스체크는 EKS에서 ALB Ingress 502 Bad Gateway 원인 9가지도 같이 보면 진단 속도가 빨라집니다.

403의 “발생 지점”부터 분리하기

403을 고치기 전에, 누가 403을 만들었는지부터 분리해야 합니다.

1) ALB Access Log로 403의 주체 확인

ALB 액세스 로그를 켜면 elb_status_code, target_status_code로 분리가 됩니다.

  • elb_status_code=403 이고 target_status_code가 비어 있거나 -에 가깝다 → ALB(또는 ALB 인증/룰)이 403 생성
  • elb_status_code=403 이고 target_status_code=403백엔드가 403 생성 (Ingress/ALB는 전달만)

액세스 로그가 없다면 먼저 활성화하세요.

aws elbv2 modify-load-balancer-attributes \
  --load-balancer-arn <ALB_ARN> \
  --attributes Key=access_logs.s3.enabled,Value=true \
               Key=access_logs.s3.bucket,Value=<S3_BUCKET> \
               Key=access_logs.s3.prefix,Value=alb-access

2) Ingress 이벤트와 Controller 로그 확인

Ingress 설정이 의도대로 반영됐는지, 룰/리스너가 실패했는지 확인합니다.

kubectl describe ingress <ingress-name> -n <ns>
kubectl -n kube-system logs deploy/aws-load-balancer-controller --tail=200

여기서 Failed build model / listener rule / auth 관련 메시지가 나오면 403의 단서가 됩니다.


원인 1) ALB Listener Rule의 “고정 응답(fixed-response) 403”이 걸려 있음

의외로 가장 흔한 케이스입니다. 팀이 유지보수 모드/차단용으로 fixed-response 403 액션을 걸어놓고 잊는 경우가 많습니다.

증상

  • 특정 Host/Path만 403
  • elb_status_code=403, target_status_code=- (대개)

확인

  • AWS 콘솔에서 ALB → Listener → Rules 확인
  • 또는 CLI로 룰을 덤프
aws elbv2 describe-rules --listener-arn <LISTENER_ARN> \
  --query 'Rules[*].{Priority:Priority,Conditions:Conditions,Actions:Actions}'

해결

  • 의도치 않은 fixed-response 룰 제거/우선순위 조정
  • Ingress에서 액션 어노테이션을 사용 중이면(예: alb.ingress.kubernetes.io/actions.*) 해당 액션 정의를 점검

원인 2) ALB 인증(OIDC/Cognito) 설정이 “403”을 내는 케이스

ALB는 리스너 액션으로 OIDC/Cognito 인증을 수행할 수 있고, 설정/쿠키/리다이렉트 URI가 꼬이면 401이 아니라 403으로 보이는 경우도 있습니다(특히 토큰 교환 실패, 세션 쿠키 문제, 잘못된 scope 등).

증상

  • 로그인/콜백 경로에서만 403
  • 브라우저에서는 리다이렉트가 반복되거나 특정 조건에서만 실패

확인

  • Ingress 어노테이션에 alb.ingress.kubernetes.io/auth-type 존재 여부
kubectl get ingress <ingress-name> -n <ns> -o yaml | yq '.metadata.annotations'
  • ALB 규칙의 authenticate-oidc / authenticate-cognito 액션 확인

해결

  • 콜백 URL, issuer, client id/secret, scope, session cookie 설정 재점검
  • 헤더 전달/프록시 환경에서 인증 헤더가 누락되는지도 확인

> 이 파트는 케이스가 많아서 별도 체크리스트를 권합니다: EKS ALB Ingress 401 반복 - OIDC·JWT·헤더 점검


원인 3) Ingress 그룹(group.name) 충돌로 “남의 룰”이 섞여 들어옴

AWS Load Balancer Controller는 alb.ingress.kubernetes.io/group.name으로 여러 Ingress를 하나의 ALB로 묶을 수 있습니다. 문제는 같은 그룹에 다른 팀/다른 서비스 Ingress가 섞이면 우선순위/조건이 꼬여서 전혀 예상 못한 403 룰이 적용될 수 있다는 점입니다.

증상

  • 배포 후 갑자기 403 (내 Ingress는 변경 없는데)
  • 특정 호스트/패스가 다른 서비스로 라우팅되거나 403으로 고정됨

확인

  • 같은 그룹을 쓰는 Ingress 목록 찾기
kubectl get ingress -A -o json \
| jq -r '.items[]
  | select(.metadata.annotations["alb.ingress.kubernetes.io/group.name"] != null)
  | [.metadata.namespace,.metadata.name,.metadata.annotations["alb.ingress.kubernetes.io/group.name"]]
  | @tsv'

해결

  • 그룹을 서비스/도메인 단위로 분리
  • 우선순위(conditions) 충돌이 나지 않게 host/path를 명확히

원인 4) Host 헤더 불일치(도메인/리다이렉트/프록시)로 기본 액션이 403

ALB Ingress는 대개 Host 기반 라우팅을 합니다. 그런데 실제 요청의 Host가 Ingress가 기대하는 값과 다르면, 해당 룰이 매칭되지 않아 기본(Default) 액션으로 떨어집니다. 기본 액션이 고정 응답이거나(팀이 막아둠), 다른 서비스 인증 룰로 이어지면 403이 됩니다.

흔한 트리거

  • CloudFront/Cloudflare/Nginx 같은 앞단 프록시가 Host를 바꿈
  • www/apex 도메인 정규화가 안 되어 있음
  • 내부 테스트에서 ALB DNS로 직접 호출하면서 Host를 안 맞춤

확인

  • curl로 Host를 강제해서 비교
# ALB DNS로 직접 호출하되 Host를 서비스 도메인으로 맞춘다
curl -i https://<alb-dns-name>/health \
  -H 'Host: api.example.com'

# Host를 일부러 틀려본다
curl -i https://<alb-dns-name>/health \
  -H 'Host: wrong.example.com'

해결

  • Ingress에 필요한 host를 모두 선언
  • 앞단 프록시에서 Host/X-Forwarded-Host 정책 정리

원인 5) 백엔드 앱이 403을 반환(네트워크가 아니라 “인증/인가/Origin 정책”)

ALB가 아니라 애플리케이션이 403을 내는 케이스도 많습니다. 특히 아래는 쿠버네티스/ALB 환경에서 자주 터집니다.

  • 허용 IP/프록시 신뢰 설정 미흡 (X-Forwarded-For 처리)
  • CORS/CSRF 정책이 Origin/Host 기준으로 차단
  • 프레임워크 보안 미들웨어가 X-Forwarded-Proto를 보고 https 강제/차단

확인

  • ALB 로그에서 target_status_code=403인지 확인
  • Pod 로그에서 403 원인을 찾기
kubectl logs deploy/<app> -n <ns> --since=10m | tail -n 200

해결

  • 앱의 “프록시 뒤 동작” 설정 점검
    • 예: Django SECURE_PROXY_SSL_HEADER, FastAPI/Starlette proxy_headers=True, Spring ForwardedHeaderFilter
  • 허용 Origin/Host 목록에 실제 도메인 추가

원인 6) Kubernetes NetworkPolicy / Security Group / NACL 문제로 “앱이 방어적으로 403”

보통 네트워크가 막히면 502/504를 떠올리지만, 일부 앱/프록시(Envoy/Nginx)가 업스트림이 특정 조건에서 거부될 때 403으로 매핑하는 구성도 있습니다. 또한 Pod는 뜨는데 트래픽이 0인 상황과 함께 나타나기도 합니다.

확인 포인트

  • 같은 클러스터에서 Pod 간 통신이 되는지
  • 노드 SG가 ALB SG에서 오는 트래픽을 허용하는지(특히 instance target 모드)
  • NetworkPolicy가 ingress를 막고 있지 않은지

빠른 네트워크 진단 루틴은 아래 글의 체크리스트가 매우 유용합니다.

간단 재현/검증용 명령

클러스터 내부에서 서비스로 직접 호출해 403이 재현되는지 확인합니다.

kubectl run -n <ns> tmp-curl --rm -it --restart=Never \
  --image=curlimages/curl:8.5.0 -- \
  curl -i http://<service-name>.<ns>.svc.cluster.local:<port>/
  • 내부 호출도 403이면: 앱/미들웨어/정책 문제 가능성 ↑
  • 내부 호출은 200인데 ALB만 403이면: Host/인증/리스너 룰 문제 가능성 ↑

원인 7) Ingress 어노테이션/타깃 타입/포트 불일치로 “의도치 않은 경로만 차단”

403 자체는 연결 실패와 다르지만, Ingress 설정이 꼬이면 결과적으로 특정 path가 다른 액션으로 라우팅되거나, 잘못된 서비스 포트로 보내는 룰이 만들어져 예상치 못한 403이 나오기도 합니다.

자주 실수하는 지점

  • alb.ingress.kubernetes.io/target-type (ip vs instance)와 서비스 타입/노드 보안그룹의 불일치
  • Ingress path가 ImplementationSpecific로 해석되어 매칭이 달라짐
  • 서비스 포트/타깃포트가 다르거나, readiness는 통과하지만 실제 라우팅 path에서 앱이 403

확인

Ingress/Service의 포트와 path 타입을 한 번에 확인합니다.

kubectl get ingress <ingress-name> -n <ns> -o yaml
kubectl get svc <service-name> -n <ns> -o yaml

또한 Controller가 만든 Target Group의 포트/헬스체크 path를 확인합니다.

aws elbv2 describe-target-groups --load-balancer-arn <ALB_ARN> \
  --query 'TargetGroups[*].{Name:TargetGroupName,Port:Port,Protocol:Protocol,HCPath:HealthCheckPath,HCMatcher:Matcher}'

해결

  • path 매칭을 명확히: pathType: Prefix 권장
  • 서비스 포트/타깃포트 정합성 확보
  • target-type에 맞게 SG/NodePort/Pod IP 접근 경로 정리

10분 트리아지: 어디부터 보면 가장 빠른가

운영 중 장애에서 “빨리 좁히기” 순서로 정리하면 아래가 효율적입니다.

  1. ALB Access Log에서 elb_status_code vs target_status_code로 1차 분리
  2. elb_status_code=403Listener Rules(fixed-response / auth / host/path 매칭) 확인
  3. target_status_code=403Pod 로그/앱 보안 설정(Origin/Proxy headers) 확인
  4. Ingress 그룹 충돌 여부(group.name) 확인
  5. 내부 curl로 클러스터 내부 재현 여부 확인(내부도 403이면 앱/정책)

예시: “Host 불일치로 기본 403”을 최소 설정으로 재현/해결

아래는 Host 기반 라우팅을 하는 전형적인 Ingress 예시입니다. api.example.com이 아닌 Host로 들어오면 룰이 매칭되지 않아 기본 액션으로 떨어질 수 있습니다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api
  namespace: prod
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
spec:
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-svc
                port:
                  number: 80

검증:

# 정상(Host 일치)
curl -i https://<alb-dns-name>/ -H 'Host: api.example.com'

# 비정상(Host 불일치)
curl -i https://<alb-dns-name>/ -H 'Host: other.example.com'

해결 방향은 두 가지입니다.

  • 실제로 여러 도메인을 허용해야 한다면 rules.host를 추가
  • ALB DNS로 직접 테스트해야 한다면 curl에서 Host를 항상 맞추기

마무리

EKS ALB Ingress의 403은 WAF가 없어도 충분히 발생하며, 대부분은 (1) ALB 룰/인증/Host 매칭 또는 (2) 백엔드 앱의 프록시/Origin/인가 정책에서 나옵니다. 핵심은 “403을 누가 만들었는지”를 로그에서 먼저 갈라내는 것입니다.

다음 장애에서 바로 써먹을 한 줄 요약은 이것입니다.

  • target_status_code가 비어 있으면 ALB/Ingress 룰을 의심
  • target_status_code=403이면 백엔드 앱/정책을 의심

원하면, 사용 중인 Ingress YAML(어노테이션 포함)과 ALB 액세스 로그 한 줄(403 발생 요청)을 익명화해서 주면, 위 7가지 중 어디에 해당하는지 더 구체적으로 짚어서 수정안까지 같이 정리해드릴게요.