소개
팀은 S3 오리진을 비공개로 유지하면서 고객이 CloudFront를 통해 릴리스 페이지를 읽도록 하려 합니다. 제공된 페이지를 업로드하고 배포와 오리진 액세스 제어를 연결한 뒤 해당 배포에만 객체 읽기를 허용합니다. 작동하는 사용자 경로와 거부되는 직접 오리진 경로를 모두 시험합니다.
먼저 AWS Foundations, S3 객체 작업, IAM 리소스 정책 개념을 학습하세요. 이 독립 VM은 설정된 AWS CLI 접근, index.html, 별도의 참조 버킷을 제공합니다. 위쪽 AWS View에서 배포와 오리진을 관찰하고 아래쪽 Terminal에서 작업하세요. 개인 AWS 계정이나 공개 도메인은 필요하지 않습니다. 일반 S3 오리진 하나를 사용하며 사용자 캐시와 HTTPS는 별도 실습에서 다룹니다.
자격증 시험 연계
| 자격증 | 시험 과제 | 실습 내용 |
|---|---|---|
| Solutions Architect – Associate (SAA-C03) | 과제 1.1 | 리소스 정책으로 S3 읽기를 대상 CloudFront 배포로 제한하기. |
실습 개요

프라이빗 오리진 콘텐츠 준비하기
이 단계에서는 프라이빗 S3 버킷을 만들고 준비된 페이지를 업로드합니다.
**오리진(origin)**은 CloudFront가 가져오는 콘텐츠를 저장합니다. **뷰어(viewer)**는 CloudFront에 콘텐츠를 요청하는 클라이언트입니다. 뷰어와 오리진 접근은 서로 다른 권한입니다. 배포를 통해 페이지를 읽을 수 있어도 익명 직접 S3 읽기는 계속 거부할 수 있습니다.
제공된 프로젝트 디렉터리에서 작업하세요. 참조 버킷 labex-n02-reference는 변경하지 마세요:
cd /home/labex/project
cat index.html
aws s3api create-bucket \
--bucket labex-n02-content
객체 소유권을 Bucket owner enforced로 설정하세요. 버킷 소유자가 소유권을 가지며 ACL 기반 허용은 비활성화됩니다. OAC는 대신 버킷 정책을 사용합니다. 네 가지 Block Public Access 보호를 모두 활성화하여 공개 ACL 또는 정책 허용을 막으세요:
aws s3api put-bucket-ownership-controls \
--bucket labex-n02-content \
--ownership-controls '{"Rules":[{"ObjectOwnership":"BucketOwnerEnforced"}]}'
aws s3api put-public-access-block \
--bucket labex-n02-content \
--public-access-block-configuration '{"BlockPublicAcls":true,"IgnorePublicAcls":true,"BlockPublicPolicy":true,"RestrictPublicBuckets":true}'
제공된 페이지를 업로드하세요. --content-type text/html은 객체를 HTML 문서로 지정합니다:
aws s3api put-object \
--bucket labex-n02-content \
--key index.html \
--body index.html \
--content-type text/html
aws s3api head-object \
--bucket labex-n02-content \
--key index.html
객체 크기와 ContentType을 살펴보세요. CLI 요청은 설정된 운영 사용자로 인증됩니다. AWS 자격 증명을 보내지 않는 익명 HTTP 요청과 비교하세요:
curl --noproxy '*' \
--output /dev/null \
--write-out 'Direct origin: HTTP %{http_code}\n' \
http://127.0.0.1:5000/labex-n02-content/index.html
Direct origin: HTTP 403이 예상됩니다. --output /dev/null은 오류 본문을 버리고 --write-out은 HTTP 상태를 출력합니다. 명시된 실습 엔드포인트에서 직접 S3 객체 접근을 시험하며 버킷을 공개하지 않습니다. 프라이빗 오리진 검사를 실행하세요.

