Published on

EKS IRSA로 Pod AWS 권한 오류 5분 해결

Authors

서버리스나 EC2에서는 잘 되던 AWS 권한이 EKS Pod 안에서만 갑자기 AccessDenied로 터질 때, 대부분의 원인은 IRSA 설정의 작은 불일치입니다. 특히 아래 조합이 흔합니다.

  • 서비스어카운트에 role-arn을 달았는데도 여전히 노드 IAM Role로 호출됨
  • AssumeRoleWithWebIdentity가 실패하거나, 아예 STS 호출 자체가 불안정
  • sub/aud 조건이 맞지 않아 신뢰 정책(Trust Policy) 에서 거절

이 글은 “원인 후보를 넓게 나열”하기보다, 5분 안에 결론을 내는 순서로 정리합니다. (진짜로 대부분 3~10분 내 해결됩니다.)

> 참고: STS 엔드포인트 접근 자체가 불안정하거나 502가 난다면 IRSA 이전에 네트워크/프록시 문제일 수 있습니다. 이 경우 EKS Pod에서 STS 502 Bad Gateway 원인·해결도 함께 확인하세요.

IRSA 권한 오류의 전형적인 증상

애플리케이션 로그에서 주로 아래 형태로 나타납니다.

  • AccessDenied: User: arn:aws:sts::...:assumed-role/<node-role>/<instance-id> is not authorized to perform: s3:ListBucket
    • 중요 포인트: 호출 주체가 assumed-role/<node-role>이면 IRSA가 아니라 노드 역할로 호출 중입니다.
  • InvalidIdentityToken / AccessDenied (AssumeRoleWithWebIdentity)
    • OIDC/신뢰정책/토큰 마운트/aud 불일치 가능성이 큽니다.
  • NoCredentialProviders / Unable to locate credentials
    • 웹 아이덴티티 토큰 파일 경로/환경변수/SDK 버전 문제일 수 있습니다.

5분 해결 체크리스트 (가장 빠른 순서)

아래 순서대로 보면 “어디서 깨지는지”가 즉시 좁혀집니다.

1) Pod가 정말 IRSA 서비스어카운트를 쓰고 있나?

먼저 Pod가 어떤 ServiceAccount로 떠 있는지 확인합니다.

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

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

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-sa
  namespace: my-ns
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::<ACCOUNT_ID>:role/my-irsa-role
  • 어노테이션 키 오타 (rolearn, role_arn 등) 매우 흔합니다.
  • Helm/Kustomize로 배포 시 다른 SA로 덮어쓰기 되는 경우도 많습니다.

2) Pod 안에 IRSA 토큰이 마운트되어 있나?

IRSA는 기본적으로 Pod에 아래가 주입됩니다.

  • AWS_ROLE_ARN
  • AWS_WEB_IDENTITY_TOKEN_FILE

Pod 내부에서 확인합니다.

kubectl -n <ns> exec -it <pod> -- sh -c 'env | egrep "AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION|AWS_DEFAULT_REGION"'

kubectl -n <ns> exec -it <pod> -- sh -c 'ls -al $AWS_WEB_IDENTITY_TOKEN_FILE && head -c 20 $AWS_WEB_IDENTITY_TOKEN_FILE && echo'

판단 기준:

  • AWS_WEB_IDENTITY_TOKEN_FILE가 비어 있거나 파일이 없으면
    • ServiceAccount 토큰 마운트가 꺼졌거나(automountServiceAccountToken: false),
    • Admission(Webhook) 주입이 안 된 것입니다.

Pod/SA에 automount가 꺼져 있는지 확인하세요.

kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.automountServiceAccountToken}{"\n"}'
kubectl -n <ns> get sa <sa> -o jsonpath='{.automountServiceAccountToken}{"\n"}'
  • 둘 중 하나라도 false면 토큰이 안 들어갈 수 있습니다.

3) 호출 주체가 노드 Role로 나오면: SDK가 IRSA를 무시하는 케이스

로그에 노드 역할이 찍힌다면, 애플리케이션이 웹 아이덴티티 체인을 못 타는 겁니다.

주요 원인:

  • 너무 오래된 AWS SDK/라이브러리
  • 커스텀 credential provider를 강제로 사용
  • AWS_EC2_METADATA_DISABLED=false 상태에서 메타데이터(IMDS)가 우선되어 노드 자격증명을 집어감(언어/버전에 따라 다름)

해결 방향:

  • AWS SDK 버전 업데이트 (특히 Java/Python/Go 모두 최신 권장)
  • 불필요한 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY가 컨테이너에 주입되어 있지 않은지 확인
kubectl -n <ns> exec -it <pod> -- sh -c 'env | egrep "AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|AWS_SESSION_TOKEN"'

만약 위 값들이 존재하면 IRSA보다 우선될 수 있으니 제거하세요.

4) IAM Role 신뢰 정책(Trust Policy)에서 sub/aud가 정확한가?

IRSA의 핵심은 IAM Role의 신뢰 정책이 EKS OIDC Provider + 특정 ServiceAccount만 허용하도록 되어 있어야 한다는 점입니다.

정상 예시(가장 흔한 패턴):

{
  "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:my-ns:my-sa"
        }
      }
    }
  ]
}

