Published on

EKS IRSA AssumeRoleWithWebIdentity 403 해결

Authors

서버리스나 EC2 Instance Profile과 달리, EKS의 IRSA(IAM Roles for Service Accounts)는 sts:AssumeRoleWithWebIdentity 흐름이 조금만 어긋나도 곧바로 403으로 떨어집니다. 특히 애플리케이션 로그에는 단순히 AccessDenied 정도로만 보여서 “권한이 부족한가?”로 오해하기 쉽지만, 실제로는 OIDC Provider 미등록, Trust Policy 조건 불일치, ServiceAccount 어노테이션/네임스페이스 불일치, 토큰 audience 문제 같은 설정 계층의 불일치가 대부분입니다.

이 글은 403을 “권한 문제”로 뭉뚱그리지 않고, 증상별로 원인을 빠르게 좁히는 진단 루트정답 설정 예시를 제공합니다.

관련해서 EKS에서 403을 다룬 글로는 EKS Pod에서 AWS ECR 403 AccessDenied 해결도 함께 보면, “Pod 내부에서 AWS 호출이 막힐 때” 공통으로 가져가야 할 점검 습관을 잡는 데 도움이 됩니다.

1) 먼저 확인: 정말 IRSA 경로로 STS를 호출하고 있나

IRSA가 정상이라면, Pod 안에는 다음 환경변수가 주입됩니다.

  • AWS_ROLE_ARN
  • AWS_WEB_IDENTITY_TOKEN_FILE

Pod에서 바로 확인합니다.

kubectl -n myns exec -it deploy/myapp -- sh -lc 'env | egrep "AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION|AWS_DEFAULT_REGION"'

둘 중 하나라도 비어 있으면, 403 이전에 IRSA 자체가 주입되지 않은 상태일 가능성이 큽니다.

또 하나의 흔한 함정은 이미지/런타임에서 AWS SDK가 IRSA를 지원하지 않거나, 커스텀 credential provider가 기본 체인을 덮어써서 IRSA를 무시하는 경우입니다. 이때는 SDK 로그에서 “web identity provider”를 탐지했는지 확인해야 합니다.

2) 403의 정체: 어디서 403이 나오는지 분리하기

AssumeRoleWithWebIdentity 403은 크게 두 부류입니다.

  1. STS가 거절: Trust Policy 또는 OIDC 관련 문제
  2. AssumeRole은 성공했는데 이후 API가 거절: Role permission policy 문제

애플리케이션 로그에 AssumeRoleWithWebIdentity 자체가 실패로 찍힌다면 1번입니다. 반대로 STS는 성공하고, 예를 들어 S3에서 AccessDenied가 나면 2번입니다.

이번 글의 핵심은 1번(진짜 IRSA 403)입니다.

3) 가장 흔한 원인 1: OIDC Provider가 IAM에 등록되지 않음

EKS 클러스터에 OIDC issuer URL이 있어도, IAM에 OIDC Provider 리소스를 만들어두지 않으면 STS가 웹 아이덴티티를 신뢰할 수 없어 실패합니다.

클러스터의 issuer를 확인합니다.

aws eks describe-cluster \
  --name my-cluster \
  --query 'cluster.identity.oidc.issuer' \
  --output text

출력 예시는 보통 다음 형태입니다(부등호는 MDX 빌드 이슈가 있어 엔티티로 표기합니다).

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

IAM OIDC Provider 존재 여부를 확인합니다.

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

없다면 eksctl로 생성하는 것이 가장 안전합니다.

eksctl utils associate-iam-oidc-provider \
  --cluster my-cluster \
  --region ap-northeast-2 \
  --approve

Terraform을 쓰는 팀이라면 여기서 “적용은 됐는데 state lock이 걸려 작업이 멈춘다” 같은 운영 이슈도 종종 엮입니다. 그런 경우는 Terraform state 잠금 오류 - DynamoDB 412 해결처럼 인프라 파이프라인 안정화도 같이 챙기는 편이 좋습니다.

4) 가장 흔한 원인 2: IAM Role Trust Policy의 sub 조건 불일치

IRSA에서 가장 많이 틀리는 부분이 Trust Policy의 조건입니다.

원칙은 다음과 같습니다.

  • Principal.Federated는 OIDC Provider ARN
  • Actionsts:AssumeRoleWithWebIdentity
  • Condition.StringEquals
    • issuer hostpath + :subsystem:serviceaccount:네임스페이스:서비스어카운트명
    • issuer hostpath + :audsts.amazonaws.com

예시 Trust Policy(정상 예):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/ABCDEFG"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.ap-northeast-2.amazonaws.com/id/ABCDEFG:sub": "system:serviceaccount:myns:myapp-sa",
          "oidc.eks.ap-northeast-2.amazonaws.com/id/ABCDEFG:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}

여기서 자주 발생하는 실수:

  • sub에 네임스페이스를 빼먹음
  • ServiceAccount 이름이 실제와 다름(예: default로 생성됐는데 policy는 myapp-sa)
  • issuer id가 다른 클러스터 것(복수 클러스터 운영 시 흔함)
  • StringLike/StringEquals를 잘못 사용하거나 키 이름을 틀림

빠른 검증: ServiceAccount 실제 이름과 네임스페이스

kubectl get sa -n myns
kubectl describe sa -n myns myapp-sa

