如何将 S3 网站的访问限制为 Cloudfront?

Posted

技术标签:

【中文标题】如何将 S3 网站的访问限制为 Cloudfront?【英文标题】:How can I restrict access to an S3 website to Cloudfront? 【发布时间】:2020-07-10 10:45:29 【问题描述】:

我想将 Cloudfront CDN 放在静态网站的 S3 website bucket 前面,并将存储桶的读取访问权限限制为 Cloudfront 分发。很常见,documented by AWS 和其他来源。但由于某种原因,我无法让它工作。

我不是第一个偶然发现这一点的人。 (1、2、3)。我已经尝试了那里发布的解决方案,但同样没有运气。

我的设置,作为 Cloudformation 模板,如下所示:

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  s3BucketName:
    Type: String
  domainName:
    Type: String
  certificateArn:
    Type: String
  bucketAuthHeader:
    Type: String

Resources:
  cloudfrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
         Enabled: true
         PriceClass: PriceClass_100
         Origins:
           - Id: !Sub "ID-$s3BucketName"
             DomainName: !Sub "$s3BucketName.s3-website.eu-central-1.amazonaws.com"
             CustomOriginConfig:
               OriginProtocolPolicy : http-only
             OriginCustomHeaders:
               - HeaderName: User-Agent
                 HeaderValue: !Ref bucketAuthHeader
         DefaultCacheBehavior:
           AllowedMethods:
             - GET
             - HEAD
             - OPTIONS
           CachedMethods:
             - GET
             - HEAD
             - OPTIONS
           DefaultTTL: 600
           ForwardedValues:
               QueryString: false
           TargetOriginId: !Sub "ID-$s3BucketName"
           ViewerProtocolPolicy: redirect-to-https

  s3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref s3BucketName
      AccessControl: Private
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      WebsiteConfiguration:
        IndexDocument: index.html
        ErrorDocument: _errors/404/index.html
    DeletionPolicy: Delete

  s3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref s3BucketName
      PolicyDocument:
        Version: 2012-10-17
        Id: "Cloudfront Bucket Access"
        Statement:
          - Sid: "Cloudfront Bucket Access via Referer"
            Effect: Allow
            Principal: "*"
            Action: "s3:GetObject"
            Resource:  !Sub "arn:aws:s3:::$s3BucketName/*"
            Condition:
              StringEquals:
                aws:UserAgent:
                  - !Ref bucketAuthHeader

但是,在应用此功能时,我无法通过 Cloudfront 访问文件,总是得到 403。我还尝试调整 PublicAccessBlockConfigurationAccessControl 中的值,并尝试使用 aws s3 sync … --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers 上传存储桶内容。

但我总是以 要么 公共 S3 内容结束,要么 内容也无法通过 Cloudfront 获得。

有人知道我还能尝试什么吗?

【问题讨论】:

请试试这个作为测试:aws.amazon.com/blogs/networking-and-content-delivery/… 从 AWS 内部对 S3 存储桶进行基于角色的访问不是一个好的做法。最好对静态网站本身进行一些其他限制。 您是否考虑过使用Origin Access Identity 仅允许从 CloudFront 访问您的存储桶? @Nikhil 我们在这里讨论的是 S3 网站 存储桶,因此该链接与此脚本无关。 AWS 自己也明确推荐基于角色的方法,请参阅我的问题中的链接。 @Marcin OAI 不适用于 S3 网站 存储桶。 【参考方案1】:

我是使用 AWS 云开发工具包 (CDK) 为这样的 SPA 完成的:

import * as path from "path";
import * as s3 from "@aws-cdk/aws-s3";
import * as cloudfront from "@aws-cdk/aws-cloudfront";
import * as cloudfrontOrigins from "@aws-cdk/aws-cloudfront-origins";
import * as lambda from "@aws-cdk/aws-lambda"

// Private website bucket
const websiteBucket = new s3.Bucket(this, "WebsiteBucket", 
  encryption: s3.BucketEncryption.S3_MANAGED,
  blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
  publicReadAccess: false,
);

// Access identity that we can attach to the bucket to give it access
const websiteOriginAccessIdentity = new cloudfront.OriginAccessIdentity(
  this,
  "OriginAccessIdentity"
);

// Grant the access identity access to this bucket
websiteBucket.grantRead(websiteOriginAccessIdentity)

// Use this bucket and origin access identity to Cloudfront
const websiteBucketOrigin = new cloudfrontOrigins.S3Origin(
  props.websiteBucket,
  
    originPath: "/",
    originAccessIdentity: websiteOriginAccessIdentity,
  
);

