- Published on
EKS IRSA가 안 먹을 때 STS·OIDC·토큰 진단
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
IRSA(IAM Roles for Service Accounts)는 “Pod 안에서 AWS SDK가 알아서 역할을 Assume 한다”는 기대 때문에, 한 번 삐끗하면 원인 파악이 어렵습니다. 특히 에러 메시지는 대개 AccessDenied, InvalidIdentityToken, NoCredentialProviders처럼 뭉뚱그려져 나오고, 실제로는 OIDC Provider 설정, ServiceAccount 어노테이션, JWT 토큰의 audience/subject, IAM Role trust policy, STS 네트워크/프록시 문제 중 하나가 깨진 경우가 많습니다.
이 글은 IRSA의 핵심 호출인 sts:AssumeRoleWithWebIdentity를 기준으로, “어디에서 끊기는지”를 재현 가능한 명령과 함께 단계적으로 좁혀 나가는 진단 절차를 제공합니다. (STS 자체가 5xx를 내는 케이스는 별도 이슈일 수 있으니 EKS Pod에서 STS 502 Bad Gateway 원인·해결도 함께 참고하면 좋습니다.)
IRSA 동작 흐름(문제 지점 지도)
IRSA는 아래 흐름이 모두 맞아야 정상 동작합니다.
- EKS 클러스터에 OIDC Issuer가 존재한다.
- AWS IAM에 OIDC Provider가 등록되어 있고, thumbprint/URL이 일치한다.
- Kubernetes ServiceAccount에 role-arn 어노테이션이 걸려 있다.
- Pod에 Projected ServiceAccount Token(JWT) 이 마운트된다.
- AWS SDK(또는 aws-cli)가
AWS_WEB_IDENTITY_TOKEN_FILE과AWS_ROLE_ARN을 읽는다. - STS
AssumeRoleWithWebIdentity호출이 성공한다. - 반환된 임시 자격증명으로 S3/ECR/Secrets Manager 등 실제 API를 호출한다.
진단은 4~6 단계에서 가장 많이 막힙니다. 즉, “토큰이 없거나”, “토큰은 있는데 claim이 안 맞거나”, “trust policy가 토큰 claim을 거부하거나”, “STS로 못 나가거나”입니다.
1) 증상부터 분류: 어떤 에러인가?
먼저 Pod 로그 또는 애플리케이션 에러를 분류합니다.
NoCredentialProviders/Unable to locate credentials- SDK가 IRSA 환경변수/토큰 파일을 못 찾음(마운트 실패, SA 미지정, SDK 버전/설정 문제)
InvalidIdentityToken/The identity token is invalid- OIDC Provider 불일치, audience/issuer 문제, 토큰 서명 검증 실패
AccessDeniedwithAssumeRoleWithWebIdentity- IAM trust policy의
sub/aud조건 불일치, role-arn 잘못, OIDC provider ARN 잘못
- IAM trust policy의
- STS 호출 자체가 타임아웃/5xx
- 네트워크/NAT/프록시/방화벽/DNS 문제(또는 STS 장애)
이 분류만 해도 절반은 해결 방향이 잡힙니다.
2) ServiceAccount와 Pod 연결 확인(가장 흔한 실수)
IRSA는 “Pod가 어떤 ServiceAccount를 쓰는지”가 출발점입니다.
ServiceAccount에 role-arn이 있는지
kubectl get sa -n <ns> <sa-name> -o yaml
아래처럼 어노테이션이 있어야 합니다.
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME>
Pod가 그 ServiceAccount를 쓰는지
kubectl get pod -n <ns> <pod-name> -o jsonpath='{.spec.serviceAccountName}'
Deployment/Job에서 serviceAccountName을 빼먹으면 기본 SA를 쓰고, 당연히 IRSA가 안 먹습니다.
(주의) Pod 재시작 필요
ServiceAccount 어노테이션을 바꿔도 이미 떠 있는 Pod의 토큰/환경은 갱신되지 않을 수 있습니다. 가장 안전한 방법은 롤링 재시작입니다.
kubectl rollout restart deploy -n <ns> <deploy-name>
3) Pod 내부에서 IRSA 환경변수/토큰 파일 확인
IRSA가 제대로 주입되면 보통 아래 두 값이 존재합니다.
AWS_ROLE_ARNAWS_WEB_IDENTITY_TOKEN_FILE
kubectl exec -n <ns> <pod-name> -- /bin/sh -c 'env | egrep "AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION|AWS_DEFAULT_REGION"'
토큰 파일도 확인합니다.
kubectl exec -n <ns> <pod-name> -- /bin/sh -c 'ls -al $AWS_WEB_IDENTITY_TOKEN_FILE && head -c 30 $AWS_WEB_IDENTITY_TOKEN_FILE && echo'
- 파일이 없다면: projected token 마운트가 안 된 것(구버전 설정/권한/SA 연결 문제)
- 파일은 있는데 env가 없다면: IRSA 주입이 안 된 것(대개 SA 어노테이션/Pod SA 미지정)
4) 토큰(JWT) claim을 직접 까서 issuer/aud/sub 검증
IRSA 문제의 핵심은 JWT claim이 IAM trust policy 조건과 일치하는가입니다.
JWT 디코드(서명 검증 없이 claim만 확인)
Pod 안에 python이 없을 수 있으니, 로컬로 토큰을 복사하거나 Pod에서 base64 decode를 씁니다.
# Pod 내부에서 JWT payload 확인 (두 번째 조각)
kubectl exec -n <ns> <pod-name> -- /bin/sh -c '
TOKEN=$(cat $AWS_WEB_IDENTITY_TOKEN_FILE);
PAYLOAD=$(echo $TOKEN | cut -d. -f2);
echo $PAYLOAD | tr "-_" "+/" | base64 -d 2>/dev/null | cat; echo'
확인해야 할 대표 claim:
iss: EKS OIDC issuer URLaud: 보통sts.amazonaws.comsub:system:serviceaccount:<namespace>:<serviceaccount>
예시:
{
"iss": "https://oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E",
"aud": "sts.amazonaws.com",
"sub": "system:serviceaccount:my-ns:my-sa",
"exp": 1710000000
}
여기서 sub가 예상과 다르면 Pod가 다른 SA를 쓰고 있거나, IRSA로 의도한 SA가 아닌 것입니다.
5) EKS OIDC Issuer와 IAM OIDC Provider 매칭
클러스터의 issuer를 확인합니다.
aws eks describe-cluster \
--name <cluster-name> \
--query 'cluster.identity.oidc.issuer' \
--output text
IAM에 등록된 OIDC provider 목록에서 해당 URL이 있는지 확인합니다.
aws iam list-open-id-connect-providers
찾은 Provider ARN으로 상세를 봅니다.
aws iam get-open-id-connect-provider \
--open-id-connect-provider-arn <provider-arn>
- URL이 다르면: 다른 클러스터의 provider를 참조 중
- Thumbprint/ClientIDList가 이상하면: audience 검증에서 실패 가능
특히 ClientIDList에 보통 sts.amazonaws.com가 있어야 합니다.
6) IAM Role Trust Policy: sub/aud 조건이 토큰과 일치하는지
가장 많이 틀리는 부분입니다. Role의 trust policy를 확인합니다.
aws iam get-role --role-name <role-name> --query 'Role.AssumeRolePolicyDocument'
정상 예시(핵심만):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLE"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLE:aud": "sts.amazonaws.com",
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLE:sub": "system:serviceaccount:my-ns:my-sa"
}
}
}
]
}
자주 발생하는 불일치 패턴
sub의 namespace/sa 이름 오타StringLike/StringEquals선택 실수- OIDC provider 경로(
.../id/EXAMPLE)가 다른 값 aud조건 누락 또는sts.amazonaws.com가 아닌 값
여러 SA를 허용하려고 와일드카드를 쓴다면:
"StringLike": {
"...:sub": "system:serviceaccount:my-ns:*"
}
단, 보안상 최소 권한 원칙을 유지해야 합니다.
7) STS 호출을 Pod 안에서 직접 재현(aws-cli)
애플리케이션이 어떤 SDK를 쓰든, 결국 STS 호출이 성공해야 합니다. 가능하면 디버그용 임시 Pod에 aws-cli를 넣어 재현합니다.
디버그 Pod 실행(예: amazon/aws-cli)
kubectl run irsa-debug -n <ns> -it --rm \
--image=amazon/aws-cli:2.15.0 \
--serviceaccount=<sa-name> \
--command -- sh
안에서 다음을 실행합니다.
aws sts get-caller-identity
- 성공하면: IRSA 자체는 OK, 이후 권한 정책(예: S3 권한) 문제일 가능성
AccessDenied면: trust policy/SA/토큰 claim 문제- 네트워크 에러면: STS 엔드포인트 접근 문제
STS 엔드포인트/네트워크 문제 빠른 분리
# DNS
nslookup sts.amazonaws.com || true
# HTTPS 연결
curl -sS -o /dev/null -w "%{http_code}\n" https://sts.amazonaws.com/
사내 프록시, VPC egress, NAT, 보안그룹, NetworkPolicy 등으로 막히면 STS가 안 됩니다. STS가 502 같은 형태로 실패한다면 위에서 언급한 글(EKS Pod에서 STS 502 Bad Gateway 원인·해결)의 케이스(프록시/미들박스/MTU/DNS)도 함께 점검하세요.
8) SDK/런타임 쪽 함정: 자격증명 체인 우선순위
IRSA가 정상인데도 앱이 계속 “자격증명 없음”을 뱉는다면, SDK가 다른 자격증명 공급자를 우선하거나 IRSA를 읽지 못하는 경우가 있습니다.
체크리스트:
- 컨테이너에
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY가 빈 값으로라도 주입되어 있지 않은가?- 일부 SDK는 환경변수 존재만으로 그 공급자를 우선하다가 실패할 수 있습니다.
AWS_EC2_METADATA_DISABLED=true같은 설정이 문제를 가리는가?- 보통 IRSA에는 영향 없지만, 디버깅 시 혼선을 줄 수 있습니다.
- (Java) AWS SDK v1/v2 버전이 너무 낮아 web identity 토큰을 기대대로 처리 못하는가?
- (Node)
AWS_SDK_LOAD_CONFIG=1및 shared config가 다른 프로파일을 강제하지 않는가?
가능하면 애플리케이션에서 자격증명 로딩 로그를 켜거나, 위의 aws sts get-caller-identity로 “Pod 레벨에서 되는지”를 먼저 확정하세요.
9) 시간이 지나면 갑자기 실패: 토큰 만료/Projected Token 설정
IRSA 토큰은 만료(exp)가 있고, Kubernetes는 projected token을 회전시킵니다. 하지만 다음과 같은 경우 회전이 깨질 수 있습니다.
- 커스텀 사이드카/스크립트가 토큰 파일을 복사해서 고정된 위치만 읽음
- 애플리케이션이 토큰 파일을 한 번만 읽고 캐시(재로딩 없음)
automountServiceAccountToken: false설정으로 토큰 마운트 자체가 꺼짐
Pod spec에 이런 설정이 있는지 확인합니다.
kubectl get pod -n <ns> <pod-name> -o yaml | egrep -n "automountServiceAccountToken|serviceAccountName" -n
10) 실전용 “한 번에” 점검 스크립트(명령 묶음)
아래는 현장에서 가장 빠르게 원인을 좁히는 순서입니다.
# 1) Pod가 어떤 SA를 쓰는지
kubectl get pod -n <ns> <pod> -o jsonpath='{.spec.serviceAccountName}'; echo
# 2) SA에 role-arn이 있는지
kubectl get sa -n <ns> <sa> -o jsonpath='{.metadata.annotations.eks\.amazonaws\.com/role-arn}'; echo
# 3) Pod에 IRSA env/토큰이 있는지
kubectl exec -n <ns> <pod> -- sh -c 'env | egrep "AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE"'
# 4) STS 호출 재현(컨테이너에 aws-cli가 있을 때)
kubectl exec -n <ns> <pod> -- sh -c 'aws sts get-caller-identity || true'
# 5) 토큰 claim 확인(iss/aud/sub)
kubectl exec -n <ns> <pod> -- sh -c '
T=$(cat $AWS_WEB_IDENTITY_TOKEN_FILE); P=$(echo $T | cut -d. -f2);
echo $P | tr "-_" "+/" | base64 -d 2>/dev/null | cat; echo'
여기까지의 결과만으로도 보통 “SA 연결 문제인지 / trust policy 문제인지 / 네트워크 문제인지”가 결정됩니다.
마무리: IRSA는 ‘토큰-정책 매칭 게임’이다
IRSA가 안 먹을 때 가장 중요한 관점은, AWS가 보는 것은 Kubernetes가 발급한 JWT 토큰이며 IAM Role trust policy는 그 토큰의 iss/aud/sub를 조건으로 검증한다는 점입니다. 따라서 진단 순서는 항상 다음이 가장 효율적입니다.
- Pod ↔ ServiceAccount 연결 확인
- 토큰 존재 여부 및 claim(iss/aud/sub) 확인
- IAM OIDC Provider URL/ClientIDList 확인
- Role trust policy의 조건이 토큰 claim과 일치하는지 확인
- 마지막으로 STS 네트워크/프록시 문제 확인
EKS 운영 중 네트워크 이슈로 다른 증상도 같이 보인다면, EKS에서 kubectl port-forward 끊김·hang 해결처럼 “클러스터 내부 egress/연결 안정성” 관점의 점검도 병행하면 문제를 더 빨리 분리할 수 있습니다.