- Published on
EKS IRSA 설정했는데 AccessDenied 뜰 때 점검
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
EKS에서 IRSA(IAM Roles for Service Accounts)를 구성해 Pod에 AWS 권한을 위임했는데, 애플리케이션 로그에 아래와 같은 오류가 뜨는 경우가 많습니다.
AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentityInvalidIdentityTokenNo OpenIDConnect provider found in your account
대부분 “IRSA를 설정했다”는 사실만으로는 충분하지 않습니다. IRSA는 (1) 클러스터 OIDC Provider, (2) IAM Role의 Trust Policy, (3) Kubernetes ServiceAccount 어노테이션, (4) Pod에 주입되는 토큰/환경변수, (5) 네트워크로 STS 접근 가능 여부가 모두 맞물려야 동작합니다.
이 글은 원인별로 재현 가능한 체크리스트와 함께, 실제로 어떤 값을 확인해야 하는지 커맨드 중심으로 정리합니다. (네트워크 이슈로 STS 호출 자체가 막히는 경우도 있으니, egress가 의심되면 EKS에서 Pod는 정상인데 egress만 막힐 때 점검도 함께 보세요.)
1) 에러 메시지로 1차 분류하기
먼저 에러 문구가 무엇인지에 따라 방향이 갈립니다.
A. AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity
- Trust policy 조건 불일치(sub/aud)
- ServiceAccount 이름/네임스페이스 불일치
- OIDC Provider ARN/URL 불일치
- Role에 붙인 Policy는 맞는데 AssumeRole 자체가 거부되는 케이스
B. No OpenIDConnect provider found
- 클러스터 OIDC Provider를 IAM에 미등록
- 다른 계정/리전에 등록한 Provider를 참조
C. InvalidIdentityToken / invalid audience
- Trust policy의
aud조건이 잘못됨(대개sts.amazonaws.com) - 토큰 파일이 예상과 다름(구식 토큰, projected token 미사용 등)
D. STS가 403/timeout인데 IRSA처럼 보이는 경우
- IPv6/라우팅/DNS 등으로 STS 엔드포인트 접근 실패 → SDK가 “자격 증명 획득 실패”로 포장
- 특히 IPv6 환경에서 STS만 403이 나는 특이 케이스는 EKS Pod에서 IPv6로만 STS 403 뜰 때 해결을 참고하세요.
2) 가장 흔한 원인: Trust Policy의 sub/aud 조건 불일치
IRSA의 핵심은 IAM Role의 Trust Relationship입니다. 여기서 Principal.Federated(OIDC Provider)와 Condition(sub/aud)이 한 글자라도 다르면 AccessDenied가 납니다.
2-1. 올바른 Trust Policy 예시
아래는 가장 표준적인 형태입니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>:aud": "sts.amazonaws.com",
"oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>:sub": "system:serviceaccount:<NAMESPACE>:<SERVICEACCOUNT_NAME>"
}
}
}
]
}
자주 틀리는 포인트
sub의 포맷은 반드시system:serviceaccount:<ns>:<sa>- 네임스페이스/SA 이름을 바꿨는데 Role Trust는 그대로인 경우
StringLike/StringEquals를 섞어 쓰다 의도치 않게 매칭 실패- OIDC ID가 다른 클러스터의 값(클러스터 재생성 후 OIDC가 바뀜)
2-2. 현재 Role Trust Policy 확인
aws iam get-role \
--role-name <ROLE_NAME> \
--query 'Role.AssumeRolePolicyDocument' \
--output json
여기서 Principal.Federated와 Condition의 키가 클러스터 OIDC URL과 정확히 일치하는지 확인합니다.
3) OIDC Provider 자체가 잘못되었거나 누락된 경우
3-1. 클러스터 OIDC Issuer URL 확인
aws eks describe-cluster \
--name <CLUSTER_NAME> \
--region <REGION> \
--query 'cluster.identity.oidc.issuer' \
--output text
출력 예:
https://oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E
3-2. IAM에 OIDC Provider가 등록되어 있는지 확인
aws iam list-open-id-connect-providers --output json
그리고 해당 Provider의 URL이 맞는지:
aws iam get-open-id-connect-provider \
--open-id-connect-provider-arn arn:aws:iam::<ACCOUNT_ID>:oidc-provider/oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>
누락되었다면(대표적으로 eksctl 사용)
eksctl utils associate-iam-oidc-provider \
--cluster <CLUSTER_NAME> \
--region <REGION> \
--approve
4) ServiceAccount 어노테이션이 Role ARN을 제대로 가리키는지
IRSA는 ServiceAccount에 아래 어노테이션이 있어야 합니다.
eks.amazonaws.com/role-arn: arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME>
4-1. ServiceAccount 확인
kubectl -n <NAMESPACE> get sa <SERVICEACCOUNT_NAME> -o yaml
정상 예:
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-sa
namespace: my-ns
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-irsa-role
자주 틀리는 포인트
- Pod는
defaultSA를 쓰는데, 다른 SA에만 어노테이션을 붙임 - Helm/Kustomize 적용 과정에서 어노테이션이 덮어씌워짐
- Role ARN의 계정/이름 오타
4-2. Pod가 실제로 어떤 SA를 쓰는지 확인
kubectl -n <NAMESPACE> get pod <POD_NAME> -o jsonpath='{.spec.serviceAccountName}'; echo
5) Pod 내부에서 IRSA 토큰/환경변수 주입 여부 확인
IRSA가 정상이라면 Pod에는 보통 다음이 존재합니다.
AWS_WEB_IDENTITY_TOKEN_FILEAWS_ROLE_ARN/var/run/secrets/eks.amazonaws.com/serviceaccount/token
5-1. Pod 내부에서 확인
kubectl -n <NAMESPACE> exec -it <POD_NAME> -- sh -c '
env | egrep "AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION|AWS_DEFAULT_REGION" || true
ls -l /var/run/secrets/eks.amazonaws.com/serviceaccount/ || true
head -c 50 /var/run/secrets/eks.amazonaws.com/serviceaccount/token 2>/dev/null || true
echo
'
주입이 안 된다면
- Pod가 해당 ServiceAccount를 사용하지 않음
- (드물게) admission/webhook/PSA 정책이 projected volume을 막음
- 매우 오래된 EKS/구성에서 토큰 프로젝션 설정이 꼬였을 수 있음
6) STS 호출을 직접 테스트해 “AssumeRole이 되는지” 확인
애플리케이션(SDK) 문제인지, IRSA/IAM 문제인지 분리하려면 Pod에서 STS를 직접 호출해보는 게 가장 빠릅니다.
6-1. awscli 디버그로 확인
kubectl -n <NAMESPACE> exec -it <POD_NAME> -- sh -c '
aws sts get-caller-identity --debug 2>&1 | tail -n 60
'
여기서도 AssumeRoleWithWebIdentity가 AccessDenied면 Trust Policy/SA/OIDC 문제일 확률이 높습니다.
6-2. 토큰으로 AssumeRoleWithWebIdentity를 강제 실행
kubectl -n <NAMESPACE> exec -it <POD_NAME> -- sh -c '
TOKEN=$(cat $AWS_WEB_IDENTITY_TOKEN_FILE)
aws sts assume-role-with-web-identity \
--role-arn "$AWS_ROLE_ARN" \
--role-session-name irsa-debug \
--web-identity-token "$TOKEN" \
--duration-seconds 900
'
- 여기서 성공하면: SDK/환경변수/리전 설정 또는 애플리케이션 자격증명 체인 문제
- 여기서 실패하면: IRSA 핵심 구성(Trust/OIDC/SA) 문제
7) aud 조건(=sts.amazonaws.com) 불일치로 인한 거부
간혹 Trust Policy에서 aud를 빼거나 다른 값으로 넣어 문제가 생깁니다. EKS IRSA의 표준 audience는 대부분 sts.amazonaws.com입니다.
7-1. 토큰 payload에서 aud/sub 확인(간단 버전)
JWT를 완전 검증하진 않더라도 payload를 base64 decode 해서 값만 확인할 수 있습니다.
kubectl -n <NAMESPACE> exec -it <POD_NAME> -- sh -c '
python - <<"PY"
import os, json, base64
p = os.environ.get("AWS_WEB_IDENTITY_TOKEN_FILE")
if not p:
raise SystemExit("AWS_WEB_IDENTITY_TOKEN_FILE not set")
jwt = open(p,"r").read().strip().split(".")
payload = jwt[1] + "=" * (-len(jwt[1]) % 4)
data = json.loads(base64.urlsafe_b64decode(payload))
print("aud:", data.get("aud"))
print("sub:", data.get("sub"))
print("iss:", data.get("iss"))
PY
'
출력된 sub가 Trust Policy의 ...:sub와 정확히 같은지, aud가 sts.amazonaws.com인지 확인합니다.
8) (의외로 흔함) 같은 이름의 SA를 다른 네임스페이스에서 쓰는 실수
Trust Policy는 system:serviceaccount:<namespace>:<name>로 네임스페이스까지 포함합니다.
default:app로 Trust를 만들어놓고- 실제 Pod는
prod:app을 쓰면
즉시 AccessDenied가 발생합니다.
해결은 둘 중 하나입니다.
- Trust Policy의
sub를 정확한 ns로 수정 - 혹은
StringLike로 여러 SA를 허용(권한 범위가 넓어지니 신중)
예:
"Condition": {
"StringEquals": {
"oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>:aud": "sts.amazonaws.com"
},
"StringLike": {
"oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>:sub": "system:serviceaccount:prod:*"
}
}
9) 네트워크/엔드포인트 문제로 STS에 도달 못하는 케이스
IRSA 설정이 맞아도 Pod가 STS에 접근하지 못하면 결과적으로 “자격증명 획득 실패”가 납니다. 특히 아래 상황에서 STS가 실패합니다.
- NAT/라우팅/SG/NACL로 인터넷 egress 차단
- Private cluster에서 STS VPC Endpoint 미구성
- DNS 문제로
sts.<region>.amazonaws.com해석 실패
이 경우 IRSA 자체보다는 egress 진단이 먼저입니다. Pod egress가 의심되면 EKS에서 Pod는 정상인데 egress만 막힐 때 점검을 먼저 확인하세요.
간단한 확인:
kubectl -n <NAMESPACE> exec -it <POD_NAME> -- sh -c '
nslookup sts.${AWS_REGION}.amazonaws.com || true
wget -S -O- https://sts.${AWS_REGION}.amazonaws.com/ 2>&1 | head -n 30 || true
'
- DNS가 안 되면 CoreDNS/노드 DNS/라우팅 문제
- HTTPS 연결이 안 되면 NAT/엔드포인트/방화벽 문제
10) 빠른 결론: “IRSA AccessDenied” 10분 점검 순서
운영에서 시간을 아끼려면 아래 순서가 효율적입니다.
- Pod가 올바른 ServiceAccount를 쓰는지 확인
kubectl get pod ... -o jsonpath='{.spec.serviceAccountName}'
- ServiceAccount에 role-arn 어노테이션 확인
kubectl get sa ... -o yaml
- Pod에 AWS_ROLE_ARN / AWS_WEB_IDENTITY_TOKEN_FILE 주입 확인
kubectl exec ... env, 토큰 파일 존재 확인
- 클러스터 OIDC Issuer URL 확인
aws eks describe-cluster ...
- IAM OIDC Provider 등록 여부 확인
aws iam list-open-id-connect-providers
- Role Trust Policy의 Principal/Condition이 정확한지 확인
aws iam get-role ...
- Pod에서 sts get-caller-identity로 최종 검증
이 흐름대로 보면 대부분의 sts:AssumeRoleWithWebIdentity AccessDenied는 1~6번에서 잡힙니다.
마무리
IRSA는 “권한 정책(Policy)을 잘 붙였다”보다 “AssumeRoleWithWebIdentity가 성립하도록 Trust/OIDC/SA가 정확히 맞물렸다”가 핵심입니다. 특히 sub 조건의 네임스페이스/서비스어카운트 이름 불일치, OIDC Provider 누락/오참조가 가장 흔한 원인입니다.
만약 Trust/SA/OIDC가 모두 맞는데도 STS 호출이 실패한다면, 그때는 네트워크(egress)나 IPv6/엔드포인트 이슈를 의심해 보세요. IPv6 환경에서 STS만 403이 나는 특이 케이스는 EKS Pod에서 IPv6로만 STS 403 뜰 때 해결이 빠른 지름길이 될 수 있습니다.