Published on

EKS Pod에서 IPv6로만 STS 403 뜰 때 해결

Authors

서론

EKS에서 IRSA(서비스어카운트 기반 IAM Role)나 AWS SDK를 사용하는 애플리케이션이 sts:AssumeRoleWithWebIdentity 또는 GetCallerIdentity를 호출할 때, 특정 Pod에서만 혹은 IPv6로만 나가는 경로에서 HTTP 403이 튀는 경우가 있습니다. 흥미로운 점은 같은 코드·같은 IAM 정책인데도 IPv4로 호출하면 정상, IPv6로 호출하면 403처럼 보일 수 있다는 것입니다.

이 글은 “STS 403”을 단순 권한 문제로만 보지 않고, EKS 듀얼스택/IPv6 egress, VPC 엔드포인트(PrivateLink), DNS 해석, 프록시/방화벽/WAF, SDK 엔드포인트 선택 로직까지 포함해 실전적으로 정리합니다.

> 참고로 ‘DNS는 되는데 HTTPS만 실패’처럼 네트워크 계층에서 증상이 꼬일 때의 체크리스트는 아래 글도 함께 보면 좋습니다: EKS Pod DNS는 되는데 HTTPS만 실패할 때 점검


증상 패턴 정리 (IPv6로만 STS 403)

다음 중 하나로 관측되는 경우가 많습니다.

  • Pod에서 curl https://sts.<region>.amazonaws.com/ 또는 SDK STS 호출 시 403
  • 같은 Pod에서 curl -4 ...는 정상(200/403이 아닌 정상 응답)인데 curl -6 ...만 403
  • 노드/다른 네임스페이스/다른 서브넷의 Pod는 정상
  • IRSA 사용 시 애플리케이션 로그에 AccessDenied 또는 InvalidIdentityToken 계열이 섞여 보이기도 함

중요: **STS의 403은 “권한이 없다” 외에도 “요청이 AWS에 도달했지만, 중간 계층이 다른 정책으로 막았다”**는 의미일 수 있습니다. 특히 IPv6 경로에서만 발생하면 IAM보다는 네트워크/엔드포인트 경로 차이를 먼저 의심해야 합니다.


1) 먼저 재현: IPv4/IPv6로 강제 호출해보기

Pod 안에서 아래처럼 강제로 주소 패밀리를 나눠 확인합니다.

# STS 엔드포인트 확인
REGION=ap-northeast-2

# IPv4 강제
curl -sS -4 -D- "https://sts.${REGION}.amazonaws.com/" -o /dev/null

# IPv6 강제
curl -sS -6 -D- "https://sts.${REGION}.amazonaws.com/" -o /dev/null

-6에서만 403이면, 최소한 애플리케이션/IAM 자체보다는 IPv6 egress 경로(라우팅, 엔드포인트, 방화벽, 프록시) 쪽으로 범위를 좁힐 수 있습니다.

추가로 DNS가 어떤 레코드를 주는지 확인합니다.

# A/AAAA 레코드 확인
nslookup sts.${REGION}.amazonaws.com
# 또는
getent ahosts sts.${REGION}.amazonaws.com

듀얼스택 환경에서는 SDK/라이브러리가 AAAA를 우선하거나(또는 Happy Eyeballs), OS의 주소 선택 정책에 따라 IPv6로 먼저 붙을 수 있습니다.


2) 가장 흔한 원인: STS를 VPC 엔드포인트로 “부분적으로” 프라이빗화

EKS에서 보안을 위해 STS에 Interface VPC Endpoint(PrivateLink) 를 붙이는 경우가 많습니다. 여기서 함정은 다음입니다.

  • VPC Endpoint는 기본적으로 IPv4 사설 IP로 동작하는 구성이 일반적
  • 그런데 Pod의 egress가 IPv6로 나가면, STS 도메인 해석/라우팅이 퍼블릭 IPv6로 빠질 수 있음
  • 조직 정책(예: egress firewall, 중앙 프록시, WAF/보안장비)이 퍼블릭 IPv6 경로만 별도로 차단/검사하여 403을 반환할 수 있음

