Published on

EKS Pod에서 STS 403 SignatureDoesNotMatch 해결

Authors

서버리스나 EC2에서는 멀쩡한데 EKS Pod 안에서만 AWS STS 호출이 403 SignatureDoesNotMatch로 터지는 경우가 있습니다. 이 에러는 “키가 틀렸다”라기보다 서명에 사용된 입력값(시간, 리전, 호스트, 헤더, 바디)이 AWS가 계산한 값과 달라졌다는 의미에 가깝습니다. EKS에서는 특히 IRSA(WebIdentity), 프록시/서비스메시, 노드 시간 동기화, 리전/엔드포인트 자동추론이 겹치며 원인 파악이 어려워집니다.

이 글에서는 Pod에서 STS 403을 원인별로 빠르게 분류하고, kubectl로 현장에서 바로 확인하는 체크리스트와 근본 해결책을 정리합니다.

> IRSA 자체가 AccessDenied로 막히는 케이스(권한/TrustPolicy/OIDC)는 결이 다릅니다. 해당 진단은 별도 글인 EKS IRSA인데 AccessDenied? OIDC·TrustPolicy·SA 점검도 함께 참고하세요.

증상 패턴: 어떤 요청에서 터지나?

대표적으로 아래 호출에서 발생합니다.

  • SDK가 자동으로 AssumeRoleWithWebIdentity를 호출(= IRSA)
  • 애플리케이션이 직접 STS API 호출
  • ECR, S3 등 다른 서비스 호출 중 내부적으로 STS가 선행 호출

로그/에러 메시지 예시는 다음과 같습니다.

  • botocore.exceptions.ClientError: An error occurred (SignatureDoesNotMatch) when calling the AssumeRoleWithWebIdentity operation
  • The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method.

핵심은 “Secret이 틀렸다”가 아니라 서명에 포함된 요소가 중간에서 바뀌었거나(프록시/메시), 시간/리전/호스트가 어긋났거나, 토큰/환경변수 조합이 꼬였다는 점입니다.

빠른 분류 체크리스트 (10분 내)

아래 순서대로 보면 대부분 잡힙니다.

  1. Pod의 시간이 맞는가? (노드 NTP/chrony)
  2. STS 리전/엔드포인트가 올바른가? (sts.amazonaws.com vs sts.<region>.amazonaws.com)
  3. IRSA 사용 시 AWS_WEB_IDENTITY_TOKEN_FILE, AWS_ROLE_ARN이 정상 주입됐는가?
  4. 서비스메시/프록시가 Host/헤더/바디를 변조하거나, Expect: 100-continue/HTTP2 변환을 하는가?
  5. SDK가 잘못된 자격 증명 소스(예: 오래된 env key, shared config)를 우선 사용하지는 않는가?

1) 가장 흔한 원인: 노드/컨테이너 시간 불일치

SigV4 서명은 X-Amz-Date(혹은 Date 헤더)에 강하게 의존합니다. 노드 시간이 몇 분만 틀어져도 STS가 서명을 다르게 계산합니다. EKS에서 특히 Spot/커스텀 AMI/부팅 초기화 스크립트 문제로 chrony가 제대로 안 붙는 경우가 있습니다.

Pod에서 즉시 확인

kubectl exec -it deploy/myapp -- date -u

AWS 표준 시간과 비교해 몇 분 이상 차이가 나면 의심해야 합니다.

노드에서 확인(권한이 있다면)

kubectl get nodes -o wide
# 노드에 SSM/SSH로 접속 후
chronyc tracking || timedatectl status

해결

  • 노드 AMI에서 chrony/systemd-timesyncd 활성화
  • VPC에서 NTP 차단 여부(Security Group/NACL) 확인
  • 부팅 시점에 시간이 튀는 이미지라면 UserData에서 타임싱크 강제

> 시간 문제는 STS뿐 아니라 TLS, 토큰 검증 등 광범위한 장애를 유발합니다. EKS에서 “갑자기 일부 Pod만 인증 실패” 패턴이면 1순위로 의심하세요.

2) STS 리전/엔드포인트 불일치 (특히 중국/ Gov / VPC 엔드포인트)

STS는 글로벌 엔드포인트(sts.amazonaws.com)와 리전 엔드포인트(sts.ap-northeast-2.amazonaws.com)가 공존합니다. 서명에는 **호스트와 리전(scope)**이 들어가므로, SDK가 생각한 리전과 실제 요청 호스트가 어긋나면 SignatureDoesNotMatch가 날 수 있습니다.

