Published on

AWS CloudFront 403 실전 - OAC·S3 정책 점검

Authors

CloudFront로 S3 정적 자산을 배포하다 보면 어느 날 갑자기 403이 터집니다. 특히 OAI에서 OAC로 전환했거나, S3 버킷 퍼블릭 액세스를 막아둔 상태에서 정책을 조금만 잘못 건드려도 403 Forbidden 또는 AccessDenied가 쉽게 재현됩니다.

이 글은 “CloudFront 403”을 증상별로 분해해서, OAC 설정과 S3 정책에서 실제로 가장 많이 틀리는 지점을 빠르게 찾도록 돕는 실전 가이드입니다.

관련해서 네트워크 계층에서 막히는 문제를 점검하는 글도 함께 보면 진단 감이 빨리 잡힙니다. 예를 들어 VPC 라우팅 계열의 체크리스트는 문제 접근 방식 자체에 도움이 됩니다: AWS VPC 피어링 통신 불가? 라우트·SG·NACL 점검

403을 먼저 분류하기: 누가 거절했나

CloudFront 403은 크게 2종류로 나뉩니다.

1) CloudFront가 자체적으로 403을 반환

대표적으로 다음 케이스입니다.

  • Viewer request에서 WAF 또는 Geo restriction으로 차단
  • Signed URL 또는 Signed Cookie 요구 설정인데 서명이 없음
  • Origin request 정책에 따라 특정 헤더가 없어서 동작이 깨짐

이 경우 CloudFront 응답 헤더에 x-cache: Error from cloudfront가 보이는 경우가 많습니다.

2) Origin(S3)이 403을 반환하고 CloudFront가 전달

대표적으로 다음 케이스입니다.

  • S3 버킷 정책에서 CloudFront(OAC) 접근을 허용하지 않음
  • OAC 서명 검증이 실패(잘못된 배포, 다른 OAC, 다른 계정)
  • 객체 ACL, KMS 키 정책, S3 Block Public Access와의 조합 문제

이 경우 CloudFront 표면상 403이지만, 본질은 S3 AccessDenied입니다.

가장 빠른 1분 진단: CloudFront 표준 로그 또는 실시간 로그

403은 “추측”으로 고치기 어렵고, 로그로 “확정”해야 시간이 줄어듭니다.

  • CloudFront Standard Logs(배포 로그) 또는 Real-time logs를 켭니다.
  • 상태 코드가 403인 요청의 cs-uri-stem, x-edge-result-type, x-edge-detailed-result-type를 봅니다.

예시로 x-edge-detailed-result-typeAccessDenied류면 S3 정책 또는 KMS 정책 쪽으로 바로 들어가면 됩니다.

추가로 운영에서 로그를 켜다 비용이 튀는 경우가 많으니, 로그 비용 관리 관점은 아래 글도 참고할 만합니다: CloudWatch Logs 비용 폭증 원인과 절감 10가지

OAI가 아니라 OAC를 쓰는 이유와, 403이 늘어나는 지점

OAC는 CloudFront가 S3에 요청할 때 SigV4로 서명해서 “CloudFront만 접근 가능”을 더 명확하게 만들 수 있습니다. 반대로 말하면, 정책이 조금만 어긋나도 S3가 냉정하게 403을 반환합니다.

OAC에서 403이 자주 나는 지점은 다음 3가지입니다.

  • S3 버킷 정책의 Principal을 잘못 씀
  • Condition에서 AWS:SourceArn 또는 AWS:SourceAccount가 배포와 불일치
  • OAC를 만들었지만 배포 Origin에 연결을 안 했거나, 다른 OAC가 연결됨

체크리스트 1: CloudFront 배포 설정에서 OAC 연결 확인

CloudFront 콘솔에서 Distribution Origins로 들어가서 다음을 확인합니다.

  • Origin type이 S3인지
  • Origin access가 OAC로 설정되어 있는지
  • 연결된 OAC가 의도한 OAC인지
  • Origin path를 썼다면 S3 경로와 실제 객체 키가 맞는지