즉, “STS는 프라이빗으로만 나가야 한다”는 설계를 해놓고, IPv6에서는 그 설계가 깨져 다른 출구로 나가면서 403이 되는 케이스가 생깁니다.

체크 포인트

  • STS Interface Endpoint가 있는가?
  • 해당 Endpoint에 Private DNS가 활성화되어 있는가?
  • 클러스터/노드가 있는 서브넷의 라우팅/보안그룹/NACL이 Endpoint로의 트래픽을 허용하는가?
  • IPv6 egress는 어디로 나가고 있는가? (Egress-only IGW, NAT64, 중앙 방화벽 등)

3) STS “글로벌 엔드포인트” vs “리전 엔드포인트” 혼선

AWS SDK는 설정에 따라 sts.amazonaws.com(글로벌) 또는 sts.<region>.amazonaws.com(리전)을 사용할 수 있습니다.

  • 조직에서 특정 리전만 허용하거나, VPC Endpoint가 특정 리전 STS만 커버하는데
  • SDK가 글로벌 엔드포인트(또는 다른 리전)로 나가면
  • 네트워크 장비/정책에서 403으로 차단될 수 있습니다.

해결: SDK에서 STS 리전 엔드포인트 강제

애플리케이션이 사용하는 SDK에 따라 아래 중 하나를 적용합니다.

(예) AWS SDK for Java v2

StsClient sts = StsClient.builder()
    .region(Region.AP_NORTHEAST_2)
    // 필요 시 명시적으로 엔드포인트 지정
    // .endpointOverride(URI.create("https://sts.ap-northeast-2.amazonaws.com"))
    .build();

(예) AWS SDK for Go v2

cfg, err := config.LoadDefaultConfig(ctx,
  config.WithRegion("ap-northeast-2"),
)
if err != nil { panic(err) }

stsClient := sts.NewFromConfig(cfg)

(예) 환경변수로 리전 고정

export AWS_REGION=ap-northeast-2
export AWS_DEFAULT_REGION=ap-northeast-2

IRSA를 쓰는 Pod에서 리전이 비어 있으면, 일부 라이브러리/설정 조합에서 의도치 않은 엔드포인트로 갈 수 있습니다.


4) IPv6 egress에서만 거치는 “보안 계층”이 403을 만든다

IPv6 트래픽이 IPv4와 다른 경로를 타면, 중간에 다음이 끼어들 수 있습니다.

  • 중앙 egress 프록시(HTTP CONNECT)
  • 방화벽/보안장비(도메인 기반 차단, SNI 검사)
  • 조직용 WAF/봇 차단 정책

이 경우 STS는 실제로는 정상인데, 중간 장비가 403을 반환합니다. 응답 헤더에 장비 흔적이 남는 경우가 많습니다.

curl -6 -v "https://sts.ap-northeast-2.amazonaws.com/" 2>&1 | sed -n '1,120p'

확인할 것:

  • server: 헤더가 awselb, AmazonSTS가 아니라 다른 제품명/프록시명인지
  • via, x-cache, x-amzn-errortype 등이 비정상인지

만약 403이 WAF/봇 차단류라면, 원리 자체는 다르지만 “403이 계속될 때 어디서 막히는지” 접근법은 유사합니다. 필요하면 이 글도 참고하세요: AWS WAF Bot Control 막힘으로 403 지속될 때


5) IRSA에서 403처럼 보이는 케이스: 토큰/시간/CA 문제

IPv6 자체가 원인이 아니라, IPv6로만 통신할 때 다음 조건이 겹치며 IRSA가 실패하는 경우도 있습니다.

  • 프록시가 OIDC 토큰 교환 요청을 변조/차단
  • 노드 시간 오차(NTP)로 InvalidIdentityToken 유발
  • 컨테이너 이미지의 CA 번들이 오래되어 TLS 핸드셰이크가 꼬이는데, 라이브러리가 이를 403으로 래핑해 로깅

다만 이 케이스는 보통 403이 아니라 4xx/5xx가 섞여 나오거나, SDK 예외 메시지에 힌트가 있습니다.

IRSA/자격증명 계열의 기본 점검은 아래 글이 큰 도움이 됩니다: EKS에서 AWS SDK NoCredentialProviders 해결 가이드


