Score:1

CloudFront distribution can't access S3 bucket for website static assets

mx flag

The problem

I'm trying to deploy an S3 bucket hosting my static website asset and a cloudfront distribution to access it, but the distribution still return a bare xml file for an 'access denied' error:

<Error>
  <Code>AccessDenied</Code>
  <Message>Access Denied</Message>
  <RequestId>5N0Z412GZ0VGV79E</RequestId>
  <HostId>GkpQbOpKeDiaCdFJM7kDq6ouWL/dvdijNu7NseC7KeIIogNabowVrDcfjPZ0xajKpDTx3SmgoEI=</HostId>
</Error>

As this docs page states in the blue Notice alert, I've not made the bucket a website endpoint, this way I can use an OAC to restrict access to its content.

A strange thing is that checking the distribution origin from the web console I see this blue alert, but the copyable policy is the same I found in the bucket permission at the given link.

enter image description here

I have no error during deploy, so it must be a silly configuration error, but it keeps giving me headaches since a week now and I can't figure out what is wrong.

Bucket and object owners corresponds

Since mi website assets are uploaded to the bucket from a different project/pipeline i followed this guide to check if the bucket and the object owners were different but actually corresponds:

> aws s3api list-buckets --query Owner.ID
"3fdbd1e5cad4dd2bbf4c66a3dbaded6b888fdb67ff6aa6e66203a4107fe17b72"

> aws s3api list-objects --bucket my-test-bucket --prefix index.html
{
    "Contents": [
        {
            "Key": "index.html",
            "LastModified": "2023-01-20T11:05:38+00:00",
            "ETag": "\"52f2df5ddf8c35391f3f15a7614def58\"",
            "Size": 325,
            "StorageClass": "STANDARD",
            "Owner": {
                "ID": "3fdbd1e5cad4dd2bbf4c66a3dbaded6b888fdb67ff6aa6e66203a4107fe17b72"
            }
        }
    ]
}

CloudFormation template

