Panfactum LogoPanfactum
Infrastructure ModulesSubmodulesAWSaws_cdn
aws_cdn
Beta
Submodule
Source Code Link

AWS CloudFront CDN

Deploys an AWS CloudFront distribution (CDN) that can be used to proxy requests to your upstream origins.

This module performs several functions in addition to providing sensible defaults for the CDN:

  • Establishes the CDN TLS certificates
  • Deploys the DNS records for the CDN
  • Provides pre-packaged edge function behaviors (e.g., URL rewrite rules)
  • Configures request logging (optional)

Limitations

  • The domain names used for the CDN must be served by Route53 zones in the AWS account within which this module is deployed. This zone must be deployed before using this module because this module automatically configures all the necessary DNS records.

  • Your origin servers must be identified by domain name (not IP address) and be able to serve HTTPS traffic from their domain (i.e., have valid TLS certificates).

Usage

Overview

Conceptually, a CloudFront distribution is a set of servers distributed all over the world that collectively comprise a content distribution network (CDN). These servers are called points-of-presence (PoPs). PoPs serve several purposes:

  • Enhancing TLS performance by speeding up the initial connection between the client and server (reference)
  • Improving overall performance and reducing load on servers by caching responses from origin servers
  • Running edge computations (CloudFront functions)

Unlike traditional servers where DNS records such as example.com route to a single set of servers, CloudFront works with AWS's DNS servers to ensure that clients are always routed to the nearest PoP.

The set of domains that get routed this way is defined by the domains input to this module. How traffic gets routed from the PoP to your origin servers is configured by origin_configs.

Using the Global Provider

terraform {
   required_providers {
     ...
     aws = {
       source                = "hashicorp/aws"
       version               = "5.70.0"
       configuration_aliases = [aws.global]
     }
     ...
  }
}

module "cdn" {
  source = "${var.pf_module_source}aws_cdn${var.pf_module_ref}"

  # Since the CDN operates globally, you must use the global
  # provider as follows:
  providers = {
    aws.global = aws.global
  }

  name            = "example"
  domains         = [...]
  origin_configs  = [...]
}

Configuring Origin Routing

Overview

You can specify multiple upstream origins via origin_configs. Which origin traffic gets routed to is controlled via path_prefix. Each origin must have a unique path_prefix.

Traffic that matches a given path_prefix will:

  1. The CDN will check to see if the request path matches any of the path_match_behavior keys (e.g., *.jpg). If so, the rules from that behavior configuration will be applied. See below.

  2. If no path_match_behavior keys are matched (or none are provided), the default_cache_behavior configuration will take effect.

The cache "behavior" for both default_cache_behavior and path_match_behavior works as follows:

  1. The viewer_protocol_policy (docs) will be applied. 1

  2. Any global redirect rules (see below) will take effect. Otherwise, the request will be processed.

  3. If caching_enabled is true, the request's HTTP method is in cached_methods, and the request has a cached response, then a cached response will be immediately returned.

  4. If no cached response is found and the request HTTP method is in allowed_methods, the request be forwarded to the origin_domain in its original form (including the original Host header) except for anything blocked by cookies_not_forwarded, headers_not_forwarded, and query_strings_forwarded.

  5. When a response is received, CloudFront will automatically compress responses if compression_enabled is true and the client accepts either the Gzip or Brotli compression formats (via the Accept-Encoding HTTP header).

  6. After optional compression, the request will be cached if caching_enabled is true and the request's HTTP method is in cached_methods. The cache key is determined by cookies_in_cache_key, headers_in_cache_key, and query_strings_in_cache_key (the HTTP method and path are always included in the key). 2

    How long the request will stay cached depends on the following values and the response's Cache-Control and Expires headers:

    • min_ttl: The minimum amount of time in seconds that CloudFront will keep responses in the cache before revalidating them with the origin server.
    • default_ttl: The amount of time in seconds that CloudFront will keep responses in the cache only when the response does not supply Cache-Control or Expires headers.
    • max_ttl: The maximum amount of time in seconds that Cloudfront will keep responses in the cache before revalidating them with the origin server regardless of the response's Cache-Control or Expires headers.
    • Cache-Control response headers: CloudFront respects the max-age, no-cache, no-store, private, stale-while-revalidate and stale-if-error directives.
    • Expires response headers: Cloudfront respects this header but Cache-Control should be preferred due to its increased specificity.

    For a much more detailed breakdown of CloudFront caching, see AWS's documentation.

  7. The response will be sent to the client.

No Origin Matches

If the distribution receives a request but does not match any path_prefix, CloudFront will return an HTTP 404 status code.

Redirect Rules

