Providing password-based access to a private S3 bucket with Lambda and CloudFront
Amazon’s Simple Storage Service doesn’t natively support password-protected access, however we can use a CloudFront distribution and private ACL to control access to the bucket and then use Lambda to issue signed cookies after validating a password.
How it works
-
A user visits the CloudFront distribution. This could either be directly to the abcde.cloudfront.net hostname or a CNAME. You can also setup SSL with an Amazon provided certificate.
-
They are denied access, as the bucket has a
private
ACL. -
CloudFront is configured with a custom error page that presents a login page.
-
The user enters the password, and this is validated with a lambda function.
-
The lambda function returns the values for several cookies. These aren’t set by the function itself / API Gateway as it would require setting up a custom domain name for API Gateway and also using the same domain for the CloudFront distribution.
-
The login page sets the cookies using the provided values, and redirects the user back to the homepage.
Tutorial
Protip: For an easy way to create all of the required infrastructure, I’ve created a Terraform module.
-
Create a bucket with a private ACL. The ACL prevents direct access to the bucket.
Do not set up bucket website hosting.
-
Create a CloudFront distribution that points to the S3 bucket and also a CloudFront origin identity.
-
Upload the
bucket-access-button.html
to your S3 bucket and rename it tologin.html
. -
On your CloudFront distribution, add a custom error response that redirects errors with code
403
to/login.html
and a200
status code.This step will show users the login page, even if they don’t have access to the access the object directly.
-
In the “Security Credentials” section of IAM, create a CloudFront private key.
-
Create a new KMS key and encrypt the CloudFront private key.
-
Create a new lambda function using the
bucket-access-button.js
linked above. You will have to set the following environment variables.-
ENCRYPTED_PASSWORD
The password that will protect access to your bucket, encrypted with the above KMS key.
-
ENCRYPTED_CLOUDFRONT_PRIVATE_KEY
The encrypted CloudFront private key from step 6.
-
CLOUDFRONT_KEYPAIR_ID
The ID associated with your CloudFront private key.
-
CLOUDFRONT_DOMAIN_NAME
The hostname of your CloudFront distribution (or CNAME if applicable).
-
-
Create a new IAM role for the lambda function, with access to
kms:Decrypt
with the above key. -
Create an API Gateway REST API, resource and method that points to your new lambda function.
NB: Ensure you use the “Enable CORS” option on your method.
-
Point users to the CloudFront distribution and provide them the password!
Learning AWS' CDK in TypeScript? Check out my course on Udemy.