Published on

EKS IRSA가 안 먹을 때 STS·OIDC·토큰 진단

Authors

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는 아래 흐름이 모두 맞아야 정상 동작합니다.

  1. EKS 클러스터에 OIDC Issuer가 존재한다.
  2. AWS IAM에 OIDC Provider가 등록되어 있고, thumbprint/URL이 일치한다.
  3. Kubernetes ServiceAccount에 role-arn 어노테이션이 걸려 있다.
  4. Pod에 Projected ServiceAccount Token(JWT) 이 마운트된다.
  5. AWS SDK(또는 aws-cli)가 AWS_WEB_IDENTITY_TOKEN_FILEAWS_ROLE_ARN을 읽는다.
  6. STS AssumeRoleWithWebIdentity 호출이 성공한다.
  7. 반환된 임시 자격증명으로 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 문제, 토큰 서명 검증 실패
  • AccessDenied with AssumeRoleWithWebIdentity
    • IAM trust policy의 sub/aud 조건 불일치, role-arn 잘못, OIDC provider ARN 잘못
  • 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_ARN
  • AWS_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 URL
  • aud: 보통 sts.amazonaws.com
  • sub: 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를 조건으로 검증한다는 점입니다. 따라서 진단 순서는 항상 다음이 가장 효율적입니다.

  1. Pod ↔ ServiceAccount 연결 확인
  2. 토큰 존재 여부 및 claim(iss/aud/sub) 확인
  3. IAM OIDC Provider URL/ClientIDList 확인
  4. Role trust policy의 조건이 토큰 claim과 일치하는지 확인
  5. 마지막으로 STS 네트워크/프록시 문제 확인

EKS 운영 중 네트워크 이슈로 다른 증상도 같이 보인다면, EKS에서 kubectl port-forward 끊김·hang 해결처럼 “클러스터 내부 egress/연결 안정성” 관점의 점검도 병행하면 문제를 더 빨리 분리할 수 있습니다.