Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 85 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,86 @@ module "cdn" {
}
```

### CloudFront distribution with CloudFront Functions

```hcl
module "cdn" {
source = "terraform-aws-modules/cloudfront/aws"

aliases = ["cdn.example.com"]

comment = "CloudFront with Functions"
enabled = true
is_ipv6_enabled = true
price_class = "PriceClass_All"
retain_on_delete = false
wait_for_deployment = false

# Enable CloudFront Functions
create_cloudfront_function = true

cloudfront_functions = {
viewer-request-function = {
runtime = "cloudfront-js-2.0"
comment = "Function to add security headers and modify requests"
code = file("${path.module}/functions/viewer-request.js")
publish = true
}

viewer-response-function = {
runtime = "cloudfront-js-2.0"
comment = "Function to add security response headers"
code = file("${path.module}/functions/viewer-response.js")
publish = true
# Optional: Associate with CloudFront KeyValueStore
key_value_store_associations = ["arn:aws:cloudfront::123456789012:key-value-store/example-store"]
}
}

origin = {
s3_bucket = {
domain_name = "my-bucket.s3.amazonaws.com"
s3_origin_config = {
origin_access_identity = "s3_bucket"
}
}
}

default_cache_behavior = {
target_origin_id = "s3_bucket"
viewer_protocol_policy = "redirect-to-https"

allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
compress = true
query_string = true

# Associate CloudFront Functions with cache behavior
# Option 1: Direct ARN reference (recommended for external functions)
# function_association = {
# viewer-request = {
# function_arn = aws_cloudfront_function.external.arn
# }
# }

# Option 2: Dynamic reference to module-managed functions by key/name
function_association = {
viewer-request = {
function_key = "viewer-request-function"
}
viewer-response = {
function_key = "viewer-response-function"
}
}
}

viewer_certificate = {
acm_certificate_arn = "arn:aws:acm:us-east-1:135367859851:certificate/1032b155-22da-4ae0-9f69-e206f825458b"
ssl_support_method = "sni-only"
}
}
```

## Examples

- [Complete](https://github.com/terraform-aws-modules/terraform-aws-cloudfront/tree/master/examples/complete) - Complete example which creates AWS CloudFront distribution and integrates it with other [terraform-aws-modules](https://github.com/terraform-aws-modules) to create additional resources: S3 buckets, Lambda Functions, CloudFront Functions, VPC Origins, ACM Certificate, Route53 Records.
Expand All @@ -86,7 +166,7 @@ module "cdn" {
- `Error: updating CloudFront Distribution (ETXXXXXXXXXXXX): InvalidArgument: The parameter ForwardedValues cannot be used when a cache policy is associated to the cache behavior.`
- When defining a behavior in `ordered_cache_behavior` and `default_cache_behavior` with a cache policy, you must specify `use_forwarded_values = false`.

```
```hcl
ordered_cache_behavior = [{
path_pattern = "/my/path"
target_origin_id = "my-origin"
Expand Down Expand Up @@ -124,6 +204,7 @@ No modules.
| Name | Type |
|------|------|
| [aws_cloudfront_distribution.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution) | resource |
| [aws_cloudfront_function.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_function) | resource |
| [aws_cloudfront_monitoring_subscription.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_monitoring_subscription) | resource |
| [aws_cloudfront_origin_access_control.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_origin_access_control) | resource |
| [aws_cloudfront_origin_access_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_origin_access_identity) | resource |
Expand All @@ -138,8 +219,10 @@ No modules.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_aliases"></a> [aliases](#input\_aliases) | Extra CNAMEs (alternate domain names), if any, for this distribution. | `list(string)` | `null` | no |
| <a name="input_cloudfront_functions"></a> [cloudfront\_functions](#input\_cloudfront\_functions) | Map of CloudFront Function configurations. Key is used as default function name if 'name' not specified. | <pre>map(object({<br/> name = optional(string)<br/> runtime = optional(string, "cloudfront-js-2.0")<br/> comment = optional(string)<br/> publish = optional(bool)<br/> code = string<br/> key_value_store_associations = optional(list(string))<br/> }))</pre> | `null` | no |
| <a name="input_comment"></a> [comment](#input\_comment) | Any comments you want to include about the distribution. | `string` | `null` | no |
| <a name="input_continuous_deployment_policy_id"></a> [continuous\_deployment\_policy\_id](#input\_continuous\_deployment\_policy\_id) | Identifier of a continuous deployment policy. This argument should only be set on a production distribution. | `string` | `null` | no |
| <a name="input_create_cloudfront_function"></a> [create\_cloudfront\_function](#input\_create\_cloudfront\_function) | Controls if CloudFront Functions should be created | `bool` | `false` | no |
| <a name="input_create_distribution"></a> [create\_distribution](#input\_create\_distribution) | Controls if CloudFront distribution should be created | `bool` | `true` | no |
| <a name="input_create_monitoring_subscription"></a> [create\_monitoring\_subscription](#input\_create\_monitoring\_subscription) | If enabled, the resource for monitoring subscription will created. | `bool` | `false` | no |
| <a name="input_create_origin_access_control"></a> [create\_origin\_access\_control](#input\_create\_origin\_access\_control) | Controls if CloudFront origin access control should be created | `bool` | `false` | no |
Expand Down Expand Up @@ -186,6 +269,7 @@ No modules.
| <a name="output_cloudfront_distribution_status"></a> [cloudfront\_distribution\_status](#output\_cloudfront\_distribution\_status) | The current status of the distribution. Deployed if the distribution's information is fully propagated throughout the Amazon CloudFront system. |
| <a name="output_cloudfront_distribution_tags"></a> [cloudfront\_distribution\_tags](#output\_cloudfront\_distribution\_tags) | Tags of the distribution's |
| <a name="output_cloudfront_distribution_trusted_signers"></a> [cloudfront\_distribution\_trusted\_signers](#output\_cloudfront\_distribution\_trusted\_signers) | List of nested attributes for active trusted signers, if the distribution is set up to serve private content with signed URLs |
| <a name="output_cloudfront_functions"></a> [cloudfront\_functions](#output\_cloudfront\_functions) | The CloudFront Functions created |
| <a name="output_cloudfront_monitoring_subscription_id"></a> [cloudfront\_monitoring\_subscription\_id](#output\_cloudfront\_monitoring\_subscription\_id) | The ID of the CloudFront monitoring subscription, which corresponds to the `distribution_id`. |
| <a name="output_cloudfront_origin_access_controls"></a> [cloudfront\_origin\_access\_controls](#output\_cloudfront\_origin\_access\_controls) | The origin access controls created |
| <a name="output_cloudfront_origin_access_controls_ids"></a> [cloudfront\_origin\_access\_controls\_ids](#output\_cloudfront\_origin\_access\_controls\_ids) | The IDS of the origin access identities created |
Expand Down
1 change: 0 additions & 1 deletion examples/complete/.gitignore

This file was deleted.

12 changes: 8 additions & 4 deletions examples/complete/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
# Complete CloudFront distribution with most of supported features enabled
# Complete CloudFront Distribution

Configuration in this directory creates CloudFront distribution which demos such capabilities:

- access logging
- origins and origin groups
- caching behaviours
- Origin Access Identities (with S3 bucket policy)
- Origin Access Control (recommended over OAI)
- Lambda@Edge
- CloudFront Functions
- Response Headers Policies
- ACM certificate
- Route53 record
- VPC Origins
Expand All @@ -15,9 +19,9 @@ Configuration in this directory creates CloudFront distribution which demos such
To run this example you need to execute:

```bash
$ terraform init
$ terraform plan
$ terraform apply
terraform init
terraform plan
terraform apply
```

Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources.
Expand Down
46 changes: 46 additions & 0 deletions examples/complete/functions/ab-testing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
function handler(event) {
// A/B testing function using CloudFront Functions
// Assigns users to test groups and routes to different origin paths

var request = event.request;
var headers = request.headers;
var cookies = request.cookies;

// Check if user already has an A/B test cookie
var abTestGroup = null;

if (cookies['ab-test-group']) {
abTestGroup = cookies['ab-test-group'].value;
} else {
// Assign new users to a test group (50/50 split)
// Use CloudFront viewer ID for consistent assignment
var viewerId = event.viewer.address;
var hash = 0;

for (var i = 0; i < viewerId.length; i++) {
hash = ((hash << 5) - hash) + viewerId.charCodeAt(i);
hash = hash & hash; // Convert to 32bit integer
}

abTestGroup = (Math.abs(hash) % 2 === 0) ? 'A' : 'B';

// Note: CloudFront Functions cannot set cookies
// You would set this cookie in the response (using viewer-response function)
// or via JavaScript on the client side
}

// Route to different paths based on test group
var uri = request.uri;

if (abTestGroup === 'B' && !uri.startsWith('/variant-b/')) {
// Rewrite path for variant B users
request.uri = '/variant-b' + uri;
}

// Add header for origin to know which variant was served
headers['x-ab-test-group'] = {
value: abTestGroup
};

return request;
}
46 changes: 46 additions & 0 deletions examples/complete/functions/kvstore-redirect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
function handler(event) {
// CloudFront Function with KeyValueStore integration
// Uses KV store for dynamic URL redirects and feature flags

var request = event.request;
var uri = request.uri;

// Note: To use KeyValueStore, associate the KV store ARN with this function
// Example: key_value_store_associations = ["arn:aws:cloudfront::123456789012:key-value-store/redirects"]

// Uncomment when KV store is associated:
/*
var kvsHandle = event.context.kvs;

// Look up redirect mapping in KeyValueStore
var redirectTarget = kvsHandle.get(uri);

if (redirectTarget) {
// Redirect to target URL from KV store
var response = {
statusCode: 301,
statusDescription: 'Moved Permanently',
headers: {
'location': { value: redirectTarget },
'cache-control': { value: 'max-age=3600' }
}
};
return response;
}

// Check feature flags in KV store
var featureFlags = kvsHandle.get('feature-flags');
if (featureFlags) {
var flags = JSON.parse(featureFlags);

// Add feature flag headers for origin
if (flags.enableNewUI) {
request.headers['x-feature-new-ui'] = { value: 'true' };
}
}
*/

// For now, return request as-is
// When KV store is configured, uncomment the code above
return request;
}
35 changes: 35 additions & 0 deletions examples/complete/functions/viewer-request-security.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
function handler(event) {
// Viewer request function to add security headers and normalize cache keys
// This function runs before CloudFront checks the cache

var request = event.request;
var headers = request.headers;

// Normalize Host header for consistent caching
if (headers.host) {
headers.host.value = headers.host.value.toLowerCase();
}

// Remove query parameters that don't affect content (for better cache hit ratio)
var uri = request.uri;
var querystring = request.querystring;

// Example: Remove tracking parameters but keep content-affecting ones
var allowedParams = ['id', 'page', 'category'];
var newQuerystring = {};

for (var param in querystring) {
if (allowedParams.includes(param)) {
newQuerystring[param] = querystring[param];
}
}

request.querystring = newQuerystring;

// Add custom header for logging/debugging
headers['x-viewer-country'] = {
value: event.viewer.country || 'unknown'
};

return request;
}
42 changes: 42 additions & 0 deletions examples/complete/functions/viewer-response-headers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
function handler(event) {
// Viewer response function to add security and performance headers
// This function runs after CloudFront receives the response from origin

var response = event.response;
var headers = response.headers;

// Add security headers
headers['strict-transport-security'] = {
value: 'max-age=63072000; includeSubdomains; preload'
};

headers['x-content-type-options'] = {
value: 'nosniff'
};

headers['x-frame-options'] = {
value: 'DENY'
};

headers['x-xss-protection'] = {
value: '1; mode=block'
};

headers['referrer-policy'] = {
value: 'strict-origin-when-cross-origin'
};

// Add cache control for static assets
if (event.request.uri.match(/\.(jpg|jpeg|png|gif|ico|css|js|woff|woff2)$/)) {
headers['cache-control'] = {
value: 'public, max-age=31536000, immutable'
};
}

// Add custom header to identify CloudFront Functions processing
headers['x-cloudfront-function'] = {
value: 'viewer-response-headers'
};

return response;
}
Loading