배포를 오리진에 연결하기
이 단계에서는 CloudFront 배포를 S3 버킷에 연결하고 연결만으로는 오리진 권한이 부여되지 않음을 관찰합니다.
**오리진 액세스 제어(OAC)**는 CloudFront가 오리진 요청을 인증하는 방법을 정합니다. S3, Signature Version 4, always 서명을 선택하세요. 일반 CLI 설정을 작성합니다:
cat > oac.json <<'JSON'
{
"Name": "labex-n02-oac",
"Description": "Read the private release origin",
"SigningProtocol": "sigv4",
"SigningBehavior": "always",
"OriginAccessControlOriginType": "s3"
}
JSON
OAC_ID=$(aws cloudfront create-origin-access-control \
--origin-access-control-config file://oac.json \
--query OriginAccessControl.Id \
--output text)
$(...)는 반환된 OAC ID를 OAC_ID에 저장합니다. --query는 다음 설정에서 참조할 ID만 선택합니다. 실습 내내 이 Terminal을 열어 두세요.
배포 설정은 content-origin을 일반 S3 버킷 엔드포인트에 연결하며 S3 웹 사이트 엔드포인트를 사용하지 않습니다. TargetOriginId가 해당 오리진을 선택하고 DefaultRootObject는 /를 index.html에 연결합니다. 기존 방식의 ForwardedValues 블록은 쿠키나 쿼리 문자열을 전달하지 않게 합니다. TTL이 모두 0이므로 오리진 권한 시험에서 캐시된 성공 결과를 재사용하지 않습니다. allow-all은 이 실습의 HTTP 뷰어 요청을 허용하며 HTTPS는 별도로 배웁니다.
다음 here-document에는 따옴표가 없으므로 셸이 JSON 파일에서 $OAC_ID를 실제 값으로 확장합니다:
cat > distribution.json <<JSON
{
"CallerReference": "labex-n02-release",
"Comment": "labex-n02:private-content",
"Enabled": true,
"DefaultRootObject": "index.html",
"Origins": {
"Quantity": 1,
"Items": [{
"Id": "content-origin",
"DomainName": "labex-n02-content.s3.amazonaws.com",
"S3OriginConfig": {"OriginAccessIdentity": ""},
"OriginAccessControlId": "$OAC_ID"
}]
},
"DefaultCacheBehavior": {
"TargetOriginId": "content-origin",
"ViewerProtocolPolicy": "allow-all",
"TrustedSigners": {"Enabled": false, "Quantity": 0},
"ForwardedValues": {"QueryString": false, "Cookies": {"Forward": "none"}},
"MinTTL": 0,
"DefaultTTL": 0,
"MaxTTL": 0
}
}
JSON
DIST_ID=$(aws cloudfront create-distribution \
--distribution-config file://distribution.json \
--query Distribution.Id \
--output text)
DIST_DOMAIN=$(aws cloudfront get-distribution \
--id "$DIST_ID" \
--query Distribution.DomainName \
--output text)
연결된 오리진을 살펴보세요:
aws cloudfront get-distribution-config \
--id "$DIST_ID" \
--query DistributionConfig.Origins
S3 도메인과 OriginAccessControlId는 버킷 및 OAC와 일치해야 합니다. 시험하기 전에 배포의 제어 영역 반영을 기다리세요. 공식 waiter는 상태를 반복해서 읽으며 뷰어 트래픽을 만들지 않습니다:
aws cloudfront wait distribution-deployed \
--id "$DIST_ID"
이제 뷰어 경로를 시험하세요. --resolve는 이 정확한 배포 이름과 실습 포트를 VM의 제공된 콘텐츠 엔드포인트에 연결합니다. 시스템 DNS를 변경하거나 도메인을 등록하지 않습니다:
curl --noproxy '*' \
--resolve "${DIST_DOMAIN}:8082:127.0.0.1" \
--output /dev/null \
--write-out 'Viewer before permission: HTTP %{http_code}\n' \
"http://${DIST_DOMAIN}:8082/index.html"
HTTP 403이 예상됩니다. 배포 생성과 OAC 선택은 연결을 설명할 뿐이며 S3에는 해당 배포를 허용하는 정책이 더 필요합니다. 이 결과를 해결하려고 버킷을 공개하지 마세요. 연결 검사를 실행하세요.

