Published on

EKS IRSA 설정했는데 AccessDenied 뜰 때 점검

Authors

서론

EKS에서 IRSA(IAM Roles for Service Accounts)를 구성해 Pod에 AWS 권한을 위임했는데, 애플리케이션 로그에 아래와 같은 오류가 뜨는 경우가 많습니다.

  • AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity
  • InvalidIdentityToken
  • No OpenIDConnect provider found in your account

대부분 “IRSA를 설정했다”는 사실만으로는 충분하지 않습니다. IRSA는 (1) 클러스터 OIDC Provider, (2) IAM Role의 Trust Policy, (3) Kubernetes ServiceAccount 어노테이션, (4) Pod에 주입되는 토큰/환경변수, (5) 네트워크로 STS 접근 가능 여부가 모두 맞물려야 동작합니다.

이 글은 원인별로 재현 가능한 체크리스트와 함께, 실제로 어떤 값을 확인해야 하는지 커맨드 중심으로 정리합니다. (네트워크 이슈로 STS 호출 자체가 막히는 경우도 있으니, egress가 의심되면 EKS에서 Pod는 정상인데 egress만 막힐 때 점검도 함께 보세요.)


1) 에러 메시지로 1차 분류하기

먼저 에러 문구가 무엇인지에 따라 방향이 갈립니다.

A. AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity

  • Trust policy 조건 불일치(sub/aud)
  • ServiceAccount 이름/네임스페이스 불일치
  • OIDC Provider ARN/URL 불일치
  • Role에 붙인 Policy는 맞는데 AssumeRole 자체가 거부되는 케이스

B. No OpenIDConnect provider found

  • 클러스터 OIDC Provider를 IAM에 미등록
  • 다른 계정/리전에 등록한 Provider를 참조

C. InvalidIdentityToken / invalid audience

  • Trust policy의 aud 조건이 잘못됨(대개 sts.amazonaws.com)
  • 토큰 파일이 예상과 다름(구식 토큰, projected token 미사용 등)

D. STS가 403/timeout인데 IRSA처럼 보이는 경우

  • IPv6/라우팅/DNS 등으로 STS 엔드포인트 접근 실패 → SDK가 “자격 증명 획득 실패”로 포장
  • 특히 IPv6 환경에서 STS만 403이 나는 특이 케이스는 EKS Pod에서 IPv6로만 STS 403 뜰 때 해결을 참고하세요.

2) 가장 흔한 원인: Trust Policy의 sub/aud 조건 불일치

IRSA의 핵심은 IAM Role의 Trust Relationship입니다. 여기서 Principal.Federated(OIDC Provider)와 Condition(sub/aud)이 한 글자라도 다르면 AccessDenied가 납니다.

2-1. 올바른 Trust Policy 예시

아래는 가장 표준적인 형태입니다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>:aud": "sts.amazonaws.com",
          "oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>:sub": "system:serviceaccount:<NAMESPACE>:<SERVICEACCOUNT_NAME>"
        }
      }
    }
  ]
}

자주 틀리는 포인트

  • sub의 포맷은 반드시 system:serviceaccount:<ns>:<sa>
  • 네임스페이스/SA 이름을 바꿨는데 Role Trust는 그대로인 경우
  • StringLike/StringEquals를 섞어 쓰다 의도치 않게 매칭 실패
  • OIDC ID가 다른 클러스터의 값(클러스터 재생성 후 OIDC가 바뀜)

2-2. 현재 Role Trust Policy 확인

aws iam get-role \
  --role-name <ROLE_NAME> \
  --query 'Role.AssumeRolePolicyDocument' \
  --output json

여기서 Principal.FederatedCondition의 키가 클러스터 OIDC URL과 정확히 일치하는지 확인합니다.


3) OIDC Provider 자체가 잘못되었거나 누락된 경우

3-1. 클러스터 OIDC Issuer URL 확인

aws eks describe-cluster \
  --name <CLUSTER_NAME> \
  --region <REGION> \
  --query 'cluster.identity.oidc.issuer' \
  --output text

출력 예:

  • https://oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E

3-2. IAM에 OIDC Provider가 등록되어 있는지 확인

aws iam list-open-id-connect-providers --output json

그리고 해당 Provider의 URL이 맞는지:

aws iam get-open-id-connect-provider \
  --open-id-connect-provider-arn arn:aws:iam::<ACCOUNT_ID>:oidc-provider/oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>

