Danny Brown

A Blog on Code and Occasionally Other Things

Securing Your S3 Bucket Using the Serverless Framework

Danny BrownMay 14, 2022

I was recently developing a project with an S3 bucket. One of the steps of the development process was to add a security layer to the S3 bucket, namely:

  1. Add server-side encryption to the bucket, ensuring that data is encrypted at rest.
  2. Enforce SSL on all interactions with the bucket, ensuring that data is encrypted in transit.

Each of these tasks takes a different approach, and both can be accomplished in the serverless.yml file. You can add these security features in your Serverless project too.

1. Adding SSE to the Bucket

First of all, I ran into this issue so I’m going to highlight this to save you the trouble: you can’t change a pre-existing bucket from unencrypted to encrypted. You are going to need to rename it. If you have an IAM policy constraining your bucket names/ARNs, such as arn:aws:s3:*:*:bucket/s3-*-stage, be sure to make the change in a wildcard section.

I chose to just add -sse- into the bucket name. This allows for easily distinguishing between the now-two buckets you’ll have in AWS. If you need the data from the unencrypted bucket in the encrypted bucket, you’ll need to build a pipeline to retrieve, encrypt, and upload the data from one bucket to another (that’s beyond the scope of this post).

Here are the changes I made to configure my bucket to use server-side encryption in my serverless.yml file. Once you’ve made the changes, you should be able to npm run deploy your stack:

custom:
  bucketName: s3-sse-bucket-test

provider:
  s3:
    BucketTest:
      name: ${self:custom.bucketName}
      bucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256

functions:
  test_lambda:
    events:
      - s3:
          bucket: BucketTest

The Bucket with SSE has Broken My Local End-to-End Tests!

My project has end-to-end tests that can run in the cloud or can simulate AWS services and be run locally. While the above change pushed code up to the cloud that worked as intended, for whatever reason, the serverless-s3-local plugin was creating two different buckets, s3-sse-bucket-test and also BucketTest. The end-to-end tests would load data into the SSE-secured one appropriately but would then try to retrieve data from the second bucket, which was empty.

This problem/solution may be a little specific, but hopefully it will help someone else out. We used the plugin serverless-plugin-ifelse to detect whether we running tests in the cloud or locally and react accordingly:

custom:
  serverlessIfElse:
  - If: >
      '${env:IS_OFFLINE, "false"}' == 'true'
    Set:
      functions.test_lambda.events.0.s3.bucket: ${self:custom.config.bucket_name}

All this is doing is saying that if our environment variable IS_OFFLINE is true, i.e., we are running local tests, change the line with BucketTest to the name configured in the custom section, s3-sse-bucket-test. Doing it this way allowed both local and cloud-based end-to-end tests to pass!

One final note on this is that Serverless plugins perform operations in the order they are listed in the YAML file. Until I moved serverless-plugin-ifelse above serverless-s3-local in my plugins list, I was still getting the same error.

2. Enforce SSL on Bucket

The good news is that this part is relatively more straightforward than the SSE portion. However, there’s still weird stuff that Serverless is doing to make things difficult.

The way we’re going to accomplish enforcing SSL on the bucket is by adding a bucket policy to the bucket. This is defined in the resources section of the serverless.yml file, like so:

resources:
  BucketPolicyBucketTest:
  Type: 'AWS::S3::BucketPolicy'
  Properties:
    PolicyDocument:
      Statement:
        - Sid: 'HttpsOnly'
          Effect: 'Deny'
          Principal: '*'
          Action:
            - 's3:*'
          Resource:
            - 'arn:aws:s3:::${self:custom.bucketName}'
            - 'arn:aws:s3:::${self:custom.bucketName}/*'
          Condition:
            Bool:
              aws:secureTransport: 'false'
    Bucket:
      Ref: S3BucketBucketTest
      # ^ 'S3Bucket' is automatically prepended to 'BucketTest'
      # defined in the provider section and is required here to
      # reference properly

The effect of this policy is that for any request made to the bucket or its resources, if it is not an HTTPS request, it will be denied.

Take special note of the comment at the bottom of this code block. When referencing the bucket for the bucket policy, S3Bucket is prepended in order to properly reference the bucket created by the test_lambda function. Note that this is the only place it appears this way – in the provider and functions section, we just use TestBucket.

Posted In code | S3 | Serverless

Post navigation

PreviousUsing Python to Publish a Post with Embedded Images to WordPress
NextCoding Conway’s Game of Life with Python’s Standard Library

Danny Brown

A Dev Blog with Some Tangents

About

Categories

  • code
    • APIs
    • Bash
    • CSS
    • Django
    • HTML
    • JavaScript
    • Python
    • S3
    • Selenium
    • Serverless
    • TypeScript
  • games
  • music
    • concert reviews
    • synthesizers
  • opinion
  • sports
  • tech
    • Bitbucket
    • Git
    • GitHub
    • MS Teams
    • WordPress
  • theater

Recent Posts

  • Open Pull Requests from the Terminal (One of My Favorite Dotfiles Scripts)
  • Dotfiles Script for a New TypeScript/Node Project
  • So I Told You to Go See a Broadway Play? Tips for Theater in New York
  • Build a Simple Microsoft Teams Bot Easily, No SDK Required
  • Creating a GUI for Conway’s Game of Life Using Pygame and Numpy

External Links

  • GitHub
  • LinkedIn

Recent Posts

  • Open Pull Requests from the Terminal (One of My Favorite Dotfiles Scripts)
  • Dotfiles Script for a New TypeScript/Node Project
  • So I Told You to Go See a Broadway Play? Tips for Theater in New York
  • Build a Simple Microsoft Teams Bot Easily, No SDK Required
  • Creating a GUI for Conway’s Game of Life Using Pygame and Numpy

Categories

  • code
    • APIs
    • Bash
    • CSS
    • Django
    • HTML
    • JavaScript
    • Python
    • S3
    • Selenium
    • Serverless
    • TypeScript
  • games
  • music
    • concert reviews
    • synthesizers
  • opinion
  • sports
  • tech
    • Bitbucket
    • Git
    • GitHub
    • MS Teams
    • WordPress
  • theater
Copyright © 2025. Danny Brown
Powered By WordPress and Meritorious