Published on

EKS IRSA STS AccessDenied 10분 해결 체크리스트

Authors

서버리스나 노드 IAM Role 대신 IRSA(IAM Roles for Service Accounts)를 붙였는데, 애플리케이션 로그에 AccessDenied 혹은 Not authorized to perform sts:AssumeRoleWithWebIdentity가 뜨면 대부분은 설정 1~2곳이 어긋난 것입니다. 이 글은 원인 후보를 “가장 많이 틀리는 순서”로 정리해, 실제로 10분 안에 복구할 수 있는 체크리스트 형태로 안내합니다.

아래는 대표 증상입니다.

  • AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity
  • InvalidIdentityToken: No OpenIDConnect provider found in your account
  • AccessDeniedException: User: arn:aws:sts::... is not authorized to perform: ...
  • SDK가 자격 증명을 못 찾아 NoCredentialProviders 유사 에러를 내거나, EC2 메타데이터로 떨어지는 현상

0) 먼저 결론: IRSA 동작 조건 4가지

IRSA는 아래 4개가 동시에 맞아야 합니다.

  1. EKS 클러스터에 OIDC Provider가 연결되어 있어야 함
  2. IAM Role의 Trust Policy가 OIDC issuer, sub, aud 조건과 일치해야 함
  3. Kubernetes ServiceAccount에 role ARN 어노테이션이 정확해야 함
  4. Pod가 해당 ServiceAccount를 사용하고, WebIdentity 토큰이 마운트되어야 함

이 중 하나라도 어긋나면 STS에서 거절합니다.

1) 1분 진단: 지금 Pod가 어떤 자격 증명을 쓰는지 확인

먼저 “IRSA로 가려는지”부터 확인합니다. 컨테이너 안에서 아래를 확인하세요.

kubectl -n <namespace> exec -it <pod-name> -- sh

# IRSA에서 핵심이 되는 환경변수
env | grep -E 'AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION|AWS_DEFAULT_REGION'

# 토큰 파일 존재 여부
ls -al /var/run/secrets/eks.amazonaws.com/serviceaccount/
cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token | head -c 20 && echo
  • AWS_ROLE_ARNAWS_WEB_IDENTITY_TOKEN_FILE 이 보이고 토큰 파일이 존재하면, 최소한 “Pod까지의 연결”은 되어 있습니다.
  • 둘 다 없다면, ServiceAccount 어노테이션/Pod의 SA 지정/토큰 마운트 설정부터 봐야 합니다.

2) 가장 흔한 원인: ServiceAccount 어노테이션 오타 또는 Pod가 다른 SA 사용

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

  • eks.amazonaws.com/role-arn: IRSA로 사용할 IAM Role ARN

확인:

kubectl -n <namespace> get sa <serviceaccount-name> -o yaml

예시:

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

그리고 Pod가 정말 그 SA를 쓰는지 확인합니다.

kubectl -n <namespace> get pod <pod-name> -o jsonpath='{.spec.serviceAccountName}'

여기서 default 가 나오면, SA를 제대로 지정하지 않은 것입니다. Deployment에 spec.template.spec.serviceAccountName 을 추가하세요.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  template:
    spec:
      serviceAccountName: app
      containers:
        - name: app
          image: ...

3) OIDC Provider 미연결: No OpenIDConnect provider found 해결

클러스터 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/XXXXXXXXXXXX

이 issuer에 대응하는 IAM OIDC Provider가 계정에 존재해야 합니다.

aws iam list-open-id-connect-providers

없다면 생성해야 합니다. eksctl 사용이 가장 빠릅니다.

eksctl utils associate-iam-oidc-provider --cluster <cluster-name> --approve

생성 후에도 AccessDenied가 지속되면, 다음 섹션의 Trust Policy 조건 불일치 가능성이 큽니다.

4) 3분 컷 핵심: IAM Role Trust Policy의 sub/aud 불일치

IRSA의 STS 거절 대부분은 Trust Policy 조건이 실제 토큰 클레임과 다르기 때문입니다.

4-1) 올바른 Trust Policy 예시

아래는 가장 표준적인 형태입니다. 특히 Principal.FederatedCondition.StringEquals 키가 정확해야 합니다.

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

여기서 자주 틀리는 포인트:

  • sub 의 네임스페이스/SA 이름 오타
  • audsts.amazonaws.com 이 아닌 다른 값으로 둠
  • issuer URL에 https:// 를 포함해버림 (Condition key에는 보통 호스트 경로만 들어감)
  • 클러스터가 여러 개인데 다른 클러스터의 OIDC id를 참조

4-2) 실제 토큰 클레임을 확인해서 “정답”을 맞추기

토큰을 디코딩해서 subaud 를 직접 확인하면 가장 빠릅니다.

TOKEN=$(kubectl -n <namespace> exec -it <pod-name> -- cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token)

# JWT payload 디코딩 (busybox 환경이면 base64 옵션이 다를 수 있음)
echo "$TOKEN" | awk -F'.' '{print $2}' | base64 -d 2>/dev/null | cat

출력 JSON에서 다음을 찾습니다.

  • sub: 보통 system:serviceaccount:<namespace>:<serviceaccount>
  • aud: 보통 sts.amazonaws.com

