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({ |
[] |
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) |
[ |
no |
| cognito_supported_identity_providers | Supported identity providers for the Cognito app client | list(string) |
[ |
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. |