Published on

EKS IRSA AccessDenied 5분 해결 체크리스트

Authors
Binance registration banner

EKS에서 IRSA(IAM Roles for Service Accounts)를 적용했는데도 애플리케이션 로그에 AccessDenied가 뜨면, 대부분은 “권한이 부족”이라기보다 신뢰 정책(AssumeRoleWithWebIdentity) 매칭 실패 또는 ServiceAccount 토큰/어노테이션 불일치처럼 IRSA 연결 고리가 끊긴 경우가 많습니다.

이 글은 장애 상황에서 가장 빨리 원인을 좁히는 순서대로, 5분 내 진단 루틴과 바로 고칠 수 있는 설정 예시를 제공합니다.

참고로 OIDC 기반 AssumeRole 흐름은 GitHub Actions OIDC와도 구조가 유사합니다. IRSA가 아니라 CI에서 STS 세션/신뢰정책 문제를 겪고 있다면 GitHub Actions OIDC AWS AssumeRole 1시간 제한 해결도 함께 보면 원인 파악에 도움이 됩니다.

0. 증상 패턴부터 분류하기

IRSA 문제의 AccessDenied는 크게 두 갈래로 나뉩니다.

  1. STS에서 막힘
  • 예: AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity
  • 예: InvalidIdentityToken 또는 No OpenIDConnect provider found
  1. STS는 되는데 AWS API에서 막힘
  • 예: AccessDenied: Access Denied (Service: Amazon S3; Status Code: 403; ...)
  • 예: User: arn:aws:sts::...:assumed-role/... is not authorized to perform: s3:GetObject

진단은 1)부터 먼저입니다. STS가 실패하면 어떤 정책을 붙여도 해결되지 않습니다.

1. 5분 진단 루틴(가장 빠른 순서)

1-1. Pod가 정말 IRSA 토큰을 쓰는지 30초 확인

먼저 해당 Pod에 AWS 웹 아이덴티티 환경 변수가 주입됐는지 봅니다.

kubectl -n <namespace> 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/your-irsa-role
  • AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token

둘 중 하나라도 없다면:

  • ServiceAccount 어노테이션이 빠졌거나
  • Pod가 그 ServiceAccount를 쓰지 않거나
  • (드물게) EKS Pod Identity Webhook 주입이 안 되는 상황입니다.

또한 토큰 파일이 존재하는지도 확인합니다.

kubectl -n <namespace> exec -it <pod> -- ls -l /var/run/secrets/eks.amazonaws.com/serviceaccount/token

1-2. Pod가 어떤 ServiceAccount를 쓰는지 10초 확인

kubectl -n <namespace> get pod <pod> -o jsonpath='{.spec.serviceAccountName}{"\n"}'

원래 의도한 ServiceAccount가 아니라면 Deployment/Job 설정부터 수정해야 합니다.

1-3. ServiceAccount 어노테이션 확인(1분)

kubectl -n <namespace> get sa <serviceaccount> -o yaml

아래 어노테이션이 핵심입니다.

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

오타가 흔합니다.

  • 계정 ID 틀림
  • role 이름 틀림
  • namespace 다른데 같은 이름 SA를 보고 있음

1-4. IAM Role 신뢰 정책의 sub/aud가 정확한지(2분)

IRSA에서 가장 흔한 원인입니다. IAM Role의 Trust policy에서 ConditionServiceAccount의 주체(subject) 와 정확히 매칭돼야 합니다.

주체는 아래 형식입니다.

  • system:serviceaccount:<namespace>:<serviceaccount>

그리고 aud는 일반적으로 sts.amazonaws.com 입니다.

IAM Role의 신뢰 정책을 확인합니다.

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

정상 예시는 아래와 같습니다(핵심 부분만).

