- Published on
EKS Pod에서 STS 403 InvalidIdentityToken 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
EKS에서 IRSA(IAM Roles for Service Accounts)를 붙인 Pod가 AWS SDK 호출을 시작하자마자 sts:AssumeRoleWithWebIdentity에서 403 InvalidIdentityToken을 뱉는 경우가 있습니다. 메시지는 단순하지만 원인은 꽤 다양합니다. 예를 들면 OIDC Provider가 잘못되었거나(thumbprint/issuer 불일치), ServiceAccount 토큰의 aud/sub 클레임이 Trust Policy 조건과 맞지 않거나, Pod가 아예 다른 SA로 떠 있거나, 심지어 노드 시간 드리프트로 토큰의 nbf/exp 검증이 깨지는 경우도 있습니다.
이 글은 “무엇부터 확인해야 하는지”를 체크리스트처럼 정리하고, 실제로 손으로 검증 가능한 커맨드와 예시 정책을 함께 제공합니다. IRSA 자체 점검이 더 필요하다면 EKS IRSA인데 AccessDenied? OIDC·TrustPolicy·SA 점검도 함께 보시면 원인 분류가 빨라집니다. 또한 OIDC thumbprint 변경 이슈가 의심된다면 EKS OIDC Thumbprint 변경 후 IRSA 403 복구가 바로 맞는 케이스일 수 있습니다.
1) 에러의 의미: InvalidIdentityToken은 “토큰이 신뢰되지 않음”
InvalidIdentityToken은 대체로 다음 중 하나입니다.
- STS가 OIDC issuer(클러스터 OIDC URL) 자체를 신뢰하지 못함
- IAM OIDC Provider 미생성/삭제/오류
- thumbprint 불일치(갱신/프록시/중간 CA 변경)
- STS는 issuer는 알지만, 토큰 클레임이 Role trust policy 조건과 불일치
sub(serviceaccount) 불일치aud가sts.amazonaws.com이 아님
- Pod 내부에서 사용하는 토큰이 웹 아이덴티티 토큰이 아닌 다른 토큰
- 잘못된
AWS_WEB_IDENTITY_TOKEN_FILE - SA 토큰 projection이 꺼져 있거나(구버전/설정) 파일이 비어 있음
- 잘못된
- 시간 문제
- 노드/컨테이너 시간 오차로
exp/nbf/iat검증 실패
- 노드/컨테이너 시간 오차로
핵심은 “IRSA 토큰이 STS 관점에서 유효한 OIDC JWT로 보이는가?”입니다.
2) 가장 빠른 재현/검증: Pod 안에서 환경변수와 토큰부터 확인
먼저 문제 Pod에 들어가 아래를 확인합니다.
kubectl -n <ns> exec -it <pod> -- sh
# 1) IRSA 환경변수 존재 확인
env | egrep 'AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION|AWS_DEFAULT_REGION'
# 2) 토큰 파일 존재/크기 확인
ls -l ${AWS_WEB_IDENTITY_TOKEN_FILE}
wc -c ${AWS_WEB_IDENTITY_TOKEN_FILE}
# 3) (옵션) AWS SDK가 어떤 자격증명을 쓰는지 디버그
# Go SDK: AWS_SDK_LOAD_CONFIG=1, Java: -Daws.sdk.disableCertChecking 등은 케이스별
정상이라면 보통 다음이 보입니다.
AWS_ROLE_ARN=arn:aws:iam::<account-id>:role/<role-name>AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token
여기서부터 갈립니다.
- 환경변수 자체가 없다 → IRSA 주입이 안 된 것(대개 SA annotation 누락 또는 Pod가 다른 SA로 실행)
- 토큰 파일이 없거나 0바이트 → SA 토큰 projection 문제 또는 볼륨 마운트 문제
- 토큰은 있는데 STS가 거부 → OIDC Provider/Trust Policy/클레임 불일치 가능성이 큼
3) ServiceAccount가 맞게 붙었는지: “Pod spec”이 진실
IRSA는 “ServiceAccount에 role-arn annotation” + “Pod가 그 SA를 사용”해야 합니다.
kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.serviceAccountName}'; echo
kubectl -n <ns> get sa <sa-name> -o yaml
ServiceAccount에 아래와 같은 annotation이 있어야 합니다.
apiVersion: v1
kind: ServiceAccount
metadata:
name: myapp
namespace: myns
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/myapp-irsa-role
주의할 점:
- Deployment에서
serviceAccountName을 빼먹으면 기본 SA(default)로 뜹니다. - Helm/Kustomize로 배포 시 namespace가 섞여 SA가 다른 곳에 생성되는 실수도 흔합니다.
4) 토큰(JWT) 클레임을 직접 확인: sub/aud/iss를 눈으로 맞추기
웹 아이덴티티 토큰은 JWT입니다. payload를 디코딩해서 iss, sub, aud를 확인하면 원인 분류가 매우 빨라집니다.
4-1) Pod 내부에서 JWT payload 디코딩
TOKEN_FILE=${AWS_WEB_IDENTITY_TOKEN_FILE:-/var/run/secrets/eks.amazonaws.com/serviceaccount/token}
# JWT의 2번째 파트(payload)를 base64url 디코딩
PAYLOAD=$(cut -d. -f2 < "$TOKEN_FILE" | tr '_-' '/+' | awk '{print $0"=="}' )
echo "$PAYLOAD" | base64 -d 2>/dev/null | sed 's/,/,&\n/g'
확인 포인트:
iss:https://oidc.eks.<region>.amazonaws.com/id/<OIDC_ID>형태sub:system:serviceaccount:<namespace>:<serviceaccount>aud: 보통sts.amazonaws.com
4-2) Trust Policy 조건과 매칭
Role의 trust policy를 확인합니다.
aws iam get-role --role-name <role-name> --query 'Role.AssumeRolePolicyDocument' --output json
정상 예시(가장 흔한 패턴):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:aud": "sts.amazonaws.com",
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:sub": "system:serviceaccount:myns:myapp"
}
}
}
]
}
여기서 한 글자라도 다르면(특히 namespace/SA 이름) STS는 토큰을 거부합니다.
- 토큰의
sub와 trust policy의...:sub가 정확히 일치해야 함 - 토큰의
aud가sts.amazonaws.com인데 trust policy가 다른 값을 요구하면 실패 Principal.Federated의 OIDC provider ARN이 클러스터의 issuer와 다르면 실패
5) OIDC Provider가 “클러스터 issuer”와 일치하는지 확인
EKS 클러스터의 OIDC issuer를 확인합니다.
aws eks describe-cluster --name <cluster> --region <region> \
--query 'cluster.identity.oidc.issuer' --output text
출력 예:
https://oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E
IAM에 등록된 OIDC provider 목록과 매칭합니다.
aws iam list-open-id-connect-providers --query 'OpenIDConnectProviderList[*].Arn' --output text
# 해당 ARN의 설정 확인
aws iam get-open-id-connect-provider --open-id-connect-provider-arn <arn>
다음이 일치해야 합니다.
- Provider URL(issuer에서
https://제거한 호스트/패스) - Client ID 목록에
sts.amazonaws.com포함 - Thumbprint가 현재 인증서 체인과 맞음(여기서 어긋나면 403 계열이 터지기 쉬움)
thumbprint 변경/불일치가 의심되면 상세 복구 절차는 EKS OIDC Thumbprint 변경 후 IRSA 403 복구를 참고하세요.
6) 자주 나오는 실전 원인 6가지와 해결
6-1) (가장 흔함) Trust Policy의 sub가 틀림
- 증상: 특정 namespace/SA만 실패, 다른 SA는 정상
- 해결: 토큰 payload의
sub를 그대로 trust policy에 반영
# 예: namespace가 default로 되어 있거나, SA 이름이 바뀐 경우
# trust policy의 StringEquals ...:sub 값을 수정
aws iam update-assume-role-policy --role-name <role-name> --policy-document file://trust.json
6-2) aud 조건 누락/불일치
- 일부 구성은
StringLike로 넓게 잡기도 하지만, 보안상StringEquals권장 - 토큰의
aud가 배열로 들어가는 경우도 있으니 payload를 실제로 확인
해결: ...:aud를 sts.amazonaws.com으로 맞춥니다.
6-3) OIDC Provider가 다른 클러스터 것(issuer/id 불일치)
- 멀티 클러스터/테라폼 모듈 재사용 시 흔함
- trust policy의 key prefix(
oidc.eks.../id/...:)가 다른 클러스터 ID면 무조건 실패
해결:
- 올바른 issuer로 OIDC provider를 생성/수정
- role trust policy의 Federated ARN 및 조건 key를 올바른 issuer로 교체
6-4) ServiceAccount 토큰 projection 문제(토큰 파일 없음)
EKS는 보통 자동으로 projected token을 마운트하지만, 다음에서 문제가 납니다.
- 매우 오래된 클러스터/설정
automountServiceAccountToken: false가 SA 또는 Pod에 설정
kubectl -n <ns> get sa <sa> -o jsonpath='{.automountServiceAccountToken}'; echo
kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.automountServiceAccountToken}'; echo
해결: 필요 시 automountServiceAccountToken: true로 명시하고 재배포합니다.
6-5) 노드 시간 드리프트로 토큰 유효기간 검증 실패
JWT는 시간에 민감합니다. 노드의 시간이 틀어지면 InvalidIdentityToken로 보일 수 있습니다.
- 증상: 특정 노드에 스케줄된 Pod만 실패했다가 재스케줄하면 해결
확인(노드에서):
# 노드에 SSM/SSH 접근 가능할 때
sudo timedatectl
chronyc tracking 2>/dev/null || true
해결: NTP/chrony 정상화, AMI/부팅 스크립트 점검, 노드 교체(Managed Node Group면 롤링) 등.
6-6) SDK가 IRSA 대신 다른 자격증명을 우선 사용
예: 컨테이너 이미지에 ~/.aws/credentials가 들어있거나, 환경변수 AWS_ACCESS_KEY_ID가 주입되면 웹 아이덴티티 플로우를 우회할 수 있습니다.
env | egrep 'AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|AWS_SESSION_TOKEN'
해결: 정적 키 제거, 애플리케이션 런타임에서 credential provider chain 확인.
7) 최소 재현 코드: Pod에서 STS 호출로 IRSA 검증하기
애플리케이션이 복잡하면 “내 코드 문제인지/IRSA 문제인지” 분리하기 어렵습니다. 아래처럼 STS GetCallerIdentity만 호출하는 최소 코드를 Pod에 넣어 검증하면 빠릅니다.
7-1) Python(boto3) 예제
import boto3
import os
print("ROLE_ARN:", os.getenv("AWS_ROLE_ARN"))
print("TOKEN_FILE:", os.getenv("AWS_WEB_IDENTITY_TOKEN_FILE"))
sts = boto3.client("sts")
print(sts.get_caller_identity())
실행:
python app.py
- 여기서도
InvalidIdentityToken이면 애플리케이션 로직이 아니라 IRSA/OIDC 계층 문제입니다.
7-2) Node.js(AWS SDK v3) 예제
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
const client = new STSClient({});
const out = await client.send(new GetCallerIdentityCommand({}));
console.log(out);
8) 문제 해결 후 재발 방지 체크리스트
- 클러스터 생성/업그레이드 파이프라인에서 OIDC issuer와 IAM OIDC provider 존재/일치를 자동 검증
- IRSA role trust policy는 가능하면 템플릿화하되,
sub/issuer는 클러스터/네임스페이스별로 정확히 렌더링 - 운영에서 403이 났을 때를 대비해 Pod에
AWS_ROLE_ARN,AWS_WEB_IDENTITY_TOKEN_FILE로그(민감정보 제외)와GetCallerIdentity헬스체크를 준비
추가로, EKS에서 “Pod 자체가 정상 생성되지 않아” IRSA 이전 단계에서 막히는 경우도 많습니다. Pod가 아예 ContainerCreating에서 멈춘다면 EKS Pod가 ContainerCreating에 멈출 때 10분 진단 체크리스트로 인프라 계층부터 정리한 뒤 IRSA를 보시는 게 효율적입니다.
9) 요약
STS 403 InvalidIdentityToken은 대부분 OIDC issuer/provider 불일치 또는 trust policy의aud/sub조건 불일치입니다.- Pod 안에서
AWS_ROLE_ARN,AWS_WEB_IDENTITY_TOKEN_FILE확인 → JWT payload의iss/sub/aud확인 → IAM Role trust policy와 1:1 매칭이 가장 빠른 해결 루트입니다. - 특정 노드에서만 재현되면 시간 동기화도 반드시 의심하세요.
이 흐름대로 점검하면 “감으로 설정을 바꾸는” 시간을 줄이고, 어떤 값이 실제로 틀렸는지 증거 기반으로 고칠 수 있습니다.