대상 배포에 접근 허용하기
이 단계에서는 선택한 CloudFront 배포가 객체를 읽도록 허용하고 익명 직접 오리진 접근은 계속 거부합니다.
ARN은 AWS 리소스와 계정을 식별합니다. 배포 ARN을 읽으세요:
DIST_ARN=$(aws cloudfront get-distribution \
--id "$DIST_ID" \
--query Distribution.ARN \
--output text)
버킷 정책은 cloudfront.amazonaws.com을 서비스 **보안 주체(principal)**로 지정하고 s3:GetObject만 허용하며 자신의 버킷 객체로 범위를 제한합니다. AWS:SourceArn 조건은 서비스 요청을 이 특정 배포로 제한합니다. 이는 공개 Principal: "*" 정책과 다릅니다. 얻은 ARN으로 작성하세요:
cat > bucket-policy.json <<JSON
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "cloudfront.amazonaws.com"},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::labex-n02-content/*",
"Condition": {"StringEquals": {"AWS:SourceArn": "$DIST_ARN"}}
}]
}
JSON
aws s3api put-bucket-policy \
--bucket labex-n02-content \
--policy file://bucket-policy.json
객체를 다시 요청하세요. 이제 --fail은 성공하지 않은 HTTP 상태에서 명령을 실패시키고 --include는 실제 문서와 응답 헤더를 함께 표시합니다:
curl --fail --include --noproxy '*' \
--resolve "${DIST_DOMAIN}:8082:127.0.0.1" \
"http://${DIST_DOMAIN}:8082/index.html"
HTTP 200, 콘텐츠 유형 text/html, Release one이 포함된 페이지가 예상됩니다. 성공한 설정 응답뿐 아니라 배포를 통해 전달된 실제 객체 바이트를 확인했습니다.
즉시 익명 직접 오리진 시험을 반복하세요:
curl --noproxy '*' \
--output /dev/null \
--write-out 'Direct origin after viewer success: HTTP %{http_code}\n' \
http://127.0.0.1:5000/labex-n02-content/index.html
여전히 HTTP 403을 반환해야 합니다. 뷰어 경로는 작동하고 직접 오리진은 비공개로 남습니다. AWS View는 연결된 오리진과 최근 뷰어 결과를 표시합니다. 접근 검사를 실행하세요.

자신의 콘텐츠 제공 리소스만 삭제하기
이 단계에서는 배포를 비활성화하고 삭제한 뒤 OAC와 S3 콘텐츠를 삭제합니다. 참조 버킷은 변경하지 마세요.
CloudFront는 설정 변경의 버전 토큰으로 ETag를 사용합니다. 토큰을 추측하지 말고 현재 설정과 ETag를 가져오세요:
aws cloudfront get-distribution-config \
--id "$DIST_ID" \
--query DistributionConfig \
--output json > distribution-current.json
DIST_ETAG=$(aws cloudfront get-distribution-config \
--id "$DIST_ID" \
--query ETag \
--output text)
이 실습의 현재 설정에서는 배포의 Enabled만 같은 이름의 속성 중 true입니다. 다음 표준 sed 치환은 오리진 설정을 보존하면서 비활성화된 복사본을 만듭니다:
sed 's/"Enabled": true/"Enabled": false/' distribution-current.json > distribution-disabled.json
aws cloudfront update-distribution \
--id "$DIST_ID" \
--if-match "$DIST_ETAG" \
--distribution-config file://distribution-disabled.json
비활성화된 설정이 반영될 때까지 기다리세요. AWS에서는 설정 전파에 시간이 걸릴 수 있으며 이 실습은 전 세계 배포 지연을 측정하지 않습니다:
aws cloudfront wait distribution-deployed \
--id "$DIST_ID"
업데이트하면 ETag가 바뀝니다. 비활성화된 배포를 삭제하기 전에 최신 토큰을 읽으세요:
DIST_ETAG=$(aws cloudfront get-distribution-config \
--id "$DIST_ID" \
--query ETag \
--output text)
aws cloudfront delete-distribution \
--id "$DIST_ID" \
--if-match "$DIST_ETAG"
OAC 자체의 ETag로 OAC를 삭제하세요. 배포 ID와 OAC ID는 서로 다른 리소스를 가리킵니다:
OAC_ETAG=$(aws cloudfront get-origin-access-control \
--id "$OAC_ID" \
--query ETag \
--output text)
aws cloudfront delete-origin-access-control \
--id "$OAC_ID" \
--if-match "$OAC_ETAG"
자신이 만든 객체와 버킷만 삭제하세요:
aws s3api delete-object \
--bucket labex-n02-content \
--key index.html
aws s3api delete-bucket \
--bucket labex-n02-content
aws s3api list-buckets \
--query Buckets[].Name
참조 버킷은 남고 콘텐츠 버킷은 없어야 합니다. AWS View에는 콘텐츠 배포 없이 참조 객체만 표시되어야 합니다. 정리 검사를 실행하세요. 실패한 API 요청은 리소스가 삭제되었다는 증거가 아닙니다.

요약
일반 프라이빗 S3 오리진에 배포를 연결하고 항상 서명하는 OAC 요청을 설정하여 대상 배포에만 읽기 권한을 부여했습니다. 실제 HTTP 시험으로 허용된 뷰어 접근과 거부된 익명 오리진 접근을 구분했습니다. 이후 현재 ETag를 사용해 자신의 리소스를 비활성화하고 삭제하며 별도의 콘텐츠를 보존했습니다. 다음으로 캐시 재사용을 관찰하고 업데이트된 객체의 캐시를 무효화합니다.



