Published on

EKS IRSA인데 AccessDenied? OIDC·TrustPolicy·SA 점검

Authors

서론

EKS에서 IRSA(IAM Roles for Service Accounts)를 구성해 ServiceAccount에 IAM Role ARN까지 달아줬는데도 애플리케이션에서 AccessDenied가 터지면 멘탈이 흔들립니다. 특히 S3, SQS, Secrets Manager, STS AssumeRoleWithWebIdentity 단계에서 막히면 “정말 IRSA가 붙은 게 맞나?”부터 의심하게 되죠.

결론부터 말하면, IRSA의 AccessDenied는 대개 세 군데에서 납니다.

  1. 클러스터 OIDC Provider(issuer)와 IAM OIDC Provider가 불일치
  2. IAM Role Trust Policy의 Principal/Condition(sub, aud) 오타 또는 대상 SA/네임스페이스 불일치
  3. Kubernetes ServiceAccount 주석(annotaion) 또는 토큰 마운트/환경변수(웹 아이덴티티 토큰) 불일치

이 글은 “어디가 틀렸는지”를 빠르게 좁히는 실전 점검 순서와, 자주 보는 로그 패턴별 원인-해결을 정리합니다. (운영 중 장애 대응 관점이라 체크리스트 형태로 진행합니다.)

관련해서 쿠버네티스 자체 트러블슈팅 루틴이 필요하다면 Kubernetes CrashLoopBackOff 원인별 로그·Probe·리소스 디버깅도 함께 참고하면 좋습니다.


AccessDenied가 나는 “지점”부터 구분하기

IRSA에서 AccessDenied는 크게 두 단계로 나뉩니다.

1) STS AssumeRoleWithWebIdentity 단계에서 실패

애플리케이션 로그/SDK 에러에 아래 중 하나가 보이면 여기에 해당합니다.

  • AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity
  • InvalidIdentityToken
  • No OpenIDConnect provider found in your account for ...
  • The audience in the token is invalid

이 경우는 OIDC Provider 등록 또는 Trust Policy 조건 문제일 확률이 매우 높습니다.

2) STS는 성공했는데 실제 AWS API 호출에서 실패

예: S3 호출에서

  • AccessDenied: Access Denied (S3)
  • is not authorized to perform: secretsmanager:GetSecretValue

이 경우는 IRSA 연결은 되었고, Role에 붙은 Permission Policy(권한 정책) 가 부족한 경우가 많습니다.

이 글의 초점은 주제대로 1) OIDC, 2) TrustPolicy, 3) SA를 우선 점검하되, 마지막에 “권한 정책”까지 빠르게 확인하는 방법도 넣겠습니다.


1단계: EKS OIDC Issuer와 IAM OIDC Provider 일치 확인

먼저 클러스터의 OIDC issuer URL을 확인합니다.

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

출력 예:

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

이제 IAM에 해당 issuer가 OIDC Provider로 등록돼 있는지 확인합니다.

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

나열된 ARN 중 하나를 골라 상세를 봅니다.

aws iam get-open-id-connect-provider \
  --open-id-connect-provider-arn arn:aws:iam::<ACCOUNT_ID>:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E

여기서 핵심 체크 포인트:

  • Url이 EKS issuer에서 https://를 뺀 값과 동일해야 함
  • ClientIDList에 최소 sts.amazonaws.com이 포함돼야 함
  • Thumbprint가 지나치게 오래되었거나 잘못되면 토큰 검증이 실패할 수 있음(드문 편이지만, 조직 정책/프록시 환경에서 간헐적으로 발생)

자주 나는 실수

  • 같은 계정에 클러스터가 여러 개 있고, 다른 클러스터의 OIDC provider를 trust policy에 사용
  • 클러스터를 재생성했는데 issuer id가 바뀌었고, 기존 OIDC provider를 그대로 둔 상태

빠른 해결(eksctl 사용 시)

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

2단계: IAM Role Trust Policy에서 sub/aud 조건 정확성 검증

IRSA의 핵심은 IAM Role의 Trust Policy입니다. 여기서 한 글자라도 틀리면 STS가 거절합니다.

