- Published on
EKS IRSA 권한 안 먹힘 - AccessDenied 12가지
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스처럼 보이지만, EKS의 IRSA(IAM Roles for Service Accounts)는 사실상 OIDC + ServiceAccount 토큰 + STS AssumeRoleWithWebIdentity 3요소가 정확히 맞물려야만 동작합니다. 한 군데라도 어긋나면 애플리케이션은 "권한이 있는데도" AccessDenied 를 맞습니다.
이 글은 IRSA가 "안 먹히는" 상황을 12가지로 쪼개서, 증상과 확인 명령, 해결책을 한 번에 정리한 실전 체크리스트입니다.
참고: 파드가 재시작되며 원인 파악이 어려우면 먼저 K8s CrashLoopBackOff 원인별 로그·Probe 해결 가이드처럼 로그를 안정적으로 확보한 뒤 IRSA를 진단하는 편이 빠릅니다.
IRSA 동작 흐름 30초 요약
- EKS 클러스터는 OIDC Provider를 가짐
- Pod는 ServiceAccount에 의해 projected token을 받음
- AWS SDK는
AWS_ROLE_ARN과AWS_WEB_IDENTITY_TOKEN_FILE로AssumeRoleWithWebIdentity를 호출 - STS가 IAM Role의 Trust Policy 조건(
sub,aud)을 검증하고 임시 자격증명을 발급 - 애플리케이션이 해당 자격증명으로 AWS API 호출
어디가 깨졌는지 찾으려면, 먼저 Pod 안에서 아래를 확인하는 게 가장 확실합니다.
kubectl -n <namespace> exec -it <pod> -- sh -lc '
echo "ROLE=$AWS_ROLE_ARN";
echo "TOKEN_FILE=$AWS_WEB_IDENTITY_TOKEN_FILE";
ls -al $AWS_WEB_IDENTITY_TOKEN_FILE 2>/dev/null || true;
aws sts get-caller-identity || true
'
위 명령에서 get-caller-identity 가 실패하면 IRSA 체인이 깨진 겁니다. 성공하지만 특정 서비스 호출만 AccessDenied 면 "역할은 Assume 됐지만 권한 정책이 부족"한 케이스입니다.
1) OIDC Provider가 클러스터에 연결되지 않음
증상
AssumeRoleWithWebIdentity호출 자체가 실패- 에러에
InvalidIdentityToken또는 issuer 관련 메시지
확인
aws eks describe-cluster --name <cluster> --query "cluster.identity.oidc.issuer" --output text
aws iam list-open-id-connect-providers | grep -i oidc || true
해결
OIDC provider가 없다면 생성/연결이 필요합니다. eksctl 사용 시 아래가 가장 간단합니다.
eksctl utils associate-iam-oidc-provider --cluster <cluster> --approve
2) Trust Policy의 Principal 이 잘못된 OIDC Provider를 가리킴
증상
- 같은 계정/리전의 다른 클러스터 OIDC로 설정해둔 경우
- 특정 클러스터에서만 IRSA 실패
확인
aws iam get-role --role-name <role> --query "Role.AssumeRolePolicyDocument" --output json
Trust Policy의 Federated 값이 클러스터 OIDC ARN과 일치해야 합니다.
해결
OIDC provider ARN을 올바른 것으로 교체합니다.
3) Trust Policy의 sub 조건이 ServiceAccount와 불일치
IRSA에서 가장 흔한 실수입니다.
증상
AccessDenied혹은Not authorized to perform sts:AssumeRoleWithWebIdentity- ServiceAccount를 바꿨거나 namespace를 바꾼 뒤부터 실패
확인
Trust Policy 조건에 보통 아래 형태가 들어갑니다.
system:serviceaccount:<namespace>:<serviceaccount>
MDX 빌드 에러 방지를 위해 부등호는 엔티티로 표기합니다.
{
"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/EXAMPLE:sub": "system:serviceaccount:prod:my-sa"
}
}
}
]
}
해결
- 실제 사용하는 namespace, serviceaccount로
sub를 맞춥니다. - 여러 SA를 허용하려면
StringLike와 와일드카드를 검토합니다.
4) Trust Policy의 aud 조건 누락/불일치
EKS projected token의 audience는 일반적으로 sts.amazonaws.com 입니다.
증상
InvalidIdentityToken또는 AssumeRole 실패- 오래된 예제 그대로 붙여넣은 경우(조건이 누락되거나 다른 audience)
확인
aws iam get-role --role-name <role> --query "Role.AssumeRolePolicyDocument" --output json
해결
aud 조건을 추가하거나 올바르게 수정합니다.
"Condition": {
"StringEquals": {
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLE:aud": "sts.amazonaws.com",
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLE:sub": "system:serviceaccount:prod:my-sa"
}
}
5) ServiceAccount에 role annotation이 없음/오타
증상
- Pod 환경변수
AWS_ROLE_ARN이 비어있음 - SDK가 노드 IAM role(또는 다른 체인)로 떨어짐
확인
kubectl -n <namespace> get sa <sa> -o yaml | sed -n '1,120p'
정상이라면 아래 annotation이 있어야 합니다.
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/<role>
해결
kubectl -n <namespace> annotate sa <sa> eks.amazonaws.com/role-arn=arn:aws:iam::<account>:role/<role> --overwrite
그리고 이미 떠 있는 Pod는 재생성해야 반영됩니다.
kubectl -n <namespace> rollout restart deploy <deployment>
6) Deployment가 다른 ServiceAccount를 참조함
ServiceAccount를 만들고 annotation도 달았는데, 정작 Deployment는 default 를 쓰는 경우가 많습니다.
확인
kubectl -n <namespace> get deploy <deploy> -o jsonpath='{.spec.template.spec.serviceAccountName}{"\n"}'
해결
Deployment(또는 Job, CronJob, StatefulSet)의 serviceAccountName 을 올바르게 지정합니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
template:
spec:
serviceAccountName: my-sa
containers:
- name: app
image: <image>
7) Pod 토큰 projected volume이 비활성화되었거나 경로가 깨짐
일부 보안 설정이나 커스텀 템플릿에서 automountServiceAccountToken: false 를 넣어버리면 IRSA는 즉시 죽습니다.
증상
AWS_WEB_IDENTITY_TOKEN_FILE는 있는데 파일이 없음- 또는 환경변수 자체가 없음
확인
kubectl -n <namespace> get pod <pod> -o jsonpath='{.spec.automountServiceAccountToken}{"\n"}'
kubectl -n <namespace> exec -it <pod> -- sh -lc 'ls -al /var/run/secrets/eks.amazonaws.com/serviceaccount || true'
해결
- Pod spec 또는 ServiceAccount의
automountServiceAccountToken을true로 - 보안상 꺼야 한다면 IRSA를 포기하고 다른 인증 전략을 써야 합니다.
8) AWS SDK가 IRSA 대신 다른 자격증명을 먼저 집어감
컨테이너 이미지에 AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY 가 남아있거나, ~/.aws/credentials 가 마운트되면 SDK는 IRSA를 건너뛸 수 있습니다.
증상
get-caller-identity는 되는데 예상한 role이 아님- 노드 role 또는 오래된 IAM user로 호출됨
확인
kubectl -n <namespace> exec -it <pod> -- sh -lc '
env | grep -E "^AWS_(ACCESS_KEY_ID|SECRET_ACCESS_KEY|SESSION_TOKEN|PROFILE)=" || true
ls -al ~/.aws 2>/dev/null || true
aws sts get-caller-identity
'
해결
- 정적 키 환경변수 제거
AWS_PROFILE제거- 이미지/차트에서 불필요한 credential 마운트 제거
9) STS 엔드포인트 접근 불가(프라이빗 서브넷, VPC 엔드포인트 이슈)
프라이빗 클러스터에서 NAT 없이 운영하거나, VPC 엔드포인트 정책이 막혀 있으면 STS 호출이 실패합니다.
증상
RequestError: send request failed류 네트워크 에러- 또는 STS만 타임아웃
확인
kubectl -n <namespace> exec -it <pod> -- sh -lc '
nslookup sts.amazonaws.com 2>/dev/null || true
wget -qO- https://sts.amazonaws.com/ 2>/dev/null | head || true
'
해결
- NAT Gateway/Instance 구성
- 또는 STS VPC Endpoint(Interface) 구성
- 엔드포인트 정책에서
sts:AssumeRoleWithWebIdentity허용
10) 리전 설정 불일치로 잘못된 STS로 감
일부 SDK/CLI는 리전이 비어 있으면 기본값으로 동작하거나, 다른 리전의 STS로 붙으며 정책/엔드포인트와 충돌할 수 있습니다.
증상
- 특정 리전에서만 실패
InvalidClientTokenId같은 혼동성 에러
확인
kubectl -n <namespace> exec -it <pod> -- sh -lc '
echo "AWS_REGION=$AWS_REGION";
echo "AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION";
aws configure list || true
'
해결
- 컨테이너에
AWS_REGION또는AWS_DEFAULT_REGION을 명시 - 멀티리전 환경이면 서비스별 엔드포인트도 점검
11) 역할은 Assume 됐는데, 실제 IAM Permission Policy가 부족
IRSA가 "안 먹힌다"고 느끼는 많은 케이스가 사실은 여기입니다. sts get-caller-identity 는 성공하지만, S3/ECR/Secrets Manager 호출에서 AccessDenied 가 납니다.
증상
AccessDenied에러 메시지에 호출한 API가 명시됨- 예:
s3:GetObject,secretsmanager:GetSecretValue,kms:Decrypt
- 예:
확인
kubectl -n <namespace> exec -it <pod> -- sh -lc 'aws sts get-caller-identity'
그리고 CloudTrail에서 해당 Role이 어떤 API를 거절당했는지 확인합니다.
해결
- Role에 필요한 action/resource를 정확히 추가
- KMS가 끼면 KMS Key policy까지 같이 봐야 합니다(다음 항목)
12) KMS/리소스 기반 정책(Resource Policy)에서 Role을 거부
S3 버킷 정책, KMS Key policy, ECR 리포지토리 정책, Secrets Manager 리소스 정책 등은 IAM permission만으로 끝나지 않습니다.
증상
- IAM policy에 권한을 줬는데도 계속
AccessDenied - 특히
kms:Decrypt는 IAM과 Key policy가 모두 허용해야 성공
확인 포인트
- KMS Key policy에 해당 Role ARN이 허용되는가
- S3 bucket policy에
Principal로 Role이 허용되는가 - 조직 SCP, Permission Boundary로 차단되는가
해결
- KMS Key policy에 Role을 추가
- S3 bucket policy에 Role을 허용
- SCP/Boundary가 있으면 해당 레이어에서 허용
빠른 진단 체크리스트(운영자용)
아래 순서대로 보면 대부분 10분 안에 원인이 좁혀집니다.
- Pod 안에서
aws sts get-caller-identity가 되나 - 결과 ARN이 기대한 Role인가
AWS_ROLE_ARN/AWS_WEB_IDENTITY_TOKEN_FILE이 존재하나- ServiceAccount annotation과 Deployment의
serviceAccountName이 일치하나 - IAM Role Trust policy의
sub/aud가 정확한가 - STS 네트워크가 열려 있나(NAT 또는 VPC Endpoint)
- 권한이 부족한 건 아닌가(CloudTrail로 거절 API 확인)
- 리소스 기반 정책(KMS/S3/Secrets)에서 막는 건 아닌가
재현 가능한 테스트용 매니페스트 예시
아래는 IRSA 검증을 위한 최소 Pod 예시입니다. aws-cli 로 get-caller-identity 를 찍어보고, 기대 Role이면 IRSA 파이프라인은 정상입니다.
apiVersion: v1
kind: Pod
metadata:
name: irsa-debug
namespace: prod
spec:
serviceAccountName: my-sa
restartPolicy: Never
containers:
- name: aws
image: public.ecr.aws/aws-cli/aws-cli:2.15.0
command: ["sh", "-lc"]
args:
- |
echo "ROLE=$AWS_ROLE_ARN"
echo "TOKEN_FILE=$AWS_WEB_IDENTITY_TOKEN_FILE"
aws sts get-caller-identity
sleep 3600
마무리
IRSA의 AccessDenied 는 "IAM 정책을 더 주면" 해결되는 문제가 아니라, 대개 신원(AssumeRole) 단계에서 막히거나 리소스 정책/KMS 같은 다른 레이어에서 거부되는 문제입니다. 위 12가지를 위에서 아래로 점검하면, 감으로 디버깅하던 시간을 구조적으로 줄일 수 있습니다.
추가로 ECR 인증 문제로 파드가 뜨지도 못하는 상황이라면 IRSA 이전 단계에서 막히는 것이므로 K8s ImagePullBackOff - ECR 인증·토큰 만료 해결도 함께 점검하는 것을 권장합니다.