Published on

EKS IRSA 설정했는데 AccessDenied? OIDC·SA 점검

Authors

서버리스든 노드 그룹이든, EKS에서 AWS 권한을 Pod 단위로 부여하려고 IRSA(IAM Roles for Service Accounts)를 설정했는데도 AccessDenied가 뜨는 경우가 있습니다. 특히 AssumeRoleWithWebIdentity 단계에서 막히면 애플리케이션 로그에는 단순히 403만 남고, 원인이 OIDC인지 ServiceAccount인지 IAM 정책인지 감이 안 잡히기 쉽습니다.

이 글은 “IRSA가 동작하는 최소 조건”을 OIDC ProviderIAM Role Trust PolicyKubernetes ServiceAccountPod 토큰STS 호출 순서로 쪼개서, 어디서 어긋났는지 빠르게 찾는 방법을 정리합니다.

관련해서 Secrets Manager 권한 문제로 403이 나는 케이스는 아래 글도 함께 보면 진단 속도가 빨라집니다.

IRSA에서 AccessDenied가 나는 대표 패턴 3가지

IRSA 관련 AccessDenied는 크게 아래로 나뉩니다.

  1. STS AssumeRoleWithWebIdentity 자체가 거부

    • 메시지 예: Not authorized to perform sts:AssumeRoleWithWebIdentity 또는 InvalidIdentityToken
    • 원인: OIDC Provider 미등록, Trust Policy 조건 불일치(sub, aud), ServiceAccount 주석 누락 등
  2. Role Assume은 성공했는데 AWS 서비스 API에서 거부

    • 메시지 예: AccessDeniedException(Secrets Manager), AccessDenied(S3), UnauthorizedOperation(EC2)
    • 원인: Role에 붙은 권한 정책이 부족하거나, 리소스 ARN/조건이 틀림
  3. 아예 IRSA가 적용되지 않고 노드 Role로 호출

    • 증상: 예상과 다른 계정/Role로 호출되거나, 권한이 과도/부족하게 동작
    • 원인: Pod가 다른 ServiceAccount를 사용, automountServiceAccountToken 비활성화, SDK가 웹 아이덴티티 체인을 못 타는 환경 변수 설정 등

이제부터는 1번(가장 흔한)인 OIDC·Trust·SA 불일치부터 순서대로 확인합니다.

1) 클러스터 OIDC Issuer와 IAM OIDC Provider 일치 확인

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

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

출력 예는 보통 https://oidc.eks.ap-northeast-2.amazonaws.com/id/XXXX 형태입니다. 여기서 https://를 제거한 호스트/경로가 IAM OIDC Provider의 url과 정확히 일치해야 합니다.

IAM에 등록된 OIDC Provider 목록을 확인합니다.

aws iam list-open-id-connect-providers

각 Provider의 상세를 찍어 URL이 같은지 봅니다.

aws iam get-open-id-connect-provider \
  --open-id-connect-provider-arn arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/XXXX

흔한 실수

  • 클러스터를 재생성했는데 예전 OIDC Provider를 그대로 사용
  • 다른 리전/다른 클러스터의 OIDC Provider ARN을 Role Trust에 넣음
  • URL은 맞는데 thumbprint가 오래되어 TLS 검증이 꼬이는 케이스(드물지만 존재)

OIDC Provider가 없다면 eksctl utils associate-iam-oidc-provider로 연결합니다.

eksctl utils associate-iam-oidc-provider \
  --cluster my-eks \
  --approve

2) IAM Role Trust Policy의 sub/aud 조건 점검

IRSA는 결국 Pod 안의 토큰으로 sts:AssumeRoleWithWebIdentity를 호출합니다. 이때 STS는 토큰의 클레임을 보고 Role Trust Policy 조건을 검사합니다.

Role의 Trust Policy를 확인합니다.

aws iam get-role \
  --role-name my-irsa-role \
  --query "Role.AssumeRolePolicyDocument" \
  --output json

정상적인 Trust Policy 예시는 아래와 비슷합니다. 본문에서 oidc.eks.../id/XXXXnamespace:serviceaccount가 실제와 일치해야 합니다.

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

