Amazon Cloudfront: Create a distribution + Lambda (tutorial)

Published on: Sat Nov 12 2022

Series

Content

Introduction

When working with Amazon Cloudfront, you need to start off by creating a “distribution”.

These distribution contain configurations that tell Cloudfront how to deliver the content, where to get it from etc.

In this guide, we will be going over the steps on how to achieve this using Terraform!

Let’s get right into it.

What we are building

To setup the sanbox for the upcoming tutorial to learn cloudfront policies, we will be creating a basic architecture with Amazon Cloudfront, Lambda and Cloudwatch.

The Cloudfront distribution will sit at the edge locations and act as the gateway to our Lambda (origin server).

Then we will also have logs of the forwarded requests that are addded to Cloudwatch logs.

The Architecture

Illustration of the sandbox architecture with Cloudfront and Lambda
Illustration of the sandbox architecture with Cloudfront and Lambda

The steps

  1. The Viewer (client) request comes in

  2. Cloudfront forwards the request to the origin server (Lambda URL + Lambda)

  3. The logs from the request are added into Cloudwatch Logs

  4. The Lambda function returns a response

  5. The Viewer (client) receives the response

Before you start

Before you start going through the tutorial, make sure you are using the starter - amazon-cloudfront-policies-starter.

This streamlines some of the things like building the lambda functions and writing out all the boilerplate files.

It will be the base from which we will build from!

Cloudfront distribution

Now onto setting up the infrastructure.

1. Choose a name for the S3 bucket

Since S3 buckets need to be globally unique, you will need to change the name.

This S3 bucket is for holding the assets of our Lambda functions.

So, within infra/main.tf , make the edit on the S3 resource name:

Add the following changes:

// infra/main.tf

resource "aws_s3_bucket" "lambda_bucket" {
  bucket        = "<insert-name-here>"
  acl           = "private"
}

2. Create a lambda function URL

Now Lambda function supports an option to expose an URL, so we can use our function as our “origin server” for testing the request origin policy.

Add the following changes:

// infra/main.tf

resource "aws_lambda_function_url" "origin" {
  function_name      = module.lambda_origin.lambda[0].function_name
  qualifier         = "origin-server-fn"
  # For testing purposes we won’t have authorization
  authorization_type = "NONE"
}

Helpful Reference

3. Create the Cloudfront distribution (origin)

Now that we have our lambda function and url, its time to setup our cloudfront distribution.

Add the following changes:

// infra/main.tf

resource "aws_cloudfront_distribution" "cf_distribution" {
  origin {
    # This is required because "domain_name" needs to be in a specific format
    domain_name = replace(replace(aws_lambda_function_url.origin.function_url, "https://", ""), "/", "")
    origin_id = module.lambda_origin.lambda[0].function_name

    custom_origin_config {
      https_port = 443
      http_port = 80
      origin_protocol_policy = "https-only"
      origin_ssl_protocols = ["TLSv1.2"]
    }
  }
}

Helpful Reference

4. Add the default cache behavior

In all Cloudfront distribution, and for every origin, you need a default cache behavior.

Let’s set that up.

Add the following changes:

// infra/main.tf

resource "aws_cloudfront_distribution" "cf_distribution" {
  origin {
    # This is required because "domain_name" needs to be in a specific format
    domain_name = replace(replace(aws_lambda_function_url.origin.function_url, "https://", ""), "/", "")
    origin_id = module.lambda_origin.lambda[0].function_name

    custom_origin_config {
      https_port = 443
      http_port = 80
      origin_protocol_policy = "https-only"
      origin_ssl_protocols = ["TLSv1.2"]
    }
  }

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = module.lambda_origin.lambda[0].function_name
    forwarded_values {
      query_string = false
      cookies {
        forward = "none"
      }
    }
    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = local.min_ttl
    default_ttl            = local.default_ttl
    max_ttl                = local.max_ttl
  }
}

Helpful Reference

5. Add the other fields

When creating the Cloudfront distribution, there are a few options that are required.

We are just going to focus on the minimum options that we need to create a distribution.

Be sure to check the documentation for all the other options.

Add the following changes:

// infra/main.tf

resource "aws_cloudfront_distribution" "cf_distribution" {
  origin {
    # This is required because "domain_name" needs to be in a specific format
    domain_name = replace(replace(aws_lambda_function_url.origin.function_url, "https://", ""), "/", "")
    origin_id = module.lambda_origin.lambda[0].function_name

    custom_origin_config {
      https_port = 443
      http_port = 80
      origin_protocol_policy = "https-only"
      origin_ssl_protocols = ["TLSv1.2"]
    }
  }

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = module.lambda_origin.lambda[0].function_name
    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = local.min_ttl
    default_ttl            = local.default_ttl
    max_ttl                = local.max_ttl
  }

  price_class = var.cf_price_class
  enabled = true
  is_ipv6_enabled     = true
  comment             = "origin request policy test"
  default_root_object = "index.html"

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  tags = {
    Environment = "production"
  }

  viewer_certificate {
    cloudfront_default_certificate = true
  }
}

Helpful Reference

6. Uncomment the outputs

The outputs will show us the information of the generated resources (cloudfront, lambda urls) we have defined when they are created.

// infra/ouputs.tf

output "function_alias_name_origin" {
  description = "Name of the function alias"
  value = module.lambda_origin.alias[0].name
}

output "lambda_url" {
  description = "Lambda URL"
  value = aws_lambda_function_url.origin.function_url
}

output "cf_distribution_domain_url" {
  value = "https://${aws_cloudfront_distribution.cf_distribution.domain_name}"
}

7. Apply the infrastructure

Now we have all our definition in Terraform, all that is left is to generate it.

Run the following:

// This will re-generate the assets
pnpm run generate-assets --filter "@function/*"

export AWS_ACCESS_KEY_ID=<your-key>
export AWS_SECRET_ACCESS_KEY=<your-secret>
export AWS_DEFAULT_REGION=us-east-1

terraform init
terraform plan
terraform apply -auto-approve

If the infrastructure applied successfully then you should see something like this:

Illustration of the Terraform outputs
Illustration of the Terraform outputs

Testing it out

1. Make a request

Curl:

curl -vvv "<cf_distribution_domain_url>"

Postman:

Illustration of using Postman for the request
Illustration of using Postman for the request

2. Check the logs

Apart from the response from cloudfront, be sure to check the logs of your lambda function (our origin server).

Make you are able to see it as it will be useful later when we start to integrate the origin request policies.

Illustration of the Lambda function logs
Illustration of the Lambda function logs/blockquote>

For reference, here is the repository of completed tutorial - Github: amazon-cloudfront-create-distribution.

Conclusion

Congrats! You’ve just setup a Cloudfront distribution that uses a Lambda function as the server to respond to requests.

In practice, a more common approach is pointing the Cloudfront distribution to an Amazon S3 Bucket (There likely will be a tutorial on this as well!).

For our use case of learning about cloudfront policies, we’ll use this setup as our sandbox!

If you aren’t sure what cloudfront policies are or need a refresher, then be sure to check out my article on it 👉 Amazon Cloudfront: An overview of policies.

And... that’s all for now, stay tuned for more!

If you found this helpful or learned something new, please do share this article with a friend or co-worker 🙏❤️ (Thanks!)


Enjoy the content ?

Then consider signing up to get notified when new content arrives!

Jerry Chang 2023. All rights reserved.