여기서 흔한 실수는 Origin path/static을 넣었는데 실제 S3 키는 static/...가 아니라 assets/...인 경우입니다. 이건 S3 기준으로는 NoSuchKey 또는 권한 문제처럼 보일 수 있고, 커스텀 에러 응답을 걸어둔 경우 403으로 뭉개져 보이기도 합니다.

체크리스트 2: S3 버킷 정책에서 OAC 허용(정답 템플릿)

OAC를 쓰는 경우, S3 버킷 정책은 보통 “CloudFront 서비스 프린시플”을 허용하고, 조건으로 특정 배포만 허용하는 형태가 가장 안전합니다.

아래는 대표적인 정책 예시입니다. YOUR_BUCKET_NAME, YOUR_DISTRIBUTION_ID, YOUR_ACCOUNT_ID는 환경에 맞게 바꾸세요.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontServicePrincipalReadOnly",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::YOUR_ACCOUNT_ID:distribution/YOUR_DISTRIBUTION_ID"
        }
      }
    }
  ]
}

자주 하는 실수 1: AWS:SourceArn에 배포 ARN을 잘못 넣음

  • 배포 ID를 잘못 복사
  • 다른 계정의 배포 ARN을 넣음
  • 스테이징 배포 ARN을 프로덕션 버킷에 넣음

이 경우 S3는 CloudFront 요청을 “다른 배포에서 온 요청”으로 보고 403을 냅니다.

