amazon-web-servicesamazon-s3aws-cloudformationaws-sam

Creating a public S3 bucket with AWS SAM / Cloud Formation


I am having issues creating public buckets as AWS SAM recently deprecated the use of:

AccessControl: PublicRead

on AWS S3 Bucket resources.

Example template:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  public-bucket-issues

################################
# Parameters
################################

Parameters:
  BucketName:
    Description: S3 bucket name
    Type: String
    Default: 'my-test-public-bucket-abc123'

################################
# Resources
################################

Resources:

  PublicBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref BucketName
      # Set up for public website hosting
      AccessControl: PublicRead
      WebsiteConfiguration:
        IndexDocument: index.html
        ErrorDocument: index.html
      CorsConfiguration:
        CorsRules:
          - AllowedOrigins:
              - "*"
              - !Ref BucketName
            AllowedMethods:
              - GET
              - POST
              - PUT
              - DELETE
              - HEAD
            AllowedHeaders:
              - "*"
            MaxAge: 3000
      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerPreferred

This configuration works when you deploy it, but I get the following linting errors (breaking all CICD):

W3045 Consider using AWS::S3::BucketPolicy instead of AccessControl
/path/to/template.yaml:27:7

Error: Linting failed. At least one linting rule was matched to the provided template.

As you can see from the Cloud Formation documentation, this is indeed deprecated: ACL docs

The SAM Linter is so kind to give me a hint that I should use a bucket policy! So I add the bucket policy to my bucket as follows:


  PublicBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref PublicBucket
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: PublicReadGetObject
            Effect: Allow
            Principal: "*"
            Action: "s3:GetObject"
            Resource: !Sub "arn:aws:s3:::${BucketName}/*"

This gives me a permissions error when I deploy when it tries to create the policy (even through I am an administrator on the account with no explicit DENYs on any user policies):

CREATE_FAILED                                      AWS::S3::BucketPolicy                              PublicBucketPolicy                              Resource handler returned message: "Access       
                                                                                                                                                         Denied (Service: S3, Status Code: 403, Request... HandlerErrorCode: AccessDenied)  

So, CloudFormation has deprecated the AccessControl attribute on the S3 bucket, but not given us clear instructions on how to properly setup a public bucket. They point to a guide that talks about how ACLs work, but not how to enable them on a bucket via CloudFormation. Any help on how I can set this up would be very helpful. I would like to be able to setup public website hosting via cloudfront but am having issues setting up a policy as directed!


Solution

  • You need to specify the Public Access configuration of your bucket (AWS doc). I had the same issue on terraform until I did that.

    According to the doc, it should look like this:

    Resources:
      Bucket:
        Type: AWS::S3::Bucket
        Properties:
          PublicAccessBlockConfiguration:
            BlockPublicAcls       : true
            BlockPublicPolicy     : false # Will allow the creation of the policy
            IgnorePublicAcls      : true
            RestrictPublicBuckets : false # Will allow non-AWS entities to read what is inside the bucket
    

    Also, here the terraform version of this for reference:

    resource "aws_s3_bucket_public_access_block" "public_image_bucket_access" {
      bucket = aws_s3_bucket.public_image_bucket.id
    
      block_public_acls  = true
      ignore_public_acls = true
    }
    

    Hope this helps