- Published on
EKS Pod에서 STS 403 SignatureDoesNotMatch 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스나 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 operationThe request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method.
핵심은 “Secret이 틀렸다”가 아니라 서명에 포함된 요소가 중간에서 바뀌었거나(프록시/메시), 시간/리전/호스트가 어긋났거나, 토큰/환경변수 조합이 꼬였다는 점입니다.
빠른 분류 체크리스트 (10분 내)
아래 순서대로 보면 대부분 잡힙니다.
- Pod의 시간이 맞는가? (노드 NTP/chrony)
- STS 리전/엔드포인트가 올바른가? (
sts.amazonaws.comvssts.<region>.amazonaws.com) - IRSA 사용 시
AWS_WEB_IDENTITY_TOKEN_FILE,AWS_ROLE_ARN이 정상 주입됐는가? - 서비스메시/프록시가 Host/헤더/바디를 변조하거나,
Expect: 100-continue/HTTP2 변환을 하는가? - 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는 대개 다음 흐름을 탑니다.
AWS_WEB_IDENTITY_TOKEN_FILE에서 JWT 읽음AWS_ROLE_ARN으로AssumeRoleWithWebIdentity호출- 임시 자격증명 발급 후 실제 서비스 호출
여기서 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])
method가assume-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가지 중 하나로 수렴합니다.
- 시간 불일치(노드 NTP)
- 리전/엔드포인트 불일치(DNS/VPC Endpoint 포함)
- IRSA(WebIdentity) 주입/토큰 문제
- 프록시/서비스메시가 요청을 변조
위 체크리스트대로 Pod 내부에서 환경변수/시간/DNS/프록시 여부를 먼저 확인하고, 그 다음 IRSA(OIDC/TrustPolicy)와 STS 엔드포인트 설정을 정리하면 재발도 크게 줄일 수 있습니다.
추가로 IRSA 권한 자체가 막히는 케이스는 EKS IRSA인데 AccessDenied? OIDC·TrustPolicy·SA 점검에서 더 깊게 다뤘으니 함께 보면 전체 인증 흐름을 한 번에 정리할 수 있습니다.