{
  "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:sub": "system:serviceaccount:your-ns:your-sa",
          "oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}

여기서 자주 틀리는 지점:

  • OIDC provider URL의 리전/클러스터 ID가 다름
  • sub의 namespace 또는 sa 이름이 다름
  • StringLike로 와일드카드를 쓰다가 의도치 않게 매칭 실패 또는 과도한 허용

1-5. 클러스터에 OIDC Provider가 등록돼 있는지(1분)

클러스터 OIDC Issuer URL을 확인합니다.

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

출력은 보통 https://oidc.eks.<region>.amazonaws.com/id/<id> 형태입니다.

이 Issuer에서 https://를 뺀 도메인이 IAM OIDC provider로 등록돼 있어야 합니다.

aws iam list-open-id-connect-providers

등록이 안 되어 있으면 IRSA 자체가 성립하지 않습니다. 이 경우 eksctl을 쓴다면 아래로 생성합니다.

eksctl utils associate-iam-oidc-provider --cluster <cluster-name> --approve

2. 가장 흔한 원인 7가지와 즉시 수정법

2-1. Deployment가 다른 ServiceAccount를 사용

의도한 SA를 만들고 어노테이션을 붙였는데도 Pod가 default SA를 쓰는 경우가 많습니다.

Deployment에 아래를 명시합니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
  namespace: your-ns
spec:
  template:
    spec:
      serviceAccountName: your-sa

적용 후 Pod를 재시작해야 환경 변수가 다시 주입됩니다.

kubectl -n your-ns rollout restart deploy/app

2-2. ServiceAccount 어노테이션 키 오타

정확한 키는 아래입니다.

  • eks.amazonaws.com/role-arn

예시:

kubectl -n your-ns annotate sa your-sa eks.amazonaws.com/role-arn=arn:aws:iam::123456789012:role/your-irsa-role --overwrite

2-3. Trust policy의 sub가 namespace/sa와 불일치

가장 빠른 해결은 Trust policy를 실제 값에 맞춰 고치는 것입니다.

  • namespace: your-ns
  • serviceaccount: your-sa

sub는 반드시 system:serviceaccount:your-ns:your-sa 입니다.

수정 후에도 기존 Pod는 계속 실패할 수 있으니 재기동합니다.

2-4. aud 누락 또는 값 불일치

기본적으로 EKS IRSA는 aud=sts.amazonaws.com 매칭을 기대합니다. 신뢰 정책에 aud 조건이 없거나 다른 값이면 AssumeRoleWithWebIdentity가 거부될 수 있습니다.

신뢰 정책에 아래를 포함시키세요.

  • ...:aud equals sts.amazonaws.com

2-5. 같은 이름의 ServiceAccount가 다른 namespace에 존재

your-sa가 여러 namespace에 있고, Trust policy는 prod를 가리키는데 실제 Pod는 dev에 떠 있는 식의 실수가 잦습니다.

아래로 전체를 검색합니다.

kubectl get sa -A | grep -E '(^NAMESPACE|your-sa)'

2-6. STS는 성공하지만 S3 등 권한이 부족

이 경우에는 IRSA 연결은 정상이고, 단순히 role permission policy가 부족합니다.

애플리케이션 로그에서 호출한 API 액션을 보고 최소 권한으로 추가합니다.

예시: 특정 버킷 읽기

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject"],
      "Resource": ["arn:aws:s3:::your-bucket/*"]
    },
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": ["arn:aws:s3:::your-bucket"]
    }
  ]
}

여기서도 흔한 함정이 있습니다.

  • ListBucket은 버킷 ARN에, GetObject는 오브젝트 ARN에 부여해야 함

2-7. SDK가 IRSA를 안 쓰고 다른 자격 증명을 우선함

컨테이너 이미지에 아래가 들어있으면 IRSA보다 우선될 수 있습니다.

  • AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY가 하드코딩된 환경 변수
  • ~/.aws/credentials가 이미지에 포함

Pod 환경에서 키가 보이면 제거하세요.

kubectl -n <namespace> exec -it <pod> -- env | egrep 'AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|AWS_PROFILE'

3. “지금 당장” 원인 확정하는 디버그 Pod

운영 Pod에 직접 들어가기 어렵다면, 같은 ServiceAccount로 디버그 Pod를 띄워 STS 호출부터 확인하는 게 가장 빠릅니다.

아래는 AWS CLI가 있는 이미지로 테스트하는 예시입니다.

kubectl -n your-ns run irsa-debug \
  --image=amazon/aws-cli:2.15.40 \
  --serviceaccount=your-sa \
  --restart=Never \
  -it --rm -- sh

쉘에 들어가면 다음을 확인합니다.

aws sts get-caller-identity

정상이라면 Arnassumed-role/your-irsa-role 형태로 나옵니다.

추가로 실제 문제였던 API를 재현합니다.

aws s3 ls s3://your-bucket/
  • 여기서 AccessDenied가 나면 permission policy 문제
  • 여기서 성공하면 애플리케이션 쪽 SDK 설정/환경 변수 문제 가능성이 큽니다

4. 재발 방지: IRSA 변경 시 체크해야 할 것들

4-1. 변경 후에는 반드시 Pod 재기동

IRSA는 Pod 생성 시점에 토큰/환경 변수가 주입됩니다. ServiceAccount 어노테이션이나 role trust policy를 바꿨다면, 아래 중 하나가 필요합니다.

kubectl -n your-ns rollout restart deploy/<name>

또는 Job/CronJob이면 새로 실행되게 해야 합니다.

4-2. GitOps 환경(Argo CD)에서는 드리프트 확인

IRSA 관련 리소스는 ServiceAccountDeployment가 함께 맞물립니다. GitOps에서 일부만 반영되면 계속 AccessDenied가 반복될 수 있습니다. 동기화 상태가 꼬였을 때는 Argo CD Sync 실패 - OutOfSync·Degraded 해결처럼 상태/이벤트를 먼저 정리한 뒤 IRSA 변경을 적용하는 게 안전합니다.

4-3. 최소 권한 + 명시적 리소스 범위

IRSA role에 광범위 권한을 주면 빠르게 해결되는 것처럼 보이지만, 나중에 권한 축소 시 다시 장애가 납니다. AccessDenied가 났던 API 액션과 리소스 ARN을 기준으로 좁혀가세요.

5. 결론: 5분 안에 끝내는 핵심만 요약

  • Pod에서 AWS_ROLE_ARNAWS_WEB_IDENTITY_TOKEN_FILE이 보이는지 확인
  • Pod가 의도한 serviceAccountName을 쓰는지 확인
  • ServiceAccount에 eks.amazonaws.com/role-arn이 정확한지 확인
  • IAM Role trust policy의 OIDC provider, sub, aud가 정확히 매칭되는지 확인
  • STS가 되면 그 다음은 permission policy(예: S3 ARN 범위) 문제

위 순서대로 보면, 대부분의 EKS IRSA AccessDenied는 “권한을 더 주는 것”이 아니라 “신뢰 정책과 ServiceAccount 매칭을 바로잡는 것”만으로 빠르게 해결됩니다.