Resources:

  BucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    DependsOn:
      - AppBucket
      - CloudFrontDistribution
    Properties:
      Bucket: !Ref AppBucket
      PolicyDocument:
        Id: MyPolicy
        Version: '2012-10-17'
        Statement:
          Sid: PolicyForCloudFrontPrivateContent
          Action: s3:GetObject
          Effect: Allow
          Principal:
            Service: cloudfront.amazonaws.com
          Condition:
            StringEquals:
              AWS:SourceArn: !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}
          Resource: !Sub arn:aws:s3:::${AppBucket}/*

  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    DependsOn:
      - AppBucket
      - DefaultCachePolicy
      - DistributionOAC
    Properties:
      DistributionConfig:
        Enabled: true
        Origins:
          - Id: AppBucket
            DomainName: !GetAtt AppBucket.DomainName
            OriginPath: /*
            S3OriginConfig: {}
            OriginAccessControlId: !Ref DistributionOAC
        DefaultRootObject: index.html
        DefaultCacheBehavior:
          ViewerProtocolPolicy: redirect-to-https
          TargetOriginId: AppBucket
          CachePolicyId: !Ref DefaultCachePolicy

  DistributionOAC:
    Type: AWS::CloudFront::OriginAccessControl
    Properties: 
      OriginAccessControlConfig: 
          Name: ExampleOAC
          OriginAccessControlOriginType: s3
          SigningBehavior: always
          SigningProtocol: sigv4

  AppBucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: 'test-spa-stack-bucket-app'
      PublicAccessBlockConfiguration:
        BlockPublicAcls : false
        BlockPublicPolicy : false
        IgnorePublicAcls : false
        RestrictPublicBuckets : false
              
  DefaultCachePolicy:
    Type: AWS::CloudFront::CachePolicy
    Properties: 
      CachePolicyConfig: 
        Name: test-cache-policy
        DefaultTTL: 10
        MaxTTL: 10
        MinTTL: 1
        ParametersInCacheKeyAndForwardedToOrigin: 
            CookiesConfig: 
              CookieBehavior: none
            EnableAcceptEncodingBrotli: true
            EnableAcceptEncodingGzip: true
            HeadersConfig: 
              HeaderBehavior: none
            QueryStringsConfig: 
              QueryStringBehavior: none
Tim P avatar
af flag
I haven't done this from a launch template yet, but from the GUI you can check that the S3 Bucket Policy (on the bucket) allows access for the CloudFront Access Origin. It should be the policy you get by "copying the policy" in the blue box. Without this CloudFront cannot access the contents of your S3 bucket. If you are using the current defaults for S3, the bucket is private and CloudFront cannot read from it, hence the 403 response you are seeing.
fudo avatar
mx flag
I just tried it, I copied the policy from the distribution origin page and copied in the bucket settings, but it still not working. I can't understand if i wrongly set some other configuration, but checking at any online docs it looks correct to me.
Tim P avatar
af flag
An S3 403 response can also be returned when the object does not actually exist. Are you sure the content you are trying to access is actually in S3 and is in the path you are using. If you specified a prefix in the Origin, that will be added to the S3 request.
fudo avatar
mx flag
Yes, i'm sure the object exists
Score:0
at flag

I had the same error. In my case I had set the default root object on the Distribution (General > Settings) to /index.html and I also set the origin path (Origins > my s3 origin) to /index.html. I only needed to set the object as index.html on the Distribution for it to start responding as expected.

This helped me discover the problem.

Score:-1
mx flag

Ok, I found a YT video that worked from cloud console, then I replicated it via CloudFormation stack template.

I think that my problem could be caused from either of these configurations:

  • bucket encription: I read somewhere (sorry I can't find the exact page) that CloudFront distribution cannot (directly) read bucket objects if encripted with anything different from SSE-S3, and since I omitted that configuration it probably did fallback on a non-compatible configuration
  • objects ownership: here too I forgot to add the cuket owner enforced policy
  • distribution accepted http version: here I accepted v.2

working template

Resources:
  BucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    DependsOn:
      - AppBucket
      - CloudFrontDistribution
    Properties:
      Bucket: !Ref AppBucket
      PolicyDocument:
        Id: PolicyForCloudFrontPrivateContent
        Version: '2008-10-17'
        Statement:
          Sid: AllowCloudFrontServicePrincipal
          Action: s3:GetObject
          Effect: Allow
          Principal:
            Service: cloudfront.amazonaws.com
          Condition:
            StringEquals:
              AWS:SourceArn: !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}
          Resource: !Sub arn:aws:s3:::${AppBucket}/*

  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    DependsOn:
      - AppBucket
      - DistributionOAC
      - LogsBucket
    Properties:
      DistributionConfig:
        Enabled: true
        HttpVersion: http2
        Origins:
          - Id: AppBucket
            DomainName: !GetAtt AppBucket.DomainName
            S3OriginConfig: {}
            OriginAccessControlId: !Ref DistributionOAC
        DefaultRootObject: index.html
        DefaultCacheBehavior:
          Compress: true
          ViewerProtocolPolicy: allow-all
          TargetOriginId: AppBucket
          CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6   # default CachingOptimized
        Logging:
          Bucket: !GetAtt LogsBucket.DomainName
          IncludeCookies: true
          Prefix: distribution/

  AppBucket:
    Type: 'AWS::S3::Bucket'
    DependsOn:
      - LogsBucket
    Properties:
      BucketName: 'test-spa-stack-bucket-app'
      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerEnforced
      PublicAccessBlockConfiguration:
        BlockPublicAcls : true
        BlockPublicPolicy : true
        IgnorePublicAcls : true
        RestrictPublicBuckets : true
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      LoggingConfiguration:
        DestinationBucketName: !Ref LogsBucket
        LogFilePrefix: app/

  DistributionOAC:
    Type: AWS::CloudFront::OriginAccessControl
    Properties: 
      OriginAccessControlConfig: 
          Name: test-spa-distribution-oac
          OriginAccessControlOriginType: s3
          SigningBehavior: always
          SigningProtocol: sigv4


  LogsBucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: 'test-spa-stack-bucket-logs'
      PublicAccessBlockConfiguration:
        BlockPublicAcls : true
        BlockPublicPolicy : true
        IgnorePublicAcls : true
        RestrictPublicBuckets : true
      AccessControl: LogDeliveryWrite
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
        - ServerSideEncryptionByDefault:
            SSEAlgorithm: 'AES256'
I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.