- Published on
EKS IRSA STS AccessDenied 10분 해결 체크리스트
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스나 노드 IAM Role 대신 IRSA(IAM Roles for Service Accounts)를 붙였는데, 애플리케이션 로그에 AccessDenied 혹은 Not authorized to perform sts:AssumeRoleWithWebIdentity가 뜨면 대부분은 설정 1~2곳이 어긋난 것입니다. 이 글은 원인 후보를 “가장 많이 틀리는 순서”로 정리해, 실제로 10분 안에 복구할 수 있는 체크리스트 형태로 안내합니다.
아래는 대표 증상입니다.
AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentityInvalidIdentityToken: No OpenIDConnect provider found in your accountAccessDeniedException: User: arn:aws:sts::... is not authorized to perform: ...- SDK가 자격 증명을 못 찾아
NoCredentialProviders유사 에러를 내거나, EC2 메타데이터로 떨어지는 현상
0) 먼저 결론: IRSA 동작 조건 4가지
IRSA는 아래 4개가 동시에 맞아야 합니다.
- EKS 클러스터에 OIDC Provider가 연결되어 있어야 함
- IAM Role의 Trust Policy가 OIDC issuer,
sub,aud조건과 일치해야 함 - Kubernetes ServiceAccount에 role ARN 어노테이션이 정확해야 함
- Pod가 해당 ServiceAccount를 사용하고, WebIdentity 토큰이 마운트되어야 함
이 중 하나라도 어긋나면 STS에서 거절합니다.
1) 1분 진단: 지금 Pod가 어떤 자격 증명을 쓰는지 확인
먼저 “IRSA로 가려는지”부터 확인합니다. 컨테이너 안에서 아래를 확인하세요.
kubectl -n <namespace> exec -it <pod-name> -- sh
# IRSA에서 핵심이 되는 환경변수
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 && echo
AWS_ROLE_ARN과AWS_WEB_IDENTITY_TOKEN_FILE이 보이고 토큰 파일이 존재하면, 최소한 “Pod까지의 연결”은 되어 있습니다.- 둘 다 없다면, ServiceAccount 어노테이션/Pod의 SA 지정/토큰 마운트 설정부터 봐야 합니다.
2) 가장 흔한 원인: ServiceAccount 어노테이션 오타 또는 Pod가 다른 SA 사용
ServiceAccount에 아래 어노테이션이 있어야 합니다.
eks.amazonaws.com/role-arn: IRSA로 사용할 IAM Role ARN
확인:
kubectl -n <namespace> get sa <serviceaccount-name> -o yaml
예시:
apiVersion: v1
kind: ServiceAccount
metadata:
name: app
namespace: myns
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/myns-app-irsa
그리고 Pod가 정말 그 SA를 쓰는지 확인합니다.
kubectl -n <namespace> get pod <pod-name> -o jsonpath='{.spec.serviceAccountName}'
여기서 default 가 나오면, SA를 제대로 지정하지 않은 것입니다. Deployment에 spec.template.spec.serviceAccountName 을 추가하세요.
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
template:
spec:
serviceAccountName: app
containers:
- name: app
image: ...
3) OIDC Provider 미연결: No OpenIDConnect provider found 해결
클러스터 OIDC issuer URL을 확인합니다.
aws eks describe-cluster --name <cluster-name> --query 'cluster.identity.oidc.issuer' --output text
출력 예시는 대략 다음 형태입니다.
https://oidc.eks.ap-northeast-2.amazonaws.com/id/XXXXXXXXXXXX
이 issuer에 대응하는 IAM OIDC Provider가 계정에 존재해야 합니다.
aws iam list-open-id-connect-providers
없다면 생성해야 합니다. eksctl 사용이 가장 빠릅니다.
eksctl utils associate-iam-oidc-provider --cluster <cluster-name> --approve
생성 후에도 AccessDenied가 지속되면, 다음 섹션의 Trust Policy 조건 불일치 가능성이 큽니다.
4) 3분 컷 핵심: IAM Role Trust Policy의 sub/aud 불일치
IRSA의 STS 거절 대부분은 Trust Policy 조건이 실제 토큰 클레임과 다르기 때문입니다.
4-1) 올바른 Trust Policy 예시
아래는 가장 표준적인 형태입니다. 특히 Principal.Federated 와 Condition.StringEquals 키가 정확해야 합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/XXXXXXXXXXXX"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.ap-northeast-2.amazonaws.com/id/XXXXXXXXXXXX:aud": "sts.amazonaws.com",
"oidc.eks.ap-northeast-2.amazonaws.com/id/XXXXXXXXXXXX:sub": "system:serviceaccount:myns:app"
}
}
}
]
}
여기서 자주 틀리는 포인트:
sub의 네임스페이스/SA 이름 오타aud를sts.amazonaws.com이 아닌 다른 값으로 둠- issuer URL에
https://를 포함해버림 (Condition key에는 보통 호스트 경로만 들어감) - 클러스터가 여러 개인데 다른 클러스터의 OIDC id를 참조
4-2) 실제 토큰 클레임을 확인해서 “정답”을 맞추기
토큰을 디코딩해서 sub 와 aud 를 직접 확인하면 가장 빠릅니다.
TOKEN=$(kubectl -n <namespace> exec -it <pod-name> -- cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token)
# JWT payload 디코딩 (busybox 환경이면 base64 옵션이 다를 수 있음)
echo "$TOKEN" | awk -F'.' '{print $2}' | base64 -d 2>/dev/null | cat
출력 JSON에서 다음을 찾습니다.
sub: 보통system:serviceaccount:<namespace>:<serviceaccount>aud: 보통sts.amazonaws.com
Trust Policy의 Condition이 이 값과 1글자라도 다르면 STS는 거절합니다.
5) 권한 정책(permissions policy) 누락: AssumeRole은 되는데 API가 AccessDenied
STS AssumeRoleWithWebIdentity 자체는 성공하지만, 그 다음 S3/Secrets Manager/DynamoDB 등 호출에서 AccessDenied 가 날 수 있습니다. 이 경우는 IRSA 연결 문제가 아니라 IAM Role에 붙은 권한 정책이 부족한 것입니다.
확인 방법:
- 애플리케이션 로그에서 “어떤 API가 거절되는지” 확인
- CloudTrail에서
eventSource와eventName확인
예: S3 읽기가 필요하면 최소한 다음이 있어야 합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::my-bucket/*"]
}
]
}
운영 환경에서는 리소스 범위를 좁히고, 필요 액션만 남기는 방식으로 점진적으로 tighten 하는 것을 권장합니다.
6) 토큰 마운트/Projected SA 토큰 이슈: 환경변수는 있는데 파일이 없을 때
보안 강화로 automountServiceAccountToken: false 를 걸어둔 경우, IRSA 토큰이 마운트되지 않아 실패합니다.
점검 포인트:
- ServiceAccount에
automountServiceAccountToken: false설정 여부 - Pod spec에
automountServiceAccountToken: false설정 여부
확인:
kubectl -n <namespace> get sa <serviceaccount-name> -o yaml | grep -n 'automountServiceAccountToken'
kubectl -n <namespace> get pod <pod-name> -o yaml | grep -n 'automountServiceAccountToken'
IRSA를 쓰는 Pod에서는 토큰이 필요하므로, 최소한 해당 워크로드에는 토큰 마운트를 허용해야 합니다.
7) SDK가 IRSA를 안 타는 경우: 오래된 SDK/커스텀 자격증명 로더
간혹 애플리케이션이 IRSA 대신 다른 프로바이더 체인을 타서 엉뚱한 주체로 요청합니다.
대표 케이스:
- 너무 오래된 AWS SDK가 WebIdentity를 기본 지원하지 않음
- 코드에서
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY를 강제로 주입 credential_process같은 외부 프로파일을 컨테이너에 넣음
진단:
- 컨테이너 환경변수에 정적 키가 들어가 있는지 확인
- 애플리케이션이 사용하는 SDK 버전 확인
Node.js 예시로, IRSA가 정상이라면 별도 설정 없이도 동작해야 합니다.
import { S3Client, ListBucketsCommand } from "@aws-sdk/client-s3";
const client = new S3Client({ region: process.env.AWS_REGION || "ap-northeast-2" });
const res = await client.send(new ListBucketsCommand({}));
console.log(res.Buckets);
위 코드에서 굳이 credentials를 주입하고 있다면 제거하고, 기본 체인이 WebIdentity를 인식하도록 두는 편이 장애가 적습니다.
8) 10분 복구 루틴(현장용)
시간 없을 때는 아래 순서대로만 보면 됩니다.
- Pod 내부에서
AWS_ROLE_ARN,AWS_WEB_IDENTITY_TOKEN_FILE존재 확인 - ServiceAccount 어노테이션
eks.amazonaws.com/role-arn확인 - Pod가 그 ServiceAccount를 쓰는지 확인
- 클러스터 OIDC issuer 확인 후, IAM OIDC Provider 존재 확인
- IAM Role Trust Policy에서 issuer id,
sub,aud정확히 일치시키기 - AssumeRole은 되는데 서비스 API만 거절되면 permissions policy 보강
대부분은 2~5에서 끝납니다.
9) 문제를 더 빨리 좁히는 로그/추적 팁
IRSA 문제는 “원인이 분산돼 보이는” 전형적인 인프라 트러블입니다. 이런 유형의 장애는 원인 추적 루틴이 중요합니다. 비슷한 방식으로 원인 분해가 필요한 케이스는 systemd 서비스가 계속 재시작될 때 원인 추적법 글의 접근(관측 지점 늘리기, 가정 제거하기)이 그대로 적용됩니다.
네트워크 경로까지 함께 의심된다면(예: 프록시/엔드포인트 경유, 사설망 STS 접근 문제 등) 트래픽 관점에서 AWS VPC Reachability Analyzer로 502 추적하기에서 소개한 “경로를 도구로 증명”하는 방법도 도움이 됩니다.
10) 마무리: 재발 방지 체크
- 네임스페이스와 ServiceAccount 이름은 바뀌기 쉬우므로, Trust Policy의
sub를 IaC로 고정 관리 - 클러스터를 재생성/이관하면 OIDC id가 바뀔 수 있으니, OIDC Provider와 Role Trust를 세트로 재검증
- 워크로드별 Role을 분리해 최소 권한 원칙 적용 (하나의 Role을 여러 SA가 공유하면 추적이 어려움)
IRSA의 STS AccessDenied 는 “어디가 틀렸는지”만 찾으면 수정은 단순합니다. 위 체크리스트대로 보면, 대부분 10분 안에 정상화할 수 있습니다.