Published on

EKS IRSA AccessDenied 권한 오류 빠른 해결

Authors

서버리스나 노드 IAM에 의존하지 않고 Pod 단위로 AWS 권한을 분리하려면 IRSA가 사실상 표준입니다. 그런데 운영에서 가장 자주 터지는 문제가 AccessDenied 입니다. 특히 로그에는 S3, SQS, Secrets Manager 같은 서비스의 AccessDenied 로 보이지만, 실제 원인은 STS AssumeRole 단계(Trust Policy, OIDC, ServiceAccount 바인딩)에서 이미 잘못된 경우가 많습니다.

이 글은 원인별로 “어디부터 확인하면 가장 빨리 끝나는지”에 집중합니다. 아래 체크리스트대로 따라가면 대개 10분 내에 원인 지점이 좁혀집니다.

관련해서 STS 토큰/AssumeRole 이슈까지 포함해 더 깊게 보고 싶다면 이 글도 함께 보세요: AWS STS 토큰 만료로 403? IRSA·AssumeRole 점검

1) 증상 분류: AccessDenied가 “어디서” 났는지부터

IRSA에서 AccessDenied 는 크게 두 부류입니다.

  1. STS AssumeRoleWithWebIdentity 단계 실패

    • Pod가 역할을 아예 못 받아옵니다.
    • 애플리케이션이 AWS SDK 호출 전에 이미 자격증명 로딩에서 실패하거나, STS 관련 에러가 로그에 보입니다.
  2. 역할 Assume 성공, 서비스 API 호출 권한 부족

    • STS는 성공했지만, 예를 들어 S3 GetObject 나 SQS ReceiveMessage 권한이 정책에 없습니다.

가장 빠른 분기 방법은 Pod 안에서 현재 자격증명이 어떤 ARN으로 잡혔는지 확인하는 것입니다.

Pod 내부에서 현재 역할 ARN 확인

아래는 AWS CLI가 있는 컨테이너 기준입니다.

aws sts get-caller-identity
  • 여기서 Arn 이 기대한 Role ARN이면 “AssumeRole은 성공”입니다. 이제 IAM 정책(permissions policy) 쪽을 봐야 합니다.
  • 여기서 실패하거나, 아예 다른 Role(예: 노드 Role)로 나오면 IRSA 바인딩/Trust/OIDC를 의심합니다.

CLI가 없다면 SDK 로그에서 credential provider chain 메시지를 켜거나, 디버그용 ephemeral container를 붙여 확인하는 게 빠릅니다.

2) 1분 체크: ServiceAccount에 role-arn이 정확히 붙었나

IRSA의 출발점은 ServiceAccount annotation 입니다.

kubectl -n your-namespace get sa your-sa -o yaml

다음을 확인합니다.

  • eks.amazonaws.com/role-arn 값이 정확한지
  • 네임스페이스가 맞는지
  • Deployment/Pod가 실제로 그 ServiceAccount를 쓰는지

Deployment가 기본 ServiceAccount를 쓰고 있는 경우가 생각보다 흔합니다.

kubectl -n your-namespace get deploy your-deploy -o jsonpath='{.spec.template.spec.serviceAccountName}'

출력이 비어 있으면 default 를 쓰고 있는 것입니다.

즉시 수정 예시

kubectl -n your-namespace patch deploy your-deploy \
  -p '{"spec":{"template":{"spec":{"serviceAccountName":"your-sa"}}}}'

ServiceAccount를 바꾸면 Pod 재기동이 필요합니다.

kubectl -n your-namespace rollout restart deploy your-deploy

3) OIDC Provider 존재 여부와 Cluster OIDC Issuer 확인

IRSA는 EKS OIDC Provider가 IAM에 등록되어 있어야 합니다. 클러스터에 OIDC issuer URL이 있고, 동일한 issuer가 IAM OIDC provider로 생성되어 있어야 합니다.

클러스터 OIDC issuer 확인

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

출력 예시는 보통 https://oidc.eks.ap-northeast-2.amazonaws.com/id/XXXX 형태입니다. 여기서 https:// 는 나중에 trust policy의 condition key를 만들 때 제거해서 씁니다.

