Published on

EKS Pod에서 STS 403 AccessDenied 원인 8가지

Authors

서버리스/마이크로서비스 환경에서 EKS Pod가 AWS 리소스(S3, DynamoDB, KMS 등)에 접근하려면 대개 STS를 거쳐 임시 자격증명을 발급받습니다. 그런데 애플리케이션 로그에 AccessDenied가 찍히고, AWS SDK는 다음과 같은 형태로 터집니다.

An error occurred (AccessDenied) when calling the AssumeRoleWithWebIdentity operation: Not authorized to perform sts:AssumeRoleWithWebIdentity

혹은 AssumeRole/GetCallerIdentity/GetSessionToken 등 STS API 호출 자체가 403으로 막힙니다. 이 글은 EKS Pod에서 STS 403 AccessDenied가 나는 대표 원인 8가지를 “어디를 보면 바로 좁혀지는지” 중심으로 정리합니다.

> 참고: EKS 운영 중 다른 장애 트러블슈팅도 함께 보려면 Terraform apply 후 EKS 노드 NotReady - CNI·IRSA·보안그룹 점검, EKS Pod ImagePullBackOff 401 해결 가이드도 같이 보면 디버깅 흐름이 빨라집니다.

먼저: “어떤 STS 호출이 403인가”부터 확정

STS 403은 원인이 다양합니다. 먼저 어떤 방식으로 자격증명을 얻는지를 확인해야 합니다.

  • IRSA(권장): AssumeRoleWithWebIdentity
  • 노드 IAM Role(비권장/레거시): IMDS에서 노드 Role credentials 획득
  • 앱이 직접 AssumeRole (기본 자격증명 필요)

Pod 안에서 빠르게 확인하는 방법:

# Pod 내부
aws sts get-caller-identity

# 어떤 환경변수가 있는지
env | egrep 'AWS_(ROLE_ARN|WEB_IDENTITY_TOKEN_FILE|REGION|DEFAULT_REGION)' || true

# IRSA 토큰 파일 존재 여부
ls -al /var/run/secrets/eks.amazonaws.com/serviceaccount/ || true
cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token | head -c 20; echo
  • AWS_ROLE_ARN + AWS_WEB_IDENTITY_TOKEN_FILE가 있으면 IRSA 경로입니다.
  • 둘 다 없고 aws sts get-caller-identity노드 Role로 찍히면 IMDS 경로일 수 있습니다.

이제부터 원인을 8가지로 나눠 봅니다.

1) ServiceAccount에 IRSA 어노테이션(또는 Pod SA 지정) 누락

가장 흔한 케이스입니다. ServiceAccount에 role-arn이 없거나, Deployment가 다른 SA를 쓰고 있으면 Pod는 원하는 Role을 못 씁니다.

증상

  • AssumeRoleWithWebIdentity 대신 노드 Role로 호출되거나
  • NoCredentialProviders/AccessDenied가 혼재

확인

kubectl -n <ns> get sa <sa-name> -o yaml | yq '.metadata.annotations'

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

정상이라면 아래 키가 있어야 합니다.

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

해결

  • 올바른 ServiceAccount에 어노테이션을 추가
  • Deployment/StatefulSet이 해당 SA를 사용하도록 수정
  • 수정 후 Pod 재기동(기존 Pod는 env/토큰이 갱신되지 않는 경우가 많음)

2) IAM Role 신뢰 정책(Trust Policy)에서 OIDC 조건 불일치

IRSA는 IAM Role의 Trust Policy가 “이 클러스터의 OIDC + 특정 SA(subject)”를 신뢰해야 합니다. 여기서 sub(namespace/sa)나 aud가 조금만 달라도 STS가 403을 냅니다.

증상

  • 전형적인 에러:
AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity

확인: Role trust policy

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

IRSA의 대표적인 신뢰 정책 예시는 다음과 같습니다.

{
  "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:<NAMESPACE>:<SERVICEACCOUNT>"
        }
      }
    }
  ]
}

