Published on

EKS IRSA는 되는데 S3만 403? 30분 진단

Authors

서버리스/마이크로서비스 환경에서 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, HostId
  • AccessDenied, InvalidAccessKeyId, SignatureDoesNotMatch

특히 SignatureDoesNotMatch 는 권한보다 리전/엔드포인트/프록시/시간 문제일 가능성이 큽니다.

2-3) 흔한 권한 누락 패턴

  • aws s3 ls s3://bucket만 403 → s3:ListBucket 누락
  • GetObject만 403 → s3:GetObject 또는 버킷 정책 조건 불일치
  • PutObject만 403 → s3:PutObject 또는 KMS Encrypt/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}'

ServerSideEncryptionaws: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만 403

    • s3:ListBucket 누락 또는 버킷 정책에서 prefix 조건/Principal 조건 불일치
  • GetObject는 되는데 PutObject만 403

    • s3: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 연동 전반에서 재발 방지 포인트를 정리하는 데 도움이 됩니다.