// Add a Lambda@Edge to redirect all non-asset requests to `/index.html`. 
// Just like a rewrite rule with Amplify.
const websiteRedirectFunction = new lambda.Function(
  this,
  "RedirectFunction",
  
    code: lambda.Code.fromAsset(path.resolve(__dirname, "../redirect"), 
      bundling: 
        command: [
          "bash",
          "-c",
          "npm install && npm run build && cp -rT /asset-input/dist/ /asset-output/",
        ],
        image: lambda.Runtime.NODEJS_12_X.bundlingDockerImage,
        user: "root",
      ,
    ),
    handler: "index.redirect",
    runtime: lambda.Runtime.NODEJS_12_X,
  
);

// Create Cloudfront distribution with the origin
const websiteCdn = new cloudfront.Distribution(this, "WebsiteCdn", 
  defaultBehavior: 
    origin: websiteBucketOrigin,
    edgeLambdas: [
      
        eventType: cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST,
        functionVersion: websiteRedirectFunction.currentVersion,
      ,
     ],
  ,
);
// ../redirect/src/index.ts

import * as lambdaTypes from "aws-lambda";

export const redirect = (
  event: lambdaTypes.CloudFrontRequestEvent,
  _context: lambdaTypes.Context,
  callback: lambdaTypes.CloudFrontRequestCallback
): void => 
  const request = event.Records[0].cf.request;

  console.info( request );

  // Redirects all files to index.html except for the specific file extensions for assets
  const isPageRequest = /^[^.]+$|\.(?!(css|gif|ico|jpg|js|png|txt|svg|woff|ttf|map|json)$)([^.]+$)/;

  console.log( isPageRequest );

  if (isPageRequest) 
    request.uri = "/index.html";
  

  console.log( updatedRequest: request );

  callback(null, request);
;

此示例假设您将网站部署到我们在此处创建的 websiteBucket 存储桶。为简洁起见,有几种不同的方法可以将您的网站部署到 S3。例如,in a CodePipeline。

注意:此示例使用 TypeScript,并假设您已将其设置为构建到 ./dist/ 中。下面以简单的 TSConfig 为例:


  "compilerOptions": 
    "target": "es5",
    "module": "commonjs",
    "outDir": "dist",
    "rootDir": "src"
  ,
  "include": ["src/**/*"]


// package.json

"scripts": 
  "build": "tsc -b"

【讨论】:

非常感谢您的分享!看到您使用带有 OAI 的网站存储桶,我感到有些惊讶,因为正式而言,这是不可能的。但也许有一些隐藏的功能只有 SDK 才支持。但话说回来,您使用的是 Lambda,这有点言过其实,因为我认为您实际上是在使用该 Lambda 来进行访问,而不是通过 Cloudfront。我必须在接下来几天的某个时间分析您的设置,并让您知道它是否可以解决问题。 它实际上不是一个网站存储桶 - 这只是我提供的 ID/名称以供参考。对困惑感到抱歉!你可以看到我有它阻止公共访问,这不是网站存储桶会做的。【参考方案2】:

使用 AWS CDK 时,它实际上比 @SeanWLawrence 的建议更简单。当您使用未配置为网站的 S3 存储桶创建 CloudFront 分配时,AWS 将自动为您创建一个对象访问身份并授予该身份对存储桶的读取权限。此功能反映了 AWS 控制台中发生的情况。

此处记录了此行为 - https://docs.aws.amazon.com/cdk/api/latest/docs/aws-cloudfront-readme.html#from-an-s3-bucket

【讨论】:

您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center。 感谢您的帖子。但是,就像已经与其他人讨论过的那样,这是关于 S3 网站 存储桶的。所以你的回答没有抓住重点。请阅读原始问题的 cmets 部分。

以上是关于如何将 S3 网站的访问限制为 Cloudfront?的主要内容,如果未能解决你的问题,请参考以下文章

仅将 S3 对象 URL 限制为网站上的登录用户

在不将 ip 列入白名单的情况下限制对 S3 托管网站的访问

限制对云端分发背后的 s3 静态网站的访问

如何将 EBS 上 Rails 应用程序上的入站安全组限制为仅来自我的应用程序(在 S3 上)并防止其他服务器访问我的 API?

除非 url 被签名,否则限制对 S3 对象的访问

如何使用 CORS 限制 AWS S3 访问?