【问题标题】:Restricting access to AWS S3 bucket based on referer基于引用者限制对 AWS S3 存储桶的访问
【发布时间】:2018-02-11 03:23:52
【问题描述】:

我正在尝试限制对 S3 存储桶的访问,并且仅允许基于引用者的列表中的某些域。

桶策略基本上是:

{
"Version": "2012-10-17",
"Id": "http referer domain lock",
"Statement": [
    {
        "Sid": "Allow get requests originating from specific domains",
        "Effect": "Allow",
        "Principal": "*",
        "Action": "s3:GetObject",
        "Resource": "arn:aws:s3:::example.com/*",
        "Condition": {
            "StringLike": {
                "aws:Referer":  [ 
                    "*othersite1.com/*",
                    "*othersite2.com/*",
                    "*othersite3.com/*"
                ]
            }
        }
    }
 ]
}

此 othersite1、2 和 3 调用我存储在域 example.com 下的 s3 存储桶中的对象。 我还有一个附加到存储桶的云端分发。我在字符串条件之前和之后使用 * 通配符。引用者可以是 othersite1.com/folder/another-folder/page.html。引用者也可以使用 http 或 https。

我不知道为什么会收到 403 Forbidden 错误。

我这样做主要是因为我不希望其他网站调用该对象。

任何帮助将不胜感激。

