- Published on
EKS에서 AWS SDK 403 MissingAuthenticationToken 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스/컨테이너 환경에서 AWS SDK를 쓰다 보면 403 MissingAuthenticationToken은 꽤 당황스럽습니다. 특히 EKS에서는 Pod가 어떤 방식으로 AWS 자격 증명(credential)을 얻는지가 여러 갈래(IRSA, 노드 IAM, 정적 키, 웹 아이덴티티 토큰)로 나뉘어 있어, 같은 403이라도 원인이 다릅니다.
이 글은 “EKS에서 AWS SDK 호출이 403 MissingAuthenticationToken으로 실패한다”는 상황을 기준으로, 재현 가능한 진단 순서와 가장 흔한 원인별 해결법을 정리합니다. (IRSA 자체는 되는데 특정 API만 403인 경우는 원인이 달라질 수 있어, 필요하면 EKS IRSA는 되는데 KMS Decrypt 403 해결법도 함께 확인하세요.)
1) MissingAuthenticationToken이 의미하는 것
MissingAuthenticationToken은 대체로 다음 중 하나를 뜻합니다.
- 요청이 서명(Signature V4)되지 않았거나, 서명에 필요한 키를 못 구함
- 잘못된 엔드포인트/리전으로 호출해서 AWS가 “이건 인증 토큰이 없는 요청”으로 판단
- 프록시/미들웨어가 Authorization 헤더를 제거
- (EKS에서 흔함) IRSA 환경변수/토큰 파일이 없거나, SDK가 이를 읽지 못함
중요 포인트는, 이 에러가 “권한 부족(AccessDenied)”과는 결이 다르다는 점입니다.
AccessDenied: 인증은 됐는데 권한이 없음MissingAuthenticationToken: 인증 자체가 성립하지 않음(서명/토큰/헤더/엔드포인트 문제)
2) 가장 빠른 1차 분기: Pod에서 어떤 Credential Provider를 쓰는가
EKS Pod의 AWS 인증 경로는 보통 다음 중 하나입니다.
- IRSA(WebIdentity): ServiceAccount에 role annotation + projected token +
AWS_ROLE_ARN,AWS_WEB_IDENTITY_TOKEN_FILE - 노드 IAM(EC2 IMDS): Pod가 노드의 인스턴스 프로파일을 그대로 사용(권장 X)
- 정적 키:
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY를 Secret로 주입(권장 X)
먼저 Pod 안에서 아래를 확인합니다.
kubectl exec -it deploy/myapp -- sh -lc '
echo "AWS_ROLE_ARN=$AWS_ROLE_ARN";
echo "AWS_WEB_IDENTITY_TOKEN_FILE=$AWS_WEB_IDENTITY_TOKEN_FILE";
env | egrep "^AWS_|^HTTP" | sort
'
- IRSA를 의도했다면
AWS_ROLE_ARN/AWS_WEB_IDENTITY_TOKEN_FILE가 보여야 합니다. - 반대로 정적 키가 들어가 있으면(
AWS_ACCESS_KEY_ID등) IRSA보다 우선될 수 있어, 잘못된 키로 서명되어 403이 날 수도 있습니다.
3) IRSA 환경에서 가장 흔한 원인 5가지
3.1 ServiceAccount에 role annotation이 없거나 다른 SA를 쓰는 경우
Deployment가 실제로 어떤 ServiceAccount를 쓰는지부터 확인합니다.
kubectl get deploy myapp -o jsonpath='{.spec.template.spec.serviceAccountName}{"\n"}'
kubectl get sa myapp-sa -o yaml
ServiceAccount에 다음 형태가 있어야 합니다.
apiVersion: v1
kind: ServiceAccount
metadata:
name: myapp-sa
namespace: default
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/myapp-irsa-role
- Deployment는
serviceAccountName: myapp-sa를 가리켜야 합니다. - Helm/Kustomize에서 SA 이름이 바뀌거나, 기본 SA를 쓰게 되면 IRSA가 적용되지 않습니다.
3.2 OIDC Provider 미설정/불일치
IRSA는 EKS 클러스터의 OIDC issuer와 IAM OIDC provider가 매칭되어야 합니다.
확인:
aws eks describe-cluster --name my-cluster --query 'cluster.identity.oidc.issuer' --output text
aws iam list-open-id-connect-providers --query 'OpenIDConnectProviderList[*].Arn' --output text
OIDC provider가 없거나 issuer가 다르면, SDK가 STS AssumeRoleWithWebIdentity 단계에서 실패할 수 있고, 애플리케이션 레벨에서는 “결국 서명 없는 요청”으로 이어져 MissingAuthenticationToken처럼 보이기도 합니다(라이브러리/예외 처리 방식에 따라 메시지가 달라짐).
3.3 Trust policy의 sub/aud 조건 불일치
IRSA Role의 trust policy에서 sub(namespace/sa) 조건이 실제 SA와 일치해야 합니다.
예시(trust policy):
{
"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:aud": "sts.amazonaws.com",
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:sub": "system:serviceaccount:default:myapp-sa"
}
}
}
]
}
여기서 namespace나 serviceaccount 이름이 하나라도 다르면 AssumeRole이 안 됩니다.
3.4 Pod에 projected token이 없거나 경로가 다름
IRSA는 Pod에 토큰 파일이 마운트되어야 하고, SDK는 AWS_WEB_IDENTITY_TOKEN_FILE 경로를 읽습니다.
kubectl exec -it deploy/myapp -- sh -lc '
ls -al $AWS_WEB_IDENTITY_TOKEN_FILE;
head -c 20 $AWS_WEB_IDENTITY_TOKEN_FILE; echo
'
- 파일이 없으면 IRSA가 아닌 상태입니다.
- 보안 하드닝/PSA/OPA 정책으로 projected volume이 막힌 경우도 있습니다.
3.5 SDK 버전/로딩 순서 문제(특히 오래된 SDK)
일부 오래된 SDK/런타임은 web identity provider를 기본 체인에 포함하지 않거나, 환경변수 우선순위가 달라 문제를 만듭니다.
- Java: AWS SDK v2 권장, v1은 설정에 따라 web identity 인식이 애매할 수 있음
- Node.js: v3 사용 시 기본 credential chain이 잘 동작
- Python(boto3): 비교적 안정적이나, 컨테이너 이미지에 CA/시간 동기 문제까지 겹치면 다른 에러로 변질
가능하면 SDK를 최신으로 올리고, “명시적으로 credential provider를 지정”하는 것도 방법입니다.
4) 노드 IAM(IMDS)로 떨어지는 케이스: IMDS 차단/홉 제한
IRSA가 적용되지 않으면 SDK는 다음 후보로 **EC2 Instance Metadata Service(IMDS)**를 시도합니다. 그런데 EKS에서는 아래 이유로 IMDS가 막혀 있을 수 있습니다.
- 노드가 IMDSv2 강제 + hop limit(1) 설정
- CNI/네트워크 정책/iptables로 169.254.169.254 차단
이 경우 SDK는 credential을 못 얻고, 결과적으로 서명 없는 요청 → MissingAuthenticationToken이 됩니다.
Pod에서 IMDS 접근 여부를 확인:
kubectl exec -it deploy/myapp -- sh -lc '
apk add --no-cache curl >/dev/null 2>&1 || true
curl -sS -m 1 http://169.254.169.254/latest/meta-data/ || echo "IMDS blocked"
'
- IRSA를 쓸 거라면 IMDS 접근이 되든 안 되든 상관없지만, IRSA가 깨졌을 때 IMDS로 fallback되는지 확인하는 지표가 됩니다.
5) 리전/엔드포인트/서비스 URL 실수로 인한 403
의외로 많습니다. 예:
- STS를
https://sts.amazonaws.com이 아닌 다른 URL로 호출 - 리전이 비어 있거나,
AWS_REGION/AWS_DEFAULT_REGION이 엉뚱함 - S3를 path-style/virtual-hosted-style로 잘못 구성 + 프록시가 Host 헤더 변경
진단 체크:
kubectl exec -it deploy/myapp -- sh -lc '
echo "AWS_REGION=$AWS_REGION";
echo "AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION";
'
애플리케이션에서 AWS SDK 클라이언트 생성 시 리전을 명시하는 것도 안정적입니다.
(예) Node.js AWS SDK v3에서 리전/자격 자동 체인 사용
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
const client = new STSClient({
region: process.env.AWS_REGION || "ap-northeast-2",
});
const out = await client.send(new GetCallerIdentityCommand({}));
console.log(out);
GetCallerIdentity는 “내가 어떤 자격으로 호출 중인지” 확인하는 데 가장 좋습니다.
6) 프록시/서비스메시가 Authorization 헤더를 제거하는 케이스
사내 프록시, Envoy, Nginx, 또는 일부 서비스메시 설정에서 hop-by-hop 헤더 처리나 보안 정책으로 Authorization 헤더가 제거/변경되면 SigV4가 깨집니다.
- 특히 “애플리케이션 → 프록시 → AWS 서비스” 구조에서 발생
- 외부로 나가는 egress proxy를 강제하는 환경에서 흔함
확인 포인트:
- 프록시가
Authorization,X-Amz-Date,X-Amz-Security-Token,Host헤더를 그대로 전달하는지 - HTTP/2 변환 과정에서 헤더 정규화/삭제가 없는지
이 케이스는 애플리케이션 로그에 “서명 생성은 했는데 AWS에서 토큰이 없다고 함”처럼 보일 수 있습니다.
7) 실전 진단 런북: 10분 안에 결론 내기
아래 순서대로 보면 대부분 원인이 좁혀집니다.
7.1 Pod 내부에서 현재 CallerIdentity 확인
가능하면 애플리케이션에 진단용 엔드포인트를 추가하거나, 임시로 AWS CLI Pod를 띄웁니다.
kubectl run -it --rm awscli --image=amazon/aws-cli:2.15.0 --restart=Never -- \
sh -lc 'aws sts get-caller-identity && aws configure list'
- 여기서도 실패하면 클러스터/SA/IRSA 레벨 문제
- 여기서는 성공하고 앱만 실패하면 SDK 버전/환경변수/프록시/리전/엔드포인트 문제
7.2 ServiceAccount ↔ Role 매핑 확인
kubectl get sa myapp-sa -o jsonpath='{.metadata.annotations.eks\.amazonaws\.com/role-arn}{"\n"}'
7.3 토큰 파일 존재/권한 확인
kubectl exec -it deploy/myapp -- sh -lc 'test -r "$AWS_WEB_IDENTITY_TOKEN_FILE" && echo OK || echo NO'
7.4 Trust policy 조건(sub/aud) 확인
IAM 콘솔 또는 CLI로 role trust policy를 점검합니다.
8) 해결 패턴별 처방전
패턴 A: IRSA 의도했는데 환경변수/토큰이 없다
- Deployment의
serviceAccountName수정 - ServiceAccount에
eks.amazonaws.com/role-arnannotation 추가 - OIDC provider 생성/연결
패턴 B: 토큰은 있는데 AssumeRole이 안 된다
- IAM Role trust policy의
sub,aud조건 수정 - namespace/SA 이름 변경이 있었는지 확인
패턴 C: 앱에서만 MissingAuthenticationToken
- SDK 업그레이드
- 리전 명시
- 프록시가 Authorization 계열 헤더를 제거하는지 확인
- 컨테이너에
AWS_EC2_METADATA_DISABLED=true를 줘서 IMDS fallback을 차단하고, IRSA만 쓰도록 강제(원인 규명에 도움)
env:
- name: AWS_EC2_METADATA_DISABLED
value: "true"
- name: AWS_REGION
value: "ap-northeast-2"
패턴 D: IRSA는 되는데 특정 서비스만 403
이건 MissingAuthenticationToken이 아니라 AccessDenied로 보이는 경우가 더 많지만, KMS/S3/ECR 등 서비스별 정책/키 정책 문제로 갈라집니다. 예를 들어 KMS는 키 정책이 추가로 관여합니다. 이 경우는 EKS IRSA는 되는데 KMS Decrypt 403 해결법처럼 서비스별로 접근해야 합니다.
또 ECR pull 자체가 막힌 케이스는 증상이 다르지만(이미지 pull 단계), 운영 중 함께 터지는 경우가 많아 EKS Pod에서 AWS ECR 403 AccessDenied 해결도 참고가 됩니다.
9) (부록) Terraform로 IRSA 구성 예시
IRSA를 “대충 콘솔에서 클릭”으로 만들면 나중에 누락/불일치가 잦습니다. IaC로 고정해두면 재현성이 좋아집니다.
아래는 핵심 개념만 담은 예시입니다(환경에 맞게 수정 필요).
data "aws_eks_cluster" "this" {
name = var.cluster_name
}
data "aws_eks_cluster_auth" "this" {
name = var.cluster_name
}
# OIDC issuer URL에서 https:// 제거한 host/path가 IAM OIDC provider의 URL에 해당
locals {
oidc_issuer = replace(data.aws_eks_cluster.this.identity[0].oidc[0].issuer, "https://", "")
}
resource "aws_iam_role" "irsa" {
name = "myapp-irsa-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Federated = aws_iam_openid_connect_provider.this.arn
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"${local.oidc_issuer}:aud" = "sts.amazonaws.com"
"${local.oidc_issuer}:sub" = "system:serviceaccount:${var.namespace}:${var.serviceaccount_name}"
}
}
}]
})
}
resource "aws_iam_role_policy_attachment" "attach" {
role = aws_iam_role.irsa.name
policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" # 예시
}
이후 Kubernetes ServiceAccount에 annotation을 붙입니다.
apiVersion: v1
kind: ServiceAccount
metadata:
name: myapp-sa
namespace: default
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/myapp-irsa-role
10) 마무리: MissingAuthenticationToken을 “인증 경로 문제”로 단순화하자
EKS에서 403 MissingAuthenticationToken은 대부분 (1) Pod가 어떤 credential을 쓸지 결정되는 구간(IRSA/IMDS/정적키)에서 꼬이거나, (2) 서명 헤더가 전송 중 훼손되거나, (3) 리전/엔드포인트가 틀린 문제입니다.
정리하면 다음 3가지만 먼저 확인하면 해결 속도가 빨라집니다.
- Pod에
AWS_ROLE_ARN,AWS_WEB_IDENTITY_TOKEN_FILE가 있는가(=IRSA 적용 여부) aws sts get-caller-identity가 Pod에서 되는가(=자격 획득 여부)- 프록시/메시가
Authorization/Host/X-Amz-*헤더를 건드리지 않는가(=서명 보존 여부)
여기까지 확인했는데도 원인이 애매하면, 실패하는 AWS API(서비스/리전/엔드포인트)와 SDK 언어/버전, Pod의 환경변수 목록을 기준으로 더 좁혀 들어가면 됩니다.