IAM에 OIDC provider가 있는지 확인

aws iam list-open-id-connect-providers

OIDC provider ARN을 찾은 뒤 상세를 봅니다.

aws iam get-open-id-connect-provider \
  --open-id-connect-provider-arn your-oidc-provider-arn
  • Url 이 클러스터 issuer와 동일해야 합니다.
  • Thumbprint가 오래되었거나(드물지만), 다른 클러스터 issuer로 등록된 경우 IRSA가 실패합니다.

OIDC provider가 없다면 eksctl utils associate-iam-oidc-provider 로 연결하는 것이 가장 빠릅니다.

eksctl utils associate-iam-oidc-provider \
  --cluster your-cluster \
  --approve

4) Trust Policy에서 subaud 조건이 정확한지

IRSA의 핵심은 IAM Role의 Trust Policy(AssumeRole policy)입니다. 여기서 가장 흔한 실수는 sub 값이 ServiceAccount와 불일치하는 것, 혹은 aud 조건 누락/오타입니다.

올바른 Trust Policy 형태

아래 예시에서 YOUR_ACCOUNT_ID, OIDC_ID, your-namespace, your-sa 를 환경에 맞게 바꾸세요. 또한 condition key에 들어가는 issuer는 https:// 를 제거한 문자열입니다.

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

sub 가 와일드카드인 경우

여러 ServiceAccount에 같은 Role을 주고 싶어서 StringLike 와 와일드카드를 쓰는 경우가 있습니다.

"Condition": {
  "StringEquals": {
    "oidc.eks.ap-northeast-2.amazonaws.com/id/OIDC_ID:aud": "sts.amazonaws.com"
  },
  "StringLike": {
    "oidc.eks.ap-northeast-2.amazonaws.com/id/OIDC_ID:sub": "system:serviceaccount:your-namespace:*"
  }
}

운영 관점에서는 최소 권한을 위해 가능한 한 sub 를 구체적으로 박는 것이 보안상 안전합니다.

현재 Role trust policy 확인

aws iam get-role --role-name your-irsa-role \
  --query 'Role.AssumeRolePolicyDocument'

여기서 sub 의 네임스페이스/SA명이 실제와 다르면 100% AssumeRole이 실패합니다.

5) Pod에 IRSA 환경변수가 주입되는지 확인

EKS는 IRSA가 설정된 ServiceAccount를 사용하는 Pod에 다음과 같은 것들을 주입합니다.

  • AWS_ROLE_ARN
  • AWS_WEB_IDENTITY_TOKEN_FILE

Pod에서 확인합니다.

kubectl -n your-namespace exec -it pod-name -- env | grep AWS_

두 값이 없다면:

  • Pod가 올바른 ServiceAccount를 쓰지 않거나
  • EKS 버전/설정 문제, 혹은 admission 단계에서 변형이 막힌 경우(특정 보안 정책, 커스텀 webhook 등)

를 의심해야 합니다.

토큰 파일 존재도 확인합니다.

kubectl -n your-namespace exec -it pod-name -- sh -c 'ls -al $(dirname "$AWS_WEB_IDENTITY_TOKEN_FILE")'

6) Assume는 됐는데 여전히 AccessDenied: 권한 정책(permissions policy) 점검

aws sts get-caller-identity 가 기대 Role을 반환한다면 Trust/OIDC는 통과한 상태입니다. 이제는 Role에 붙은 permissions policy가 실제 API를 허용하는지 확인해야 합니다.

Role에 붙은 정책 확인

aws iam list-attached-role-policies --role-name your-irsa-role
aws iam list-role-policies --role-name your-irsa-role
  • attached 는 managed policy
  • role-policies 는 inline policy

가장 빠른 진단: CloudTrail에서 거부 이벤트 찾기

CloudTrail이 켜져 있다면 AccessDenied 이벤트에서 userIdentity.arneventName 을 보면 정확히 어떤 액션이 막혔는지 바로 나옵니다.