자주 하는 실수 2: Resource를 버킷 ARN까지만 주고 /*를 빼먹음

s3:GetObject는 객체 리소스에 걸어야 합니다. 아래처럼 arn:aws:s3:::버킷명/*가 필요합니다.

자주 하는 실수 3: Actions3:GetObject가 아니라 s3:* 또는 엉뚱한 액션

권한을 넓히면 되려나 싶어 s3:*로 바꾸는 경우가 있는데, 보안적으로도 나쁘고 디버깅에도 도움이 안 됩니다. 최소 권한으로 정확히 허용하세요.

체크리스트 3: S3 Block Public Access와 “퍼블릭 정책” 경고

OAC를 쓰면 버킷은 퍼블릭일 필요가 없습니다. 오히려 다음이 권장됩니다.

  • S3 Block Public Access는 켜둠
  • 버킷 정책은 CloudFront 서비스 프린시플만 허용
  • 객체 ACL은 가능하면 사용하지 않음(버킷 오너 강제)

다만, 버킷 정책이 “퍼블릭으로 해석”되는 형태(예: Principal*)가 섞이면 Block Public Access에 의해 정책이 무시되거나 경고가 생길 수 있습니다. OAC 구성에서는 Principal을 서비스 프린시플로 고정하는 편이 안전합니다.

체크리스트 4: KMS로 암호화한 S3 객체라면 키 정책도 봐야 함

S3 버킷이 SSE-KMS를 사용하면, s3:GetObject 권한만으로는 부족할 수 있습니다. CloudFront가 S3에서 객체를 읽는 과정에서 KMS Decrypt가 필요하기 때문입니다.

이때는 다음을 점검합니다.

  • 해당 KMS 키 정책에서 CloudFront 경유 접근을 허용했는지
  • 또는 S3 서비스가 KMS를 사용할 수 있게 키 정책이 구성되어 있는지

증상은 동일하게 403으로 보이는데, 원인이 S3 정책이 아니라 KMS 키 정책인 경우가 꽤 있습니다.

체크리스트 5: 캐시 때문에 “고쳤는데 계속 403”처럼 보이는 경우

정책을 고친 뒤에도 CloudFront가 403을 계속 내보내면 다음을 확인합니다.

  • 해당 객체 경로가 CloudFront에 403으로 캐시되어 있는지
  • Error caching 최소 TTL 설정이 큰지
  • 배포 Invalidations를 날렸는지

운영에서는 무턱대고 전체 무효화보다, 문제 경로만 무효화하는 편이 비용과 안정성 면에서 낫습니다.

CLI 예시:

aws cloudfront create-invalidation \
  --distribution-id YOUR_DISTRIBUTION_ID \
  --paths "/index.html" "/assets/*"

체크리스트 6: S3 웹사이트 엔드포인트를 Origin으로 쓰면 OAC가 안 맞을 수 있음

OAC는 “S3 REST API 엔드포인트”를 대상으로 설계된 흐름입니다. 만약 Origin을 S3 website endpoint로 잡아두면, 기대한 방식으로 인증이 적용되지 않아 403 또는 이상 동작이 날 수 있습니다.

  • 정적 웹 호스팅 기능이 필요 없다면 S3 REST 엔드포인트를 사용
  • 리다이렉트, 디렉터리 인덱스 같은 웹사이트 기능이 필요하면 아키텍처를 재검토

실전 디버깅: curl로 헤더 확인하기

브라우저 대신 curl로 CloudFront 응답을 보면 힌트가 많습니다.

curl -I https://YOUR_DOMAIN_NAME/assets/app.js

여기서 확인 포인트:

  • x-cache 값이 Miss from cloudfront, Hit from cloudfront, Error from cloudfront 중 무엇인지
  • via 헤더가 CloudFront를 타는지
  • age가 있는지(캐시 여부)

만약 Hit from cloudfront인데 403이라면, “이미 403이 캐시됨” 가능성이 큽니다.

실전 디버깅: S3에서 CloudTrail Data events로 거절 사유 찾기

403이 S3에서 왔다고 의심되면, S3 Data events(오브젝트 레벨)를 CloudTrail에 켜고 GetObject 이벤트를 확인합니다. 비용이 발생할 수 있으니 문제 재현 시간에만 제한적으로 켜는 것을 권장합니다.

확인할 것:

  • errorCodeAccessDenied인지
  • userIdentity가 CloudFront 서비스 프린시플로 보이는지
  • requestParameters의 버킷, 키가 기대값인지

자주 묻는 케이스별 처방

케이스 A: 특정 경로만 403

  • S3에 해당 키가 실제로 존재하는지
  • Origin path 또는 빌드 산출물 경로가 바뀌지 않았는지
  • 특정 프리픽스만 정책에서 제외되어 있지 않은지

케이스 B: 로컬에서는 되는데 배포하면 403

  • CloudFront가 붙으면서 Host 헤더, 경로, 인코딩이 달라짐
  • SPA 라우팅을 S3로 직접 보내면 403 또는 404가 나고, 커스텀 에러 응답 설정에 의해 403으로 보일 수 있음

케이스 C: 정책을 바꿨는데도 계속 403

  • 403 캐시 무효화
  • 잘못된 배포를 수정하고 있는지(스테이징 배포 수정 중)
  • 멀티 오리진 환경에서 다른 오리진으로 라우팅되는지

안전한 권한 모델 권장안

운영에서 가장 사고가 적은 조합은 아래입니다.

  • CloudFront Origin Access는 OAC 사용
  • S3는 퍼블릭 차단 유지
  • S3 버킷 정책은 CloudFront 서비스 프린시플만 s3:GetObject 허용
  • 조건으로 AWS:SourceArn을 배포 ARN으로 제한
  • KMS 사용 시 키 정책까지 함께 점검

이 구성을 기본값으로 두고, 403이 나면 “배포 연결 OAC 확인 → S3 버킷 정책 ARN/리소스 확인 → 캐시/무효화 확인 → KMS/CloudTrail 확인” 순으로 내려가면 대부분의 케이스가 빠르게 정리됩니다.

마무리: 403은 권한 문제이기 전에 ‘매칭 문제’다

CloudFront 403은 단순히 권한이 없어서가 아니라, 정책의 조건(배포 ARN, 계정, 리소스 범위)이 현재 요청과 정확히 매칭되지 않아서 발생하는 경우가 많습니다. 로그로 거절 주체를 확정하고, OAC 연결과 S3 정책의 매칭을 기계적으로 검증하면 재발도 크게 줄일 수 있습니다.