We provide an input, redirect_rules, that allows you to specify redirects that will be applied before requests get sent to your origin servers.

For example:

redirect_rules = [{
   source = "https?://example.com(/.*)"
   target = "https://new.example.com$1"
}]

The above rule would redirect a request for http://example.com/some/resource to https://new.example.com/some/rseource.

CORS

This module allows CORS header handling to happen inside CloudFront rather than by your origin servers.

To enable this functionality, set cors_enabled to true.

When this is enabled, CloudFront will respond to all OPTIONS preflight requests without forwarding any requests to the origin. Additionally, CloudFront will ensure all responses returned from the origin have the appropriate CORS headers set before forwarding the response to the client.

You can customize the CORS behavior with the following inputs:

  • cors_max_age_seconds: Sets the Access-Control-Max-Age response header.
  • cors_allowed_headers: Sets the Access-Control-Allow-Headers response header.
  • cors_allowed_methods: Sets the Access-Control-Allow-Methods response header.

By default, the only allowed origins will be from the domains input. If you want to allow additional origins, you must set cors_additional_allowed_origins. Adding "*" to this input will allow all origins.

S3 Origins

If you want to use an S3 bucket as the origin for this CloudFront distribution, you should use our aws_s3_public_website module.

Number of PoPs

The number of PoPs is configured by CloudFront's price class. If you are serving to non-NA clients, you may want to change the default price_class for this module.

Origin Shield

By default, caching is performed independently at each PoP. That means if content is cached in one PoP, other PoPs will still have to make requests to the origin server to retrieve that content.

If you enable Origin Shield, an additional, unified caching layer is introduced before your origin servers. That means that if content is cached for one PoP, it will be cached for all PoPs.

Be aware that this incurs additional charges that can be significant depending on your workload, and it is best used for workloads that serve mostly static content. See this documentation.

Security

Restricting Client Geographies

You can control what countries clients can connect to your distribution from by setting geo_restriction_type and geo_restriction_list. For example, if you set geo_restriction_type to "whitelist" and geo_restriction_list to ["US"], then only clients can connect from the United States. If you set geo_restriction_type to "blacklist", then client can connect from anywhere but the United States.

Logging

Logging of requests can be enabled by setting logging_enabled to true.

This only configures standard logging, not the more expensive real-time logs. As a result, logs may take up to an hour to be created in the S3 bucket (outputs.log_bucket).

Invalidating the Cache

If you need to manually purge the distribution of all cached responses, you can do so by following this documentation.

Providers

The following providers are needed by this module:

  • aws (5.70.0)

  • pf (0.0.3)

  • random (3.6.0)

Required Inputs

The following input variables are required:

domains

Description: A list of domains to use for the CDN

Type: list(string)

name

Description: The name of the CDN that will get created

Type: string

origin_configs

Description: A list of configuration settings for communicating with the upstream origins

Type:

list(object({
    origin_id                = optional(string)          # A globally unique identifier for this origin (will be automatically computed if not provided)
    origin_domain            = string                    # The domain name of the ingress origin
    path_prefix              = optional(string, "/")     # Only traffic with this HTTP path prefix will be routed to the indicated origin
    extra_origin_headers     = optional(map(string), {}) # Headers sent from the CDN to the origin
    origin_access_control_id = optional(string, null)    # The OAC id to use for accessing private origins

    # The default behavior of the CDN before routing requests to this origin
    default_cache_behavior = optional(object({
      caching_enabled      = optional(bool, true)                                                                 # Whether the CDN should cache responses from the origin (overrides all other caching settings)
      allowed_methods      = optional(list(string), ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]) # What HTTP methods are allowed
      cached_methods       = optional(list(string), ["GET", "HEAD"])                                              # What HTTP methods will be cached
      min_ttl              = optional(number, 0)                                                                  # Minimum cache time
      default_ttl          = optional(number, 86400)                                                              # Default cache time
      max_ttl              = optional(number, 31536000)                                                           # Maximum cache time
      cookies_in_cache_key = optional(list(string), ["*"])                                                        # Which cookies will be included in the cache key (Providing "*" means ALL cookies)
      headers_in_cache_key = optional(list(string), [                                                             # Which headers will be included in the cache key
        "Authorization",
        "Origin",
        "x-http-method-override",
        "x-http-method",
        "x-method-override",
        "x-forwarded-host",
        "x-host",
        "x-original-url",
        "x-rewrite-url",
        "forwarded"
      ])
      query_strings_in_cache_key  = optional(list(string), ["*"])         # Which query strings will be included in the cache key (Providing "*" means ALL query strings)
      cookies_not_forwarded       = optional(list(string), [])            # Which cookies will NOT be forwarded to the ingress from the CDN
      headers_not_forwarded       = optional(list(string), [])            # Which headers will NOT be forwarded to the ingress from CDN
      query_strings_not_forwarded = optional(list(string), [])            # Which query strings will NOT be forwarded to the ingress from the CDN
      compression_enabled         = optional(bool, true)                  # Whether the CDN performs compression on your assets
      viewer_protocol_policy      = optional(string, "redirect-to-https") # What should happen based on the client protocol (HTTP vs HTTPS). One of: allow-all, https-only, redirect-to-https
    }))

    # Similar to default_cache_behavior but allows you to specific specific rules for certain path patterns
    # The keys for this map are the path patterns (e.g., "*.jpg")
    # Path patterns will automatically be prefixed with the path_prefix value, so it can be omitted
    path_match_behavior = optional(map(object({
      caching_enabled      = optional(bool, true)
      allowed_methods      = optional(list(string), ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"])
      cached_methods       = optional(list(string), ["GET", "HEAD"])
      min_ttl              = optional(number, 0)
      default_ttl          = optional(number, 86400)
      max_ttl              = optional(number, 31536000)
      cookies_in_cache_key = optional(list(string), ["*"])
      headers_in_cache_key = optional(list(string), [
        "Authorization",
        "Origin",
        "x-http-method-override",
        "x-http-method",
        "x-method-override",
        "x-forwarded-host",
        "x-host",
        "x-original-url",
        "x-rewrite-url",
        "forwarded"
      ])
      query_strings_in_cache_key  = optional(list(string), ["*"])
      cookies_not_forwarded       = optional(list(string), [])
      headers_not_forwarded       = optional(list(string), [])
      query_strings_not_forwarded = optional(list(string), [])
      compression_enabled         = optional(bool, true)
      viewer_protocol_policy      = optional(string, "redirect-to-https")
    })), {})
  }))