여기서 자주 터지는 포인트

  • sub의 네임스페이스/SA 이름이 다름
  • aud를 빼거나 sts.amazonaws.com이 아닌 값으로 제한
  • StringLike/StringEquals를 섞다가 와일드카드가 의도대로 안 먹음

예를 들어 여러 SA를 허용하려면 StringLike로 바꿔야 합니다.

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

3) ServiceAccount에 Role ARN 주석이 정확히 붙었는지 확인

Kubernetes ServiceAccount에 아래 주석이 있어야 합니다.

  • eks.amazonaws.com/role-arn: IRSA Role ARN

확인 명령:

kubectl -n my-ns get sa my-sa -o yaml

정상 예:

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

흔한 실수

  • Deployment에서 serviceAccountName을 지정하지 않아 default SA로 뜸
  • SA는 수정했는데 Pod가 재시작되지 않아 이전 설정이 계속 사용됨
  • Helm values에서 SA 이름이 바뀌었는데 Trust Policy의 sub는 그대로

Deployment의 SA 설정 확인:

kubectl -n my-ns get deploy my-app -o jsonpath='{.spec.template.spec.serviceAccountName}'; echo

4) Pod 내부에서 웹 아이덴티티 토큰과 환경 변수 확인

IRSA가 적용된 Pod에는 보통 아래 환경 변수가 자동 주입됩니다.

  • AWS_ROLE_ARN
  • AWS_WEB_IDENTITY_TOKEN_FILE

Pod에서 확인합니다.

kubectl -n my-ns exec -it deploy/my-app -- sh -lc 'env | grep -E "AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION|AWS_DEFAULT_REGION"'

토큰 파일이 실제로 존재하는지도 봅니다.

kubectl -n my-ns exec -it deploy/my-app -- sh -lc 'ls -l "$AWS_WEB_IDENTITY_TOKEN_FILE"'

그리고 토큰의 클레임을 디코딩해 subaud가 Trust Policy와 같은지 확인하면 원인 추적이 매우 빨라집니다. 아래는 jq로 JWT payload를 보는 예입니다.

kubectl -n my-ns exec -it deploy/my-app -- sh -lc '
  TOKEN=$(cat "$AWS_WEB_IDENTITY_TOKEN_FILE");
  PAYLOAD=$(echo "$TOKEN" | cut -d . -f2);
  echo "$PAYLOAD" | base64 -d 2>/dev/null | jq
'

여기서 subsystem:serviceaccount:my-ns:my-sa인지, audsts.amazonaws.com인지 확인하세요.

토큰 디코딩이 깨질 때

base64 패딩 문제로 디코딩이 실패하는 환경도 있습니다. 그럴 땐 아래처럼 패딩을 보정하거나, 컨테이너에 python이 있다면 파이썬으로 디코딩하는 편이 안정적입니다.

kubectl -n my-ns exec -it deploy/my-app -- sh -lc '
python - << "PY"
import os, json, base64
p = open(os.environ["AWS_WEB_IDENTITY_TOKEN_FILE"]).read().split(".")[1]
p += "=" * (-len(p) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(p)), indent=2))
PY
'

5) STS AssumeRoleWithWebIdentity를 Pod에서 직접 재현

애플리케이션 로그만 보면 모호하니, Pod에서 STS 호출을 직접 해보면 결론이 빨리 납니다. 컨테이너에 AWS CLI가 없다면 디버그용 ephemeral container를 붙이거나, 별도 debug Pod를 띄우는 방식이 좋습니다.

예: AWS CLI가 있는 이미지로 임시 Pod 실행

kubectl -n my-ns run irsa-debug \
  --rm -it \
  --image public.ecr.aws/aws-cli/aws-cli:2 \
  --serviceaccount my-sa \
  --command -- sh

Pod 안에서 다음을 실행합니다.

aws sts get-caller-identity
  • 여기서 AccessDenied면 Trust/OIDC/SA가 문제일 확률이 큽니다.
  • 여기서 성공하고 Arnassumed-role/my-irsa-role/...로 나오면 IRSA는 붙은 것입니다. 이후는 서비스 권한 정책을 봐야 합니다.

6) “IRSA는 붙었는데 여전히 AccessDenied”면 권한 정책을 점검