6) 실전 해결책 모음 (우선순위 순)

아래는 “IPv6로만 STS 403”에서 실제로 효과가 컸던 해결책들입니다.

6.1 STS를 리전 엔드포인트로 고정하고, 글로벌 사용을 끊기

  • 애플리케이션 SDK 리전 설정
  • 필요 시 STS endpoint override
  • 조직 정책에서 sts.amazonaws.com 차단 중이라면 특히 중요
  • com.amazonaws.<region>.sts Interface Endpoint 생성
  • Private DNS 활성화
  • Endpoint SG에 노드/Pod CIDR에서 443 허용

이렇게 하면 Pod가 sts.<region>.amazonaws.com을 조회했을 때 퍼블릭으로 빠지지 않고, VPC 내부 엔드포인트로 유도됩니다(IPv4 기반 구성인 경우가 대부분이므로, IPv6 egress 경로와 섞여 문제가 나는 상황을 줄입니다).

6.3 IPv6 egress 경로를 단일화/명확화

듀얼스택에서 “IPv6만 중앙 방화벽을 타고, IPv4는 NAT로 나간다” 같은 비대칭이 있으면, 정책 차이로 403이 나기 쉽습니다.

  • IPv6도 동일한 egress 정책을 적용
  • 혹은 반대로, STS 같은 AWS API는 VPC Endpoint로만 나가게 강제

6.4 (임시 우회) 애플리케이션에서 IPv4 우선 사용

근본 해결 전 빠른 복구가 필요하면, 런타임/OS의 주소 선택 정책을 조정하거나, 애플리케이션 레벨에서 IPv4를 우선하도록 구성할 수 있습니다.

예: 일부 환경에서 curl -4로는 되는데 SDK만 실패한다면, SDK의 HTTP 클라이언트/DNS 정책(Happy Eyeballs) 설정을 확인하세요.

> 단, 이는 장기적으로는 추천하지 않습니다. 듀얼스택의 이점을 포기하고, 다른 서비스에서도 유사 문제가 재발할 수 있습니다.


7) Kubernetes에서 빠르게 확인하는 디버그 Pod 템플릿

문제 Pod와 같은 네임스페이스/서비스어카운트로 디버그 Pod를 띄워 재현하는 것이 가장 빠릅니다.

apiVersion: v1
kind: Pod
metadata:
  name: sts-ipv6-debug
  namespace: default
spec:
  serviceAccountName: your-irsa-sa
  containers:
  - name: net
    image: public.ecr.aws/amazonlinux/amazonlinux:2023
    command: ["bash", "-lc"]
    args:
      - |
        dnf -y install curl bind-utils jq >/dev/null
        REGION=ap-northeast-2
        echo "== DNS ==";
        nslookup sts.${REGION}.amazonaws.com || true
        echo "== IPv4 ==";
        curl -sS -4 -D- "https://sts.${REGION}.amazonaws.com/" -o /dev/null || true
        echo "== IPv6 ==";
        curl -sS -6 -D- "https://sts.${REGION}.amazonaws.com/" -o /dev/null || true
        sleep 36000

이 Pod에서 curl -6만 403이면, 애플리케이션 문제가 아니라 클러스터 네트워크/egress 정책 문제로 결론 내리기 쉽습니다.


결론

EKS Pod에서 STS 403이 “IPv6로만” 발생한다면, IAM 정책부터 보기보다 먼저 경로가 달라졌는지를 확인해야 합니다. 듀얼스택에서는 DNS가 AAAA를 주는 순간, 트래픽이 퍼블릭 IPv6/중앙 보안장비/다른 프록시 체인을 타면서 403이 발생할 수 있습니다.

정리하면 우선순위는 다음이 가장 안전합니다.

  1. STS를 리전 엔드포인트로 고정(SDK/환경변수)
  2. STS VPC Interface Endpoint + Private DNS로 프라이빗 경로 강제
  3. IPv6 egress 정책을 IPv4와 동일한 보안/라우팅 기준으로 정리

이 3가지를 적용하면 “IPv6로만 STS 403”의 대부분은 재발 없이 정리됩니다.