해결 포인트

  • sub의 namespace/sa가 정확한지
  • audsts.amazonaws.com인지
  • OIDC Provider ARN이 현재 클러스터의 OIDC인지(클러스터 재생성/교체 시 자주 틀어짐)

3) 클러스터 OIDC Provider 미등록/잘못 등록

EKS에 OIDC issuer는 존재하지만, IAM에 OIDC Provider 리소스가 없거나 잘못된 issuer로 등록되면 STS가 거부합니다.

증상

  • IRSA 구성은 했는데 모든 Pod가 STS 403
  • 특정 클러스터에서만 재현

확인

aws eks describe-cluster --name <cluster> --query 'cluster.identity.oidc.issuer' --output text
aws iam list-open-id-connect-providers

# 해당 issuer의 provider가 있는지 찾아서
aws iam get-open-id-connect-provider --open-id-connect-provider-arn <PROVIDER_ARN>

해결

  • 올바른 issuer로 OIDC provider 생성/수정
  • (IaC 사용 시) Terraform/eksctl 설정을 클러스터와 동기화

4) 권한 정책은 STS가 아니라 “대상 서비스 권한”이 없음

STS 호출 자체는 성공하지만, 실제로 S3/KMS/DynamoDB 등 호출에서 403이 날 수 있습니다. 로그에서 “STS 403”으로 뭉뚱그려 보이기도 하니, 에러 메시지의 Operation을 분리해야 합니다.

증상

  • AssumeRoleWithWebIdentity는 성공
  • 이후 서비스 호출에서 AccessDeniedException / AccessDenied 발생

확인

# 현재 Pod가 어떤 Role로 동작하는지
aws sts get-caller-identity

# CloudTrail에서 실제 Deny 이벤트의 eventName 확인
# (콘솔 또는 Athena/CloudTrail Lake 권장)

해결

  • Role에 필요한 권한을 최소 권한으로 추가
  • 특히 KMS는 키 정책/그랜트/암호화 컨텍스트까지 함께 점검

예: S3 읽기 권한 최소 예시

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

5) Permission Boundary(권한 경계) 또는 Session Policy로 인해 STS가 막힘

조직 표준으로 Role에 Permission Boundary가 걸려 있거나, 애플리케이션이 AssumeRole 시 Session Policy를 붙이는 경우, “정책상 허용”처럼 보여도 경계에서 잘려 403이 납니다.

증상

  • IAM 정책/인라인 정책에 Allow가 있는데도 AccessDenied
  • 특정 계정/특정 Role에서만 반복

확인

aws iam get-role --role-name <ROLE_NAME> --query 'Role.PermissionsBoundary'

CloudTrail의 errorMessage에 boundary 관련 힌트가 나오기도 합니다.

해결

  • Boundary 정책에 필요한 액션(sts:AssumeRoleWithWebIdentity 또는 대상 서비스 액션)을 포함
  • 조직 정책에 맞게 Role 설계를 재조정

6) AWS Organizations SCP/Control Tower Guardrail에 의해 Deny

SCP는 계정 내 IAM Allow를 모두 무시하고 상위에서 Deny를 걸 수 있습니다. 특히 보안 팀이 sts:* 일부를 제한하거나, 특정 리전에 대한 STS를 제한하는 경우가 있습니다.

증상

  • 동일 Role이 다른 계정에서는 되는데 특정 계정에서만 403
  • CloudTrail에 “explicit deny”가 찍힘

확인/해결

  • 계정이 속한 OU의 SCP 확인(권한이 없다면 보안/플랫폼 팀에 요청)
  • CloudTrail에서 Deny 근거 정책을 추적

실무 팁: “우리 Role 정책엔 Allow가 있는데 왜 안 되지?”라면 SCP/Boundary를 1순위로 의심하는 게 시간을 줄입니다.

7) STS Regional Endpoint/리전 설정 불일치로 인한 오해(403처럼 보이는 케이스)