Optional Inputs

The following input variables are optional (have default values):

cors_additional_allowed_origins

Description: Specifies which origins are allowed besides the domain name specified. Use '*' to allow any origin.

Type: list(string)

Default: []

cors_allowed_headers

Description: Specifies which headers are allowed for CORS requests.

Type: list(string)

Default:

[
  "Content-Length"
]

cors_allowed_methods

Description: Specifies which methods are allowed. Can be GET, PUT, POST, DELETE or HEAD.

Type: list(string)

Default:

[
  "GET",
  "HEAD"
]

cors_enabled

Description: True if the CloudFront distribution should handle adding CORS headers instead of the origin.

Type: bool

Default: false

cors_max_age_seconds

Description: Time in seconds that the browser can cache the response for a preflight CORS request.

Type: number

Default: 3600

description

Description: A description for this CDN

Type: string

Default: null

geo_restriction_list

Description: A list of ISO 3166 country codes for the geographic restriction list (works for both whitelist and blacklist)

Type: list(string)

Default: []

geo_restriction_type

Description: What type of geographic restrictions to you want to apply to CDN clients. Must be one of: none, blacklist, whitelist.

Type: string

Default: "none"

logging_cookies_enabled

Description: Whether cookies should be included in the request logs

Type: bool

Default: false

logging_enabled

Description: Whether request logging should be enabled for the CloudFront distribution

Type: bool

Default: false

logging_expire_after_days

Description: The number of days after which logs will be deleted. (0 to disable)

Type: number

Default: 0

origin_shield_enabled

Description: Whether origin shield should be enabled for the CloudFront distribution

Type: bool

Default: false

price_class

Description: The price class for the CDN. Must be one of: PriceClass_All, PriceClass_200, PriceClass_100.

Type: string

Default: "PriceClass_100"

redirect_rules

Description: A list of redirect rules that the ingress will match against before sending requests to the upstreams

Type:

list(object({
    source    = string                # A regex string for matching the entire request url (^https://domain.com(/.*)?$)
    target    = string                # The redirect target (can use numbered capture groups from the source - https://domain2.com/$1)
    permanent = optional(bool, false) # If true will issue a 301 redirect; otherwise, will use 302
  }))

Default: []

Outputs

The following outputs are exported:

distribution_id

Description: The ID of the CloudFront distribution

logging_bucket_name

Description: The name of the log bucket for CloudFront access logs

Maintainer Notes

No notes.

Footnotes

  1. We default to redirect-to-https for backwards-compatibility, but it is more secure to use https-only. See the documentation for HTTP Strict Transport Security.

  2. The more values you specify for the cache key, the lower your hit ratio will be, so tune this carefully. By default, we include all cookies and query strings in the cache key to ensure that responses are not unintentionally shared across clients. You will likely want to loosen these settings for your specific scenario. For more information on tuning the cache key see this documentation.