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.

via GIPHY

How it works

  1. 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.

  2. They are denied access, as the bucket has a private ACL.

  3. CloudFront is configured with a custom error page that presents a login page.

    See: bucket-access-button.html on GitHub

  4. The user enters the password, and this is validated with a lambda function.

    See: bucket-access-button.js on GitHub

  5. 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.

  6. 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.

  1. Create a bucket with a private ACL. The ACL prevents direct access to the bucket.

    Do not set up bucket website hosting.

  2. Create a CloudFront distribution that points to the S3 bucket and also a CloudFront origin identity.

  3. Upload the bucket-access-button.html to your S3 bucket and rename it to login.html.

  4. On your CloudFront distribution, add a custom error response that redirects errors with code 403 to /login.html and a 200 status code.

    This step will show users the login page, even if they don’t have access to the access the object directly.

  5. In the “Security Credentials” section of IAM, create a CloudFront private key.

  6. Create a new KMS key and encrypt the CloudFront private key.

  7. 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).

  8. Create a new IAM role for the lambda function, with access to kms:Decrypt with the above key.

  9. 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.

  10. Point users to the CloudFront distribution and provide them the password!