- Published on
EKS에서 IRSA가 안 먹을 때 STS AccessDenied 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스든 노드 기반이든 EKS에서 AWS API를 호출해야 하는 순간이 옵니다. 이때 가장 안전한 방식이 IRSA(IAM Roles for Service Accounts)인데, 설정했다고 믿었는데도 애플리케이션 로그에 AccessDenied 혹은 Not authorized to perform sts:AssumeRoleWithWebIdentity가 뜨면 멘탈이 흔들립니다.
이 글은 “IRSA가 안 먹는다”라는 현상을 증상별로 빠르게 분류하고, OIDC부터 Trust Policy 조건, ServiceAccount 어노테이션, 토큰 마운트, SDK 동작까지 단계적으로 확인해 STS AccessDenied를 끝내는 실전 가이드입니다.
관련해서 네트워크 이슈로 AWS STS 엔드포인트 자체가 안 나가는 경우도 있어, egress가 의심되면 함께 확인해보세요: EKS에서 Pod는 정상인데 egress만 막힐 때 점검
대표 증상과 원인 빠른 매핑
먼저 로그에 찍히는 메시지에 따라 원인이 갈립니다.
1) AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity
- IRSA의 핵심인 웹 아이덴티티 토큰 기반 AssumeRole이 거부됨
- 원인 후보
- IAM Role Trust Policy의
sub혹은aud조건 불일치 - OIDC Provider가 클러스터와 불일치(다른 클러스터 OIDC 사용)
- ServiceAccount 어노테이션 누락 또는 오타
- Pod가 해당 ServiceAccount를 실제로 사용하지 않음
- IAM Role Trust Policy의
2) InvalidIdentityToken 또는 No OpenIDConnect provider found
- OIDC Provider 등록 자체가 잘못되었거나, thumbprint 이슈 또는 ARN 불일치
3) AccessDenied인데 STS가 아니라 S3, DynamoDB 같은 서비스 액션에서만 발생
- AssumeRole은 성공했지만 권한 정책(permissions policy) 이 부족
4) SDK가 계속 노드 IAM Role(Instance Profile)로 호출하는 느낌
- Pod에 토큰이 마운트되지 않았거나, SDK가 IRSA 환경변수를 못 읽는 구성
IRSA 동작 원리(문제 지점 찾기용)
IRSA는 대략 다음 흐름입니다.
- EKS가 Pod에 ServiceAccount 토큰(웹 아이덴티티 토큰)을 마운트
- Pod 환경변수로
AWS_ROLE_ARN,AWS_WEB_IDENTITY_TOKEN_FILE제공 - AWS SDK가 STS의
AssumeRoleWithWebIdentity호출 - STS가 IAM Role Trust Policy 조건을 검증하고 임시 자격 증명 발급
따라서 실패 지점은 크게 네 군데입니다.
- OIDC Provider 등록
- ServiceAccount 어노테이션과 Pod 연결
- Trust Policy 조건
- 애플리케이션 런타임에서 토큰/환경변수/SDK 동작
1단계: Pod가 진짜 그 ServiceAccount를 쓰는지 확인
IRSA 문제의 상당수는 “ServiceAccount를 만들었는데 Deployment가 그걸 안 씀”입니다.
kubectl -n myns get deploy myapp -o jsonpath='{.spec.template.spec.serviceAccountName}'
출력이 비어 있으면 기본 ServiceAccount를 쓰고 있는 겁니다.
다음으로 실제 Pod가 어떤 SA로 떠 있는지 확인합니다.
kubectl -n myns get pod -l app=myapp -o jsonpath='{.items[0].spec.serviceAccountName}'
원하는 ServiceAccount가 아니라면 Deployment에 명시하세요.
spec:
template:
spec:
serviceAccountName: myapp-sa
2단계: ServiceAccount 어노테이션 확인
IRSA는 ServiceAccount에 Role ARN 어노테이션이 필수입니다.
kubectl -n myns get sa myapp-sa -o yaml
다음 형태가 있어야 합니다.
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/myapp-irsa-role
오타가 잦은 포인트
- 키:
eks.amazonaws.com/role-arn정확히 - 값: Role ARN 계정/이름 정확히
3단계: Pod 내부에서 IRSA 환경변수와 토큰 파일 확인
Pod에 들어가서 다음을 확인합니다.
kubectl -n myns exec -it deploy/myapp -- sh
env | grep -E 'AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION|AWS_DEFAULT_REGION'
ls -al /var/run/secrets/eks.amazonaws.com/serviceaccount/
cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token | head -c 20
정상이라면
AWS_ROLE_ARN존재AWS_WEB_IDENTITY_TOKEN_FILE존재- 토큰 파일이 존재
만약 토큰 디렉터리가 없거나 비어 있으면 다음을 의심합니다.
- Pod 혹은 SA에
automountServiceAccountToken: false가 설정됨 - 구형 방식 토큰 경로만 기대하는 커스텀 이미지/런타임
예를 들어 다음 설정이 있으면 IRSA가 사실상 동작할 수 없습니다.
spec:
automountServiceAccountToken: false
4단계: 클러스터 OIDC Issuer와 IAM OIDC Provider 일치 확인
클러스터의 OIDC Issuer URL을 확인합니다.
aws eks describe-cluster --name mycluster --query 'cluster.identity.oidc.issuer' --output text
출력 예시는 보통 다음 형태입니다.
https://oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E
IAM에 등록된 OIDC Provider 목록에서 해당 URL이 있는지 확인합니다.
aws iam list-open-id-connect-providers
그리고 Provider ARN을 얻어 상세를 봅니다.
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...
여기서 중요한 것은
- URL이 정확히 일치하는지
ClientIDList에sts.amazonaws.com이 포함되는지
불일치하면 IRSA가 AssumeRole 단계에서 막힙니다.
5단계: IAM Role Trust Policy의 sub 와 aud 조건 점검
가장 많이 틀리는 부분이 Trust Policy 조건입니다.
정상적인 Trust Policy 예시는 다음과 같습니다. (부등호 기호를 피하기 위해 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:myapp-sa",
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:aud": "sts.amazonaws.com"
}
}
}
]
}
체크 포인트
sub 불일치
- 네임스페이스가 다르거나
- ServiceAccount 이름이 다르거나
system:serviceaccount:접두어가 빠졌거나- 와일드카드로 바꾸려다 조건 키를 잘못 씀
와일드카드를 쓰려면 StringLike를 사용합니다.
{
"Condition": {
"StringLike": {
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLE:sub": "system:serviceaccount:myns:*"
},
"StringEquals": {
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLE:aud": "sts.amazonaws.com"
}
}
}
aud 불일치
- 기본적으로 IRSA는
sts.amazonaws.com이어야 합니다. - 일부 라이브러리나 설정이
audience를 바꾸는 케이스가 있는데, 그 경우 Trust Policy도 같이 맞춰야 합니다.
6단계: 권한 정책(permissions policy)과 Trust Policy를 혼동하지 않기
STS AssumeRoleWithWebIdentity가 AccessDenied면 대개 Trust Policy 문제입니다.
반면 AssumeRole은 성공했는데 S3에서 AccessDenied가 나면 Role에 붙은 권한 정책이 부족한 겁니다.
예시로 S3 읽기만 필요하면 다음처럼 최소 권한을 부여합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
]
}
]
}
여기서 흔한 실수
ListBucket은 버킷 ARN에,GetObject는 오브젝트 ARN에 권한이 필요- 리전 서비스(예: KMS, Secrets Manager)는 리전/리소스 ARN이 정확해야 함
7단계: AWS SDK가 IRSA를 무시하는 케이스 정리
IRSA가 제공하는 핵심은 AWS_WEB_IDENTITY_TOKEN_FILE입니다. 대부분의 최신 AWS SDK는 이를 자동 인식합니다.
하지만 다음 케이스에서 “노드 Role로 호출”하거나 “자격 증명 못 찾음”이 발생할 수 있습니다.
- 컨테이너에 고정된
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY가 들어있음- SDK는 보통 정적 키를 우선합니다.
- 매우 구형 AWS SDK 사용
- 커스텀 credential provider 체인 사용
AWS_EC2_METADATA_DISABLED설정과 혼합된 이상 동작(드물지만 환경에 따라)
Pod 안에서 어떤 자격 증명을 쓰는지 확인하려면, 가능하면 AWS CLI를 임시로 설치하거나 디버그 컨테이너를 붙여 다음을 실행합니다.
aws sts get-caller-identity
여기서 반환되는 ARN이 기대하는 Role인지 확인하세요.
8단계: STS 엔드포인트 접근(egress) 문제도 배제하기
간혹 AccessDenied가 아니라 타임아웃, DNS 실패로 시작하지만 애플리케이션에서 뭉뚱그려 권한 문제처럼 보이기도 합니다.
확인 항목
- Pod에서 STS DNS 조회
- NAT Gateway, 라우팅 테이블, 보안 그룹, NACL
- VPC 엔드포인트(STS Interface Endpoint) 사용 여부
egress가 의심되면 다음 글의 체크리스트가 바로 도움이 됩니다.
9단계: 재현 가능한 최소 디버그 매니페스트
문제 원인을 빠르게 분리하려면 “애플리케이션”을 빼고, AWS CLI만으로 get-caller-identity를 돌려보는 게 가장 좋습니다.
아래는 디버그용 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", "-c"]
args:
- |
echo "ROLE=$AWS_ROLE_ARN";
echo "TOKEN=$AWS_WEB_IDENTITY_TOKEN_FILE";
aws sts get-caller-identity;
sleep 3600
이 Pod에서 aws sts get-caller-identity가 실패하면 IRSA 레이어 문제이고, 성공하면 애플리케이션 SDK/설정 문제로 좁혀집니다.
10단계: 최종 체크리스트(현장에서 많이 잡히는 순서)
- Deployment에
serviceAccountName이 지정되어 있는가 - ServiceAccount에
eks.amazonaws.com/role-arn이 정확한가 - Pod 내부에
AWS_WEB_IDENTITY_TOKEN_FILE과 토큰 파일이 존재하는가 - 클러스터 OIDC Issuer URL과 IAM OIDC Provider가 정확히 매칭되는가
- Role Trust Policy의
sub가system:serviceaccount:네임스페이스:서비스어카운트와 일치하는가 - Trust Policy에
aud가sts.amazonaws.com으로 들어있는가 - (AssumeRole 성공 후) 서비스 권한 정책이 최소 권한으로 올바른가
- STS로 나가는 네트워크 egress가 되는가
- 컨테이너에 정적 AWS 키가 주입되어 IRSA를 덮어쓰지 않는가
마무리
IRSA에서 STS AccessDenied는 “권한이 부족하다”라기보다 신원 연동(토큰, OIDC, Trust Policy 조건)이 어긋났다는 신호인 경우가 많습니다. 특히 sub 조건 불일치와 “Pod가 다른 ServiceAccount를 사용”하는 문제는 재현 빈도가 압도적으로 높습니다.
위 순서대로 확인하면 대부분 10분 안에 원인을 특정할 수 있고, 디버그 Pod로 aws sts get-caller-identity까지 성공시키면 IRSA 레이어는 끝났다고 봐도 됩니다. 이후는 애플리케이션 SDK 설정과 개별 AWS 서비스 권한 정책을 정리하는 단계로 넘어가면 됩니다.
추가로 클러스터 레벨 장애나 CNI 문제로 Pod 네트워크가 꼬일 때도 STS 호출이 실패할 수 있으니, 노드 상태 이상이 동반된다면 다음 글도 참고해보세요: EKS kubelet NotReady - CNI plugin not initialized 해결