- Published on
EKS IRSA는 되는데 S3만 403? 30분 진단
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스/마이크로서비스 환경에서 IRSA(IAM Roles for Service Accounts) 를 적용해두면 “AWS 자격증명은 잘 붙는데(SDK가 STS로 AssumeRoleWithWebIdentity 성공) S3만 403 AccessDenied” 같은 이상한 상황을 종종 만납니다. 이때 문제는 대개 (1) 실제로 어떤 Role로 호출하는지 착시, (2) S3 경로가 VPC 엔드포인트/프록시로 바뀌면서 정책 조건이 불일치, (3) SSE-KMS로 인해 KMS 권한/키 정책에서 막힘 중 하나입니다.
이 글은 30분 안에 원인을 좁히는 진단 루틴을 제공합니다. 이미 IRSA 자체는 붙었다고 가정하지만, “붙었다”의 정의(예: GetCallerIdentity 성공)부터 다시 확인합니다.
관련 주제로는 EKS ExternalSecret 미동작 - IRSA·KMS·권한 10분 진단 과 맥락이 겹치며, S3 403을 더 넓게 다루는 글은 S3 AccessDenied 403 급발생 - OAC·정책·KMS 30분 진단 도 참고할 만합니다.
0) 30분 타임박스 진단 로드맵
- 0~5분: Pod 안에서 “내가 누구인지” 확정(CallerIdentity, 환경변수, 토큰 파일)
- 5~12분: S3 API별로 어떤 권한이 필요한지 분리(List/Get/Put) + 에러 메시지/RequestId 확보
- 12~20분: 네트워크 경로 확인(VPC Endpoint, Private DNS, 리전/파티션, 프록시) 및 버킷 정책 조건 점검
- 20~30분: SSE-KMS 사용 여부 확인 → KMS Key policy + IAM policy + 암호화 컨텍스트까지 점검
핵심은 “STS는 되는데 S3는 403”을 권한 문제로만 보지 않고, 엔드포인트/조건부 정책/암호화까지 동시에 좁히는 것입니다.
1) 0~5분: IRSA가 ‘진짜로’ 적용됐는지 확정
IRSA가 된다고 말할 때 흔한 착각이 있습니다.
- SDK가 노드 IAM Role(Instance Profile) 로 폴백해서 STS는 되고, S3만 특정 버킷 정책에 의해 막히는 경우
- 잘못된 ServiceAccount를 쓰는 Pod가 섞여 있는 경우(Deployment 롤링 중)
AWS_PROFILE/AWS_SHARED_CREDENTIALS_FILE같은 설정이 컨테이너 이미지에 남아 IRSA보다 우선되는 경우
1-1) Pod에서 CallerIdentity로 Role ARN 확정
아래는 Pod 내부에서 가장 빨리 “누구로 호출 중인지”를 확정하는 방법입니다.
# (옵션) awscli가 없다면 임시로 설치하거나, 디버그 컨테이너를 붙이세요.
aws sts get-caller-identity
출력의 Arn이 기대한 Role인지 확인합니다.
- 기대:
arn:aws:sts::<ACCOUNT_ID>:assumed-role/<IRSA_ROLE_NAME>/<SESSION_NAME> - 경계 신호:
assumed-role/<NodeInstanceRole>/...또는 전혀 다른 Role
1-2) IRSA 환경 변수/토큰 파일 존재 확인
env | egrep 'AWS_(ROLE_ARN|WEB_IDENTITY_TOKEN_FILE|REGION|DEFAULT_REGION)'
ls -al $AWS_WEB_IDENTITY_TOKEN_FILE
정상이라면 다음이 보입니다.
AWS_ROLE_ARN=arn:aws:iam::...:role/...AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token
1-3) ServiceAccount 어노테이션 확인
kubectl get sa -n <ns> <sa-name> -o yaml | yq '.metadata.annotations'
필수:
eks.amazonaws.com/role-arn: arn:aws:iam::...:role/...
또한 Pod가 정말 그 SA를 쓰는지:
kubectl get pod -n <ns> <pod> -o jsonpath='{.spec.serviceAccountName}'; echo
여기서 엇갈리면 “IRSA는 되는데 S3만 403”이 아니라 IRSA가 기대대로 적용되지 않은 상태일 가능성이 큽니다.
2) 5~12분: S3에서 ‘어떤 API’가 403인지 분리
S3 403이라고 뭉뚱그리면 진단이 느려집니다. API별로 필요한 권한이 다르고, 특히 List 권한은 자주 빠집니다.
2-1) CLI로 최소 재현
# 버킷 리스트(계정 전체)
aws s3 ls
# 특정 버킷 리스트
aws s3 ls s3://<bucket>
# 객체 Head (GetObject보다 권한이 덜/다르지 않지만 메시지 분리에 도움)
aws s3api head-object --bucket <bucket> --key <key>
# 다운로드
aws s3 cp s3://<bucket>/<key> -
# 업로드
echo test | aws s3 cp - s3://<bucket>/irsa-test.txt
2-2) 에러 메시지/RequestId를 확보
SDK 로그나 CLI 출력에 다음이 나오면 반드시 저장하세요.
RequestId,HostIdAccessDenied,InvalidAccessKeyId,SignatureDoesNotMatch
특히 SignatureDoesNotMatch 는 권한보다 리전/엔드포인트/프록시/시간 문제일 가능성이 큽니다.
2-3) 흔한 권한 누락 패턴
aws s3 ls s3://bucket만 403 →s3:ListBucket누락GetObject만 403 →s3:GetObject또는 버킷 정책 조건 불일치PutObject만 403 →s3:PutObject또는 KMSEncrypt/GenerateDataKey누락
3) 12~20분: STS는 되는데 S3만 막는 ‘경로/정책 조건’ 이슈
IRSA가 정상이라면, 다음은 S3로 가는 네트워크 경로와 버킷 정책 조건(Condition) 을 봐야 합니다. 특히 사내망에서 VPC Endpoint를 붙이거나, 버킷 정책에 aws:SourceVpce, aws:PrincipalArn 같은 조건을 걸어두면 “특정 경로로만 허용”이 됩니다.
3-1) VPC Gateway Endpoint/Interface Endpoint 여부 확인
- S3는 보통 Gateway Endpoint(라우트 테이블)로 붙습니다.
- 하지만 조직에 따라 Interface Endpoint(PrivateLink) 를 쓰거나, 프록시를 태우기도 합니다.
클러스터 VPC에서 확인할 것:
- S3 Gateway Endpoint가 있는지
- Endpoint Policy가 과하게 제한되어 있지 않은지
> 엔드포인트 정책은 “IAM에서 허용해도” 추가로 거르는 필터입니다.
3-2) 버킷 정책의 조건을 점검 (SourceVpce/SourceVpc)
버킷 정책에 아래처럼 되어 있으면, Pod 트래픽이 그 VPCe를 통해 나가지 않으면 403이 납니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowOnlyFromVpce",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::<ACCOUNT_ID>:role/<IRSA_ROLE>"},
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::<bucket>/*",
"Condition": {
"StringEquals": {
"aws:SourceVpce": "vpce-xxxxxxxx"
}
}
}
]
}
체크포인트:
aws:SourceVpce값이 현재 VPC의 Endpoint와 일치하는가?- 멀티 VPC/멀티 클러스터라면, 다른 VPCe를 바라보는 정책이 아닌가?
- Pod가 NAT/IGW로 나가면 조건 불일치로 403이 날 수 있음
3-3) 리전/엔드포인트 불일치
S3는 글로벌 서비스처럼 보이지만, 실제 서명/리다이렉트에서 리전이 꼬이면 문제가 납니다.
AWS_REGION/AWS_DEFAULT_REGION이 올바른지- SDK가
s3.amazonaws.com같은 글로벌 엔드포인트로 붙었다가 리다이렉트/서명 mismatch가 나는지
CLI로 리전 명시 재현:
aws s3api get-bucket-location --bucket <bucket>
aws s3api list-objects-v2 --bucket <bucket> --region <bucket-region>
4) 20~30분: SSE-KMS 때문에 S3만 403 나는 케이스
S3에서 403이 나지만 실제 원인은 KMS인 경우가 많습니다. 특히 다음 상황에서 자주 발생합니다.
- 버킷 기본 암호화가
aws:kms로 설정됨 - 업로드 시
--ssekms-key-id또는x-amz-server-side-encryption: aws:kms사용 - 기존 객체가 KMS로 암호화되어 있고, 다운로드 시 복호화 권한이 없음
4-1) 객체/버킷이 KMS 암호화인지 확인
aws s3api get-bucket-encryption --bucket <bucket>
aws s3api head-object --bucket <bucket> --key <key> \
--query '{SSE:ServerSideEncryption,KMS:SSEKMSKeyId}'
ServerSideEncryption이 aws:kms면 KMS 권한을 같이 봐야 합니다.
4-2) IAM Policy에 KMS 권한이 있는지
S3 Put/Get 자체 권한이 있어도, KMS 키를 쓰면 다음이 필요합니다.
- 업로드(Encrypt):
kms:Encrypt,kms:GenerateDataKey - 다운로드(Decrypt):
kms:Decrypt - (선택)
kms:DescribeKey
예시(최소 권한에 가깝게):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::<bucket>",
"arn:aws:s3:::<bucket>/*"
]
},
{
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:Encrypt",
"kms:GenerateDataKey",
"kms:DescribeKey"
],
"Resource": "arn:aws:kms:<region>:<ACCOUNT_ID>:key/<key-id>"
}
]
}
4-3) KMS Key Policy가 Role을 신뢰하는지
KMS는 IAM Policy만으로 끝나지 않고, 키 정책(Key policy) 이 최종 관문이 됩니다. 키 정책에 Role이 없으면 403(또는 KMS AccessDenied)로 이어질 수 있습니다.
키 정책에서 확인할 것:
- IRSA Role ARN이 Principal에 포함되는가?
kms:ViaService조건이 걸려 있다면s3.<region>.amazonaws.com이 맞는가?- 멀티 리전/파티션(aws-cn, aws-us-gov) 혼동은 없는가?
kms:ViaService 예시:
{
"Sid": "AllowUseFromS3",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::<ACCOUNT_ID>:role/<IRSA_ROLE>"},
"Action": ["kms:Decrypt","kms:Encrypt","kms:GenerateDataKey"],
"Resource": "*",
"Condition": {
"StringEquals": {
"kms:ViaService": "s3.<region>.amazonaws.com"
}
}
}
여기서 <region> 이 틀리면 STS는 되는데 S3만 403처럼 보일 수 있습니다(실제는 S3가 KMS 호출하다가 막힘).
5) 빠른 결론을 위한 “증상 → 원인 후보” 매핑
aws sts get-caller-identity의 Arn이 기대 Role이 아님- SA/POD 매핑 오류, SDK 자격증명 우선순위(노드 role/정적 키) 폴백
ListBucket만 403s3:ListBucket누락 또는 버킷 정책에서 prefix 조건/Principal 조건 불일치
GetObject는 되는데PutObject만 403s3:PutObject누락 또는 SSE-KMS Encrypt/GenerateDataKey 누락
GetObject/PutObject모두 403인데 특정 클러스터에서만 발생- 버킷 정책
aws:SourceVpce/aws:SourceVpc조건 불일치 - VPC Endpoint policy가 제한
- 버킷 정책
에러가
SignatureDoesNotMatch/리다이렉트 느낌- 리전/엔드포인트 설정 불일치, 프록시/미들박스, 시간 오차
6) 실전용 체크리스트(복붙)
아래 순서대로 실행하면 대부분 30분 내에 결론이 납니다.
# 1) 내가 누구인지
aws sts get-caller-identity
# 2) IRSA 환경
env | egrep 'AWS_(ROLE_ARN|WEB_IDENTITY_TOKEN_FILE|REGION|DEFAULT_REGION)'
# 3) 버킷 리전
aws s3api get-bucket-location --bucket <bucket>
# 4) 최소 S3 호출
aws s3api list-objects-v2 --bucket <bucket> --max-keys 1 --region <bucket-region>
aws s3api head-object --bucket <bucket> --key <key> --region <bucket-region>
# 5) KMS 여부
aws s3api get-bucket-encryption --bucket <bucket>
aws s3api head-object --bucket <bucket> --key <key> \
--query '{SSE:ServerSideEncryption,KMS:SSEKMSKeyId}' --region <bucket-region>
추가로, 조직에서 VPC Endpoint 강제를 많이 쓰면 다음도 병행하세요.
- VPC에 S3 Endpoint 존재 여부, Endpoint policy 확인
- 버킷 정책의
aws:SourceVpce/aws:SourceVpc조건 확인
마무리
“IRSA는 되는데 S3만 403”은 대부분 (A) 실제 Role 착시, (B) VPC Endpoint/버킷 정책 조건 불일치, (C) SSE-KMS 권한/키 정책 세 축에서 갈립니다. STS 성공은 ‘인증(Authentication)’이 된 것뿐이고, S3는 여기에 리소스 정책(버킷 정책) + 네트워크 조건 + KMS 권한이 겹쳐 ‘인가(Authorization)’가 깨지기 쉽습니다.
IRSA/KMS가 엮인 다른 EKS 권한 장애 패턴은 EKS ExternalSecret 미동작 - IRSA·KMS·권한 10분 진단 도 같이 보면, “S3만”이 아니라 AWS 연동 전반에서 재발 방지 포인트를 정리하는 데 도움이 됩니다.