흔한 트리거

  • AWS_REGION/AWS_DEFAULT_REGION이 비어 있거나 잘못됨
  • STS VPC Interface Endpoint를 붙였는데 DNS가 sts.amazonaws.com을 리전 엔드포인트로 리라이트
  • 멀티리전 클러스터에서 노드/Pod 환경변수 상이

진단: Pod에서 실제로 어디로 붙는지 확인

kubectl exec -it deploy/myapp -- sh -lc 'env | egrep "AWS_REGION|AWS_DEFAULT_REGION|AWS_STS"'

kubectl exec -it deploy/myapp -- sh -lc 'getent hosts sts.amazonaws.com || nslookup sts.amazonaws.com'

해결: 리전 명시 + STS 엔드포인트 모드 고정

  • 애플리케이션/Helm values에 AWS_REGION 명시
  • Java/Python/Node SDK에서 STS endpoint mode를 명시(가능한 경우)

예: **Python(boto3/botocore)**에서 STS 클라이언트 생성 시 리전 고정

import boto3

session = boto3.session.Session(region_name="ap-northeast-2")
sts = session.client("sts", region_name="ap-northeast-2")
print(sts.get_caller_identity())

예: AWS SDK for Java v2

StsClient sts = StsClient.builder()
    .region(Region.AP_NORTHEAST_2)
    .build();
System.out.println(sts.getCallerIdentity().arn());

> STS VPC 엔드포인트를 쓰는 환경에서는 “DNS가 어디로 향하는지”가 핵심입니다. 리전과 호스트를 일관되게 맞추세요.

3) IRSA(WebIdentity) 토큰/환경변수 꼬임

EKS에서 STS 호출의 상당수는 IRSA 경유입니다. 이때 SDK는 대개 다음 흐름을 탑니다.

  1. AWS_WEB_IDENTITY_TOKEN_FILE에서 JWT 읽음
  2. AWS_ROLE_ARN으로 AssumeRoleWithWebIdentity 호출
  3. 임시 자격증명 발급 후 실제 서비스 호출

여기서 SignatureDoesNotMatch가 뜨면 토큰 자체가 변조/깨짐 또는 요청이 프록시를 거치며 바디/헤더가 바뀜을 의심해야 합니다.

IRSA 주입 상태 확인

kubectl exec -it deploy/myapp -- sh -lc 'env | egrep "AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_ROLE_SESSION_NAME"'

kubectl exec -it deploy/myapp -- sh -lc 'ls -l $AWS_WEB_IDENTITY_TOKEN_FILE && head -c 50 $AWS_WEB_IDENTITY_TOKEN_FILE && echo'
  • 파일이 없거나, 권한이 없거나, 내용이 비정상(빈 파일 등)이면 IRSA 설정 문제입니다.
  • 단, 이런 경우는 보통 InvalidIdentityToken/AccessDenied로도 나타납니다.

ServiceAccount 어노테이션 확인

kubectl get sa my-sa -n myns -o yaml | yq '.metadata.annotations'

eks.amazonaws.com/role-arn이 기대값인지 확인합니다.

IRSA의 권한/TrustPolicy/OIDC 연결 문제는 위에서 언급한 내부 링크 글을 따라가면 빠르게 해결할 수 있습니다.

4) 프록시/서비스메시가 SigV4 요청을 “살짝” 바꿔버리는 경우

SignatureDoesNotMatch의 고전적인 원인 중 하나가 중간 프록시가 요청을 변경하는 것입니다. EKS에서는 다음이 흔합니다.

  • Istio/Envoy 사이드카가 STS 트래픽을 가로채며 헤더 정규화
  • 사내 egress proxy가 Host/Connection/Transfer-Encoding 등을 변경
  • HTTP/2 업그레이드, chunked 인코딩 변경, Expect: 100-continue 처리 차이

SigV4는 canonical request를 만들 때 헤더/쿼리/바디 해시를 포함하므로, 중간에서 조금이라도 바뀌면 서명이 깨집니다.

진단 포인트

  • Pod에 사이드카가 붙어 있는가?
kubectl get pod mypod -n myns -o jsonpath='{.spec.containers[*].name}'
  • 프록시 환경변수 존재 여부
kubectl exec -it pod/mypod -- sh -lc 'env | egrep -i "http_proxy|https_proxy|no_proxy"'

해결 전략

  • **STS/AWS 도메인을 프록시 예외(NO_PROXY)**로 뺍니다.
    • NO_PROXY=169.254.169.254,.amazonaws.com,.amazonaws.com.cn,sts.amazonaws.com,sts.ap-northeast-2.amazonaws.com 등 환경에 맞게
  • 서비스메시 사용 시 AWS STS/S3 등은 sidecar bypass(예: Istio traffic.sidecar.istio.io/excludeOutboundPorts 또는 ServiceEntry/DestinationRule로 제어)
  • 가능하면 AWS SDK는 HTTPS 직접 통신으로 유지

