- Published on
EKS IRSA AccessDenied 해결 - OIDC·신뢰정책
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스/컨테이너 환경에서 AWS 권한 문제는 대부분 AccessDenied 한 줄로 뭉뚱그려 보이지만, EKS의 IRSA(IAM Roles for Service Accounts)는 특히 OIDC Provider와 IAM Role 신뢰정책(AssumeRoleWithWebIdentity) 조건이 조금만 어긋나도 즉시 실패합니다. 이 글은 “왜 거부되는지”를 증거(토큰 클레임, OIDC issuer, trust policy condition)로 좁혀가며 해결하는 실전 체크리스트입니다.
IRSA 문제를 디버깅하다 보면 파드가 정상 기동은 하는데 AWS API만 실패하는 경우가 많습니다. 이때는 애플리케이션 레벨 로그만 보지 말고, 쿠버네티스 상태/프로브 및 배포 파이프라인 이슈도 함께 의심해볼 만합니다. 예를 들어 배포 이후 상태가 꼬였다면 Argo CD Sync 실패 - OutOfSync·Degraded 해결, 파드가 재기동 루프라면 Kubernetes CrashLoopBackOff, 로그 없이 진단하는 법, 준비 상태가 미묘하게 실패한다면 EKS에서 Readiness 실패인데 로그는 정상일 때도 같이 참고하면 전체 원인 분리가 빨라집니다.
IRSA 동작 원리(어디서 틀어지는지)
IRSA는 대략 아래 흐름으로 동작합니다.
- EKS 클러스터에는 OIDC issuer URL이 있고, AWS IAM에는 그 issuer를 신뢰하는 OIDC Provider가 등록됩니다.
- 특정 Kubernetes ServiceAccount에
eks.amazonaws.com/role-arn어노테이션으로 IAM Role을 연결합니다. - 파드는 ServiceAccount 토큰(JWT)을 마운트하고, AWS SDK는
AssumeRoleWithWebIdentity로 STS에 토큰을 제출합니다. - IAM Role의 신뢰정책(trust policy) 이 토큰의
sub,aud등 클레임과 일치하면 임시 자격 증명을 발급합니다.
따라서 AccessDenied의 핵심 원인은 보통 다음 4가지로 수렴합니다.
- OIDC Provider가 없거나 issuer가 불일치
- Role trust policy의
Condition이 토큰 클레임과 불일치(sub,aud) - ServiceAccount 어노테이션/namespace/name 불일치
- 파드가 올바른 토큰을 사용하지 못함(구버전 토큰,
automountServiceAccountToken비활성화, 잘못된 env/volume)
증상별 에러 메시지로 1차 분류
CloudWatch/애플리케이션 로그에서 흔히 보는 패턴입니다.
AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity- 거의 항상 trust policy 또는 OIDC Provider 설정 문제
InvalidIdentityToken: No OpenIDConnect provider found in your account for ...- OIDC Provider 미등록 또는 issuer URL 불일치
AccessDeniedException(S3, DynamoDB 등 개별 서비스 액션 거부)- IRSA는 성공했지만 권한 정책(permissions policy) 이 부족
이 글의 초점은 첫 번째 케이스(AssumeRoleWithWebIdentity 단계에서 거부)입니다.
1단계: 클러스터 OIDC issuer 확인
먼저 EKS 클러스터의 issuer를 확인합니다.
aws eks describe-cluster \
--name my-eks \
--region ap-northeast-2 \
--query "cluster.identity.oidc.issuer" \
--output text
출력은 보통 https://oidc.eks.ap-northeast-2.amazonaws.com/id/XXXXXXXXXXXX 형태입니다. 이 값이 이후 모든 설정의 기준이 됩니다.
2단계: IAM OIDC Provider 존재/일치 확인
AWS 계정에 OIDC Provider가 등록되어 있는지 확인합니다.
aws iam list-open-id-connect-providers
목록에서 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/XXXXXXXXXXXX
여기서 중요한 포인트는 아래 2가지입니다.
- Provider URL 경로가 issuer의 호스트+패스와 정확히 일치해야 함
ClientIDList에 최소sts.amazonaws.com가 포함되어야 함
만약 Provider가 없거나 불일치라면, eksctl로 가장 빠르게 생성할 수 있습니다.
eksctl utils associate-iam-oidc-provider \
--cluster my-eks \
--region ap-northeast-2 \
--approve
3단계: ServiceAccount 어노테이션 확인
IRSA는 ServiceAccount에 Role ARN을 정확히 달아야 합니다.
kubectl -n my-namespace get sa my-sa -o yaml
다음과 같은 형태를 확인합니다.
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-sa
namespace: my-namespace
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-irsa-role
실무에서 자주 틀리는 부분:
- 파드가 실제로는 다른 ServiceAccount를 사용 중(Deployment의
spec.template.spec.serviceAccountName누락) - namespace가 달라서
sub가 불일치 - Helm/Argo CD 템플릿 변수로 SA 이름이 바뀌었는데 trust policy는 고정
파드가 어떤 SA를 쓰는지 바로 확인합니다.
kubectl -n my-namespace get pod my-pod -o jsonpath='{.spec.serviceAccountName}'
4단계: 토큰 클레임을 직접 확인(가장 확실한 증거)
신뢰정책 조건 불일치는 “감”으로 고치기 어렵습니다. 토큰을 꺼내서 sub, aud, iss를 확인하면 바로 끝납니다.
파드 안에서 토큰 파일 경로를 확인합니다.
kubectl -n my-namespace exec -it my-pod -- sh -lc 'ls -al /var/run/secrets/eks.amazonaws.com/serviceaccount'
대개 token 파일이 있습니다. JWT의 payload를 디코드합니다(서명 검증이 목적이 아니라 클레임 확인이 목적이므로 base64url decode만 합니다).
kubectl -n my-namespace exec -it my-pod -- sh -lc '
TOKEN=$(cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token);
PAYLOAD=$(echo "$TOKEN" | cut -d . -f 2 | tr "_-" "/+" | base64 -d 2>/dev/null);
echo "$PAYLOAD";
'
여기서 확인해야 할 핵심 클레임:
sub: 보통system:serviceaccount:my-namespace:my-saaud: 보통sts.amazonaws.comiss: 클러스터 OIDC issuer
이 값이 IAM Role trust policy의 조건과 1:1로 맞아야 합니다.
5단계: IAM Role 신뢰정책(trust policy) 점검
문제의 80%는 여기서 발생합니다. 역할(Role)의 trust policy를 확인합니다.
aws iam get-role \
--role-name my-irsa-role \
--query "Role.AssumeRolePolicyDocument" \
--output json
정상적인 예시는 아래와 유사합니다(가독성을 위해 줄바꿈/들여쓰기를 유지했습니다).
{
"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:my-namespace:my-sa"
}
}
}
]
}
체크 포인트:
Principal.Federated가 정확한 OIDC Provider ARN인지Action이sts:AssumeRoleWithWebIdentity인지Condition.StringEquals의 키가issuer에서 https:// 를 뺀 값 + :aud/:sub형태인지sub값의 namespace/sa가 실제와 같은지
흔한 실수 1: issuer 키에 https://를 포함
Condition 키는 https:// 없이 들어가야 합니다. 예를 들어 issuer가 https://oidc.eks.../id/ABC라면 Condition 키는 oidc.eks.../id/ABC:sub처럼 시작해야 합니다.
흔한 실수 2: StringLike 와일드카드 남용
여러 SA를 허용하려고 StringLike로 system:serviceaccount:ns:*를 쓰는 경우가 있는데, 보안상 권장되지 않습니다. 운영 환경에서는 가능한 한 SA를 명시적으로 고정하세요.
그래도 멀티 SA가 필요하면 최소 범위로 제한합니다.
"Condition": {
"StringEquals": {
"oidc.eks.ap-northeast-2.amazonaws.com/id/XXXXXXXXXXXX:aud": "sts.amazonaws.com"
},
"StringLike": {
"oidc.eks.ap-northeast-2.amazonaws.com/id/XXXXXXXXXXXX:sub": "system:serviceaccount:my-namespace:app-*"
}
}
6단계: 권한 정책(permissions policy)과 혼동하지 않기
IRSA가 성공했는데도 S3 호출에서 AccessDenied가 나면, 그건 trust policy가 아니라 Role에 붙은 permissions policy 문제입니다.
IRSA 성공 여부는 파드 내부에서 STS 호출로 빠르게 확인할 수 있습니다.
kubectl -n my-namespace exec -it my-pod -- sh -lc 'aws sts get-caller-identity'
- 여기서도 거부되면 trust/OIDC 문제
- 여기서 성공하고, 특정 서비스 액션만 거부되면 permissions policy 문제
7단계: automountServiceAccountToken 및 토큰 경로 이슈
보안 강화를 위해 ServiceAccount 또는 Pod에 automountServiceAccountToken: false를 설정해둔 경우, IRSA 토큰이 마운트되지 않아 실패할 수 있습니다.
ServiceAccount와 Pod 양쪽을 확인합니다.
kubectl -n my-namespace get sa my-sa -o jsonpath='{.automountServiceAccountToken}'
kubectl -n my-namespace get pod my-pod -o jsonpath='{.spec.automountServiceAccountToken}'
또한 EKS IRSA는 기본 서비스어카운트 토큰 경로가 아니라 eks.amazonaws.com 경로로 projected token을 사용합니다. 커스텀 이미지/엔트리포인트에서 AWS SDK 관련 env를 덮어쓰거나, AWS_WEB_IDENTITY_TOKEN_FILE를 잘못 지정하면 실패합니다.
파드에서 관련 환경 변수를 확인합니다.
kubectl -n my-namespace exec -it my-pod -- sh -lc 'env | grep -E "AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION|AWS_DEFAULT_REGION"'
8단계: 재현 가능한 “정상 IRSA” 매니페스트 예시
문제 원인을 분리하려면, 최소 구성으로 IRSA가 되는지부터 확인하는 게 좋습니다.
ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: irsa-debug-sa
namespace: irsa-debug
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-irsa-role
Pod(awscli로 STS 확인)
apiVersion: v1
kind: Pod
metadata:
name: irsa-debug
namespace: irsa-debug
spec:
serviceAccountName: irsa-debug-sa
containers:
- name: awscli
image: public.ecr.aws/aws-cli/aws-cli:2.15.0
command: ["sh", "-lc", "aws sts get-caller-identity && sleep 3600"]
적용 후 확인:
kubectl create ns irsa-debug
kubectl apply -f irsa-debug.yaml
kubectl -n irsa-debug logs -f pod/irsa-debug
이 최소 구성에서 성공하면, 원래 워크로드 쪽의 SA/토큰/env 오버라이드 문제일 확률이 큽니다.
9단계: EKS 업그레이드/재생성 후 issuer 변경 이슈
클러스터를 새로 만들거나(또는 특정 방식으로 재프로비저닝) OIDC issuer의 id/XXXXXXXX 부분이 바뀌면, 기존 IAM OIDC Provider ARN과 trust policy 키가 모두 어긋납니다.
이 경우의 전형적인 증상은:
- 갑자기 모든 IRSA 워크로드가 동시에
AssumeRoleWithWebIdentity에서 실패 get-open-id-connect-provider는 존재하지만 issuer가 다름
해결은 새 issuer로 OIDC Provider를 다시 연결하고, 관련 Role들의 trust policy condition 키를 일괄 수정하는 것입니다. 운영 환경에서는 Terraform 등 IaC로 OIDC provider와 role trust policy를 함께 관리해 “드리프트”를 줄이는 것을 권장합니다.
최종 체크리스트(가장 빨리 잡히는 순서)
aws eks describe-cluster로 issuer 확인- IAM에 해당 issuer의 OIDC Provider가 존재하고
sts.amazonaws.com가 client ID에 포함되는지 확인 - ServiceAccount 어노테이션의 role ARN, namespace/name 일치 확인
- 파드가 실제로 그 ServiceAccount를 사용하는지 확인
- 파드 내부에서 토큰 JWT payload를 디코드해
sub,aud,iss를 확인 - IAM Role trust policy의
Principal.Federated,Condition키/값이 토큰과 정확히 일치하는지 확인 aws sts get-caller-identity로 IRSA 성공 여부를 분리
IRSA의 AccessDenied는 “권한이 없다”가 아니라, 대부분 “신원 증명이 신뢰정책과 매칭되지 않는다”는 뜻입니다. 토큰 클레임을 근거로 trust policy를 맞추면, 재발도 함께 줄일 수 있습니다.