여기서 5분 내 가장 많이 잡히는 실수:

  • sub의 네임스페이스가 다름 (default로 되어 있다든지)
  • ServiceAccount 이름이 다름 (my-sa vs my_sa)
  • aud가 누락되었거나 다른 값
  • OIDC Provider ARN의 <OIDC_ID>가 다른 클러스터 값

클러스터의 OIDC issuer를 확인:

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

출력 예:

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

IAM OIDC provider가 아예 없으면 IRSA가 성립하지 않습니다. (특히 오래된 클러스터를 수동 구성했거나, 다른 계정/리전에 잘못 만들었을 때)

5) 실제 STS 호출을 Pod 안에서 즉시 재현해보기

앱 로그만 보지 말고, Pod 내부에서 STS를 직접 때리면 원인 분리가 빨라집니다.

컨테이너에 AWS CLI가 없다면 임시 디버그 파드를 띄우는 방법이 가장 빠릅니다.

kubectl -n my-ns run irsa-debug \
  --image=amazon/aws-cli:2.15.0 \
  --serviceaccount=my-sa \
  --rm -it --command -- sh

쉘에서:

aws sts get-caller-identity

기대 결과:

  • Arnassumed-role/my-irsa-role/... 형태로 나오면 IRSA는 정상입니다.
  • 여기서도 실패하면 IRSA/OIDC/신뢰정책/네트워크 문제입니다.

추가로 환경 변수 확인:

env | egrep 'AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION|AWS_DEFAULT_REGION'

6) 권한은 붙었는데 특정 AWS API만 AccessDenied라면: 정책(Policy) 문제

IRSA 자체는 성공(즉, get-caller-identity 성공)했는데 S3/DynamoDB/Secrets Manager 등에서만 실패하면, 이제는 단순히 IAM Policy 권한 부족입니다.

예: S3 버킷 목록 조회가 필요하면(예시)

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

CloudWatch Logs, KMS, Secrets Manager는 리소스 ARN/조건이 까다로워서 “붙였는데도 안 됨”이 자주 발생합니다. 이 단계에서는 CloudTrail 이벤트를 보면 정답이 바로 나옵니다.

가장 흔한 원인 TOP 7 (현장 기준)

  1. Pod가 의도한 ServiceAccount를 안 씀(Deployment spec 누락)
  2. ServiceAccount 어노테이션 오타/다른 리소스에 의해 덮임
  3. automountServiceAccountToken: false로 토큰이 안 들어감
  4. IAM Role Trust Policy의 sub에 namespace/name 불일치
  5. OIDC Provider가 다른 클러스터/리전/계정 것으로 연결됨
  6. 앱에 하드코딩된 정적 키(AK/SK) 또는 오래된 SDK로 IRSA 체인을 못 탐
  7. STS 네트워크 장애/프록시 문제(특히 사설망 + 엔드포인트 정책/프록시)

> EKS에서 “AWS 호출은 되는데 ECR만 403으로 이미지 풀 실패” 같은 케이스는 IRSA가 아니라 VPC 엔드포인트 정책/네트워크 정책이 원인인 경우가 많습니다. 비슷한 결로 EKS ImagePullBackOff 403? ECR VPC 엔드포인트 정책 점검도 참고할 만합니다.

재발 방지: 배포 파이프라인에 넣을 3가지 자동 점검

1) 매 릴리즈마다 STS caller identity 스모크 테스트

간단한 Job으로 aws sts get-caller-identity를 실행하고, Arn에 기대하는 role이 포함되는지 검사합니다.

apiVersion: batch/v1
kind: Job
metadata:
  name: irsa-smoke
  namespace: my-ns
spec:
  template:
    spec:
      serviceAccountName: my-sa
      restartPolicy: Never
      containers:
        - name: awscli
          image: amazon/aws-cli:2.15.0
          command: ["sh", "-c"]
          args:
            - |
              set -e
              aws sts get-caller-identity

2) Trust Policy의 sub를 와일드카드로 넓히지 말기

급한 해결로 system:serviceaccount:my-ns:* 같은 와일드카드를 쓰면 편하지만, 권한 경계가 무너집니다. 대신 “정확한 SA 1개”로 고정하고, 필요하면 역할을 여러 개로 쪼개세요.

3) SDK/런타임 이미지의 기본값을 표준화

  • AWS SDK 최신 버전 유지
  • 정적 키 환경변수 금지
  • 필요 시 AWS_REGION/AWS_DEFAULT_REGION 명시

결론: 5분 안에 끝내는 핵심은 ‘누가 호출했는지’부터

IRSA 트러블슈팅은 복잡해 보이지만, 실제로는 아래 두 질문으로 정리됩니다.

  1. Pod 안에서 STS get-caller-identity가 어떤 Arn을 찍는가?
  2. 그 Arn이 의도한 IRSA Role이 아니라면, 토큰/어노테이션/신뢰정책 중 어디서 끊겼는가?

위 체크리스트 순서대로 확인하면, 대부분은 ServiceAccount/Trust Policy의 불일치 또는 토큰 마운트 문제에서 바로 해결됩니다. STS 자체가 불안정한 경우라면 앞서 언급한 STS 502 글도 함께 점검해 네트워크 계층까지 빠르게 마무리하세요.