Skip to content

基于 S3 + Nginx

  • nginx
http {
  # 最多缓存 1000M, 缓存时间 30天
  proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=s3_cache:100m max_size=1000m inactive=30d;

  server {
    listen 443;
    server_name your_domain; # 替换成你自己的域名

    # s3 资源反向代理
    location ~ ^/resource/(.+)$ {
      proxy_hide_header      x-amz-id-2;
      proxy_hide_header      x-amz-request-id;
      proxy_hide_header      x-amz-meta-server-side-encryption;
      proxy_hide_header      x-amz-server-side-encryption;
      proxy_hide_header      Set-Cookie;
      proxy_ignore_headers   Set-Cookie;
      proxy_set_header       Connection "";
      proxy_set_header       Authorization '';
      proxy_set_header       Host your_bucket_name.s3.eu-west-2.amazonaws.com;

      proxy_cache            s3_cache;
      # 如果源站响应状态码大于 300, 则返回 nginx 自己的错误页面,避免泄露源站信息
      proxy_intercept_errors on;
      proxy_cache_revalidate on;
      proxy_cache_use_stale  error timeout updating http_500 http_502 http_503 http_504;
      proxy_cache_lock       on;
      # 状态码为 200 则缓存 30 天
      proxy_cache_valid      200 30d;
      # 用户浏览器缓存 365 天
      add_header             Cache-Control max-age=31536000;
      # Cloudflare 缓存 365 天
      add_header             Cloudflare-CDN-Cache-Control max-age=31536000;
      # 所有其他 CDN 缓存 365 天
      add_header             CDN-Cache-Control max-age=31536000;
      add_header             X-Cache-Status $upstream_cache_status;

      proxy_pass https://your_bucket_name.s3.eu-west-2.amazonaws.com/$1;
    }
  }
}
http {
  # 最多缓存 1000M, 缓存时间 30天
  proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=s3_cache:100m max_size=1000m inactive=30d;

  server {
    listen 443;
    server_name your_domain; # 替换成你自己的域名

    # s3 资源反向代理
    location ~ ^/resource/(.+)$ {
      proxy_hide_header      x-amz-id-2;
      proxy_hide_header      x-amz-request-id;
      proxy_hide_header      x-amz-meta-server-side-encryption;
      proxy_hide_header      x-amz-server-side-encryption;
      proxy_hide_header      Set-Cookie;
      proxy_ignore_headers   Set-Cookie;
      proxy_set_header       Connection "";
      proxy_set_header       Authorization '';
      proxy_set_header       Host your_bucket_name.s3.eu-west-2.amazonaws.com;

      proxy_cache            s3_cache;
      # 如果源站响应状态码大于 300, 则返回 nginx 自己的错误页面,避免泄露源站信息
      proxy_intercept_errors on;
      proxy_cache_revalidate on;
      proxy_cache_use_stale  error timeout updating http_500 http_502 http_503 http_504;
      proxy_cache_lock       on;
      # 状态码为 200 则缓存 30 天
      proxy_cache_valid      200 30d;
      # 用户浏览器缓存 365 天
      add_header             Cache-Control max-age=31536000;
      # Cloudflare 缓存 365 天
      add_header             Cloudflare-CDN-Cache-Control max-age=31536000;
      # 所有其他 CDN 缓存 365 天
      add_header             CDN-Cache-Control max-age=31536000;
      add_header             X-Cache-Status $upstream_cache_status;

      proxy_pass https://your_bucket_name.s3.eu-west-2.amazonaws.com/$1;
    }
  }
}

安全问题

使用 S3 这种服务,流量通常都是按量付费的,如果你不想一觉醒来收到天价账单,就需要做一些防止刷流量的措施,防范于未然,常用的措施有以下几种:

  • 防盗链,就是只允许指定的网站访问你的文件,避免被滥用产生天价流量费,一般 CDN 都支持防盗链配置
  • 防止绕过缓存,你需要避免源站 IP 和 S3 相关配置泄露,比如 Bucket 和 Endpoint,避免攻击者绕过缓存恶意刷流量,必要时你可以在 S3 控制台配置安全策略,只允许你自己的服务器 IP 访问

S3 安全配置

默认情况下如果别人知道了你的 Bucket 名称和 Endpoint 地址是可以直接绕过 CDN 直接访问 S3 源站的,这样就有被刷流量的风险,我们可以使用 S3 的存储桶策略来增强安全性

具体配置路径是:进入 S3 控制台 -> 选择 Bucket -> 点击 权限 Tab -> 下拉找到 存储桶策略

s3-console-policy

可以将 aws:Referer 设置成一个随机生成的字符串,这样后续访问 S3 如果没有携带这个 Referer 值就会被 S3 拦截

{
  "Version": "2012-10-17",
  "Id": "Policy1633794210209",
  "Statement": [
    {
      "Sid": "只允许指定的 Referer 访问公共资源",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": ["arn:aws:s3:::your_bucket/dir1/*", "arn:aws:s3:::your_bucket/dir2/*"],
      "Condition": {
        "StringNotEquals": {
          "aws:Referer": "替换成任意字符串,你可以随机生成一个ID放在这里"
        }
      }
    }
  ]
}
{
  "Version": "2012-10-17",
  "Id": "Policy1633794210209",
  "Statement": [
    {
      "Sid": "只允许指定的 Referer 访问公共资源",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": ["arn:aws:s3:::your_bucket/dir1/*", "arn:aws:s3:::your_bucket/dir2/*"],
      "Condition": {
        "StringNotEquals": {
          "aws:Referer": "替换成任意字符串,你可以随机生成一个ID放在这里"
        }
      }
    }
  ]
}

上面的 Resource 请替换成你自己的 Bucket 名称和目录

S3 安全策略配置完成后,需要在 Nginx 反向代理配置中用 proxy_set_header 指令加上 Referer 请求头,这样只有通过你的 Nginx 服务器才能访问 S3 的公共资源

location ~ ^/resource/(.+)$ {
      ...
      
      proxy_set_header Referer "替换成S3存储桶策略里填写的 Referer 值";
      
      ...
      
      proxy_pass https://your_bucket_name.s3.eu-west-2.amazonaws.com/$1;
    }
location ~ ^/resource/(.+)$ {
      ...
      
      proxy_set_header Referer "替换成S3存储桶策略里填写的 Referer 值";
      
      ...
      
      proxy_pass https://your_bucket_name.s3.eu-west-2.amazonaws.com/$1;
    }

相关链接

Cloudflare

Cloudflare 接连推出 Cloudflare ImagesCloudflare R2 Storage这两个服务,如果你是 Cloudflare 重度用户,可以研究下这两个服务,官方说法是相比传统方案它可以消除数据在不同服务商之间流转造成的大量流量出口费用

cloudflare-r2-vs-s3

跨域

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT",
            "POST",
            "DELETE"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": []
    },
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT",
            "POST",
            "DELETE"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": []
    },
    {
        "AllowedHeaders": [],
        "AllowedMethods": [
            "GET"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": [
            "x-amz-server-side-encryption",
            "x-amz-request-id",
            "x-amz-id-2"
        ],
        "MaxAgeSeconds": 3000
    }
]
[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT",
            "POST",
            "DELETE"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": []
    },
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT",
            "POST",
            "DELETE"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": []
    },
    {
        "AllowedHeaders": [],
        "AllowedMethods": [
            "GET"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": [
            "x-amz-server-side-encryption",
            "x-amz-request-id",
            "x-amz-id-2"
        ],
        "MaxAgeSeconds": 3000
    }
]