STS는 성공하는데 S3/Secrets Manager 등에서 AccessDenied가 나면, Role에 붙은 Permissions Policy가 부족하거나 리소스 ARN이 불일치합니다.

Role에 연결된 정책 확인:

aws iam list-attached-role-policies --role-name my-irsa-role
aws iam list-role-policies --role-name my-irsa-role

인라인 정책을 쓴 경우 내용 확인:

aws iam get-role-policy \
  --role-name my-irsa-role \
  --policy-name my-inline-policy

예: Secrets Manager 읽기 권한 최소 예시

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue",
        "secretsmanager:DescribeSecret"
      ],
      "Resource": "arn:aws:secretsmanager:ap-northeast-2:123456789012:secret:myapp/*"
    }
  ]
}

리소스 ARN이 조금이라도 다르면 바로 거부됩니다. 특히 Secrets Manager는 콘솔에서 보이는 이름과 실제 ARN suffix가 달라서 헷갈릴 수 있습니다.

7) SDK가 웹 아이덴티티 체인을 안 타는 케이스

IRSA는 “SDK가 WebIdentityTokenFileCredentials 체인을 인식”해야 편하게 동작합니다. 대부분의 최신 AWS SDK는 자동으로 처리하지만, 아래 경우에는 우회 설정 때문에 노드 Role을 타거나 자격 증명을 못 찾을 수 있습니다.

  • 컨테이너에 AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY가 남아있음(우선순위로 인해 IRSA 무시)
  • AWS_PROFILE을 강제로 지정
  • 매우 구버전 SDK 사용

Pod 환경 변수에서 정적 키가 있는지 먼저 제거/점검하세요.

kubectl -n my-ns exec -it deploy/my-app -- sh -lc 'env | grep -E "AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|AWS_PROFILE"'

8) 네임스페이스/SA 변경 후 “Pod 재기동” 누락

IRSA 관련 변경은 대개 “새로 뜬 Pod”부터 반영됩니다.

  • SA 주석 수정
  • Deployment의 serviceAccountName 변경
  • Trust Policy 변경

위 작업 후에는 Deployment 롤아웃을 강제로 한 번 태우는 게 안전합니다.

kubectl -n my-ns rollout restart deploy my-app
kubectl -n my-ns rollout status deploy my-app

9) CloudTrail로 최종 확정하기

그래도 애매하면 CloudTrail에서 STS 이벤트를 보면 거의 확정됩니다.

  • 이벤트 소스: sts.amazonaws.com
  • 이벤트 이름: AssumeRoleWithWebIdentity

여기서 실패 이유가 AccessDenied인지, InvalidIdentityToken인지가 명확히 나오고, 어떤 Role을 시도했는지까지 추적됩니다.

빠른 체크리스트(문제 지점을 5분 안에 좁히기)

  1. aws eks describe-cluster로 OIDC issuer 확인
  2. IAM OIDC Provider URL이 issuer와 동일한지 확인
  3. Role Trust Policy의 Principal.Federated ARN이 올바른 Provider인지 확인
  4. Trust Policy 조건의 audsts.amazonaws.com인지 확인
  5. Trust Policy 조건의 subsystem:serviceaccount:네임스페이스:SA와 일치하는지 확인
  6. SA에 eks.amazonaws.com/role-arn 주석 존재 확인
  7. Deployment가 올바른 serviceAccountName을 쓰는지 확인
  8. Pod에서 aws sts get-caller-identity로 IRSA 적용 여부 확정
  9. STS 성공 후에도 거부면 Role Permissions Policy 리소스/액션 재점검

마무리

IRSA의 AccessDenied는 “권한이 부족하다”라기보다, OIDC 토큰의 클레임과 Trust Policy 조건이 1글자라도 안 맞는 구성 오류인 경우가 훨씬 흔합니다. 위 순서대로 issuerprovidertrustsatokensts를 확인하면, 대부분은 10분 안에 원인을 찾습니다.

추가로, EKS에서 인증/토큰/키 관련 문제를 다루는 관점은 JWT 문제를 디버깅할 때와도 유사합니다. 토큰의 kid나 JWKS 캐시처럼 “메타데이터 불일치”가 원인인 경우가 많으니, 토큰 payload 확인 습관을 들이면 운영이 편해집니다.