- Published on
EKS IRSA 설정했는데 AccessDenied? 7분 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
EKS에서 IRSA(IAM Roles for Service Accounts)를 설정했는데도 애플리케이션 로그에 AccessDenied가 뜨면 당황스럽습니다. 특히 aws s3 ls 같은 단순 호출도 막히는 경우가 많죠. IRSA는 구조상 “Pod ServiceAccount sub 클레임”과 “IAM Role Trust Policy 조건”이 정확히 맞아야만 동작합니다. 하나라도 어긋나면 SDK는 다른 자격증명 체인(노드 IAM, 빈 자격증명 등)으로 떨어지거나, STS AssumeRoleWithWebIdentity 단계에서 바로 거절됩니다.
이 글은 “7분 안에” 원인을 빠르게 좁히는 실전 체크리스트와, 가장 흔한 실수 패턴별 수정 방법을 제공합니다.
참고로 EKS 운영에서 자주 같이 터지는 이슈로는 IP 고갈이나 이미지 풀 인증 문제가 있는데, 필요하면 아래 글도 함께 보세요.
0) 먼저 확인: AccessDenied가 “어디서” 났는지
IRSA 관련 AccessDenied는 보통 두 군데에서 납니다.
- STS에서 WebIdentity AssumeRole이 거절
- 에러 예:
AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity - 원인: OIDC Provider 미설정, Trust Policy 조건 불일치,
sub/audmismatch
- AssumeRole은 성공했지만 실제 AWS API 권한이 부족
- 에러 예:
AccessDenied: Access Denied(S3),AccessDeniedException(DynamoDB) - 원인: Role Policy에 액션/리소스 권한 누락, KMS/버킷 정책/리소스 정책에서 거절
가장 빠른 분기 방법은 Pod 안에서 STS 호출을 직접 해보는 겁니다.
kubectl exec -n myns deploy/myapp -c app -- sh -lc 'aws sts get-caller-identity'
- 여기서 거절되면 “IRSA 연결(Trust/OIDC/SA)” 문제
- 여기서 성공하고, 특정 서비스만 거절되면 “Role 권한 또는 리소스 정책” 문제
1) 1분 컷: Pod가 정말 그 ServiceAccount를 쓰고 있나
의외로 가장 흔합니다. 배포는 바꿨는데 serviceAccountName이 빠져 기본값(default)으로 뜨는 케이스입니다.
kubectl get pod -n myns -o wide
kubectl get pod -n myns mypod -o jsonpath='{.spec.serviceAccountName}'; echo
원하는 SA가 아니라면 Deployment에 명시하세요.
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: myns
spec:
template:
spec:
serviceAccountName: myapp-sa
그리고 SA 자체가 올바른 Role을 가리키는지 확인합니다.
kubectl get sa -n myns myapp-sa -o yaml
아래 어노테이션이 있어야 합니다.
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/myapp-irsa-role
2) 2분 컷: WebIdentity 토큰 환경변수/파일이 주입됐나
IRSA가 정상이라면 Pod에 다음이 주입됩니다.
AWS_ROLE_ARNAWS_WEB_IDENTITY_TOKEN_FILE
Pod에서 확인합니다.
kubectl exec -n myns deploy/myapp -c app -- sh -lc 'env | grep AWS_'
kubectl exec -n myns deploy/myapp -c app -- sh -lc 'ls -al $(echo $AWS_WEB_IDENTITY_TOKEN_FILE)'
만약 AWS_WEB_IDENTITY_TOKEN_FILE이 비어있거나 파일이 없다면:
- SA 어노테이션이 잘못됐거나
- (드물게) 컨테이너가 환경변수를 지워버렸거나
- EKS/Admission 단계에서 주입이 실패했을 수 있습니다.
또한 SDK가 다른 자격증명 소스를 우선하도록 설정돼 있으면 헷갈립니다. 예를 들어 AWS_PROFILE을 강제로 넣거나, 앱에서 정적 키를 읽도록 해둔 경우입니다.
3) 3분 컷: OIDC Provider가 클러스터에 연결돼 있나
IRSA는 EKS OIDC Issuer와 IAM OIDC Provider가 매칭되어야 합니다.
클러스터의 OIDC Issuer URL을 확인합니다.
aws eks describe-cluster --name my-eks --query 'cluster.identity.oidc.issuer' --output text
그 다음 IAM에 OIDC Provider가 존재하는지 확인합니다.
aws iam list-open-id-connect-providers
여기서 Provider ARN이 안 보이거나, Issuer가 다른 클러스터의 것이라면 IRSA가 동작하지 않습니다.
가장 쉬운 해결은 eksctl로 OIDC Provider를 연결하는 것입니다.
eksctl utils associate-iam-oidc-provider --cluster my-eks --approve
4) 4분 컷: Trust Policy의 sub 조건이 정확히 일치하나
STS AssumeRoleWithWebIdentity가 거절되는 최대 원인입니다.
IRSA Role의 Trust Policy는 대략 이런 형태여야 합니다. 이때 sub는 반드시 system:serviceaccount:네임스페이스:서비스어카운트와 일치해야 합니다.
아래 예시는 부등호를 피하기 위해 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:aud": "sts.amazonaws.com",
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:sub": "system:serviceaccount:myns:myapp-sa"
}
}
}
]
}
자주 하는 실수 패턴:
- 네임스페이스를
default로 넣어둠 - SA 이름 오타
StringLike로 와일드카드 쓸 때 경로를 잘못 씀aud를 빼거나sts.amazonaws.com이 아닌 값으로 둠
빠르게 확인하려면 Role의 AssumeRolePolicyDocument를 조회하세요.
aws iam get-role --role-name myapp-irsa-role --query 'Role.AssumeRolePolicyDocument'
수정은 콘솔에서 해도 되지만, IaC로 관리하는 것이 안전합니다.
5) 5분 컷: 권한은 있는데도 S3가 AccessDenied라면(리소스 정책/KMS)
aws sts get-caller-identity는 성공하는데 S3/DynamoDB만 AccessDenied라면 IRSA 연결은 된 것입니다. 이제는 “Role Policy” 또는 “리소스 정책”을 봐야 합니다.
5-1) S3 버킷 정책이 Principal을 거절
S3는 버킷 정책에서 특정 Principal만 허용하거나, aws:PrincipalArn 조건으로 제한하는 경우가 많습니다. 이때 새로 만든 IRSA Role ARN이 허용 목록에 없으면 거절됩니다.
확인:
aws s3api get-bucket-policy --bucket my-bucket --query Policy --output text
5-2) SSE-KMS 사용 시 KMS 권한 누락
S3 객체가 SSE-KMS로 암호화되어 있으면, S3 권한만으로는 부족하고 KMS 키 정책 또는 IAM 정책에 kms:Decrypt 등이 필요합니다.
Role에 최소 권한 예:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::my-bucket/*"]
},
{
"Effect": "Allow",
"Action": ["kms:Decrypt", "kms:DescribeKey"],
"Resource": ["arn:aws:kms:ap-northeast-2:123456789012:key/KEY-ID"]
}
]
}
KMS는 “키 정책”도 함께 봐야 합니다. IAM에만 추가해도 키 정책에서 막히면 동일하게 AccessDenied가 납니다.
6) 6분 컷: SDK가 IRSA를 안 쓰는 경우(자격증명 체인 충돌)
애플리케이션이 다음 중 하나를 하고 있으면 IRSA를 무시하고 다른 자격증명을 쓰려다 실패할 수 있습니다.
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY를 빈 값으로라도 주입AWS_PROFILE설정- 코드에서 명시적으로 credentials provider를 고정
예: Java AWS SDK v2에서 정적 자격증명을 강제하면 IRSA가 무시됩니다.
S3Client s3 = S3Client.builder()
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create("x", "y")
))
.build();
IRSA를 쓰려면 기본 체인을 사용하세요.
S3Client s3 = S3Client.builder().build();
Node.js도 마찬가지로, 기본 provider chain이 AWS_WEB_IDENTITY_TOKEN_FILE을 인식합니다. 커스텀 provider를 쓰고 있다면 제거하거나 WebIdentity를 명시적으로 쓰세요.
7) 7분 컷: 재현/진단용 최소 Pod로 검증하기
앱이 복잡하면 “IRSA만” 검증하는 디버그 Pod를 하나 띄우는 게 가장 빠릅니다.
apiVersion: v1
kind: Pod
metadata:
name: irsa-debug
namespace: myns
spec:
serviceAccountName: myapp-sa
containers:
- name: awscli
image: public.ecr.aws/aws-cli/aws-cli:2.15.0
command: ["sh", "-lc", "sleep 3600"]
적용 후 다음을 실행합니다.
kubectl apply -f irsa-debug.yaml
kubectl exec -n myns irsa-debug -- sh -lc 'aws sts get-caller-identity'
kubectl exec -n myns irsa-debug -- sh -lc 'aws s3 ls s3://my-bucket/'
- STS 실패면 Trust/OIDC/SA로 회귀
- STS 성공, S3 실패면 Role Policy/버킷 정책/KMS 확인
자주 나오는 실수 체크리스트(한 장 요약)
- Pod가 기대한
serviceAccountName을 쓰는가 - SA에
eks.amazonaws.com/role-arn이 정확한가 - Pod에
AWS_ROLE_ARN,AWS_WEB_IDENTITY_TOKEN_FILE이 있는가 - EKS OIDC Issuer와 IAM OIDC Provider가 연결돼 있는가
- Role Trust Policy의
sub가system:serviceaccount:ns:sa와 일치하는가 - Trust Policy의
aud가sts.amazonaws.com인가 - STS는 되는데 서비스만 안 되면 리소스 정책(S3/KMS 등)을 봤는가
- 앱 코드/환경변수가 IRSA 자격증명 체인을 방해하지 않는가
결론
IRSA에서 AccessDenied가 날 때는 “IRSA가 안 붙었다”와 “붙었지만 권한이 없다”를 먼저 분리하면 해결 속도가 급격히 빨라집니다. Pod에서 aws sts get-caller-identity 한 번으로 방향이 갈리고, 그 다음은 ServiceAccount 매칭과 Trust Policy의 sub/aud 조건이 핵심입니다.
운영 중 비슷한 ‘원인 분기’가 중요한 문제들을 다룬 글로는 아래도 참고할 만합니다.