> “Pod는 뜨는데 트래픽이 0”처럼 네트워크 계층 이슈가 겹치면 원인 추적이 더 어려워집니다. 네트워크 진단은 EKS Pod는 뜨는데 트래픽 0 - NetPol·SG·CNI 10분 진단도 함께 보면 좋습니다.

5) 자격 증명 체인이 엉켜서 ‘의도치 않은 키’로 서명

EKS에서는 보통 IRSA를 쓰지만, 이미지/차트에 다음이 섞여 들어오면 SDK가 다른 소스의 자격증명을 우선할 수 있습니다.

  • AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY가 오래된 값으로 주입됨
  • ~/.aws/credentials가 이미지에 포함됨(빌드 과정에서 들어간 경우)
  • AWS_PROFILE이 설정됨

이 경우 STS가 아니라 다른 서비스에서 먼저 실패할 수도 있고, STS에서 SignatureDoesNotMatch로 보일 수도 있습니다.

진단

kubectl exec -it deploy/myapp -- sh -lc 'env | egrep "AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|AWS_SESSION_TOKEN|AWS_PROFILE"'

애초에 IRSA를 쓸 거라면 위 env key는 주입하지 않는 것이 안전합니다.

boto3에서 실제로 어떤 자격증명을 쓰는지 출력

import boto3

session = boto3.Session()
creds = session.get_credentials()
print("method=", getattr(creds, "method", None))
print("access_key_prefix=", (creds.access_key or "")[:4])
  • methodassume-role-with-web-identity가 아니라 env/shared-credentials-file 등으로 나오면 의도와 다릅니다.

6) 재현/검증용: Pod 안에서 STS 직접 호출해보기

애플리케이션 로그만 보면 원인이 섞여 보일 수 있으니, Pod에서 최소 재현을 해보면 좋습니다.

AWS CLI로 GetCallerIdentity

(이미지에 awscli가 없다면 디버그용 ephemeral container를 붙이거나 별도 툴링 Pod를 띄우세요.)

kubectl exec -it deploy/myapp -- sh -lc 'aws sts get-caller-identity --debug 2>&1 | tail -n 50'

--debug 로그에는 실제 endpoint/헤더/서명 과정 힌트가 남습니다.

curl로 AssumeRoleWithWebIdentity (개념 검증)

STS Query API는 POST form으로 호출됩니다. IRSA 토큰을 읽어 파라미터로 전달할 수는 있지만, SigV4 서명까지 curl로 수동 구성하는 건 비현실적이므로 여기서는 “토큰이 정상인지”만 확인하는 용도로 제한합니다.

kubectl exec -it deploy/myapp -- sh -lc '
TOKEN=$(cat $AWS_WEB_IDENTITY_TOKEN_FILE | head -c 20); 
echo "token_prefix=$TOKEN"'

토큰 파일이 비어 있거나 읽기 실패면 IRSA 주입/권한 문제입니다.

운영에서의 권장 가드레일

  • 노드 시간 동기화는 SLO 항목으로 관리(부팅 직후 drift 감시)
  • IRSA 사용 시 env 기반 long-term key 주입 금지(정책/리뷰로 차단)
  • 프록시/메시 환경에서는 .amazonaws.com을 기본 NO_PROXY에 포함
  • 멀티리전/엔드포인트 사용 시 AWS_REGION을 명시하고, STS endpoint 모드를 표준화

결론

EKS Pod에서 STS 403 SignatureDoesNotMatch는 대개 다음 4가지 중 하나로 수렴합니다.

  1. 시간 불일치(노드 NTP)
  2. 리전/엔드포인트 불일치(DNS/VPC Endpoint 포함)
  3. IRSA(WebIdentity) 주입/토큰 문제
  4. 프록시/서비스메시가 요청을 변조

위 체크리스트대로 Pod 내부에서 환경변수/시간/DNS/프록시 여부를 먼저 확인하고, 그 다음 IRSA(OIDC/TrustPolicy)와 STS 엔드포인트 설정을 정리하면 재발도 크게 줄일 수 있습니다.

추가로 IRSA 권한 자체가 막히는 케이스는 EKS IRSA인데 AccessDenied? OIDC·TrustPolicy·SA 점검에서 더 깊게 다뤘으니 함께 보면 전체 인증 흐름을 한 번에 정리할 수 있습니다.