Skip to content

S3 CDN

Terraform module for creating CloudFront distribution in front of S3 bucket to serve static content.

Usage example

Example for a single page application (SPA) with a custom error response for 403 errors. The custom_error_response block is optional and can be omitted if not needed.

module "s3_cdn" {
  source = "s3::https://apro-terraform-modules.s3.eu-west-1.amazonaws.com/SET_VERSION/s3_cdn.zip"

  name_prefix                           = "example-spa-website"
  aliases                               = ["website.example.com"]
  viewer_certificate_arn                = "arn:aws:acm:eu-west-1:123456789012:certificate/12345678-1234-1234-1234-123456789012"
  hosted_zone_name                      = "example.com"
  default_root_object                  = "index.html"

  custom_error_response = {
    error_code            = 403
    response_page_path    = "/index.html"
    response_code         = "200"
    error_caching_min_ttl = 0
  }

  web_acl_id = "arn:aws:wafv2:eu-west-1:123456789012:regional/webacl/example-webacl/12345678-1234-1234-1234-123456789012"
}

This example is for a website that has multiple directories and needs to serve each directory to the default index file. E.g. /about serves /about/index.html.

module "s3_cdn" {
  source = "s3::https://apro-terraform-modules.s3.eu-west-1.amazonaws.com/SET_VERSION/s3_cdn.zip"

  name_prefix                           = "example-webstie"
  aliases                               = ["website.example.com"]
  viewer_certificate_arn                = "arn:aws:acm:eu-west-1:123456789012:certificate/12345678-1234-1234-1234-123456789012"
  hosted_zone_name                      = "example.com"
  redirect_to_default_directory_indexes = true

  custom_error_response = {
    error_code            = 403
    response_page_path    = "/404.html"
    response_code         = "200"
    error_caching_min_ttl = 0
  }

  kms_key_id = "arn:aws:kms:eu-west-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}

Excluding paths from Cognito auth

When cognito_auth_enabled is true every request is checked by the Lambda@Edge auth function. cognito_auth_excluded_path_patterns adds extra cache behaviors that skip that function entirely, so matching objects are served to anyone who has the URL — they are public and unauthenticated, and may be cached by any intermediary. Only put assets there that you are comfortable publishing, such as logos referenced from the login page.

module "s3_cdn" {
  source = "s3::https://apro-terraform-modules.s3.eu-west-1.amazonaws.com/SET_VERSION/s3_cdn.zip"

  name_prefix = "example-website"
  # ...

  cognito_auth_enabled                = true
  cognito_auth_excluded_path_patterns = ["/branding/*"]
}

Patterns are passed to CloudFront verbatim and must start with a leading slash, as in /branding/*. The one exception is an extension glob intended to match at any directory depth, which must start with *, as in *.png/*.png would only match the bucket root. Anything else is rejected by variable validation.

The variable has no effect when cognito_auth_enabled is false.

Excluded paths are not covered by redirect_to_default_directory_indexes either: under Cognito auth that rewrite happens inside the Lambda, which these behaviors deliberately skip. Reference excluded objects by full path (/branding/logo.png) rather than by directory.

Modules

Name Source Version
cdn_bucket terraform-aws-modules/s3-bucket/aws 5.12.0
cloudfront terraform-aws-modules/cloudfront/aws 6.5.0
cloudfront_log_bucket terraform-aws-modules/s3-bucket/aws 5.12.0

Inputs

Name Description Type Default Required
aliases List of alias hostnames for the CloudFront distribution list(string) [] no
bucket_name Name of the main bucket. If not provided, the name will be {name_prefix}-assets. string null no
cognito_app_client_name Name for the Cognito app client. Defaults to name_prefix-cdn-auth. string null no
cognito_auth_enabled Enable Cognito authentication at the CDN edge bool false no
cognito_auth_excluded_path_patterns Path patterns served WITHOUT Cognito edge auth (e.g. ["/branding/"]). Objects matching these are public to anyone with the URL. Patterns are passed to CloudFront verbatim and must start with a leading slash, except extension globs that are meant to match at any depth (e.g. ".png"). Only effective when cognito_auth_enabled is true. list(string) [] no
cognito_cookie_expiration_days Cookie expiration in days for the auth session number 365 no
cognito_log_level Log level for the Lambda@Edge auth function string "error" no
cognito_managed_login_branding_assets List of branding asset objects (bytes, category, color_mode, extension)
list(object({
bytes = string
category = string
color_mode = string
extension = string
}))
[] no
cognito_managed_login_branding_settings JSON settings for managed login branding. If null, uses Cognito defaults. any null no
cognito_oauth_scopes OAuth scopes for the Cognito app client list(string)
[
"openid",
"email",
"profile"
]
no
cognito_supported_identity_providers Supported identity providers for the Cognito app client list(string)
[
"COGNITO"
]
no
cognito_user_pool_domain Full Cognito User Pool domain (e.g. prefix.auth.region.amazoncognito.com). Required when cognito_auth_enabled is true. string null no
cognito_user_pool_id Cognito User Pool ID. Required when cognito_auth_enabled is true. string null no
cognito_user_pool_region Region of the Cognito User Pool. Required when cognito_auth_enabled is true. string null no
cors_origins List of CORS origins for the main bucket. By default it will not be set. any null no
create Create resources bool true no
create_records Create Route53 records for the CloudFront distribution bool true no
custom_error_response Custom error response for the CloudFront distribution any {} no
default_root_object Default root object for the CloudFront distribution string null no
enable_bucket_access_logs Enable access logging for the main bucket bool false no
hosted_zone_name Hosted zone to create the records for the CloudFront distribution in. Needs to be set if create_records is true. string null no
kms_key_id KMS key ID for encrypting the bucket and the log bucket. If not provided, the bucket will be encrypted with default AWS S3 encryption. string null no
lifecycle_rule Lifecycle rule for the main bucket. By default it will not be set. any null no
log_lifecycle_rule Lifecycle rule for the log bucket. By default, it will transition logs to STANDARD_IA after 30 days and to GLACIER after 365 days. any null no
name_prefix Prefix for the resources string n/a yes
price_class Price class for the CloudFront distribution string "PriceClass_100" no
redirect_to_default_directory_indexes Redirect to default directory indexes bool false no
versioning Enable versioning for the main bucket bool true no
viewer_certificate_arn ARN of the ACM certificate for the CloudFront distribution string null no
web_acl_id Web ACL ID for the CloudFront distribution string null no

Outputs

Name Description
cdn_bucket_arn The ARN of the S3 bucket used for the CDN content.
cdn_bucket_id The ID of the S3 bucket used for the CDN content.
cloudfront_distribution_arn The ARN of the CloudFront distribution.
cloudfront_distribution_domain_name The domain name of the CloudFront distribution.
cloudfront_distribution_hosted_zone_id The hosted zone ID of the CloudFront distribution.
cloudfront_distribution_id The ID of the CloudFront distribution.
cognito_app_client_id The ID of the Cognito app client for CDN authentication.
lambda_edge_function_arn The ARN of the Lambda@Edge authentication function.
lambda_edge_function_version The published version ARN of the Lambda@Edge authentication function.