- Published on
EKS OIDC Provider 400 invalid_client 해결 가이드
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
EKS에서 IRSA(IAM Roles for Service Accounts)를 구성한 뒤 Pod가 STS로 AssumeRoleWithWebIdentity를 호출할 때, CloudTrail/애플리케이션 로그에 400 invalid_client가 찍히는 경우가 있습니다. 이 오류는 대체로 “OIDC 발급자(issuer)와 AWS IAM에 등록된 OIDC Provider, 그리고 신뢰 정책(trust policy) 조건이 서로 정확히 일치하지 않는다”는 뜻에 가깝습니다.
문제는 증상이 비슷한데 원인이 여러 갈래라는 점입니다. 예를 들어 STS AccessDenied는 역할/정책 문제일 가능성이 큰 반면, invalid_client는 OIDC Provider 자체(issuer/Thumbprint/ClientID/신뢰정책 조건/토큰 audience) 불일치 쪽으로 무게가 실립니다. 본 글에서는 실전에서 가장 자주 밟는 지뢰를 중심으로, “어디가 틀렸는지”를 빠르게 좁히는 체크리스트와 수정 방법을 제공합니다.
참고로, IRSA 자체는 되는데 다른 권한에서 막히는 케이스(예: KMS Decrypt 403)는 별개 이슈이므로 아래 글도 함께 보면 진단이 빨라집니다.
1) invalid_client가 의미하는 것(맥락)
IRSA 흐름을 아주 짧게 요약하면 다음과 같습니다.
- Kubernetes가 ServiceAccount에 대한 projected token(JWT)을 Pod에 마운트
- AWS SDK가
AWS_ROLE_ARN,AWS_WEB_IDENTITY_TOKEN_FILE을 읽고 STSAssumeRoleWithWebIdentity호출 - STS는 JWT의
iss(issuer)에 해당하는 IAM OIDC Provider를 찾고, 토큰의aud(audience) 및sub(subject) 등 클레임이 IAM Role trust policy 조건과 맞는지 검증
여기서 invalid_client는 보통 3번 단계에서 “이 issuer/클라이언트(aud)로 등록된 Provider가 없거나, Provider 설정이 토큰/신뢰정책과 매칭되지 않는다” 쪽에서 발생합니다.
2) 가장 흔한 원인 Top 6
원인 A. 클러스터 OIDC issuer URL과 IAM OIDC Provider URL 불일치
EKS 클러스터는 고유한 issuer를 가집니다.
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
IAM에 등록된 OIDC Provider 목록에서 동일한 URL(스킴 포함)이 있어야 합니다.
aws iam list-open-id-connect-providers
각 Provider ARN을 꺼내 URL을 확인합니다.
aws iam get-open-id-connect-provider \
--open-id-connect-provider-arn <PROVIDER_ARN>
여기서 Url이 클러스터 issuer(https 포함)와 정확히 같아야 합니다. 클러스터를 재생성했거나(issuer가 바뀜), 다른 클러스터의 Provider를 재사용했거나, 멀티클러스터 환경에서 잘못된 ARN을 trust policy에 연결하면 이 불일치가 흔히 생깁니다.
해결:
- 올바른 issuer로 OIDC Provider를 새로 만들고, 역할의 trust policy를 새 Provider에 맞춰 갱신
- 기존 Provider가 잘못되었다면 제거(영향 범위 확인 필수)
원인 B. OIDC Provider의 Client ID 목록에 sts.amazonaws.com 누락
IRSA에서 STS를 대상으로 하는 토큰의 audience는 보통 sts.amazonaws.com입니다. IAM OIDC Provider의 ClientIDList에 이것이 없으면 매칭이 깨질 수 있습니다.
확인:
aws iam get-open-id-connect-provider \
--open-id-connect-provider-arn <PROVIDER_ARN> \
--query "ClientIDList" \
--output json
정상 예:
["sts.amazonaws.com"]
만약 비어있거나 다른 값만 있다면, Provider를 재생성하는 편이 가장 깔끔합니다(콘솔에서도 수정이 제한적).
원인 C. Trust policy의 조건 키가 issuer host/path와 불일치
IRSA 역할의 신뢰 정책은 대체로 아래 형태입니다.
{
"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>:sub": "system:serviceaccount:<NAMESPACE>:<SA_NAME>",
"oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>:aud": "sts.amazonaws.com"
}
}
}
]
}
여기서 실수 포인트:
Condition의 키(예:oidc.eks.../id/<OIDC_ID>:sub)가 실제 issuer에서https://를 뺀 “호스트/패스”와 정확히 일치해야 함<REGION>이나<OIDC_ID>를 다른 클러스터 값으로 복붙sub에 네임스페이스/서비스어카운트 이름 오타
특히 aud 조건을 넣을 때 키가 틀리면, STS 입장에서는 “클라이언트가 등록/검증되지 않음”처럼 보이며 invalid_client로 떨어지는 케이스가 있습니다.
원인 D. ServiceAccount 토큰의 audience가 예상과 다름
EKS의 IRSA는 aud=sts.amazonaws.com을 전제로 동작합니다. 그런데 projected token 설정을 커스텀하거나(특히 자체적으로 serviceAccountToken을 명시), 특정 런타임/차트가 audience를 바꿔버리면 mismatch가 납니다.
Pod 안에서 토큰을 디코드해서 확인합니다(JWT는 서명 검증 없이도 payload는 볼 수 있습니다).
TOKEN=$(cat $AWS_WEB_IDENTITY_TOKEN_FILE)
python - << 'PY'
import os, json, base64
import sys
t = os.environ.get('TOKEN')
if not t:
t = open(os.environ['AWS_WEB_IDENTITY_TOKEN_FILE']).read().strip()
payload = t.split('.')[1]
payload += '=' * (-len(payload) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))
PY
확인할 필드:
iss: 클러스터 issuer와 동일한지aud:sts.amazonaws.com인지(배열로 나올 수도 있음)sub:system:serviceaccount:<ns>:<sa>인지
만약 aud가 다르면 두 가지 중 하나로 맞춰야 합니다.
- 토큰 audience를
sts.amazonaws.com으로 되돌리기(권장) - 또는 IAM OIDC Provider의 ClientIDList 및 trust policy의
aud조건을 해당 값으로 맞추기(대개 비권장, 운영 복잡도 증가)
원인 E. 잘못된 Thumbprint(또는 체인 변경)으로 인한 Provider 검증 실패
IAM OIDC Provider는 TLS 인증서 thumbprint를 기반으로 신뢰를 설정합니다. EKS OIDC 엔드포인트는 AWS가 관리하지만, 조직의 보안 정책/프록시/미러링 등으로 인해 다른 엔드포인트를 보게 되는 특수 환경에서는 thumbprint 불일치가 날 수 있습니다.
확인:
aws iam get-open-id-connect-provider \
--open-id-connect-provider-arn <PROVIDER_ARN> \
--query "ThumbprintList" \
--output json
Thumbprint가 잘못되었다면 Provider를 올바른 값으로 재생성하는 것이 일반적입니다. (EKS 표준 OIDC 엔드포인트를 그대로 쓰는 환경이라면 thumbprint 문제는 상대적으로 드뭅니다.)
원인 F. AWS SDK/환경변수 설정 꼬임(다른 자격증명 공급자를 타는 경우)
의외로 IRSA 설정은 맞는데, 컨테이너 내 환경변수/파일이 꼬여서 웹 아이덴티티 플로우를 타지 않고 다른 크리덴셜 소스를 시도하다가 애매한 에러를 내는 경우가 있습니다.
Pod에서 아래를 확인합니다.
env | egrep 'AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION|AWS_DEFAULT_REGION'
ls -al $AWS_WEB_IDENTITY_TOKEN_FILE
또한 AWS SDK가 디버그 로그를 켤 수 있다면(언어별 상이) “AssumeRoleWithWebIdentity를 실제로 호출했는지”를 먼저 확인하세요.
3) 빠른 진단 루틴(10분 컷 체크리스트)
3.1 클러스터 issuer 확인
ISSUER=$(aws eks describe-cluster --name <CLUSTER_NAME> --region <REGION> \
--query "cluster.identity.oidc.issuer" --output text)
echo "$ISSUER"
3.2 IAM OIDC Provider URL 일치 여부 확인
aws iam list-open-id-connect-providers --query 'OpenIDConnectProviderList[].Arn' --output text
하나씩 조회:
aws iam get-open-id-connect-provider --open-id-connect-provider-arn <PROVIDER_ARN> \
--query '{Url:Url,ClientIDList:ClientIDList,ThumbprintList:ThumbprintList}' --output json
Url이ISSUER와 동일?ClientIDList에sts.amazonaws.com존재?
3.3 Role trust policy의 Principal/Condition 점검
aws iam get-role --role-name <ROLE_NAME> --query 'Role.AssumeRolePolicyDocument' --output json
Principal.Federated가 올바른 Provider ARN?Condition.StringEquals의 키가 issuer(https 제외) 기반으로 정확?sub가 실제 SA와 정확?
3.4 Pod 안에서 JWT 클레임 확인
앞 절의 디코드 스크립트로 iss/aud/sub가 trust policy와 일치하는지 확인합니다.
4) 재발 없이 고치는 권장 절차(정석)
4.1 eksctl로 OIDC Provider를 “클러스터 기준”으로 재연결
가장 안정적인 방법 중 하나는 eksctl utils associate-iam-oidc-provider를 사용하는 것입니다.
eksctl utils associate-iam-oidc-provider \
--cluster <CLUSTER_NAME> \
--region <REGION> \
--approve
이 명령은 클러스터 issuer를 기준으로 IAM OIDC Provider를 생성/연결합니다(이미 있으면 재사용).
4.2 IRSA 역할을 다시 생성하거나 trust policy를 올바르게 갱신
예: eksctl로 서비스어카운트에 역할을 연결
eksctl create iamserviceaccount \
--cluster <CLUSTER_NAME> \
--region <REGION> \
--namespace <NAMESPACE> \
--name <SA_NAME> \
--attach-policy-arn arn:aws:iam::<ACCOUNT_ID>:policy/<POLICY_NAME> \
--approve \
--override-existing-serviceaccounts
이 방식은 trust policy의 sub 조건 등을 실수할 여지를 크게 줄여줍니다.
4.3 ServiceAccount 어노테이션 확인
kubectl get sa <SA_NAME> -n <NAMESPACE> -o yaml | yq '.metadata.annotations'
기대 값:
eks.amazonaws.com/role-arn: arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME>
(차트/오퍼레이터가 SA를 덮어쓰는 환경이라면, 배포 파이프라인에서 SA가 최종적으로 어떤 값으로 남는지까지 확인해야 합니다.)
5) 실전에서 자주 하는 실수 패턴
5.1 “다른 클러스터에서 쓰던 Role/Provider를 그대로 복사”
클러스터가 다르면 issuer의 /id/<OIDC_ID>가 달라집니다. Provider ARN과 trust policy의 condition 키도 함께 바뀌어야 합니다. 멀티계정/멀티리전에서 특히 흔합니다.
5.2 aud 조건을 생략/오타로 잘못 넣기
aud 조건을 생략하면 당장 붙을 때도 있지만(정책 구성에 따라), 보안적으로는 넣는 편이 낫습니다. 다만 키를 틀리면 invalid_client 같은 형태로 실패할 수 있어 “넣되 정확히”가 중요합니다.
5.3 STS 엔드포인트/리전 혼동
대부분의 IRSA는 글로벌 STS를 써도 되지만, VPC 엔드포인트/리전 강제 정책을 쓰는 조직에서는 STS 호출 경로가 달라질 수 있습니다. 이 경우 invalid_client가 아니라 다른 네트워크/서명 오류로 나타나는 경우가 많습니다. 만약 403이나 서명 오류가 보인다면 아래 케이스도 함께 확인하세요.
6) 최소 재현용 테스트 코드(컨테이너에서 STS 호출 확인)
애플리케이션이 복잡할수록 “IRSA가 되는지”를 분리해서 확인하는 게 빠릅니다. 아래는 boto3로 현재 호출자 ARN을 확인하는 최소 코드입니다.
import boto3
sts = boto3.client("sts")
print(sts.get_caller_identity())
실행 전 확인:
AWS_ROLE_ARN존재AWS_WEB_IDENTITY_TOKEN_FILE존재
위 코드가 invalid_client로 실패하면 애플리케이션 로직이 아니라 IRSA/OIDC 설정 문제로 확정할 수 있습니다.
7) 결론
EKS OIDC Provider 관련 400 invalid_client는 대부분 다음 3가지를 맞추면 해결됩니다.
- 클러스터 issuer URL과 IAM OIDC Provider URL이 정확히 동일
- IAM OIDC Provider의 ClientIDList에
sts.amazonaws.com포함 - Role trust policy의
Principal(Federated)와Condition(sub/aud)가 토큰의iss/sub/aud와 정확히 일치
문제가 길어질수록 “Pod 토큰 디코드 → issuer/aud/sub 확인 → IAM Provider/Role trust policy 대조” 순서로 기계적으로 좁혀가면, 감으로 콘솔을 클릭하는 것보다 훨씬 빨리 끝납니다.