예를 들어 S3 AccessDenied 면 필요한 액션은 대개 s3:GetObject, s3:ListBucket 같은 식으로 좁혀집니다.

예시: S3 읽기 권한 최소 정책

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

S3는 ListBucket 은 버킷 ARN, GetObject 는 오브젝트 ARN으로 리소스가 달라 실수하기 쉽습니다.

7) 자주 놓치는 함정 6가지

7-1) 네임스페이스가 다르다

Trust policy의 subsystem:serviceaccount:네임스페이스:SA명 입니다. 네임스페이스가 하나라도 다르면 실패합니다.

7-2) Role ARN을 잘못 붙였다

eks.amazonaws.com/role-arn 에 다른 계정/다른 Role을 넣어도 Pod는 일단 뜰 수 있습니다. 대신 STS나 서비스 호출에서 터집니다.

7-3) audsts.amazonaws.com 이 아니다

일부 예전 문서/복붙에서 aud 조건이 누락되거나 오타가 있습니다. EKS 기본은 sts.amazonaws.com 입니다.

7-4) 애플리케이션이 정적 자격증명을 우선 사용한다

컨테이너 이미지에 AWS_ACCESS_KEY_ID 등이 박혀 있으면 SDK가 그걸 먼저 집어 들고, IRSA를 안 씁니다. 이 경우 “왜 IRSA 했는데도 AccessDenied지”라는 혼란이 생깁니다.

Pod env에서 정적 키가 있는지부터 제거하세요.

7-5) 다른 컴포넌트 설치 이슈와 혼동

예를 들어 AWS Load Balancer Controller 설치 후 403이 나는 경우도 IRSA가 원인이지만, 컨트롤러 자체의 설정/차트 값이 원인일 수도 있습니다. 컨트롤러 케이스는 아래 글이 더 직접적입니다.

7-6) 토큰/시간 이슈

노드 시간 드리프트, STS 토큰 만료, 재시도 설정 부재 등으로 간헐적인 403/AccessDenied처럼 보일 수 있습니다. 이 경우는 아래 글의 체크리스트가 도움이 됩니다.

8) “빠른 해결”용 최소 커맨드 세트

현장에서 가장 빨리 결론 내는 순서로 정리하면 다음 6개가 핵심입니다.

  1. Pod가 어떤 SA를 쓰는지
kubectl -n your-namespace get pod pod-name -o jsonpath='{.spec.serviceAccountName}'
  1. SA annotation 확인
kubectl -n your-namespace get sa your-sa -o jsonpath='{.metadata.annotations.eks\.amazonaws\.com/role-arn}'
  1. Pod에 IRSA env 주입 확인
kubectl -n your-namespace exec -it pod-name -- env | grep -E 'AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE'
  1. 현재 호출자 확인
aws sts get-caller-identity
  1. 클러스터 issuer 확인
aws eks describe-cluster --name your-cluster --query 'cluster.identity.oidc.issuer' --output text
  1. Role trust policy에서 sub 확인
aws iam get-role --role-name your-irsa-role --query 'Role.AssumeRolePolicyDocument'

이 6개 결과만 모아도, 원인이 SA 바인딩 인지 OIDC 인지 Trust sub/aud 인지 permissions policy 인지 거의 확정할 수 있습니다.

9) 마무리: 재발 방지 팁

  • IRSA Role을 만들 때는 namespaceserviceAccountName 을 변수로 두고 Terraform/Helm 값으로 강제해 “복붙 실수”를 줄이세요.
  • aws sts get-caller-identity 를 readiness 이전에 한 번 호출해 로그에 ARN을 남기면, 장애 시 원인 분리가 훨씬 빨라집니다.
  • 권한 정책은 CloudTrail의 거부 이벤트를 기반으로 최소 권한으로 조정하세요.

IRSA AccessDenied 는 원인이 다양해 보이지만, 실제로는 ServiceAccount OIDC Trust Policy Permissions Policy 네 축 중 하나입니다. 위 순서대로만 확인하면 대부분 빠르게 복구됩니다.