基于引用来限制对AWS S3存储桶的访问

esd*_*ker 1 policy amazon-s3 amazon-web-services amazon-cloudfront

我正在尝试限制对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/*"
                ]
            }
        }
    }
 ]
}
Run Code Online (Sandbox Code Playgroud)

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

我不知道为什么我得到403 Forbidden错误.

我这样做基本上是因为我不希望其他网站调用该对象.

任何帮助将不胜感激.

Mic*_*bot 5

正如高速缓存行为所必需的,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标头.

因此,现在,对于每个对象,除非传入请求的Referer标头与已缓存的请求中的标头完全匹配,否则CloudFront将不会提供缓存响应...否则请求必须转到S3.而且,有关referer标题的内容,它是引用页面,而不是引用网站,因此来自授权站点的每个页面都将在CloudFront中拥有自己的这些资产的缓存副本.

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

这是否适合您是没有正确答案的,因为它非常具体到您使用CloudFront和S3的方式.

但是,这是替代方案:

您可以删除Referer从报头的白名单头转发到S3和撤消潜在的缓存命中造成负面影响,通过配置CloudFront的火一个LAMBDA @ Edge查看请求触发,因为它涉及的前门,将检查每一个请求,阻止那些不是来自您想要允许的引用页面的请求.

在匹配特定缓存行为之后但在检查实际缓存之前触发查看器请求触发器,并且大多数传入头仍然完好无损.您可以允许请求继续,可选择进行修改,或者您可以生成响应并取消其余的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);
};
Run Code Online (Sandbox Code Playgroud)