- Published on
EKS IRSA AccessDenied 5분 해결 체크리스트
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
EKS에서 IRSA(IAM Roles for Service Accounts)를 적용했는데도 애플리케이션 로그에 AccessDenied가 뜨면, 대부분은 “권한이 부족”이라기보다 신뢰 정책(AssumeRoleWithWebIdentity) 매칭 실패 또는 ServiceAccount 토큰/어노테이션 불일치처럼 IRSA 연결 고리가 끊긴 경우가 많습니다.
이 글은 장애 상황에서 가장 빨리 원인을 좁히는 순서대로, 5분 내 진단 루틴과 바로 고칠 수 있는 설정 예시를 제공합니다.
참고로 OIDC 기반 AssumeRole 흐름은 GitHub Actions OIDC와도 구조가 유사합니다. IRSA가 아니라 CI에서 STS 세션/신뢰정책 문제를 겪고 있다면 GitHub Actions OIDC AWS AssumeRole 1시간 제한 해결도 함께 보면 원인 파악에 도움이 됩니다.
0. 증상 패턴부터 분류하기
IRSA 문제의 AccessDenied는 크게 두 갈래로 나뉩니다.
- STS에서 막힘
- 예:
AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity - 예:
InvalidIdentityToken또는No OpenIDConnect provider found
- STS는 되는데 AWS API에서 막힘
- 예:
AccessDenied: Access Denied (Service: Amazon S3; Status Code: 403; ...) - 예:
User: arn:aws:sts::...:assumed-role/... is not authorized to perform: s3:GetObject
진단은 1)부터 먼저입니다. STS가 실패하면 어떤 정책을 붙여도 해결되지 않습니다.
1. 5분 진단 루틴(가장 빠른 순서)
1-1. Pod가 정말 IRSA 토큰을 쓰는지 30초 확인
먼저 해당 Pod에 AWS 웹 아이덴티티 환경 변수가 주입됐는지 봅니다.
kubectl -n <namespace> exec -it <pod> -- env | egrep 'AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION|AWS_DEFAULT_REGION'
정상이라면 아래가 보여야 합니다.
AWS_ROLE_ARN=arn:aws:iam::123456789012:role/your-irsa-roleAWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token
둘 중 하나라도 없다면:
- ServiceAccount 어노테이션이 빠졌거나
- Pod가 그 ServiceAccount를 쓰지 않거나
- (드물게) EKS Pod Identity Webhook 주입이 안 되는 상황입니다.
또한 토큰 파일이 존재하는지도 확인합니다.
kubectl -n <namespace> exec -it <pod> -- ls -l /var/run/secrets/eks.amazonaws.com/serviceaccount/token
1-2. Pod가 어떤 ServiceAccount를 쓰는지 10초 확인
kubectl -n <namespace> get pod <pod> -o jsonpath='{.spec.serviceAccountName}{"\n"}'
원래 의도한 ServiceAccount가 아니라면 Deployment/Job 설정부터 수정해야 합니다.
1-3. ServiceAccount 어노테이션 확인(1분)
kubectl -n <namespace> get sa <serviceaccount> -o yaml
아래 어노테이션이 핵심입니다.
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/your-irsa-role
오타가 흔합니다.
- 계정 ID 틀림
- role 이름 틀림
- namespace 다른데 같은 이름 SA를 보고 있음
1-4. IAM Role 신뢰 정책의 sub/aud가 정확한지(2분)
IRSA에서 가장 흔한 원인입니다. IAM Role의 Trust policy에서 Condition이 ServiceAccount의 주체(subject) 와 정확히 매칭돼야 합니다.
주체는 아래 형식입니다.
system:serviceaccount:<namespace>:<serviceaccount>
그리고 aud는 일반적으로 sts.amazonaws.com 입니다.
IAM Role의 신뢰 정책을 확인합니다.
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:your-ns:your-sa",
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:aud": "sts.amazonaws.com"
}
}
}
]
}
여기서 자주 틀리는 지점:
- OIDC provider URL의 리전/클러스터 ID가 다름
sub의 namespace 또는 sa 이름이 다름StringLike로 와일드카드를 쓰다가 의도치 않게 매칭 실패 또는 과도한 허용
1-5. 클러스터에 OIDC Provider가 등록돼 있는지(1분)
클러스터 OIDC Issuer URL을 확인합니다.
aws eks describe-cluster --name <cluster-name> --query 'cluster.identity.oidc.issuer' --output text
출력은 보통 https://oidc.eks.<region>.amazonaws.com/id/<id> 형태입니다.
이 Issuer에서 https://를 뺀 도메인이 IAM OIDC provider로 등록돼 있어야 합니다.
aws iam list-open-id-connect-providers
등록이 안 되어 있으면 IRSA 자체가 성립하지 않습니다. 이 경우 eksctl을 쓴다면 아래로 생성합니다.
eksctl utils associate-iam-oidc-provider --cluster <cluster-name> --approve
2. 가장 흔한 원인 7가지와 즉시 수정법
2-1. Deployment가 다른 ServiceAccount를 사용
의도한 SA를 만들고 어노테이션을 붙였는데도 Pod가 default SA를 쓰는 경우가 많습니다.
Deployment에 아래를 명시합니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
namespace: your-ns
spec:
template:
spec:
serviceAccountName: your-sa
적용 후 Pod를 재시작해야 환경 변수가 다시 주입됩니다.
kubectl -n your-ns rollout restart deploy/app
2-2. ServiceAccount 어노테이션 키 오타
정확한 키는 아래입니다.
eks.amazonaws.com/role-arn
예시:
kubectl -n your-ns annotate sa your-sa eks.amazonaws.com/role-arn=arn:aws:iam::123456789012:role/your-irsa-role --overwrite
2-3. Trust policy의 sub가 namespace/sa와 불일치
가장 빠른 해결은 Trust policy를 실제 값에 맞춰 고치는 것입니다.
- namespace:
your-ns - serviceaccount:
your-sa
sub는 반드시 system:serviceaccount:your-ns:your-sa 입니다.
수정 후에도 기존 Pod는 계속 실패할 수 있으니 재기동합니다.
2-4. aud 누락 또는 값 불일치
기본적으로 EKS IRSA는 aud=sts.amazonaws.com 매칭을 기대합니다. 신뢰 정책에 aud 조건이 없거나 다른 값이면 AssumeRoleWithWebIdentity가 거부될 수 있습니다.
신뢰 정책에 아래를 포함시키세요.
...:audequalssts.amazonaws.com
2-5. 같은 이름의 ServiceAccount가 다른 namespace에 존재
your-sa가 여러 namespace에 있고, Trust policy는 prod를 가리키는데 실제 Pod는 dev에 떠 있는 식의 실수가 잦습니다.
아래로 전체를 검색합니다.
kubectl get sa -A | grep -E '(^NAMESPACE|your-sa)'
2-6. STS는 성공하지만 S3 등 권한이 부족
이 경우에는 IRSA 연결은 정상이고, 단순히 role permission policy가 부족합니다.
애플리케이션 로그에서 호출한 API 액션을 보고 최소 권한으로 추가합니다.
예시: 특정 버킷 읽기
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::your-bucket/*"]
},
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::your-bucket"]
}
]
}
여기서도 흔한 함정이 있습니다.
ListBucket은 버킷 ARN에,GetObject는 오브젝트 ARN에 부여해야 함
2-7. SDK가 IRSA를 안 쓰고 다른 자격 증명을 우선함
컨테이너 이미지에 아래가 들어있으면 IRSA보다 우선될 수 있습니다.
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY가 하드코딩된 환경 변수~/.aws/credentials가 이미지에 포함
Pod 환경에서 키가 보이면 제거하세요.
kubectl -n <namespace> exec -it <pod> -- env | egrep 'AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|AWS_PROFILE'
3. “지금 당장” 원인 확정하는 디버그 Pod
운영 Pod에 직접 들어가기 어렵다면, 같은 ServiceAccount로 디버그 Pod를 띄워 STS 호출부터 확인하는 게 가장 빠릅니다.
아래는 AWS CLI가 있는 이미지로 테스트하는 예시입니다.
kubectl -n your-ns run irsa-debug \
--image=amazon/aws-cli:2.15.40 \
--serviceaccount=your-sa \
--restart=Never \
-it --rm -- sh
쉘에 들어가면 다음을 확인합니다.
aws sts get-caller-identity
정상이라면 Arn이 assumed-role/your-irsa-role 형태로 나옵니다.
추가로 실제 문제였던 API를 재현합니다.
aws s3 ls s3://your-bucket/
- 여기서
AccessDenied가 나면 permission policy 문제 - 여기서 성공하면 애플리케이션 쪽 SDK 설정/환경 변수 문제 가능성이 큽니다
4. 재발 방지: IRSA 변경 시 체크해야 할 것들
4-1. 변경 후에는 반드시 Pod 재기동
IRSA는 Pod 생성 시점에 토큰/환경 변수가 주입됩니다. ServiceAccount 어노테이션이나 role trust policy를 바꿨다면, 아래 중 하나가 필요합니다.
kubectl -n your-ns rollout restart deploy/<name>
또는 Job/CronJob이면 새로 실행되게 해야 합니다.
4-2. GitOps 환경(Argo CD)에서는 드리프트 확인
IRSA 관련 리소스는 ServiceAccount와 Deployment가 함께 맞물립니다. GitOps에서 일부만 반영되면 계속 AccessDenied가 반복될 수 있습니다. 동기화 상태가 꼬였을 때는 Argo CD Sync 실패 - OutOfSync·Degraded 해결처럼 상태/이벤트를 먼저 정리한 뒤 IRSA 변경을 적용하는 게 안전합니다.
4-3. 최소 권한 + 명시적 리소스 범위
IRSA role에 광범위 권한을 주면 빠르게 해결되는 것처럼 보이지만, 나중에 권한 축소 시 다시 장애가 납니다. AccessDenied가 났던 API 액션과 리소스 ARN을 기준으로 좁혀가세요.
5. 결론: 5분 안에 끝내는 핵심만 요약
- Pod에서
AWS_ROLE_ARN과AWS_WEB_IDENTITY_TOKEN_FILE이 보이는지 확인 - Pod가 의도한
serviceAccountName을 쓰는지 확인 - ServiceAccount에
eks.amazonaws.com/role-arn이 정확한지 확인 - IAM Role trust policy의 OIDC provider,
sub,aud가 정확히 매칭되는지 확인 - STS가 되면 그 다음은 permission policy(예: S3 ARN 범위) 문제
위 순서대로 보면, 대부분의 EKS IRSA AccessDenied는 “권한을 더 주는 것”이 아니라 “신뢰 정책과 ServiceAccount 매칭을 바로잡는 것”만으로 빠르게 해결됩니다.