A website endpoint only serves a public bucket. With CloudFront in front, the bucket can stay private, readable only through your distribution.
The pieces
- The origin is the bucket's REST endpoint,
market-static-l4iwi9.s3.us-east-1.amazonaws.com, over HTTPS. It has no index document, so the distribution setsDefaultRootObject:index.html. - An origin access control (OAC) makes CloudFront sign its requests to the bucket.
- The bucket policy lets CloudFront read, for this distribution only.
OAC=$(aws cloudfront create-origin-access-control --origin-access-control-config Name=market-oac,SigningProtocol=sigv4,SigningBehavior=always,OriginAccessControlOriginType=s3 --query OriginAccessControl.Id --output text)A whole configuration
Two origins need the full configuration in a file. The /api/* behaviour sends API calls to the load balancer with CachingDisabled; the default behaviour serves files from the bucket with CachingOptimized. Fill in the capitals (OAC-ID is $OAC, LB-DNS the load balancer's name, the two policy ids from the table below) and save as dist.json:
{ "CallerReference": "market-1", "Comment": "market", "Enabled": true,
"DefaultRootObject": "index.html",
"Origins": { "Quantity": 2, "Items": [
{ "Id": "static", "DomainName": "market-static-l4iwi9.s3.us-east-1.amazonaws.com",
"OriginAccessControlId": "OAC-ID", "S3OriginConfig": { "OriginAccessIdentity": "" } },
{ "Id": "api", "DomainName": "LB-DNS", "CustomOriginConfig":
{ "HTTPPort": 80, "HTTPSPort": 443, "OriginProtocolPolicy": "http-only" } } ] },
"DefaultCacheBehavior": { "TargetOriginId": "static",
"ViewerProtocolPolicy": "redirect-to-https", "CachePolicyId": "CACHING-OPTIMIZED-ID" },
"CacheBehaviors": { "Quantity": 1, "Items": [ { "PathPattern": "/api/*",
"TargetOriginId": "api", "ViewerProtocolPolicy": "https-only",
"CachePolicyId": "CACHING-DISABLED-ID" } ] } }OriginAccessIdentity stays empty: OAC replaces that older mechanism. https-only makes an API client fail loudly on plain HTTP instead of following a redirect. The managed policies' ids:
aws cloudfront list-cache-policies --type managed --query "CachePolicyList.Items[].CachePolicy.[CachePolicyConfig.Name,Id]" --output tableaws cloudfront create-distribution --distribution-config file://dist.json --query "Distribution.[Id,ARN,DomainName]" --output textThe bucket policy
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow",
"Principal": { "Service": "cloudfront.amazonaws.com" },
"Action": "s3:GetObject", "Resource": "arn:aws:s3:::market-static-l4iwi9/*",
"Condition": { "StringEquals": { "AWS:SourceArn": "DISTRIBUTION-ARN" } } } ] }aws s3api put-bucket-policy --bucket market-static-l4iwi9 --policy file://oac-policy.jsonWith the distribution's ARN in place of DISTRIBUTION-ARN, saved as oac-policy.json (it replaces any policy the bucket had; aws s3api get-bucket-policy --bucket market-static-l4iwi9 --query Policy --output text shows the current one). Block Public Access stays on: this policy is not public. Until it is on the bucket, CloudFront also gets 403. The bucket's own URL keeps answering 403 Forbidden:
curl -sI https://market-static-l4iwi9.s3.us-east-1.amazonaws.com/index.htmlMore at the edge
CachingDisabled keeps nothing in the cache key, so CloudFront forwards no query strings, cookies or Authorization header to the origin; a real API also needs an origin request policy such as AllViewerExceptHostHeader. A load balancer origin stays reachable directly unless its security group allows only CloudFront's managed prefix list, com.amazonaws.global.cloudfront.origin-facing.
Origin access identity (OAI) is the older way to do this. Signed URLs and signed cookies decide who may fetch a path, geo restriction from which countries, and CloudFront Functions and Lambda@Edge change requests and responses at the edge (headers, redirects).