AWS Lambda to protect your CloudFront content with username/passwords
The first step is to have CloudFront in front of your S3 bucket.
Browser ----> CloudFront ----> S3 bucket
We then add a Lambda function responsible for logging-in. When given valid credentials, this function creates signed session cookies. CloudFront will verify every request has valid cookies before forwarding them.
Browser CloudFront Lambda S3
| | | |
| ---------- get ---------> | | |
| | | |
| [no cookie] | |
| | | |
| | | |
| | | |
| <------ error page ------ | | |
| | |
| -------------------- login ------------------> | |
| <------------------- cookies ----------------- | |
| |
| ---------- get ---------> | |
| | |
| [has cookie] |
| | |
| | -----------------------------------> |
| | <------------ html page ------------ |
| <------ html page ------- |
- Create an encryption key in KMS, or choose one that you already have. Note that each KMS key costs $1/month.
- Take note of the key ID.
- Logging in with your AWS root account, generate a CloudFront key pair
- Take note of the key pair ID
- Download the private key, and encrypt it with KMS using
aws kms encrypt --key-id $KMS_KEY_ID --plaintext "$(cat pk-000000.pem)" --query CiphertextBlob --output text
- Write down the encrypted value, then secure the private key or delete it
- Create a local
htpasswd
file with your usernames and passwords. You can generate the hashes from the command-line:
$ htpasswd -nB username
New password: **********
Re-type new password: **********
username:$2a$08$eTTe9DM5N0w50CxL5OL0D.ToMtpAuip/4TCSWCSDJddoIW9gaQIym
- Encrypt your
htpasswd
file using KMS again
aws kms encrypt --key-id $KMS_KEY_ID --plaintext "$(cat htpasswd)" --query CiphertextBlob --output text
Create a configuration file called dist/config.json
, based on config.example.json.
Make sure you don't commit this file to source control (the dist
folder is ignored).
It should contain the following info - minus the comments:
[
// -------------------
// PLAIN TEXT SETTINGS
// -------------------
// the website domain, as seen by the users
"websiteDomain=website.com",
// how long the CloudFront access is granted for, in seconds
// note that the cookies are session cookies, and will get deleted when the browser is closed anyway
"sessionDuration=86400",
// if false, a successful login will return HTTP 200 (typically for Ajax calls)
// if true, a successful login will return HTTP 302 to the Referer (typically for form submissions)
"redirectOnSuccess=true",
// KMS key ID created in step 1
"kmsKeyId=00000000-0000-0000-0000-000000000000",
// CloudFront key pair ID from step 2
// This is not sensitive, and will be one of the cookie values
"cloudFrontKeypairId=APK...",
// ------------------
// ENCRYPTED SETTINGS
// ------------------
// encrypted CloudFront private key from step 2
"encryptedCloudFrontPrivateKey=AQECAH...",
// encrypted contents of the <htpasswd> file from step 3
"encryptedHtpasswd=AQECAH..."
]
You can then deploy the full stack using:
export AWS_PROFILE="your-profile"
export AWS_REGION="ap-southeast-2"
# name of an S3 bucket for storing the Lambda code
./deploy my-bucket
The output should end with the AWS API Gateway endpoint:
Endpoint URL: https://0000000000.execute-api.ap-southeast-2.amazonaws.com/Prod/login"
Take note of that URL, and test it out!
# with a HTTP Form payload
curl -X POST -d "username=hello&password=world" -H "Content-Type: x-www-form-encoded" -i "https://0000000000.execute-api.ap-southeast-2.amazonaws.com/Prod/login"
# with a JSON payload
curl -X POST -d "{\"username\":\"hello\", \"password\":\"world\"}" -H "Content-Type: application/json" -i "https://0000000000.execute-api.ap-southeast-2.amazonaws.com/Prod/login"
Final steps:
- optionally setup CloudFront in front of this URL too, so you can use a custom domain like
https://website.com/login
- once everything is working, change your CloudFront distribution to require signed cookies
and it will return
HTTP 403
for users who aren't logged in - setup CloudFront to serve a nice login page for
403
errors, or use an existing page from your website to trigger the Lambda function