누락되었다면(대표적으로 eksctl 사용)

eksctl utils associate-iam-oidc-provider \
  --cluster <CLUSTER_NAME> \
  --region <REGION> \
  --approve

4) ServiceAccount 어노테이션이 Role ARN을 제대로 가리키는지

IRSA는 ServiceAccount에 아래 어노테이션이 있어야 합니다.

  • eks.amazonaws.com/role-arn: arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME>

4-1. ServiceAccount 확인

kubectl -n <NAMESPACE> get sa <SERVICEACCOUNT_NAME> -o yaml

정상 예:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-sa
  namespace: my-ns
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-irsa-role

자주 틀리는 포인트

  • Pod는 default SA를 쓰는데, 다른 SA에만 어노테이션을 붙임
  • Helm/Kustomize 적용 과정에서 어노테이션이 덮어씌워짐
  • Role ARN의 계정/이름 오타

4-2. Pod가 실제로 어떤 SA를 쓰는지 확인

kubectl -n <NAMESPACE> get pod <POD_NAME> -o jsonpath='{.spec.serviceAccountName}'; echo

5) Pod 내부에서 IRSA 토큰/환경변수 주입 여부 확인

IRSA가 정상이라면 Pod에는 보통 다음이 존재합니다.

  • AWS_WEB_IDENTITY_TOKEN_FILE
  • AWS_ROLE_ARN
  • /var/run/secrets/eks.amazonaws.com/serviceaccount/token

5-1. Pod 내부에서 확인

kubectl -n <NAMESPACE> exec -it <POD_NAME> -- sh -c '
  env | egrep "AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION|AWS_DEFAULT_REGION" || true
  ls -l /var/run/secrets/eks.amazonaws.com/serviceaccount/ || true
  head -c 50 /var/run/secrets/eks.amazonaws.com/serviceaccount/token 2>/dev/null || true
  echo
'

주입이 안 된다면

  • Pod가 해당 ServiceAccount를 사용하지 않음
  • (드물게) admission/webhook/PSA 정책이 projected volume을 막음
  • 매우 오래된 EKS/구성에서 토큰 프로젝션 설정이 꼬였을 수 있음

6) STS 호출을 직접 테스트해 “AssumeRole이 되는지” 확인

애플리케이션(SDK) 문제인지, IRSA/IAM 문제인지 분리하려면 Pod에서 STS를 직접 호출해보는 게 가장 빠릅니다.

6-1. awscli 디버그로 확인

kubectl -n <NAMESPACE> exec -it <POD_NAME> -- sh -c '
  aws sts get-caller-identity --debug 2>&1 | tail -n 60
'

여기서도 AssumeRoleWithWebIdentity가 AccessDenied면 Trust Policy/SA/OIDC 문제일 확률이 높습니다.

6-2. 토큰으로 AssumeRoleWithWebIdentity를 강제 실행

kubectl -n <NAMESPACE> exec -it <POD_NAME> -- sh -c '
  TOKEN=$(cat $AWS_WEB_IDENTITY_TOKEN_FILE)
  aws sts assume-role-with-web-identity \
    --role-arn "$AWS_ROLE_ARN" \
    --role-session-name irsa-debug \
    --web-identity-token "$TOKEN" \
    --duration-seconds 900
'
  • 여기서 성공하면: SDK/환경변수/리전 설정 또는 애플리케이션 자격증명 체인 문제
  • 여기서 실패하면: IRSA 핵심 구성(Trust/OIDC/SA) 문제

7) aud 조건(=sts.amazonaws.com) 불일치로 인한 거부

간혹 Trust Policy에서 aud를 빼거나 다른 값으로 넣어 문제가 생깁니다. EKS IRSA의 표준 audience는 대부분 sts.amazonaws.com입니다.

7-1. 토큰 payload에서 aud/sub 확인(간단 버전)

JWT를 완전 검증하진 않더라도 payload를 base64 decode 해서 값만 확인할 수 있습니다.

kubectl -n <NAMESPACE> exec -it <POD_NAME> -- sh -c '
  python - <<"PY"
import os, json, base64
p = os.environ.get("AWS_WEB_IDENTITY_TOKEN_FILE")
if not p:
    raise SystemExit("AWS_WEB_IDENTITY_TOKEN_FILE not set")