Trust Policy의 Condition이 이 값과 1글자라도 다르면 STS는 거절합니다.

5) 권한 정책(permissions policy) 누락: AssumeRole은 되는데 API가 AccessDenied

STS AssumeRoleWithWebIdentity 자체는 성공하지만, 그 다음 S3/Secrets Manager/DynamoDB 등 호출에서 AccessDenied 가 날 수 있습니다. 이 경우는 IRSA 연결 문제가 아니라 IAM Role에 붙은 권한 정책이 부족한 것입니다.

확인 방법:

  • 애플리케이션 로그에서 “어떤 API가 거절되는지” 확인
  • CloudTrail에서 eventSourceeventName 확인

예: S3 읽기가 필요하면 최소한 다음이 있어야 합니다.

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

운영 환경에서는 리소스 범위를 좁히고, 필요 액션만 남기는 방식으로 점진적으로 tighten 하는 것을 권장합니다.

6) 토큰 마운트/Projected SA 토큰 이슈: 환경변수는 있는데 파일이 없을 때

보안 강화로 automountServiceAccountToken: false 를 걸어둔 경우, IRSA 토큰이 마운트되지 않아 실패합니다.

점검 포인트:

  • ServiceAccount에 automountServiceAccountToken: false 설정 여부
  • Pod spec에 automountServiceAccountToken: false 설정 여부

확인:

kubectl -n <namespace> get sa <serviceaccount-name> -o yaml | grep -n 'automountServiceAccountToken'
kubectl -n <namespace> get pod <pod-name> -o yaml | grep -n 'automountServiceAccountToken'

IRSA를 쓰는 Pod에서는 토큰이 필요하므로, 최소한 해당 워크로드에는 토큰 마운트를 허용해야 합니다.

7) SDK가 IRSA를 안 타는 경우: 오래된 SDK/커스텀 자격증명 로더

간혹 애플리케이션이 IRSA 대신 다른 프로바이더 체인을 타서 엉뚱한 주체로 요청합니다.

대표 케이스:

  • 너무 오래된 AWS SDK가 WebIdentity를 기본 지원하지 않음
  • 코드에서 AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY 를 강제로 주입
  • credential_process 같은 외부 프로파일을 컨테이너에 넣음

진단:

  • 컨테이너 환경변수에 정적 키가 들어가 있는지 확인
  • 애플리케이션이 사용하는 SDK 버전 확인

Node.js 예시로, IRSA가 정상이라면 별도 설정 없이도 동작해야 합니다.

import { S3Client, ListBucketsCommand } from "@aws-sdk/client-s3";

const client = new S3Client({ region: process.env.AWS_REGION || "ap-northeast-2" });
const res = await client.send(new ListBucketsCommand({}));
console.log(res.Buckets);

위 코드에서 굳이 credentials를 주입하고 있다면 제거하고, 기본 체인이 WebIdentity를 인식하도록 두는 편이 장애가 적습니다.

8) 10분 복구 루틴(현장용)

시간 없을 때는 아래 순서대로만 보면 됩니다.

  1. Pod 내부에서 AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE 존재 확인
  2. ServiceAccount 어노테이션 eks.amazonaws.com/role-arn 확인
  3. Pod가 그 ServiceAccount를 쓰는지 확인
  4. 클러스터 OIDC issuer 확인 후, IAM OIDC Provider 존재 확인
  5. IAM Role Trust Policy에서 issuer id, sub, aud 정확히 일치시키기
  6. AssumeRole은 되는데 서비스 API만 거절되면 permissions policy 보강

대부분은 2~5에서 끝납니다.

9) 문제를 더 빨리 좁히는 로그/추적 팁

IRSA 문제는 “원인이 분산돼 보이는” 전형적인 인프라 트러블입니다. 이런 유형의 장애는 원인 추적 루틴이 중요합니다. 비슷한 방식으로 원인 분해가 필요한 케이스는 systemd 서비스가 계속 재시작될 때 원인 추적법 글의 접근(관측 지점 늘리기, 가정 제거하기)이 그대로 적용됩니다.

네트워크 경로까지 함께 의심된다면(예: 프록시/엔드포인트 경유, 사설망 STS 접근 문제 등) 트래픽 관점에서 AWS VPC Reachability Analyzer로 502 추적하기에서 소개한 “경로를 도구로 증명”하는 방법도 도움이 됩니다.

10) 마무리: 재발 방지 체크

  • 네임스페이스와 ServiceAccount 이름은 바뀌기 쉬우므로, Trust Policy의 sub 를 IaC로 고정 관리
  • 클러스터를 재생성/이관하면 OIDC id가 바뀔 수 있으니, OIDC Provider와 Role Trust를 세트로 재검증
  • 워크로드별 Role을 분리해 최소 권한 원칙 적용 (하나의 Role을 여러 SA가 공유하면 추적이 어려움)

IRSA의 STS AccessDenied 는 “어디가 틀렸는지”만 찾으면 수정은 단순합니다. 위 체크리스트대로 보면, 대부분 10분 안에 정상화할 수 있습니다.