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:
- Add server-side encryption to the bucket, ensuring that data is encrypted at rest.
- 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.