- Published on
EKS IRSA AccessDenied 10분 진단·해결 체크리스트
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
EKS에서 IRSA(IAM Roles for Service Accounts)를 붙였는데도 애플리케이션이 AWS API 호출에서 AccessDenied를 뿜는 경우가 흔합니다. 원인은 대개 (1) 역할을 못 Assume 하거나, (2) Assume은 됐는데 정책이 부족하거나, (3) 아예 다른 자격증명(노드 역할 등)을 쓰는 중입니다.
이 글은 “지금 당장” 문제를 좁히는 순서로 구성했습니다. 아래 체크리스트를 위에서 아래로 따라가면 대부분 10분 내에 원인에 도달합니다.
관련해서 IMDS(인스턴스 메타데이터) 401/홉리밋 이슈로 IRSA처럼 보이는 장애가 나는 케이스도 있으니, 증상이 애매하면 함께 확인하세요: EKS Pod에서 IMDS 401 뜰 때 IRSA·HopLimit 해결
0) 먼저: AccessDenied가 “어디서” 나는지 분류
로그를 보면 보통 두 종류로 갈립니다.
A. STS AssumeRoleWithWebIdentity 단계에서 거부
예시(대표 패턴):
AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentityInvalidIdentityToken(OIDC/토큰/issuer 문제)
이 경우는 Trust Policy(OIDC 조건), ServiceAccount 주석, OIDC Provider를 먼저 봐야 합니다.
B. Assume은 성공했는데 특정 서비스 API에서 거부
예시:
AccessDeniedException: User: arn:aws:sts::...:assumed-role/... is not authorized to perform: s3:GetObjectis not authorized to perform: kms:Decrypt
이 경우는 권한 정책(permissions policy) 또는 리소스 정책(S3 bucket policy, KMS key policy 등) 문제일 확률이 큽니다.
1) Pod가 진짜 IRSA 토큰을 쓰는지 30초 확인
컨테이너 안에서 아래를 확인합니다.
kubectl exec -n <namespace> <pod> -- env | egrep 'AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION|AWS_DEFAULT_REGION'
정상이라면 최소한 다음이 보여야 합니다.
AWS_ROLE_ARN값이 기대한 IAM Role ARNAWS_WEB_IDENTITY_TOKEN_FILE경로가 존재
토큰 파일도 확인합니다.
kubectl exec -n <namespace> <pod> -- ls -l $AWS_WEB_IDENTITY_TOKEN_FILE
여기서부터 갈립니다.
- 환경변수가 없다: ServiceAccount에 role annotation이 없거나, Pod가 다른 ServiceAccount를 쓰는 중
- 토큰 파일이 없다: Projected volume가 안 붙었거나(대개 SA/IRSA 미적용), 매우 특이한 커스텀 설정 문제
2) Pod가 어떤 ServiceAccount를 쓰는지 확인
의외로 Deployment에 serviceAccountName이 빠져 기본 default SA를 쓰는 경우가 가장 흔합니다.
kubectl get pod -n <namespace> <pod> -o jsonpath='{.spec.serviceAccountName}'; echo
kubectl get sa -n <namespace> <serviceAccountName> -o yaml
ServiceAccount에 아래 주석이 있어야 합니다.
- 키:
eks.amazonaws.com/role-arn - 값:
arn:aws:iam::<account-id>:role/<role-name>
예시:
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: myns
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/app-irsa-role
만약 SA를 수정했는데도 Pod가 여전히 실패한다면, 기존 Pod는 재시작/재배포가 필요합니다.
kubectl rollout restart deploy -n <namespace> <deployment>
3) EKS OIDC Provider가 IAM에 등록돼 있는지 확인
IRSA는 EKS 클러스터 OIDC issuer가 IAM OIDC provider로 등록되어 있어야 합니다.
클러스터의 issuer URL 확인:
aws eks describe-cluster --name <cluster> --query 'cluster.identity.oidc.issuer' --output text
IAM OIDC provider 목록에서 매칭되는지 확인:
aws iam list-open-id-connect-providers
aws iam get-open-id-connect-provider --open-id-connect-provider-arn <oidc-provider-arn>
여기서 흔한 실수:
- 다른 클러스터의 OIDC provider를 역할 trust policy에 붙임
- issuer URL이 바뀌었는데(클러스터 재생성) role/trust가 예전 값
eksctl utils associate-iam-oidc-provider를 사용 중이라면 아래로 재확인할 수 있습니다.
eksctl utils associate-iam-oidc-provider --cluster <cluster> --approve
4) IAM Role Trust Policy의 sub 조건이 맞는지 확인
IRSA AccessDenied의 핵심은 대부분 여기입니다. Trust policy에서 Condition의 sub가 ServiceAccount와 정확히 일치해야 합니다.
sub는 보통 아래 형식입니다.
system:serviceaccount:<namespace>:<serviceaccount>
역할 trust policy 확인:
aws iam get-role --role-name <role-name> --query 'Role.AssumeRolePolicyDocument' --output json
정상 예시(핵심만):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:sub": "system:serviceaccount:myns:app-sa",
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:aud": "sts.amazonaws.com"
}
}
}
]
}
자주 틀리는 포인트:
- namespace 오타 (
default로 되어 있음) - SA 이름 오타
StringLike/StringEquals를 잘못 사용aud조건 누락 또는sts.amazonaws.com이 아닌 값
여러 SA를 허용하려면 StringLike에 와일드카드를 쓰기도 합니다.
"StringLike": {
"...:sub": "system:serviceaccount:myns:*"
}
운영에서는 범위를 최소화하는 게 안전합니다.
5) “Assume은 됐는데도” AccessDenied면: 실제 호출 주체 ARN부터 확인
애플리케이션이 AWS SDK를 쓰고 있다면, 컨테이너에서 sts get-caller-identity로 현재 자격증명을 확인하는 것이 가장 빠릅니다.
kubectl exec -n <namespace> <pod> -- aws sts get-caller-identity
출력의 Arn을 봅니다.
arn:aws:sts::<acct>:assumed-role/<role-name>/<session-name>이면 IRSA 역할로 동작 중arn:aws:sts::<acct>:assumed-role/<node-role>/...이면 노드 인스턴스 프로파일을 쓰는 중(= IRSA 미적용 또는 SDK 설정 문제)
노드 역할이 나온다면 다음을 의심합니다.
- Pod가 IRSA 환경변수를 못 받음(1~2단계로 회귀)
- SDK가 웹 아이덴티티 프로바이더보다 다른 크리덴셜 체인을 우선(커스텀 자격증명 주입 등)
- 컨테이너에 정적 키(
AWS_ACCESS_KEY_ID)가 들어가 있어 IRSA를 가림
환경변수에 정적 키가 있는지 확인:
kubectl exec -n <namespace> <pod> -- env | egrep 'AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|AWS_SESSION_TOKEN'
6) 권한 정책(permissions policy) 빠진 액션을 정확히 보강
AccessDenied가 특정 액션에서만 난다면, 로그에 찍힌 액션을 그대로 정책에 추가하는 것이 정석입니다.
예: S3 읽기 + KMS 복호화가 필요한 케이스
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
]
},
{
"Effect": "Allow",
"Action": ["kms:Decrypt"],
"Resource": "arn:aws:kms:ap-northeast-2:123456789012:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
]
}
여기서도 흔한 함정이 있습니다.
- S3는
ListBucket이 버킷 ARN,GetObject가 오브젝트 ARN을 필요로 함 - KMS는 IAM policy만으로 부족하고, KMS Key Policy에서 역할을 허용해야 하는 경우가 많음
- DynamoDB, SQS, SNS 등도 리소스 ARN 스코프가 잘못되면
AccessDenied가 지속
가능하면 CloudTrail에서 이벤트를 확인하면 “정확히 어떤 ARN에 대해 어떤 액션이 거부됐는지”가 선명해집니다.
7) 리소스 정책(S3/KMS) 때문에 거부되는 케이스
S3 Bucket Policy
버킷 정책에서 특정 Principal만 허용 중이면, IRSA role ARN을 허용해야 합니다.
예시(개념):
{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:role/app-irsa-role"},
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::my-bucket/*"
}
KMS Key Policy
KMS는 특히 “IAM에서 Allow인데도 AccessDenied”가 자주 납니다. Key policy에 역할이 없으면 막힐 수 있습니다.
이때는 Key policy에 role을 추가하거나, 조직 정책/보안 표준에 맞는 방식으로 위임합니다.
8) ServiceAccount 토큰/audience 이슈 빠르게 점검
IRSA는 기본적으로 aud가 sts.amazonaws.com이어야 합니다. Trust policy에 aud 조건이 있는데 토큰이 다른 audience면 거부됩니다.
토큰을 직접 디코드해서 보는 방법도 있지만(보안상 주의), 운영에서는 trust policy와 SA 구성을 먼저 맞추는 편이 빠릅니다.
또한 EKS 버전/설정에 따라 SA 토큰 projected volume 동작이 달라질 수 있으니, “토큰 파일이 없다/갱신이 안 된다”면 클러스터 설정과 함께 점검하세요.
9) 10분 해결용 “원인-조치” 요약표
- Pod에
AWS_ROLE_ARN/AWS_WEB_IDENTITY_TOKEN_FILE없음serviceAccountName지정, SA annotation 추가, Pod 재시작
sts:AssumeRoleWithWebIdentity에서AccessDenied- OIDC provider 등록, trust policy의
sub/aud/issuer 키 확인
- OIDC provider 등록, trust policy의
get-caller-identity가 노드 역할로 나옴- 정적 키 환경변수 제거, SA/annotation 적용 확인, SDK 커스텀 크리덴셜 제거
- 특정 서비스 액션에서만
AccessDenied- IAM permissions policy에 액션/리소스 ARN 보강, S3/KMS 리소스 정책도 확인
10) 재발 방지: 최소 권한 + 테스트 커맨드 템플릿
배포 전 확인 커맨드(템플릿)
# 1) Pod가 쓰는 SA
kubectl get pod -n <namespace> <pod> -o jsonpath='{.spec.serviceAccountName}'; echo
# 2) SA annotation
kubectl get sa -n <namespace> <sa> -o jsonpath='{.metadata.annotations.eks\.amazonaws\.com/role-arn}'; echo
# 3) 현재 호출 주체
kubectl exec -n <namespace> <pod> -- aws sts get-caller-identity
권한 검증(서비스별)
예: S3 접근 테스트
kubectl exec -n <namespace> <pod> -- aws s3 ls s3://my-bucket/ --region <region>
예: SQS 권한 테스트
kubectl exec -n <namespace> <pod> -- aws sqs get-queue-attributes --queue-url <queue-url> --attribute-names All
마무리
IRSA AccessDenied는 복잡해 보이지만, 실제로는 (1) IRSA 토큰을 쓰는지, (2) trust policy의 sub가 맞는지, (3) 최종 호출 주체가 누구인지, (4) 권한/리소스 정책이 충분한지 네 가지로 빠르게 수렴합니다.
특히 aws sts get-caller-identity 한 번으로 “IRSA 역할로 동작 중인지”가 갈리니, 문제를 길게 끌기 전에 반드시 먼저 찍어보는 것을 추천합니다.