是否应为 cloud object storage 配置 nginx reverse proxy?
结论
通常没必要让自建 Nginx 代理所有 object storage 流量。
如果基础设施主要在 AWS,默认可以采用下面的架构:
下载:Client → CloudFront → S3 private bucket
上传:Client → 应用鉴权 → 短期 presigned POST/PUT → S3
管理:应用记录签发、用户配额和业务审计
配合 CloudFront Origin Access Control(OAC)和 S3 bucket policy,可以阻止客户端绕过 CloudFront 读取对象。只有上传流程确实需要同步鉴权、严格的一次性令牌、内容检查或实时图片转换时,才适合引入 Nginx。普通图片下载不必经过它。
更换 object storage provider 也很难同时解决所有问题。每 IP 限速、WAF、缓存、私网接入和业务级鉴权分属不同层次,很少有存储产品能在 bucket 层原生提供全部能力。
对几个主要疑虑的判断
1. S3 日志并非“每个请求生成一个日志文件”
S3 Server Access Logging 会批量、尽力投递日志,不是一次请求对应一个对象,也不适合实时审计。开启日志通常没有单独费用,但日志对象占用的存储、后续分析和相关服务可能产生费用。
常见的日志有三类:
- S3 Server Access Logging:成本较低,但有延迟,也不保证实时投递。
- CloudTrail Data Events:适合审计对象级 API 调用,但按 data event 计费。
- CloudFront access logs:记录用户访问和缓存命中,更适合分析图片下载。
Lambda 不能简单监听全部 S3 HTTP 请求。S3 Event Notifications 主要处理对象创建、删除等事件,不是涵盖 GET、403 和所有 API 请求的通用日志流。
使用 CloudFront 时,可以用 CloudFront 日志记录用户请求,再用 S3 日志或 CloudTrail 记录少量 origin 请求和管理操作。缓存命中没有必要全部回源到 S3。
2. S3 没有 bucket 级通用最大对象尺寸策略
这个判断基本正确。S3 没有简单的 bucket policy 条件,可以统一规定“该 bucket 中任何对象不得超过 10 MiB”。
浏览器直传更适合使用 presigned POST,因为 POST policy 原生支持 content-length-range:
{
"expiration": "2026-09-13T12:00:00Z",
"conditions": [
["content-length-range", 1, 10485760],
{"bucket": "example-upload-bucket"},
["starts-with", "$key", "incoming/"],
["starts-with", "$Content-Type", "image/"]
]
}
客户端提交的 Content-Type 不能代替安全检查。上传完成后,仍要检查文件签名、实际尺寸、解码结果和压缩炸弹风险。必要时应重新编码图片,再将其移入可发布前缀。
如果需要在接收较大请求体之前统一拦截,上传网关或反向代理才有实际价值。
3. S3 bucket 不提供原生的每 IP 限速
这个判断也是正确的。S3 bucket policy 可以根据来源 IP 允许或拒绝访问,但不能表达“每个 IP 每分钟最多请求 100 次”。
可以考虑以下方案:
- 下载经过 CloudFront 缓存,并按需启用 AWS WAF rate-based rules。
- 在上传 URL 的签发 API 上限制用户、IP、租户和每日上传容量。
- 匿名且风险较高的上传使用 Nginx、API Gateway 或专门的上传服务。
- 通过 bucket policy 为固定办公网络、合作方出口 IP 或 VPC endpoint 设置 allowlist。
WAF 单次请求的价格高于一次 S3 GET,但不能据此直接判断 WAF 不划算。还要计算 CloudFront 缓存减少的 S3 请求、origin 流量、数据传输和攻击流量。如果只是低价值的公开图片,也不一定需要让 WAF 检查每次缓存命中。
AWS Budgets 和费用告警并不是硬性消费上限,不能代替入口限速。
4. Presigned URL 不是一次性令牌
Presigned URL 允许调用方在有效期内执行一个已签名请求,本身没有“使用后失效”的服务器端状态。
如果只需要防止同一个对象被覆盖,可以为每次上传生成不可预测的新 key,并使用条件写入:
If-None-Match: *
对象已经存在时,后续写入会失败。这比先执行 HEAD 再上传可靠,因为“检查后写入”存在并发竞争。
不过,这仍不是真正的一次性 URL。攻击者可以重复发起请求,只是后续写入会失败,而且失败请求仍会消耗服务资源。严格的一次性上传需要引入状态:
Client → 上传网关兑换 token → 原子地标记 token 已使用 → 写入 S3
可以使用 DynamoDB 等存储,通过条件更新消费 token。如果业务只要求“成功上传后不能覆盖对象”,随机 key、较短的过期时间和 If-None-Match: * 通常就够了。
5. 外部未授权的 403 请求收费问题已经发生变化
过去的讨论不能直接套用到现在的计费规则。AWS 已调整 S3 未授权请求的计费政策。对于由 AWS 账户或 AWS Organization 外部发起,并返回 403 AccessDenied 的未授权请求,bucket owner 通常不再承担请求费用。
具体情况仍要结合请求使用的身份、账户关系、响应类型和最新价格说明来确认。成功请求、使用合法凭证发出的请求,以及不在该政策范围内的错误请求,不能一概视为免费。
无论请求是否收费,bucket name 都不应被当作秘密。真正的安全边界是 IAM、bucket policy、Block Public Access、OAC 和短期签名,而不是隐藏名称。
6. S3 不能放进 VPC,但可以限制为私有访问路径
S3 不是部署在用户子网中的资源,因此不能直接为 bucket 关联 security group 或 NACL。不过,仍然可以限制它的访问路径。
EC2 内的上传代理可以使用:
EC2/Nginx → S3 Gateway VPC Endpoint → private S3 bucket
然后通过 bucket policy,只允许指定的 VPC endpoint 访问。这样,即使调用方知道 bucket name,从公共 S3 endpoint 发出的读写请求也会被拒绝。
CloudFront 下载则可以通过 OAC,允许指定的 distribution 访问 bucket。例如:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontOAC",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::example-image-bucket/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/EXAMPLE123"
}
}
}
]
}
同时应开启 S3 Block Public Access,不要再添加允许匿名读取的 bucket policy。
这种做法不会让 S3 endpoint 从互联网上消失,但能阻止调用方绕过 CloudFront 成功访问。安全目标应该是“公共入口无法成功访问”,而不是“互联网无法向 endpoint 发送数据包”。
推荐架构
图片下载
Client
↓ HTTPS、自有域名
CloudFront
↓ OAC
Private S3 bucket
建议这样配置:
- S3 bucket 保持 private,并开启 Block Public Access。
- 只允许指定的 CloudFront distribution 读取。
- 使用较长的缓存时间和带内容哈希的文件名,例如
images/sha256/...jpg。 - 私有图片使用 CloudFront signed URL 或 signed cookie。
- 在 CloudFront 记录 viewer access logs。
- 普通图片下载不经过应用服务器或 Nginx。
这样可以省去自建代理在带宽、连接数、磁盘缓存、故障转移和 autoscaling 方面的运维负担。
图片上传
Client
↓ 请求上传授权
Application API
↓ 返回短期上传凭证
Client
↓ presigned POST
S3 incoming prefix
↓ event
Validation/Re-encoding worker
↓
S3 published prefix
上传 API 应控制以下内容:
- 已登录用户和租户权限;
- 每个用户、IP 和租户的速率;
- 每日文件数量和总字节数;
- 允许使用的 object key;
- 1~5 分钟的过期时间;
content-length-range;- checksum;
- 随机且不可预测的 key;
- 上传记录和状态;
- 未完成上传的生命周期清理。
扩展名、MIME type 和图片元数据都不能直接信任。
什么时候值得使用 Nginx
遇到以下情况时,可以使用 Nginx 或专门的上传网关:
- 上传前必须执行严格且有状态的一次性 token;
- 必须同步检查请求体,不能等上传完成后再异步处理;
- 法规要求入口请求立即、完整地写入自管日志;
- 需要自定义认证协议;
- 需要为每个客户执行复杂的带宽或并发配额;
- 需要在 origin 前执行非标准图片转换;
- 客户端无法使用 presigned upload。
基础限制配置可以写成:
limit_req_zone $binary_remote_addr zone=upload_per_ip:20m rate=10r/s;
server {
listen 443 ssl;
server_name upload.example.com;
client_max_body_size 10m;
client_body_timeout 15s;
location /upload/ {
limit_req zone=upload_per_ip burst=20 nodelay;
proxy_request_buffering on;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
proxy_pass http://internal-upload-service;
}
}
这套方案也有成本,包括 EC2、负载均衡、跨可用区流量、磁盘、日志、监控和日常运维。Nginx 还可能成为吞吐瓶颈和新的攻击入口。
Nginx 本身也不能阻止用户绕过代理访问 S3。还需要配合 bucket policy,只允许代理使用的 IAM role、指定 VPC endpoint 或 CloudFront OAC 访问。向 S3 转发请求还涉及 AWS Signature Version 4,不应依赖未经充分审计的 Lua 代码自行实现签名和安全逻辑。
是否应更换 storage provider
不建议仅因为 S3 缺少 bucket 原生限速就进行迁移。尤其是计算、身份、监控和网络都已经部署在 AWS 时,跨云迁移可能增加 egress、延迟、故障面和运维成本。
可以根据需求重点评估以下服务:
| 主要需求 | 可重点评估的服务 |
|---|---|
| 与 AWS 集成、OAC、VPC endpoint、IAM | Amazon S3 |
| Azure 私网与企业网络集成 | Azure Blob Storage + Private Endpoint |
| GCP 数据处理生态 | Google Cloud Storage |
| 互联网 egress 成本是首要因素 | Cloudflare R2、Backblaze B2、Wasabi |
| 简单固定套餐和 S3-compatible API | DigitalOcean Spaces 等 |
这些服务的 ingress、egress、request、最低保存期限、免费额度、地区和付款政策经常变化,不适合只根据一张静态价格表做采购决定。需要特别核对:
- “免费 egress”是否附带流量比例或合理使用条件;
- DELETE、LIST、HEAD 和失败请求如何计费;
- 是否要求最低容量或最低保存期限;
- S3-compatible 是完整兼容,还是只支持部分 API;
- access logs 是否涵盖成功、失败和匿名请求;
- 私网能力作用于 bucket、账户,还是只适用于专线;
- 固定 IP allowlist 是否适用于数据面;
- 是否提供 DDoS 成本保护或异常账单政策;
- 数据驻留采用明确地区,还是自动放置。
即使换用其他 provider,多数情况下仍需要 CDN/WAF 处理每 IP 限速,并由应用负责一次性上传和业务配额。单纯迁移 object storage,很难同时满足列出的五项“理想条件”。
最终建议
优先使用 S3、CloudFront OAC 和 private bucket,不要让 Nginx 代理普通图片下载。上传使用短期 presigned POST、大小策略、随机 key、checksum 和上传后验证。速率和容量限制应放在签发上传权限的 API 上。
只有在需要同步检查上传内容、实现严格的一次性消费或执行复杂配额时,才增加专门的上传网关。即使增加了网关,也要通过 VPC endpoint 和 bucket policy 封闭 S3 数据面,不能把“隐藏 bucket name”当作安全措施。
备注:内容仅供参考。