5) 가장 흔한 원인 3: ServiceAccount 어노테이션이 Role ARN과 불일치

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

  • eks.amazonaws.com/role-arn: arn:aws:iam::...:role/...

확인:

kubectl get sa -n myns myapp-sa -o yaml | sed -n '1,120p'

정상 예:

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

여기서도 자주 틀리는 포인트:

  • Deployment는 serviceAccountName: myapp-sa를 쓰는데, 실제 SA는 default
  • SA는 맞는데 Role ARN이 오타
  • Helm values에서 SA 이름이 바뀌었는데 Trust Policy는 그대로

Deployment에서 SA가 지정됐는지 확인:

kubectl -n myns get deploy myapp -o jsonpath='{.spec.template.spec.serviceAccountName}{"\n"}'

6) 가장 흔한 원인 4: 토큰 audience가 sts.amazonaws.com이 아님

EKS의 projected service account token은 기본 audience가 다를 수 있고, 특정 설정에서 STS가 요구하는 audience와 어긋나면 403이 발생합니다.

Pod 내부에서 토큰의 payload를 확인해 aud를 봅니다. (토큰 출력은 민감할 수 있으니 운영 환경에서는 주의)

kubectl -n myns exec -it deploy/myapp -- sh -lc '
  TOKEN=$(cat $AWS_WEB_IDENTITY_TOKEN_FILE); \
  echo "$TOKEN" | cut -d . -f2 | base64 -d 2>/dev/null | sed "s/}/}\n/g" | egrep "aud|sub|iss" \
'
  • audsts.amazonaws.com이 아니라면
    • Trust Policy에서 aud 조건을 실제 값에 맞추거나
    • 더 권장되는 방식으로 토큰 audience를 STS로 맞추도록 설정합니다.

Kubernetes에서 audience를 명시하는 예시는 Pod spec의 projected token 설정이 필요할 수 있는데, EKS/애드온/버전에 따라 기본값이 달라 운영 표준을 정하는 게 좋습니다.

7) 원인 5: 잘못된 Role에 붙은 Permission Policy로 착각하기

IRSA 403을 해결했다고 생각했는데 여전히 403이 나면, 이제는 STS가 아니라 실제 AWS API 권한을 봐야 합니다.

예를 들어 S3 접근이면 role에 s3:GetObject 같은 권한이 있어야 하고, ECR이면 ecr:GetAuthorizationToken 등이 필요합니다. 이 케이스는 “AssumeRoleWithWebIdentity는 성공”한 뒤에 터지는 403이므로 로그 메시지가 다릅니다.

ECR 관련 403은 증상과 원인 분리가 특히 중요해서, 별도로 EKS Pod에서 AWS ECR 403 AccessDenied 해결에 정리해두었습니다.

8) 실전 디버깅 루틴: CloudTrail로 STS 이벤트를 확정하기

로컬/Pod 로그만으로는 “정확히 어떤 조건이 불일치인지”가 흐릿할 때가 많습니다. 이때 CloudTrail에서 AssumeRoleWithWebIdentity 이벤트를 보면 실패 사유(에러 코드/요청 파라미터)가 훨씬 명확합니다.

CloudTrail Lake나 Event history에서 다음으로 필터링합니다.

  • Event name: AssumeRoleWithWebIdentity
  • Error code: AccessDenied
  • 시간대: 문제 발생 시각

그리고 이벤트의 requestParameters에서 roleArn, webIdentityToken의 클레임(직접 토큰이 보이진 않더라도)과 매칭되는 조건을 역으로 확인합니다.

9) 재현 가능한 “정답 구성” 예시 (YAML + IAM)

아래는 가장 표준적인 구성입니다.

ServiceAccount

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

Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: myns
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      serviceAccountName: myapp-sa
      containers:
        - name: app
          image: public.ecr.aws/aws-cli/aws-cli:2.15.0
          command: ["sh", "-lc"]
          args:
            - |
              aws sts get-caller-identity
              sleep 3600

위 Pod가 올라온 뒤 aws sts get-caller-identity가 성공하면 IRSA의 AssumeRoleWithWebIdentity 경로는 정상입니다.

10) 체크리스트: 10분 안에 끝내는 403 분류

  • Pod에 AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE가 있는가
  • Deployment가 올바른 serviceAccountName을 쓰는가
  • ServiceAccount에 eks.amazonaws.com/role-arn이 정확한가
  • EKS OIDC issuer가 존재하고, IAM OIDC Provider가 등록돼 있는가
  • Role Trust Policy의 Principal.Federated가 올바른 OIDC Provider ARN인가
  • Trust Policy의 subsystem:serviceaccount:ns:sa와 정확히 일치하는가
  • Trust Policy의 audsts.amazonaws.com과 일치하는가
  • CloudTrail에서 AssumeRoleWithWebIdentity 실패 이벤트가 찍히는가

마무리

EKS IRSA의 403은 대부분 “권한 부족”이 아니라 신뢰(Trust) 조건의 불일치입니다. 특히 Trust Policy의 sub와 ServiceAccount의 조합이 1글자만 달라도 STS는 냉정하게 거절합니다.

위 순서대로 점검하면, 불필요하게 IAM permission policy를 늘리거나, 임시로 Node Role에 권한을 얹는 식의 우회 없이도 원인을 정확히 찾아 고칠 수 있습니다.