jwt = open(p,"r").read().strip().split(".")
payload = jwt[1] + "=" * (-len(jwt[1]) % 4)
data = json.loads(base64.urlsafe_b64decode(payload))
print("aud:", data.get("aud"))
print("sub:", data.get("sub"))
print("iss:", data.get("iss"))
PY
'

출력된 sub가 Trust Policy의 ...:sub와 정확히 같은지, audsts.amazonaws.com인지 확인합니다.


8) (의외로 흔함) 같은 이름의 SA를 다른 네임스페이스에서 쓰는 실수

Trust Policy는 system:serviceaccount:<namespace>:<name>네임스페이스까지 포함합니다.

  • default:app로 Trust를 만들어놓고
  • 실제 Pod는 prod:app을 쓰면

즉시 AccessDenied가 발생합니다.

해결은 둘 중 하나입니다.

  • Trust Policy의 sub를 정확한 ns로 수정
  • 혹은 StringLike로 여러 SA를 허용(권한 범위가 넓어지니 신중)

예:

"Condition": {
  "StringEquals": {
    "oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>:aud": "sts.amazonaws.com"
  },
  "StringLike": {
    "oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>:sub": "system:serviceaccount:prod:*"
  }
}

9) 네트워크/엔드포인트 문제로 STS에 도달 못하는 케이스

IRSA 설정이 맞아도 Pod가 STS에 접근하지 못하면 결과적으로 “자격증명 획득 실패”가 납니다. 특히 아래 상황에서 STS가 실패합니다.

  • NAT/라우팅/SG/NACL로 인터넷 egress 차단
  • Private cluster에서 STS VPC Endpoint 미구성
  • DNS 문제로 sts.<region>.amazonaws.com 해석 실패

이 경우 IRSA 자체보다는 egress 진단이 먼저입니다. Pod egress가 의심되면 EKS에서 Pod는 정상인데 egress만 막힐 때 점검을 먼저 확인하세요.

간단한 확인:

kubectl -n <NAMESPACE> exec -it <POD_NAME> -- sh -c '
  nslookup sts.${AWS_REGION}.amazonaws.com || true
  wget -S -O- https://sts.${AWS_REGION}.amazonaws.com/ 2>&1 | head -n 30 || true
'
  • DNS가 안 되면 CoreDNS/노드 DNS/라우팅 문제
  • HTTPS 연결이 안 되면 NAT/엔드포인트/방화벽 문제

10) 빠른 결론: “IRSA AccessDenied” 10분 점검 순서

운영에서 시간을 아끼려면 아래 순서가 효율적입니다.

  1. Pod가 올바른 ServiceAccount를 쓰는지 확인
    • kubectl get pod ... -o jsonpath='{.spec.serviceAccountName}'
  2. ServiceAccount에 role-arn 어노테이션 확인
    • kubectl get sa ... -o yaml
  3. Pod에 AWS_ROLE_ARN / AWS_WEB_IDENTITY_TOKEN_FILE 주입 확인
    • kubectl exec ... env, 토큰 파일 존재 확인
  4. 클러스터 OIDC Issuer URL 확인
    • aws eks describe-cluster ...
  5. IAM OIDC Provider 등록 여부 확인
    • aws iam list-open-id-connect-providers
  6. Role Trust Policy의 Principal/Condition이 정확한지 확인
    • aws iam get-role ...
  7. Pod에서 sts get-caller-identity로 최종 검증

이 흐름대로 보면 대부분의 sts:AssumeRoleWithWebIdentity AccessDenied는 1~6번에서 잡힙니다.


마무리

IRSA는 “권한 정책(Policy)을 잘 붙였다”보다 “AssumeRoleWithWebIdentity가 성립하도록 Trust/OIDC/SA가 정확히 맞물렸다”가 핵심입니다. 특히 sub 조건의 네임스페이스/서비스어카운트 이름 불일치, OIDC Provider 누락/오참조가 가장 흔한 원인입니다.

만약 Trust/SA/OIDC가 모두 맞는데도 STS 호출이 실패한다면, 그때는 네트워크(egress)나 IPv6/엔드포인트 이슈를 의심해 보세요. IPv6 환경에서 STS만 403이 나는 특이 케이스는 EKS Pod에서 IPv6로만 STS 403 뜰 때 해결이 빠른 지름길이 될 수 있습니다.