介绍
团队希望客户通过 CloudFront 读取发布页面,同时保持 S3 源站私有。上传预置页面,连接分配与源站访问控制,并只授予该分配读取对象的权限。分别测试可用的用户端路径和被拒绝的直接源站路径。
请先完成 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 配置不转发 Cookie 或查询字符串。此处全部 TTL 为零,防止源站权限测试复用已缓存的成功结果。allow-all 允许本实验的 HTTP 用户端请求;HTTPS 另行学习。
下一个 here-document 未加引号,因此 Shell 会把 $OAC_ID 展开到 JSON 文件中:
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 删除它。分配 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 禁用并删除所属资源,保留无关内容。接下来观察缓存复用,并使更新对象的缓存失效。



