Evolution of a S3 Bucket in CloudFormation

May 20, 2020

Evolution of a S3 Bucket in CloudFormation

AWS CloudFormation is a foundational service from AWS that allows the management of AWS resources via JSON or YAML templates.

CloudFormation has changed a lot over the years. As new features and services become available, the way to define those resources in CloudFormation is expanded or sometimes changed.

In this blog, we’ll walk through setting up a S3 bucket with CloudFormation. We’ll start with a simple resource and work up to a more real-life example with support for versioning, encryption, and preventing any objects from becoming public.

Let’s get started with just about the simplest CloudFormation template we can write:

---
AWSTemplateFormatVersion: 2010-09-09

Resources:
  MyBucket:
    Type: AWS::S3::Bucket

Save this into a file (in my case I’ll use s3-cfn-example.yaml) and then you can deploy it with the [aws cli](). I’m also going to use AWS Vault for managing my AWS profiles / credentials.

$ aws-vault exec oe-sandbox -- aws cloudformation deploy \
    --no-fail-on-empty-changeset \
    --template-file s3-cfn-example.yaml \
    --stack-name s3-cfn-example-stack

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - s3-cfn-example-stack
$

We can run this deploy command repeatedly (it is idempotent).

So after this initial deployment we can look in the console and we will see a CloudFormation stack with one resource - our bucket!

Simple bucket

Note that CloudFormation generated a unique name for our bucket since we didn’t specify the BucketName property. In general you shouldn’t explicitly specify a bucket name, because if you do then CloudFormation can’t perform updates that require replacement of this resource.

Let’s look at the basic features of our bucket:

Simple bucket

Hmm - let’s enable versioning and default encryption - update your CloudFormation template to look like this:

---
AWSTemplateFormatVersion: 2010-09-09

Resources:
  MyBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      VersioningConfiguration:
        Status: Enabled

After we re-run our deploy command, we can check the properties of our bucket again and see that we now have versioning and default encryption enabled.

Simple bucket

Nice!

Let’s take a quick look at permissions. If we go back to the list of buckets, we see that our new bucket has been listed as ‘Objects can be public’…so while they aren’t public by default, they have the option of being public.

Simple bucket

Let’s lock this down. We can use the relatively new PublicAccessBlockConfiguration property for this, as well as explicitly setting the AccessControl property to Private.

Update your template to match the below:

---
AWSTemplateFormatVersion: 2010-09-09

Resources:
  MyBucket:
    Type: AWS::S3::Bucket
    Properties:
      AccessControl: Private
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Enabled

After we run our deploy command again, we see that our bucket access now says ‘Not Public’:

Simple bucket

In this post, we started with the most basic representation of a CloudFormation resource possible (in our example a S3 bucket) and then progressively built out more features and functionality using CloudFormation. There are tons more we could do with this bucket, but I hope this simple example shows how you can build upon your resources with CloudFormation to iteratively create complex infrastructures.

Good luck & happy coding!

Dylan