Role의 trust policy를 확인합니다.

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

올바른 Trust Policy 예시

아래는 가장 흔한(그리고 권장되는) 형태입니다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::<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:<NAMESPACE>:<SERVICEACCOUNT_NAME>"
        }
      }
    }
  ]
}

체크리스트

(1) Principal의 OIDC Provider ARN이 맞는가?

  • 다른 리전/다른 OIDC_ID면 무조건 실패

(2) Condition 키의 prefix가 정확한가?

  • oidc.eks.<region>.amazonaws.com/id/<OIDC_ID>:sub 형태에서
    • 리전 오타
    • OIDC_ID 오타
    • https://를 넣는 실수

(3) sub 값이 정확한가?

  • 반드시 system:serviceaccount:<namespace>:<serviceaccount>
  • 네임스페이스를 default로 가정했다가 실제는 prod여서 실패하는 경우가 매우 많습니다.

(4) audsts.amazonaws.com인가?

  • IRSA 토큰의 audience는 일반적으로 sts.amazonaws.com
  • 조직에서 커스텀 audience를 쓰는 경우가 아니라면 여기서 틀릴 일이 없어야 합니다.

(5) StringEquals vs StringLike

  • 특정 SA 하나만 허용하려면 StringEquals가 안전
  • 여러 SA를 허용하려고 와일드카드를 쓰면 StringLike가 필요

예: 특정 네임스페이스의 모든 SA 허용(권장하진 않음)

"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:payments:*"
  }
}

3단계: Kubernetes ServiceAccount annotation과 실제 Pod 적용 여부 확인

Trust policy가 맞아도, Pod가 그 SA를 안 쓰면 IRSA는 동작하지 않습니다.

(1) ServiceAccount에 role-arn annotation이 있는지

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

필수 확인:

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

오타 포인트:

  • eks.amazonaws.com/role-arn 키 오타
  • role arn의 계정/이름 오타

(2) Pod가 해당 ServiceAccount를 사용하고 있는지

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

Deployment/Job 템플릿에서 확인하려면:

kubectl get deploy <DEPLOY_NAME> -n <NAMESPACE> -o jsonpath='{.spec.template.spec.serviceAccountName}'; echo

여기서 default가 나오면, SA를 만들었어도 Pod가 안 쓰는 상태입니다.

(3) Pod에 웹 아이덴티티 환경변수가 주입되는지

IRSA가 정상이라면 Pod 내부에 아래가 존재합니다.

  • AWS_ROLE_ARN
  • AWS_WEB_IDENTITY_TOKEN_FILE

확인:

kubectl exec -n <NAMESPACE> <POD_NAME> -- sh -c 'env | egrep "AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE"'

또는 토큰 파일 존재 확인:

kubectl exec -n <NAMESPACE> <POD_NAME> -- sh -c 'ls -al $AWS_WEB_IDENTITY_TOKEN_FILE && head -c 50 $AWS_WEB_IDENTITY_TOKEN_FILE'

값이 비어 있으면 대개:

  • Pod가 해당 SA를 사용하지 않음
  • (드물게) Admission webhook/정책이 env/volume 주입을 막음
  • ServiceAccount 토큰 자동 마운트가 꺼져 있음

(4) automountServiceAccountToken 이 꺼져 있는지

SA 혹은 Pod spec에 아래가 있으면 토큰이 마운트되지 않습니다.

automountServiceAccountToken: false

확인:

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

4단계: 실제로 어떤 IAM Role로 호출하고 있는지 “Pod 안에서” 검증

IRSA 트러블슈팅에서 가장 강력한 방법은 Pod 내부에서 sts get-caller-identity를 직접 호출해 보는 것입니다.

방법 A: awscli가 있는 컨테이너

kubectl exec -n <NAMESPACE> <POD_NAME> -- aws sts get-caller-identity

여기서 Arn이 기대한 role로 나오면 IRSA는 붙은 겁니다.

방법 B: awscli가 없으면 디버그 Pod로 확인

