Cloudfront with S3 origin returns 使用 OAI 限制存储桶策略时拒绝访问

Cloudfront with S3 origin returns AccessDenied when using OAI restricted bucket policy

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

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

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


更新: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

Cloudfront 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}/*"
        }
    ]
}

根据评论更新了答案。

我尝试重现 问题并将您的 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'

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

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