엄밀히 말하면 리전 불일치는 4xx가 403만 나오진 않지만, 환경에 따라 SDK가 잘못된 엔드포인트를 치거나, 프록시/게이트웨이가 403을 반환해 “STS 403”처럼 보이기도 합니다.

흔한 패턴

  • AWS_REGION/AWS_DEFAULT_REGION이 비어 있음
  • STS 엔드포인트가 강제로 특정 리전으로 고정
  • 사내 프록시가 sts.amazonaws.com을 차단/인증 요구로 403 반환

확인

# Pod 내부에서 실제로 어떤 엔드포인트로 나가는지
aws sts get-caller-identity --debug 2>&1 | egrep -i 'endpoint|sts' | head -n 50

# 환경변수 점검
env | egrep 'AWS_REGION|AWS_DEFAULT_REGION|HTTPS_PROXY|HTTP_PROXY|NO_PROXY'

해결

  • EKS에서는 보통 AWS_REGION을 명시하거나 SDK 기본 리전 설정을 확정
  • 프록시 환경이면 NO_PROXY에 STS 엔드포인트/169.254.169.254(IMDS) 등을 적절히 포함

8) (레거시 경로) 노드 Role/IMDS 사용 시, 노드 IAM Role에 STS 권한이 없거나 IMDS 접근이 차단

IRSA를 쓰지 않고 노드 Role에 의존하는 구성(또는 IRSA가 깨져서 폴백)에서는, Pod가 IMDS로부터 자격증명을 얻어야 합니다. 하지만

  • 노드 Role에 필요한 권한이 없거나
  • IMDSv2가 강제인데 클라이언트가 IMDSv1로 접근하거나
  • 네트워크 정책/iptables/호스트 설정으로 169.254.169.254 접근이 막히면 자격증명 획득이 실패하고, 이후 STS/서비스 호출이 연쇄적으로 실패합니다.

확인

# Pod에서 IMDS 접근(가능한 경우에만; 보안상 차단되어 있을 수 있음)
curl -sS http://169.254.169.254/latest/meta-data/iam/security-credentials/ || true

# 현재 caller 확인
aws sts get-caller-identity

해결

  • 가능한 한 IRSA로 전환(권장)
  • IMDSv2 요구사항에 맞게 설정(노드/런타임/SDK)
  • 네트워크 차단 정책이 있다면 의도된 차단인지 확인

재현/진단을 빠르게 하는 “최소 체크리스트”

아래 순서대로 보면 보통 10~20분 내에 원인을 특정합니다.

  1. Pod 내부에서 aws sts get-caller-identity 실행
  2. AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE 존재 확인
  3. ServiceAccount 어노테이션/Deployment의 SA 지정 확인
  4. IAM Role trust policy의 sub, aud, OIDC provider ARN 확인
  5. CloudTrail에서 Deny 이벤트의 eventNameerrorMessage 확인
  6. Permission Boundary/SCP 여부 확인
  7. 프록시/리전/엔드포인트 설정 확인

진단 중 네트워크/클러스터 상태가 의심되면(노드 교체 직후, CNI 이슈 등) IRSA까지 연쇄로 흔들릴 수 있으니, 클러스터 기반 점검은 Terraform apply 후 EKS 노드 NotReady - CNI·IRSA·보안그룹 점검도 함께 참고하면 좋습니다.

마무리: “403”은 결과일 뿐, Deny의 출처를 찾아야 한다

EKS Pod의 STS 403은 대부분 IRSA 신뢰 정책/어노테이션/OIDC에서 발생하지만, 조직 환경에서는 Boundary/SCP가 의외로 큰 비중을 차지합니다. 가장 확실한 방법은 (1) Pod의 실제 Caller를 확정하고 (2) CloudTrail로 “누가 어떤 정책에 의해 Deny 되었는지”를 역추적하는 것입니다.

원하면, 사용 중인 에러 로그(전체 메시지), ServiceAccount YAML, Role trust policy(JSON)를 익명화해서 공유해 주시면 위 8가지 중 어디에 해당하는지 빠르게 짚어서 수정안까지 같이 정리해 드릴게요.