diff --git a/.gitignore b/.gitignore index fa2fc8c..d998f85 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ infra/terraform.tfstate* infra/terraform.tfvars infra/backend.tf infra/templates/email-invite.html +infra/*.pem diff --git a/README.md b/README.md index 7786a70..f0cfa56 100644 --- a/README.md +++ b/README.md @@ -66,48 +66,27 @@ cd lambda ./build.sh ``` -### 4. Setup your CloudFront private key +### 4. Create a CloudFront key pair Authentication relies on cookies signed with a key pair. -This is a sensitive value, so the stack expects it to be stored encrypted in -[SSM Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-paramstore.html). - -First, generate a new key pair locally: +You must generate a new one locally: ``` bash +cd infra openssl genrsa -out private_key.pem 2048 openssl rsa -pubout -in private_key.pem -out public_key.pem ``` -Then store the public key on CloudFront: - -1. Open the CloudFront UI -2. Navigate to "Key management", then "Public keys" -3. Upload the public key, and take note of the Key ID you receive -4. Navigate to the "Key group" section just underneath -5. Create a new key group which includes the public key above - -Then store the values in Parameter Store: - -1. Open the Parameter Store UI -2. Create the 2 following entries: +These keys will be deployed as part of the infrastructure. AWS recommends rotating them every 3 months. -| Name | Type | KMS Key ID | Value | Example | -|------|------|------------|-------|---------| -| `cloudfront_keypair_id` | `String` | - | Public ID of the key pair | `ABC123456789` | -| `cloudfront_private_key` | `Secure String` | `alias/aws/ssm` | Contents of the private key | `-----BEGIN RSA PRIVATE KEY-----`
`...`
`-----END RSA PRIVATE KEY-----` | - -You should then delete the private key you have downloaded. -AWS recommends rotating this key pair every 3 months. - -*Note:* the Parameter Store values are cached in the Lambda function to speed up invocation. +Note: the values are cached by the Lambda function (in memory) to speed up invocation. When you rotate the key pair, the old value will still be used until the next **Lambda cold start**. You can re-deploy the Lambda function to force a cold-start. -### 4. Deploy infrastructure +### 5. Deploy infrastructure The whole infrastructure is written as [Terraform](https://www.terraform.io/) templates. -First, authenticate against AWS with the [AWS CLI](https://aws.amazon.com/cli‎): +First, authenticate against AWS with the [AWS CLI](https://aws.amazon.com/cli): - run `aws configure` and enter the access key and secret key - if you saved your credentials as a non-default profile, run `export AWS_PROFILE=profile_name` @@ -121,13 +100,7 @@ terraform apply ``` If you make any subsequent changes, simply re-run `terraform apply` to apply the update. - -!!NOTE!! The code has not yet been updated to take advantage of CloudFront Keypairs. -Once deployed, you must: - -- go into the CloudFront distribution -- edit the "behaviours" section for **HTML** and **Default** -- update `Trusted authorization type` from `Self` to the keygroup you created above +The Terraform code relies on `public_key.pem` and `private_key.pem` existing in the `infra` folder. ### 6. Create users diff --git a/infra/cloudfront.tf b/infra/cloudfront.tf index 0bacf57..6d27e53 100644 --- a/infra/cloudfront.tf +++ b/infra/cloudfront.tf @@ -5,6 +5,18 @@ data "aws_acm_certificate" "domain" { most_recent = true } +resource "aws_cloudfront_public_key" "cookie_signer" { + comment = "Cookie signer public key" + encoded_key = file("public_key.pem") + name = "cookie-signer" +} + +resource "aws_cloudfront_key_group" "cookie_signer" { + comment = "example key group" + items = [aws_cloudfront_public_key.cookie_signer.id] + name = "cookie-signer" +} + resource "aws_cloudfront_distribution" "distribution" { aliases = ["${var.website_domain}"] price_class = "PriceClass_100" @@ -87,7 +99,7 @@ resource "aws_cloudfront_distribution" "distribution" { path_pattern = "/api/*" smooth_streaming = false target_origin_id = "Login API gateway" - trusted_signers = [] + trusted_key_groups = [aws_cloudfront_key_group.cookie_signer.name] viewer_protocol_policy = "https-only" forwarded_values { @@ -142,7 +154,7 @@ resource "aws_cloudfront_distribution" "distribution" { min_ttl = 15552000 smooth_streaming = false target_origin_id = "S3-${var.s3_bucket_name}" - trusted_signers = ["self"] + trusted_key_groups = [aws_cloudfront_key_group.cookie_signer.name] lambda_function_association = [] viewer_protocol_policy = "redirect-to-https" diff --git a/infra/parameterstore.tf b/infra/parameterstore.tf new file mode 100644 index 0000000..0eef78b --- /dev/null +++ b/infra/parameterstore.tf @@ -0,0 +1,13 @@ +resource "aws_ssm_parameter" "keypair_id" { + name = "cloudfront_keypair_id" + description = "ID of the public key in CloudFront" + type = "String" + value. = aws_cloudfront_public_key.cookie_signer.id +} + +resource "aws_ssm_parameter" "private_key" { + name = "cloudfront_private_key" + description = "Private key for signing CloudFront cookies" + type = "SecureString" + value = file("private_key.pem") +}