【问题标题】:Cloudfront with S3 origin returns AccessDenied when using OAI restricted bucket policy使用 OAI 受限存储桶策略时,具有 S3 源的 Cloudfront 返回 AccessDenied
【发布时间】:2021-03-18 06:07:42
【问题描述】:

我正在尝试将静态网站部署到 S3,并通过 Cloudfront 提供服务。我正在使用无服务器来生成 Cloudformation 资源。创建资源后,我的构建过程(在 CodeBuild 和 CodePipeline 中)运行 npm install、npm run build 并将构建目录传输到 s3 以提供内容。对于上下文,我使用的是 react,特别是 create-react-app。 部署后,所有资源都按预期创建,但 Cloudfront 端点返回以下“拒绝访问”异常,这是我在 S3 中熟悉的:

<Error>
  <Code>AccessDenied</Code>
  <Message>Access Denied</Message>
  <RequestId><requestId></RequestId>
  <HostId><hostId> 
  </HostId>
</Error>

这让我相信它是 S3 存储桶策略,我已将其设置为具有附加到 CDN 的 Origin Access Identity 的 Principal 值。所以我有点不确定这里缺少什么来让它工作。任何帮助表示赞赏。以下是我的资料。


更新:index.html 可访问,但静态文件不可访问。我在下面添加了桶策略,下面是桶的文件结构:


asset-manifest.json
manifest.json
index.html
static/

从无服务器部署生成的 Cloudformation 模板:

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "The AWS CloudFormation template for this Serverless application",
    "Website": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "WebsiteConfiguration": {
          "ErrorDocument": "index.html",
          "IndexDocument": "index.html"
        }
      }
    },
    "CloudFrontOriginAccessIdentity": {
      "Type": "AWS::CloudFront::CloudFrontOriginAccessIdentity",
      "Properties": {
        "CloudFrontOriginAccessIdentityConfig": {
          "Comment": "Access Identity for cdn"
        }
      }
    },
    "ReadPolicy": {
      "Type": "AWS::S3::BucketPolicy",
      "Properties": {
        "Bucket": {
          "Ref": "Website"
        },
        "PolicyDocument": {
          "Statement": [
            {
              "Action": "s3:GetObject",
              "Effect": "Allow",
              "Resource": {
                "Fn::Join": [
                  "",
                  [
                    "arn:aws:s3:::",
                    {
                      "Ref": "Website"
                    },
                    "/*"
                  ]
                ]
              },
              "Principal": {
                "CanonicalUser": {
                  "Fn::GetAtt": [
                    "CloudFrontOriginAccessIdentity",
                    "S3CanonicalUserId"
                  ]
                }
              }
            }
          ]
        }
      }
    },
    "Distribution": {
      "Type": "AWS::CloudFront::Distribution",
      "Properties": {
        "DistributionConfig": {
          "Origins": [
            {
              "DomainName": {
                "Fn::GetAtt": [
                  "Website",
                  "DomainName"
                ]
              },
              "Id": {
                "Ref": "Website"
              },
              "S3OriginConfig": {
                "OriginAccessIdentity": {
                  "Fn::Join": [
                    "",
                    [
                      "origin-access-identity/cloudfront/",
                      {
                        "Ref": "CloudFrontOriginAccessIdentity"
                      }
                    ]
                  ]
                }
              }
            }
          ],
          "Enabled": true,
          "HttpVersion": "http2",
          "DefaultRootObject": "index.html",
          "CustomErrorResponses": [
            {
              "ErrorCode": 404,
              "ResponseCode": 200,
              "ResponsePagePath": "/index.html"
            }
          ],
          "DefaultCacheBehavior": {
            "AllowedMethods": [
              "DELETE",
              "GET",
              "HEAD",
              "OPTIONS",
              "PATCH",
              "POST",
              "PUT"
            ],
            "DefaultTTL": 3600,
            "ForwardedValues": {
              "QueryString": true,
              "Cookies": {
                "Forward": "none"
              }
            },
            "TargetOriginId": {
              "Ref": "Website"
            },
            "ViewerProtocolPolicy": "redirect-to-https"
          },
          "ViewerCertificate": {
            "CloudFrontDefaultCertificate": "true"
          }
        }
      }
    },
  },
  "Outputs": {
    "ServerlessDeploymentBucketName": {
      "Value": {
        "Ref": "ServerlessDeploymentBucket"
      }
    },
    "WebsiteUrl": {
      "Value": {
        "Fn::GetAtt": [
          "Website",
          "WebsiteURL"
        ]
      }
    },
    "WebSiteBucket": {
      "Value": {
        "Ref": "Website"
      }
    }
  }
}

已生成 S3 存储桶策略:

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity {ID for the OAI}"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::{BUCKETNAME}/*"
        }
    ]
}

更新

S3 存储桶 YAML 文件:

Resources:
  Website:
    Type: AWS::S3::Bucket
    Properties:
      WebsiteConfiguration:
        ErrorDocument: index.html
        IndexDocument: index.html
Outputs:
  WebsiteUrl:
    Value: !GetAtt Website.WebsiteURL
  WebSiteBucket:
    Value: !Ref Website

云端 YAML 文件:

