Skip to content
Open
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
135 changes: 135 additions & 0 deletions README.md

Large diffs are not rendered by default.

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

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** (lightweight JavaScript execution at edge locations)
- Response Headers Policies
- ACM certificate
- Route53 record
- VPC Origins

## CloudFront Functions

This example demonstrates CloudFront Functions integration with the module:

**Functions included:**

- `viewer-request-security.js` - Security headers and cache key normalization
- `viewer-response-headers.js` - Add security response headers
- `ab-testing.js` - A/B testing with path rewriting
- `kvstore-redirect.js` - Example with CloudFront KeyValueStore integration (commented)

**Features demonstrated:**

- Module-managed CloudFront Functions creation
- Function association with cache behaviors
- Runtime selection (cloudfront-js-2.0)
- KeyValueStore association pattern

## Usage

To run this example you need to execute:
Expand Down
46 changes: 46 additions & 0 deletions examples/complete/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/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;
}
121 changes: 121 additions & 0 deletions examples/complete/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -176,13 +176,28 @@ module "cloudfront" {
cache_policy_name = "Managed-CachingOptimized"
origin_request_policy_name = "Managed-UserAgentRefererHeaders"
response_headers_policy_name = "Managed-SimpleCORS"
# using a response header policy you're dynamically creating below
# response_header_policy: "cors_policy"

function_association = {
# Valid keys: viewer-request, viewer-response

# Option 1: Direct ARN reference to standalone resource
viewer-request = {
function_arn = aws_cloudfront_function.example.arn
}

# Option 2: Dynamic reference to module-managed function by name
# Uncomment to use module-managed functions instead:
# viewer-request = {
# function_name = "viewer-request-security"
# }

# viewer-response = {
# function_name = "viewer-response-headers"
# }

# For this example, using standalone function for both
viewer-response = {
function_arn = aws_cloudfront_function.example.arn
}
Expand Down Expand Up @@ -231,6 +246,112 @@ module "cloudfront" {
locations = ["NO", "UA", "US", "GB"]
}

# CloudFront Functions - module managed
create_cloudfront_function = true
cloudfront_functions = {
viewer-request-security = {
runtime = "cloudfront-js-2.0"
comment = "Security headers and cache key normalization"
code_path = "functions/viewer-request-security.js"
publish = true
}
viewer-response-headers = {
runtime = "cloudfront-js-2.0"
comment = "Add security response headers"
code_path = "functions/viewer-response-headers.js"
publish = true
}
ab-testing = {
runtime = "cloudfront-js-2.0"
comment = "A/B testing function"
code_path = "functions//ab-testing.js"
publish = true
}
# Example with KeyValueStore association (uncomment and provide actual KV store ARN)
# kvstore-redirect = {
# runtime = "cloudfront-js-2.0"
# comment = "Function using CloudFront KeyValueStore for dynamic redirects"
# code = file("${path.module}/kvstore-redirect.js")
# publish = true
# key_value_store_associations = [
# "arn:aws:cloudfront::123456789012:key-value-store/example-redirects"
# ]
# }
}

create_response_headers_policy = true
response_headers_policy = {
cors_policy = {
name = "CORSPolicy"
comment = "CORS configuration for API"

cors_config = {
access_control_allow_credentials = true
origin_override = true

access_control_allow_headers = {
items = ["*"]
}

access_control_allow_methods = {
items = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
}

access_control_allow_origins = {
items = ["https://example.com", "https://app.example.com"]
}

access_control_expose_headers = {
items = ["X-Custom-Header", "X-Request-Id"]
}

access_control_max_age_sec = 3600
}
}
custom_headers = {
name = "CustomHeadersPolicy"
comment = "Add custom response headers"

custom_headers_config = {
items = [
{
header = "X-Powered-By"
override = true
value = "MyApp/1.0"
},
{
header = "X-API-Version"
override = false
value = "v2"
},
{
header = "Cache-Control"
override = true
value = "public, max-age=3600"
}
]
}
}
remove_headers = {
name = "RemoveHeadersPolicy"
comment = "Remove unwanted headers from origin"

remove_headers_config = {
items = [
{
header = "x-robots-tag"
},
{
header = "server"
},
{
header = "x-powered-by"
}
]
}
}
}

}

######
Expand Down
35 changes: 35 additions & 0 deletions examples/complete/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/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
Loading