Search code examples
amazon-web-servicesamazon-s3aws-cloudformationamazon-cloudfront

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


This question follow a previous one, i decided to close that since the code changed a bit and the question did not reflect my actual situation any more.

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 csv file for an 'access denied' error:

enter image description here

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

Solution

  • 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'