Resources:
  CloudFrontOriginAccessIdentity:
    Type: 'AWS::CloudFront::CloudFrontOriginAccessIdentity'
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: Access Identity for cdn
  ReadPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref Website
      PolicyDocument:
        Statement:
        - Action: 's3:GetObject'
          Effect: Allow
          Resource: 
            !Join [ '', [ 'arn:aws:s3:::', !Ref Website, '/*' ] ]
          Principal:
            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId
  Distribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Origins:
          -
            # Use the Website as the origin
            DomainName: !GetAtt 'Website.DomainName'
            Id: !Ref Website
            S3OriginConfig:
              OriginAccessIdentity: !Join [ '', [ 'origin-access-identity/cloudfront/', !Ref CloudFrontOriginAccessIdentity] ]
        Enabled: true
        HttpVersion: http2
        DefaultRootObject: index.html
        # Since React takes care of our routing, we need to make sure every path is served via index.html
        # Configure the CDN cache
        CustomErrorResponses:
          - ErrorCode: 404
            ResponseCode: 200
            ResponsePagePath: /index.html
        DefaultCacheBehavior:
          AllowedMethods:
            - DELETE
            - GET
            - HEAD
            - OPTIONS
            - PATCH
            - POST
            - PUT
          DefaultTTL: 3600
          ForwardedValues:
            QueryString: true
            Cookies:
              Forward: none
          # The origin id defined above
          TargetOriginId: !Ref Website
          ViewerProtocolPolicy: "redirect-to-https" # we want to force https
        # The certificate to use when using https
        ViewerCertificate:
          CloudFrontDefaultCertificate: 'true'

存储桶策略:

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity {Id}"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::{bucket-name}/*"
        }
    ]
}

【问题讨论】:

    标签: amazon-s3 amazon-cloudformation amazon-cloudfront


    【解决方案1】:

    根据上面的@Marcins 回答,确认 index.html 的权限一切正常,但 403 仍然存在。经过长时间的调查,我在这里找到了this 帖子,其中详细说明了需要为 Cloudfront 分发添加一个特定的错误响应 403,类似于为 404 显示的错误响应。所以我的新 CustomErrorResponse 像这样:

            CustomErrorResponses:
              - ErrorCode: 404
                ResponseCode: 200
                ResponsePagePath: /index.html
              - ErrorCode: 403
                ResponseCode: 200
                ResponsePagePath: /index.html
    

    【讨论】:

      【解决方案2】:

      根据 cmets 更新答案。

      我尝试复制该问题并将您的 YAML 模板部署到我的沙盒帐户中。我发现它们是正确的。我上传到创建的存储桶的示例index.html 按预期工作,并且可以从使用以下形式的 url 创建的 CF 发行版访问:

      https://d1237123arhp6.cloudfront.net/
      

      未发现 CF 发行版、存储桶策略或 OAI 存在问题。 模板可以正常工作,无需修改。

      使用的模板:

      Resources:
      
        Website:
          Type: AWS::S3::Bucket
          Properties:
            WebsiteConfiguration:
              ErrorDocument: index.html
              IndexDocument: index.html
              
        CloudFrontOriginAccessIdentity:
          Type: 'AWS::CloudFront::CloudFrontOriginAccessIdentity'
          Properties:
            CloudFrontOriginAccessIdentityConfig:
              Comment: Access Identity for cdn
      
        ReadPolicy:
          Type: 'AWS::S3::BucketPolicy'
          Properties:
            Bucket: !Ref Website
            PolicyDocument:
              Statement:
              - Action: 's3:GetObject'
                Effect: Allow
                Resource: 
                  !Join [ '', [ 'arn:aws:s3:::', !Ref Website, '/*' ] ]
                Principal:
                  CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId
      
        Distribution:
          Type: AWS::CloudFront::Distribution
          Properties:
            DistributionConfig:
              Origins:
                -
                  # Use the Website as the origin
                  DomainName: !GetAtt 'Website.DomainName'
                  Id: !Ref Website
                  S3OriginConfig:
                    OriginAccessIdentity: !Join [ '', [ 'origin-access-identity/cloudfront/', !Ref CloudFrontOriginAccessIdentity] ]
              Enabled: true
              HttpVersion: http2
              DefaultRootObject: index.html
              # Since React takes care of our routing, we need to make sure every path is served via index.html
              # Configure the CDN cache
              CustomErrorResponses:
                - ErrorCode: 404
                  ResponseCode: 200
                  ResponsePagePath: /index.html
              DefaultCacheBehavior:
                AllowedMethods:
                  - DELETE
                  - GET
                  - HEAD
                  - OPTIONS
                  - PATCH
                  - POST
                  - PUT
                DefaultTTL: 3600
                ForwardedValues:
                  QueryString: true
                  Cookies:
                    Forward: none
                # The origin id defined above
                TargetOriginId: !Ref Website
                ViewerProtocolPolicy: "redirect-to-https" # we want to force https
              # The certificate to use when using https
              ViewerCertificate:
                CloudFrontDefaultCertificate: 'true'
      
      

      【讨论】:

      • 您能否提供有关此更改在我的示例中的外观的更新?我尝试了几种方法,但都没有奏效。我也看过这个例子,它遵循我的方法github.com/aws-samples/amazon-cloudfront-secure-static-site/…
      • @johnny_mac 我试过了,但是你的模板是无效的 json。它缺少很多部分,因此我不能只是复制并粘贴它来尝试复制问题。
      • 抱歉,我包含了从无服务器部署中生成的模板,认为这样会更有帮助。我现在已经为存储桶和分发添加了原始 cfn YAML 文件。希望对您有所帮助,感谢您告诉我
      • @johnny_mac 我将您的模板部署在我的沙箱 acc 中。这一切都有效。 Bucket、CF 发行版和我使用的示例 index.html 工作正常。您确定您使用https://d3e0234kuarhp6.cloudfront.net/ 形式的域来访问您的网站吗?常规 S3 存储桶端点将不起作用。顺便说一句,我必须更新答案,这一切都适用于我的评估。
      • 现在我很困惑。所以你没有改变任何东西,它对你有用吗?我使用了正确的域格式
      猜你喜欢
      • 2019-01-04
      • 2018-04-24
      • 1970-01-01
      • 2021-02-28
      • 2016-12-11
      • 1970-01-01
      • 2017-09-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多