- Published on
EKS IRSA 설정 후 WebIdentityToken 오류 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
EKS에서 IRSA(IAM Roles for Service Accounts)를 설정하면, Pod는 노드 IAM Role이 아니라 ServiceAccount에 매핑된 IAM Role을 통해 AWS API를 호출할 수 있습니다. 그런데 설정을 끝냈다고 생각했는데 애플리케이션 로그에 아래 같은 web identity token 계열 오류가 나오면 보통 멘탈이 흔들립니다.
InvalidIdentityToken: The web identity token that was passed is expired or is not validAccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentityNoCredentialProviders: no valid providers in chainfailed to refresh cached credentials, failed to retrieve credentialsWebIdentityErr: failed to retrieve credentials caused by: AccessDenied
이 글은 “IRSA는 켰는데 왜 STS AssumeRoleWithWebIdentity가 실패하는가?”를 원인별로 분류하고, kubectl/CLI로 증거를 뽑아가며 고치는 방법을 정리합니다. (EKS 네트워크/DNS 이슈가 섞여 있을 때는 AWS EKS CoreDNS CrashLoopBackOff와 DNS 타임아웃 해결도 같이 보세요.)
문제를 3분 안에 분류하는 체크리스트
아래 3가지만 먼저 확인하면, 문제 범위를 절반 이상 줄일 수 있습니다.
- Pod에 IRSA 환경변수가 주입됐는가?
AWS_ROLE_ARNAWS_WEB_IDENTITY_TOKEN_FILE
- ServiceAccount에 role-arn annotation이 정확한가?
eks.amazonaws.com/role-arn: arn:aws:iam::...:role/...
- IAM Role의 Trust Policy가 OIDC + sub/aud 조건과 일치하는가?
sub = system:serviceaccount:<namespace>:<sa>aud = sts.amazonaws.com
이 3개 중 하나라도 틀리면 대부분의 web identity token 오류가 발생합니다.
대표 증상과 원인 맵
1) AccessDenied: sts:AssumeRoleWithWebIdentity
대부분 IAM Role Trust Policy(신뢰 정책) 조건이 잘못됐거나, OIDC Provider ARN이 다릅니다.
- OIDC Provider가 클러스터와 불일치
Condition.StringEquals의 키(issuer URL) 오타sub에 namespace/serviceaccount가 다름aud누락 또는 값 불일치
2) InvalidIdentityToken / token is expired or is not valid
대부분 토큰 자체가 STS에서 검증 불가한 상태입니다.
- EKS OIDC issuer와 IAM OIDC provider가 불일치
- 토큰 파일 경로가 잘못 주입됨
- (드물게) 컨테이너 시간/노드 시간 문제로 iat/exp 검증 실패
3) NoCredentialProviders / SDK가 자격증명 못 찾음
IRSA가 아니라 SDK/런타임이 web identity provider를 안 타는 경우가 많습니다.
- 오래된 AWS SDK 버전
- 커스텀 credential chain이 web identity를 덮어씀
AWS_EC2_METADATA_DISABLED=true등과 함께 잘못된 환경변수 조합
1단계: Pod에 IRSA 주입이 됐는지 확인
ServiceAccount 확인
kubectl -n <ns> get sa <sa> -o yaml
다음이 있어야 합니다.
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::<account-id>:role/<role-name>
Pod에 환경변수가 들어왔는지 확인
kubectl -n <ns> exec -it <pod> -- env | egrep 'AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION|AWS_DEFAULT_REGION'
정상이라면 대략 이렇게 나옵니다.
AWS_ROLE_ARN=arn:aws:iam::123456789012:role/my-irsa-roleAWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token
만약 AWS_ROLE_ARN/AWS_WEB_IDENTITY_TOKEN_FILE이 없다면:
- Pod가 사용하는 SA가 다른지 확인 (
kubectl get pod -o yaml | grep serviceAccountName) - Deployment/StatefulSet이 기존 Pod를 재사용 중인지 확인 (롤링 재시작 필요)
kubectl -n <ns> rollout restart deploy/<deploy>
2단계: 토큰 파일이 실제로 존재하고 읽을 수 있는지 확인
kubectl -n <ns> exec -it <pod> -- ls -l /var/run/secrets/eks.amazonaws.com/serviceaccount/
kubectl -n <ns> exec -it <pod> -- head -c 50 /var/run/secrets/eks.amazonaws.com/serviceaccount/token && echo
- 파일이 없으면: IRSA용 projected token 마운트가 안 된 상태입니다. (대부분 SA/Pod 주입 문제)
- 파일이 있는데도 오류면: 다음 단계로 넘어가 OIDC/Trust Policy를 점검합니다.
3단계: EKS OIDC Issuer와 IAM OIDC Provider 매칭
IRSA는 “EKS OIDC Issuer URL”과 “IAM의 OIDC Provider”가 정확히 매칭되어야 합니다.
클러스터 OIDC Issuer 확인
aws eks describe-cluster --name <cluster> --query 'cluster.identity.oidc.issuer' --output text
출력 예:
https://oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E
IAM OIDC Provider 목록에서 해당 URL이 있는지 확인
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 <provider-arn>
여기서 Url 값이 위 EKS issuer(https 제외한 도메인/경로 포함)와 일치해야 합니다.
- 없다면: OIDC provider를 생성해야 합니다.
- eksctl 사용 시:
eksctl utils associate-iam-oidc-provider --cluster <cluster> --approve
4단계: IAM Role Trust Policy(신뢰 정책) 정밀 점검
가장 흔한 실패 지점입니다. 특히 Condition의 키가 issuer URL 기반으로 만들어지는데, 여기서 오타가 자주 납니다.
신뢰 정책 예시(정상)
아래는 namespace=app, serviceaccount=my-sa에만 AssumeRoleWithWebIdentity를 허용하는 예입니다.
{
"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:app:my-sa"
}
}
}
]
}
여기서 자주 틀리는 포인트
Federated의 provider ARN이 다른 계정/다른 클러스터 것StringEquals키에 https://를 포함하거나, 경로 일부가 누락됨- 키는 보통
oidc.eks.<region>.amazonaws.com/id/<id>:sub형태(https 제외)
- 키는 보통
sub의 namespace/sa가 실제 Pod와 다름aud조건이 없거나sts.amazonaws.com이 아님
5단계: ServiceAccount 토큰 방식(Projection)과 Kubernetes 버전 이슈
Kubernetes 1.24+에서 ServiceAccount 토큰은 기본적으로 BoundServiceAccountTokenVolume(프로젝션 토큰) 방식이 일반적이며, EKS IRSA도 이 경로를 사용합니다.
만약 다음과 같은 특이 케이스가 있다면 점검하세요.
- Pod spec에
automountServiceAccountToken: false가 설정됨 - 보안 정책/템플릿에서 토큰 마운트를 막아둠
- sidecar/agent가
/var/run/secrets/...를 덮어씀
확인:
kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.automountServiceAccountToken}'
6단계: SDK가 WebIdentity Provider를 실제로 타는지 확인
IRSA가 정상이어도 애플리케이션 코드/SDK가 web identity를 사용하지 않으면 NoCredentialProviders가 납니다.
(예) Python boto3에서 STS 호출로 검증
컨테이너 안에서 바로 확인하면 가장 빠릅니다.
kubectl -n <ns> exec -it <pod> -- python - <<'PY'
import boto3
sts = boto3.client('sts')
print(sts.get_caller_identity())
PY
- 여기서 호출이 성공하면 IRSA는 대체로 정상이며, 애플리케이션 설정(credential chain override)을 의심하세요.
- 실패하면 에러 메시지가 Trust Policy/OIDC 불일치 힌트를 줍니다.
(예) 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);
구버전 SDK 또는 커스텀 credential provider를 쓰는 경우 IRSA를 못 타는 사례가 있습니다. 특히 컨테이너 이미지에 들어있는 런타임/SDK 버전이 오래되면 web identity 지원이 불완전할 수 있습니다.
7단계: STS 엔드포인트 네트워크/DNS 문제도 의심
에러가 timeout, could not resolve host, i/o timeout처럼 보이면 인증 문제가 아니라 네트워크일 수 있습니다.
- Private cluster에서 NAT/라우팅 문제
- CoreDNS 장애로
sts.amazonaws.com해석 실패 - VPC 엔드포인트/프록시 정책 이슈
먼저 Pod에서 STS DNS/HTTPS를 확인합니다.
kubectl -n <ns> exec -it <pod> -- sh -c 'nslookup sts.amazonaws.com || true'
kubectl -n <ns> exec -it <pod> -- sh -c 'wget -qSO- https://sts.amazonaws.com/ 2>&1 | head'
DNS 자체가 흔들린다면 IRSA를 아무리 고쳐도 실패합니다. 이 경우 AWS EKS CoreDNS CrashLoopBackOff와 DNS 타임아웃 해결을 먼저 해결하세요.
실전: 가장 흔한 “sub 불일치”를 빠르게 잡는 방법
Trust Policy의 sub는 반드시 다음 형식입니다.
system:serviceaccount:<namespace>:<serviceaccount>
Pod가 실제로 어떤 SA를 쓰는지부터 확정합니다.
kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.serviceAccountName}'
그리고 namespace는 Pod의 namespace입니다. 둘을 조합해 Trust Policy의 sub를 맞추면 AccessDenied의 절반은 해결됩니다.
Terraform/Helm로 배포할 때 자주 생기는 함정
- Terraform에서 IAM Role/Policy는 바뀌었는데, Kubernetes 리소스는 롤링이 안 되어 기존 Pod가 옛 설정을 유지
- Helm chart에서
serviceAccount.create=true로 새 SA가 만들어졌는데, 애플리케이션은 다른 SA를 참조 values.yaml에서serviceAccount.annotations가 템플릿에 반영되지 않음
이런 경우는 “리소스는 존재하는데 주입이 안 되는” 형태로 나타납니다. EKS 노드/애드온/IRSA까지 같이 얽혀 있으면 Terraform apply 후 EKS 노드 NotReady - CNI·IRSA·보안그룹 점검처럼 인프라 레벨 점검도 병행하는 게 빠릅니다.
결론: 해결 순서(요약)
- Pod env에
AWS_ROLE_ARN,AWS_WEB_IDENTITY_TOKEN_FILE주입 확인 - 토큰 파일 존재/권한 확인
- EKS OIDC issuer == IAM OIDC provider 일치 확인
- IAM Role Trust Policy에서
Federated,aud,sub정확히 매칭 - 컨테이너에서
sts:GetCallerIdentity로 IRSA 동작 검증 - 타임아웃/리졸브 실패면 DNS/네트워크(CoreDNS/NAT/라우팅)부터 해결
위 순서대로 증거를 확인하면, “IRSA 설정했는데 web identity token 오류”는 대부분 30분 안에 원인이 특정됩니다. 그래도 해결이 안 되면, 실제 에러 로그(전체), SA YAML, IAM Role trust policy JSON, 그리고 describe-cluster의 OIDC issuer 출력값을 함께 놓고 비교하면 거의 항상 불일치 지점을 찾을 수 있습니다.