【问题讨论】:

  • 我不确定是否支持前导通配符。我会尝试删除它们并再次测试。
  • 在 CloudFront 缓存行为中,您是否为白名单配置了 Referer 标头以便将其转发到 S3?存储桶策略将无法使用任何未转发的标头,这将是您的第一个问题......尽管问题比这更复杂,因为如果您转发Referer,您的缓存命中率不会那么高。
  • @ÇağatayGürtürk 感谢您的评论,我确实尝试删除,甚至写下我在 chrome 检查器中看到的确切引用,但仍然无法正常工作
  • @Michael-sqlbot 我没有为白名单配置“referer”标头。我去做!谢谢!希望这会奏效
  • 还要注意,在 chaging 之后,您将需要 set the Error Caching TTL to 0 for 403 errrors 然后对 /* (所有对象)进行无效化,然后让一切都解决几分钟,然后再次测试,或者您可能会收到缓存的 403 响应,如果您确实解决了问题但测试仍然失败了几分钟,这可能会令人沮丧。 Age: 响应标头告诉您特定响应在缓存中已存在多长时间(以秒为单位),如果响应未缓存,则将不存在。

标签: amazon-web-services amazon-s3 amazon-cloudfront policy


【解决方案1】:

对于正确的缓存行为而言,CloudFront 会在将请求转发到源服务器之前从请求中剥离几乎所有的请求标头。

Referer | CloudFront 删除标头。

http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/RequestAndResponseBehaviorCustomOrigin.html#request-custom-headers-behavior

因此,如果您的存储桶试图阻止基于引用页面的请求(有时会这样做以防止盗链),则 S3 将无法(默认情况下)看到 Referer 标头,因为 CloudFront 没有转发吧。

而且,这很好地说明了为什么 CloudFront 不转发它。如果 CloudFront 转发标头然后盲目缓存结果,则存储桶策略是否具有预期效果将取决于第一个请求是来自预期站点之一,还是来自其他地方 - 其他请求者将获得缓存响应,这可能是错误响应。

(tl;dr) 将 Referer 标头列入白名单以转发到源(在 CloudFront 缓存行为设置中)可解决此问题。

但是,有一点问题。

现在您正在将 Referer 标头转发到 S3,您已经扩展了 缓存键 - CloudFront 缓存响应的内容列表 - 以包含 Referer 标头.

因此,现在,对于每个对象,CloudFront 将不会提供来自缓存的响应,除非传入请求的 Referer 标头与来自已缓存请求的 完全匹配...否则该请求已去S3。而且,关于referer 标头的事情,它是引用page,而不是引用site,所以来自授权站点的每个page 都会有它的在 CloudFront 中拥有这些资产的缓存副本。

这本身不是问题。这些额外的对象副本是免费的,这就是 CloudFront 的设计工作方式……问题是,它降低了给定对象在给定边缘缓存中的可能性,因为每个对象的引用次数必然更少.如果您有大量流量,这将变得不那么重要 - 微不足道 - 如果您的流量较小,则更重要。更少的缓存命中意味着更慢的页面加载和更多的请求到 S3。

对于这是否适合您没有正确答案,因为它非常具体地针对您如何使用 CloudFront 和 S3。

但是,这是另一种选择:

您可以从标头白名单中删除 Referer 标头以转发到 S3 并撤消对缓存命中产生负面影响的可能性,方法是将 CloudFront 配置为触发 Lambda@Edge Viewer Request trigger,该 Lambda@Edge Viewer Request trigger 将检查每个请求,因为它在前面门,并阻止那些不是来自您希望允许的引用页面的请求。

查看器请求触发器在特定缓存行为匹配之后触发,但在实际缓存被检查之前,并且大多数传入的标头仍然完好无损。您可以允许请求继续进行,可选择进行修改,或者您可以生成响应并取消 CloudFront 处理的其余部分。这就是我在下面说明的内容——如果Referer 标头的主机部分不在可接受值的数组中,我们会生成一个 403 响应;否则,请求继续,缓存被检查,并且只在需要时查询源。

触发此触发器会为每个请求增加少量开销,但与降低缓存命中率相比,该开销可能会分摊到更理想的情况下。因此,以下不是“更好”的解决方案——只是替代解决方案。

这是一个用 Node.js 6.10 编写的 Lambda 函数。

'use strict';

const allow_empty_referer = true;

const allowed_referers = ['example.com', 'example.net'];

exports.handler = (event, context, callback) => {

    // extract the original request, and the headers from the request
    const request = event.Records[0].cf.request;
    const headers = request.headers;

    // find the first referer header if present, and extract its value;
    // then take http[s]://<--this-part-->/only/not/the/path.
    // the || [])[0]) || {'value' : ''} construct is optimizing away some if(){ if(){ if(){ } } } validation

    const referer_host = (((headers.referer || [])[0]) || {'value' : ''})['value'].split('/')[2];

    // compare to the list, and immediately allow the request to proceed through CloudFront 
    // if we find a match

    for(var i = allowed_referers.length; i--;)
    {
        if(referer_host == allowed_referers[i])
        {
            return callback(null,request);
        }
    }

    // also test for no referer header value if we allowed that, above
    // usually, you do want to allow this

    if(allow_empty_referer && referer_host === "")
    {
        return callback(null,request);
    }

    // we did not find a reason to allow the request, so we deny it.

    const response = {
        status: '403',
        statusDescription: 'Forbidden',
        headers: {
            'vary':          [{ key: 'Vary',          value: '*' }], // hint, but not too obvious
            'cache-control': [{ key: 'Cache-Control', value: 'max-age=60' }], // browser-caching timer
            'content-type':  [{ key: 'Content-Type',  value: 'text/plain' }], // can't return binary (yet?)
        },
        body: 'Access Denied\n',
    };

    callback(null, response);
};

【讨论】:

  • 谢谢@Micheal,这很有帮助。正如您所说,解决方案取决于用例。我们确实有很多流量,但我们也有很多不同的推荐人。所以现在我们必须考虑成本和性能之间的权衡。即使对于高流量,Lambda 价格也是一个不错的选择,但它确实增加了一些开销。另一方面,如果目标是不断添加域并使策略变得复杂,则存储桶策略的可扩展性不高。这确实很有趣。再次感谢!
猜你喜欢
  • 2019-04-13
  • 1970-01-01
  • 2014-10-21
  • 2021-03-09
  • 1970-01-01
  • 1970-01-01
  • 2019-04-23
  • 1970-01-01
  • 2022-12-09
相关资源
最近更新 更多