kubectl run -n <NAMESPACE> irsa-debug \
  --rm -it \
  --image=amazon/aws-cli:2.15.0 \
  --serviceaccount=<SERVICEACCOUNT_NAME> \
  --command -- sh

# inside
aws sts get-caller-identity

이게 성공하면, 앱 컨테이너 이미지/SDK 설정 쪽 문제(예: 자격증명 체인 강제, AWS_PROFILE 고정 등)로 좁혀집니다.


5단계: AccessDenied 로그 패턴별 빠른 원인 매핑

패턴 1: No OpenIDConnect provider found in your account

  • IAM OIDC Provider 미등록 또는 다른 계정/다른 issuer
  • 1단계에서 issuer와 provider ARN 매칭 확인

패턴 2: InvalidIdentityToken 또는 audience is invalid

  • Trust policy에 aud 조건이 틀렸거나
  • IAM OIDC Provider의 ClientIDListsts.amazonaws.com 누락

패턴 3: Not authorized to perform sts:AssumeRoleWithWebIdentity

  • Trust policy의 sub가 SA/namespace와 불일치
  • Pod가 다른 SA를 사용하거나, 토큰 마운트가 꺼져 있음

패턴 4: STS는 되는데 S3/SecretsManager에서 AccessDenied

  • Role permission policy 부족
  • 리소스 ARN 범위/조건(예: KMS 키 정책 포함) 문제

6단계(보너스): 권한 정책까지 한 번에 점검하는 최소 루틴

IRSA 연결이 맞는데도 AccessDenied면, 이제 권한 정책을 봐야 합니다.

(1) Role에 붙은 정책 확인

aws iam list-attached-role-policies --role-name <ROLE_NAME>
aws iam list-role-policies --role-name <ROLE_NAME>

(2) 실제 호출 API를 CloudTrail에서 역추적

특히 S3/SecretsManager/KMS는 “권한은 줬는데 리소스 정책/KMS 키 정책에서 막힘”이 잦습니다. CloudTrail에서 errorCode=AccessDenied 이벤트를 보고, userIdentity.arn이 어떤 role로 찍히는지 확인하면 원인 분리가 빨라집니다.


자주 놓치는 운영 환경 변수/SDK 설정 함정

  • 컨테이너에 AWS_PROFILE이 설정되어 있으면 웹 아이덴티티 체인을 안 타는 경우가 있음
  • ~/.aws/credentials를 이미지에 baked-in 해두면(안티패턴) 그 자격증명을 우선 사용
  • 일부 SDK/라이브러리에서 STS endpoint/region을 강제해 토큰 교환이 실패

IRSA를 쓰는 워크로드는 가능한 한 “환경변수/파일 기반 고정 자격증명”을 제거하고, 기본 자격증명 체인(Default Credential Provider Chain)을 타도록 두는 게 안전합니다.


정리: 10분 안에 끝내는 IRSA AccessDenied 체크리스트

  1. aws eks describe-cluster로 issuer 확인
  2. IAM OIDC Provider가 issuer와 정확히 매칭되는지 확인(+ sts.amazonaws.com)
  3. Role trust policy에서
    • Federated ARN
    • condition key prefix(issuer id)
    • sub=system:serviceaccount:ns:sa
    • aud=sts.amazonaws.com
  4. ServiceAccount annotation eks.amazonaws.com/role-arn 확인
  5. Pod가 해당 SA를 실제로 사용하는지 확인
  6. Pod 내부에서 AWS_WEB_IDENTITY_TOKEN_FILE, AWS_ROLE_ARN 확인
  7. aws sts get-caller-identity로 최종 role 확인
  8. STS 성공 후에도 막히면 permission policy / 리소스 정책 / KMS 키 정책을 CloudTrail로 역추적

IRSA는 한 번 올바르게 붙으면 운영이 매우 편해지지만, 반대로 한 글자 오타가 즉시 AccessDenied로 이어지는 구조입니다. 위 순서대로 “issuer → provider → trust → sa → pod → sts”로 좁혀가면, 대부분의 케이스는 1~2회전 안에 원인을 찾을 수 있습니다.