Published on

EKS IRSA 설정 후 WebIdentityToken 오류 해결

Authors

서론

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 valid
  • AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity
  • NoCredentialProviders: no valid providers in chain
  • failed to refresh cached credentials, failed to retrieve credentials
  • WebIdentityErr: failed to retrieve credentials caused by: AccessDenied

이 글은 “IRSA는 켰는데 왜 STS AssumeRoleWithWebIdentity가 실패하는가?”를 원인별로 분류하고, kubectl/CLI로 증거를 뽑아가며 고치는 방법을 정리합니다. (EKS 네트워크/DNS 이슈가 섞여 있을 때는 AWS EKS CoreDNS CrashLoopBackOff와 DNS 타임아웃 해결도 같이 보세요.)


문제를 3분 안에 분류하는 체크리스트

아래 3가지만 먼저 확인하면, 문제 범위를 절반 이상 줄일 수 있습니다.

  1. Pod에 IRSA 환경변수가 주입됐는가?
  • AWS_ROLE_ARN
  • AWS_WEB_IDENTITY_TOKEN_FILE
  1. ServiceAccount에 role-arn annotation이 정확한가?
  • eks.amazonaws.com/role-arn: arn:aws:iam::...:role/...
  1. 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-role
  • AWS_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·보안그룹 점검처럼 인프라 레벨 점검도 병행하는 게 빠릅니다.


결론: 해결 순서(요약)

  1. Pod envAWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE 주입 확인
  2. 토큰 파일 존재/권한 확인
  3. EKS OIDC issuer == IAM OIDC provider 일치 확인
  4. IAM Role Trust Policy에서 Federated, aud, sub 정확히 매칭
  5. 컨테이너에서 sts:GetCallerIdentityIRSA 동작 검증
  6. 타임아웃/리졸브 실패면 DNS/네트워크(CoreDNS/NAT/라우팅)부터 해결

위 순서대로 증거를 확인하면, “IRSA 설정했는데 web identity token 오류”는 대부분 30분 안에 원인이 특정됩니다. 그래도 해결이 안 되면, 실제 에러 로그(전체), SA YAML, IAM Role trust policy JSON, 그리고 describe-cluster의 OIDC issuer 출력값을 함께 놓고 비교하면 거의 항상 불일치 지점을 찾을 수 있습니다.