- Published on
EKS IRSA 설정했는데 AccessDenied 뜨는 9가지
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스 워크로드든 배치 잡이든, EKS에서 AWS 권한을 가장 안전하게 주는 방식은 IRSA입니다. 그런데 IRSA를 “설정했다”고 생각했는데도 애플리케이션 로그에는 AccessDenied 혹은 AccessDeniedException 이 계속 찍히는 경우가 많습니다.
이 글은 “IRSA 자체가 안 붙는 문제”와 “IRSA는 붙었는데 특정 AWS 서비스에서만 거부되는 문제”를 분리해, 현장에서 자주 만나는 9가지 원인을 재현 가능한 체크리스트로 정리합니다.
관련해서 S3만 403이 나는 케이스는 아래 글도 함께 보면 진단 시간이 확 줄어듭니다.
- EKS IRSA는 되는데 S3만 403? 30분 진단
- IPv6 환경에서 STS가 403으로 보이는 특이 케이스는: EKS Pod에서 IPv6로만 STS 403 뜰 때 해결
진단 시작 전: “누가 거부당했는지”부터 확정
먼저 에러 메시지에서 User: 또는 arn:aws:sts::...:assumed-role/... 를 확인해야 합니다. IRSA 문제인지, 노드 IAM Role 문제인지가 여기서 갈립니다.
애플리케이션이 AWS SDK를 쓰는 경우, 보통 아래 중 하나로 나타납니다.
User: arn:aws:sts::123456789012:assumed-role/my-irsa-role/xxxxxxxx is not authorized to perform: ...User: arn:aws:iam::123456789012:role/eks-node-role is not authorized ...
두 번째 형태로 노드 Role이 보이면, IRSA가 적용되지 않았거나 SDK가 IRSA 자격증명 체인을 못 타고 있는 겁니다.
빠른 확인 커맨드
Pod 내부에서 다음을 확인합니다.
# IRSA에서는 이 파일이 있어야 합니다.
ls -al /var/run/secrets/eks.amazonaws.com/serviceaccount/
# SDK가 이 환경변수를 읽어야 합니다.
env | grep -E 'AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION|AWS_DEFAULT_REGION'
그리고 가능하면 aws sts get-caller-identity 로 “현재 누구로 호출되는지”를 못 박습니다.
aws sts get-caller-identity
1) ServiceAccount에 role annotation이 실제로 안 붙음
가장 흔합니다. Helm values나 Kustomize overlay에서 ServiceAccount가 따로 생성되거나, serviceAccountName 이 다른 이름을 가리키는 경우입니다.
확인:
kubectl -n myns get sa mysa -o yaml | sed -n '1,120p'
아래 annotation이 있어야 합니다.
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-irsa-role
해결 포인트:
- Deployment의
spec.template.spec.serviceAccountName이 실제 SA와 일치하는지 확인 - Helm 차트가
serviceAccount.create를true로 만들면서 다른 SA를 생성하지 않는지 확인
2) Pod가 기본 ServiceAccount로 떠버림
ServiceAccount를 만들어도 Deployment/Job에서 지정하지 않으면 기본 SA를 씁니다. 특히 CronJob에서 자주 발생합니다.
확인:
kubectl -n myns get pod mypod -o jsonpath='{.spec.serviceAccountName}'; echo
default 로 나오면 IRSA는 거의 100% 의도대로 적용되지 않습니다.
해결:
- Deployment/Job/CronJob 모두에
serviceAccountName명시 - 템플릿이 여러 개인 경우(예: Argo Rollouts, Spark Operator) 실제 Pod 템플릿 위치에 들어갔는지 재확인
3) IAM Role 신뢰 정책(trust policy)의 조건 키가 틀림
IRSA의 핵심은 IAM Role의 trust policy에서 OIDC provider와 sub 조건을 정확히 매칭하는 것입니다.
정상 예시(중요 부분만):
{
"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:mysa",
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:aud": "sts.amazonaws.com"
}
}
}
]
}
실수 패턴:
- OIDC provider URL에서
id/...값이 클러스터 것과 다름 sub에 namespace 또는 SA 이름 오타StringLike와StringEquals혼용하면서 의도치 않게 매칭 실패
검증 팁:
aws eks describe-cluster --name mycluster --query 'cluster.identity.oidc.issuer' --output text
이 issuer와 trust policy의 키 prefix가 정확히 동일해야 합니다.
4) OIDC Provider 자체가 계정에 등록되지 않았거나 다른 클러스터 것임
클러스터를 새로 만들고 IRSA Role은 예전 설정을 복붙한 경우, “provider ARN이 존재하지 않거나 다른 issuer” 일 수 있습니다.
확인:
aws iam list-open-id-connect-providers
그리고 provider 상세에서 URL 확인:
aws iam get-open-id-connect-provider --open-id-connect-provider-arn arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLE
해결:
eksctl utils associate-iam-oidc-provider를 사용하거나 Terraform로 현재 클러스터 issuer를 등록- 멀티 클러스터라면 “클러스터별 issuer” 가 다르다는 점을 전제로 Role을 분리
5) 애플리케이션이 IRSA 자격증명 체인을 안 탐
IRSA는 보통 AWS_WEB_IDENTITY_TOKEN_FILE 과 AWS_ROLE_ARN 을 통해 SDK가 AssumeRoleWithWebIdentity 를 호출하도록 유도합니다.
하지만 다음 상황이면 SDK가 이를 무시하고 다른 자격증명 소스를 씁니다.
- 컨테이너에
AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY가 하드코딩되어 우선순위를 선점 - AWS SDK 버전이 너무 낮아 web identity를 제대로 지원하지 않음
- 커스텀 credential provider를 강제로 사용
확인:
env | grep -E 'AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|AWS_PROFILE|AWS_SDK_LOAD_CONFIG'
해결:
- 정적 키 환경변수 제거
- 언어별 SDK 버전 업(특히 Java, Go, Node에서 구버전이면 IRSA 인식이 어긋나는 사례가 있음)
6) STS 호출은 되는데 대상 서비스에서 AccessDenied: 정책 리소스/조건 불일치
IRSA가 성공하면 sts get-caller-identity 는 통과합니다. 그런데 S3, DynamoDB, SQS 같은 서비스 호출에서만 거부되면 대개 IAM policy가 “역할에 붙어 있지만 조건/리소스가 안 맞는” 문제입니다.
대표 예:
- S3에서
arn:aws:s3:::my-bucket/*만 열어놓고ListBucket을arn:aws:s3:::my-bucket에 안 줌 - KMS를 쓰는데
kms:Decrypt권한이 없거나 key policy가 role을 허용하지 않음 - 조건에
aws:RequestedRegion등을 걸어놓고 실제 리전이 다름
S3 예시(자주 틀리는 포인트 포함):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListBucket",
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::my-bucket"
},
{
"Sid": "RWObjects",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-bucket/*"
}
]
}
진단은 CloudTrail에서 errorCode=AccessDenied 이벤트를 보고 “어떤 action이 어떤 resource로 평가됐는지” 확인하는 게 가장 빠릅니다.
7) Permission Boundary 또는 SCP 때문에 최종적으로 거부됨
Role에 정책을 붙여도, 다음 상위 제약이 있으면 결과는 AccessDenied입니다.
- IAM Permission Boundary
- AWS Organizations의 SCP
- 세션 정책(session policy)을 추가로 씌우는 구조
특징:
- 정책 문서만 보면 허용인데, 실제 호출은 계속 거부
- CloudTrail에
explicitDeny또는 조직 정책 관련 흔적이 보이기도 함
확인 포인트:
aws iam get-role --role-name my-irsa-role
출력에서 PermissionsBoundary 가 있는지 봅니다. SCP는 계정/OU 단에서 확인해야 합니다.
8) 토큰/시간/네트워크 이슈가 AccessDenied로 보이는 경우
정확히는 AccessDenied 가 아니라 InvalidIdentityToken RequestExpired SignatureDoesNotMatch 등으로 나타나기도 하지만, 운영 로그에서 뭉뚱그려 “권한 문제”로 분류되어 놓치기 쉽습니다.
체크:
- 노드 시간 동기화 문제로 STS 요청이 만료 처리
- 프록시/네트워크 경로에서 STS 엔드포인트 접근이 비정상
- IPv6 경로에서만 STS가 실패하는 환경
특히 IPv6-only 또는 듀얼스택에서 STS 접근이 꼬이는 케이스는 아래 글의 체크리스트가 바로 도움이 됩니다.
9) 동일 Pod에 여러 컨테이너가 있고, 일부만 IRSA를 못 받는 구조
Pod 단위로 ServiceAccount는 하나지만, 실제로는 다음 이유로 컨테이너별 동작이 달라질 수 있습니다.
- initContainer에서 AWS 호출을 하는데, 그 이미지의 SDK/CLI가 web identity를 지원하지 않음
- 사이드카(예: 로그/메트릭 에이전트)가 자체적으로 정적 키 또는 다른 프로파일을 사용
AWS_REGION이나 엔드포인트 설정이 컨테이너별로 다름
해결:
- AWS 호출이 있는 모든 컨테이너에서
aws sts get-caller-identity를 각각 실행 - initContainer 이미지에 들어있는 AWS CLI 버전 확인
# 컨테이너를 지정해서 실행
kubectl -n myns exec -it mypod -c app -- aws sts get-caller-identity
kubectl -n myns exec -it mypod -c sidecar -- aws sts get-caller-identity
재현 가능한 10분 체크리스트
아래 순서대로 보면 대부분 10분 안에 “IRSA가 안 붙는 문제”와 “권한 정책 문제”를 분리할 수 있습니다.
- Pod에서
aws sts get-caller-identity실행 - Pod의
serviceAccountName확인 - ServiceAccount annotation에
eks.amazonaws.com/role-arn확인 - Role trust policy의 issuer,
sub,aud확인 - 계정에 OIDC provider가 현재 클러스터 issuer로 등록되어 있는지 확인
- 정적 키 환경변수(
AWS_ACCESS_KEY_ID등) 유무 확인 - CloudTrail에서 거부된 action/resource 확인
- Permission boundary/SCP 존재 여부 확인
- KMS 사용 시 key policy와 role policy를 함께 점검
마무리: “IRSA는 됐는데 AccessDenied”는 결국 정책 평가 문제다
IRSA는 인증(누구냐)을 해결해주고, AccessDenied는 인가(무엇을 할 수 있냐)에서 터지는 경우가 많습니다. 그래서 진단도 sts get-caller-identity 로 인증을 먼저 확정한 뒤, CloudTrail로 인가 평가를 따라가는 방식이 가장 빠릅니다.
특정 서비스(S3)에서만 403이 반복된다면, 엔드포인트/VPC 경로/KMS까지 같이 보는 종합 진단이 필요합니다.