AWS CloudFront与S3的组合是最经典的静态网站与资源分发方案,但很多人在配置时习惯直接把S3桶设为公开可读,这等于绕过了CloudFront的所有防护能力。正确做法是借助Origin Access Control(OAC)配合S3桶策略,让S3桶彻底关闭公开访问,只接受来自指定CloudFront分配的请求。本文将完整讲解这套机制的原理与落地配置。

一、为什么要用OAC而不是让S3公开
先看问题的本质。当S3桶开放公开读取时,任何人拿到桶的URL就能直接访问对象,CloudFront的缓存优化、地域限制、签名URL、WAF防护统统形同虚设。更危险的是,攻击者可以绕过CDN直连S3源站,消耗源站流量,甚至配合对象列举权限摸清桶内全部文件结构。
OAC(Origin Access Control)是AWS官方推荐的回源鉴权机制,它取代了旧的OAI(Origin Access Identity)。原理是CloudFront在回源时使用一种特殊的服务主体身份访问S3,S3桶策略通过aws:SourceArn条件判断请求是否来自指定的CloudFront分配。这个条件是精确到分配ID级别的,即使别人知道你的桶名,也无法用自己的CloudFront分配来冒充回源。
相比旧版OAI,OAC有几个明显优势:支持S3所有区块加密配置(包括SSE-KMS)、支持动态请求(PUT、POST等,可用于S3 Transfer Acceleration场景)、并且配置后无需修改S3对象的ACL。唯一的限制是OAC要求CloudFront必须使用签名版本SigV4(Signed URL和Signed Cookies场景同样适用),这在配置_distribution时需要注意。
二、控制台配置OAC与桶策略
配置流程分为三步:创建OAC、绑定到CloudFront分配、更新S3桶策略。在CloudFront控制台进入Settings中的Origins页面,选中你的S3源站,点击Edit,将Origin access切换为Origin access control settings,再创建一个新的OAC。关键选项有两个:Origin type选择S3,Sign requests保持Yes(默认就是签名回源)。名字可以随意取,例如my-s3-oac。
保存后AWS会自动生成一段桶策略示例,可以直接复制去S3桶的Permissions页面粘贴。这段策略的核心结构如下:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontOACRead",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-example-bucket/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/E1234ABCD5678"
}
}
}
]
}
这里有几个字段必须理解到位。Principal写的是cloudfront.amazonaws.com服务主体,而不是某个IAM用户或角色,这是服务级鉴权的写法。Condition里的AWS:SourceArn是整个策略的安全核心,它把授权范围锁死到你的那个具体分配,注意这里的资源名是AWS:SourceArn,在老版本控制台生成的示例中写作aws:SourceArn,两者都有效,AWS策略JSON的条件键大小写不敏感,但建议统一用大写风格保持一致。桶策略只授予s3:GetObject,不做写入和列举授权,符合最小权限原则。
另外别忘了在S3桶的Block Public Access设置中保持全部启用(BlockPublicAcls、IgnorePublicAcls、BlockPublicPolicy、RestrictPublicBuckets四项全开),并确认没有任何Bucket Policy语句允许公开访问。配置完成后,通过S3的直连URL访问应该返回403,而通过CloudFront域名访问则正常返回内容,可以用curl快速验证。
三、用Terraform自动化完整配置
生产环境中手动点控制台不可维护,下面给出一段可直接使用的Terraform配置,涵盖OAC、分发和桶策略三个资源。假设S3桶已通过aws_s3_bucket资源创建:
resource "aws_cloudfront_origin_access_control" "oac" {
name = "my-s3-oac"
description = "OAC for static site bucket"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
resource "aws_cloudfront_distribution" "cdn" {
origin {
domain_name = aws_s3_bucket.site.bucket_regional_domain_name
origin_id = "s3-site-origin"
origin_access_control_id = aws_cloudfront_origin_access_control.oac.id
}
enabled = true
comment = "static site cdn"
default_root_object = "index.html"
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "s3-site-origin"
viewer_protocol_policy = "redirect-to-https"
forwarded_values {
query_string = false
cookies { forward = "none" }
}
}
restrictions {
geo_restriction { restriction_type = "none" }
}
viewer_certificate { cloudfront_default_certificate = true }
}
resource "aws_s3_bucket_policy" "site" {
bucket = aws_s3_bucket.site.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Sid = "AllowCloudFrontOACRead"
Effect = "Allow"
Principal = { Service = "cloudfront.amazonaws.com" }
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.site.arn}/*"
Condition = {
StringEquals = {
"AWS:SourceArn" = aws_cloudfront_distribution.cdn.arn
}
}
}]
})
}
注意Terraform中存在一个经典鸡生蛋问题:桶策略引用分发ARN,而分发又引用桶作为源。上面的写法之所以能工作,是因为aws_s3_bucket_policy在aws_cloudfront_distribution之后创建,Terraform会自动推导依赖顺序,无需显式depends_on。但如果你的桶策略还要引用别的高依赖资源,建议显式声明依赖避免循环。
还有一个容易踩的坑:源站地址必须使用bucket_regional_domain_name而不是桶名本身。CloudFront在启用OAC后不能选REST endpoint的遗留模式,同时注意别把这个桶同时配置成网站托管endpoint(形如bucket.s3-website-us-east-1.amazonaws.com),网站endpoint不支持OAC签名鉴权,会导致回源全部403。
四、从OAI迁移到OAC的注意事项
如果你的架构还在用旧版OAI,迁移时要按顺序操作:先在CloudFront创建OAC,然后把源站的Origin access从OAI切换到OAC,等待分发部署完成,最后再更新桶策略并移除旧的OAI授权语句。顺序不能乱,先删桶策略里的OAI授权会导致服务中断。
迁移后建议做一轮回归验证,重点检查三点:一是KMS加密的桶是否在OAC中正确处理(需要在KMS密钥策略中额外授权cloudfront服务主体使用密钥);二是是否有Lambda@Edge或CloudFront Functions在回源路径上改写了URI导致404;三是SigV4签名要求是否影响了你的自定义源配置。验证通过后再清理OAI资源,完成整个切换。
总结一下,S3桶策略限制仅CloudFront访问的核心就三件事:桶彻底关闭公开访问、OAC开启签名回源、桶策略用AWS:SourceArn精确锁定分配。把这套配置固化到Terraform中,既能保证安全基线,又便于后续多环境复制。
AWS CloudFrontS3桶策略Origin Access Control修改时间:2026-09-05 20:38:51