Brendan McKenzie

Redirects with Cloudfront functions

Wednesday, 30 October 2024

Implementing Domain-Level Redirects with AWS CloudFront and Terraform

When building modern web applications, you often need to handle domain redirects efficiently and securely. Whether you're migrating domains, implementing vanity URLs, or managing multiple environments, having a robust redirect system is crucial. In this post, we'll explore how to implement domain-level redirects using AWS CloudFront and Terraform.

Architecture Overview

The solution uses several AWS services working together:

How It Works

  1. When a user visits the domain, Route 53 routes the request to CloudFront
  2. CloudFront executes a CloudFront Function on the viewer-request event
  3. The function returns a 307 redirect response to the desired destination
  4. The browser follows the redirect to the new location

Implementation

Here's the complete Terraform configuration with detailed explanations:

1# CloudFront Distribution
2# This handles the request routing and executes the redirect logic
3resource "aws_cloudfront_distribution" "redirects" {
4  tags = {
5    Name        = "${local.prefix}-redirects"
6    Environment = terraform.workspace
7  }
8  depends_on = [aws_acm_certificate.redirect_cert]
9
10  # Origin configuration (required by CloudFront but not used for redirects)
11  origin {
12    domain_name = "example.com"
13    origin_id   = "noop"
14    custom_origin_config {
15      http_port              = 80
16      https_port             = 443
17      origin_protocol_policy = "http-only"
18      origin_ssl_protocols   = ["TLSv1", "TLSv1.1"]
19    }
20  }
21
22  enabled         = true
23  is_ipv6_enabled = true
24  comment         = "${local.prefix}-redirects distribution"
25
26  # Associate the SSL certificate with the distribution
27  aliases = [aws_acm_certificate.redirect_cert.domain_name]
28
29  viewer_certificate {
30    acm_certificate_arn      = aws_acm_certificate.redirect_cert.arn
31    ssl_support_method       = "sni-only"
32    minimum_protocol_version = "TLSv1.2_2019"
33  }
34
35  # Configure the default behavior to handle all requests
36  default_cache_behavior {
37    allowed_methods  = ["GET", "HEAD"]
38    cached_methods   = ["GET", "HEAD"]
39    target_origin_id = "noop"
40
41    viewer_protocol_policy = "redirect-to-https"
42    min_ttl                = 0
43
44    # Associate the redirect function with the distribution
45    function_association {
46      event_type   = "viewer-request"
47      function_arn = aws_cloudfront_function.redirects.arn
48    }
49    
50    forwarded_values {
51      query_string = false
52      cookies {
53        forward = "none"
54      }
55    }
56
57    compress = true
58  }
59
60  restrictions {
61    geo_restriction {
62      restriction_type = "none"
63    }
64  }
65}
66
67# Route 53 A record to point the domain to CloudFront
68resource "aws_route53_record" "redirect" {
69  zone_id = aws_route53_zone.primary.zone_id
70  name    = ""
71  type    = "A"
72
73  alias {
74    name                   = aws_cloudfront_distribution.redirects.domain_name
75    zone_id                = aws_cloudfront_distribution.redirects.hosted_zone_id
76    evaluate_target_health = false
77  }
78}
79
80# SSL Certificate configuration
81resource "aws_acm_certificate" "redirect_cert" {
82  provider = aws.us-east-1  # Certificates for CloudFront must be in us-east-1
83
84  domain_name       = "example.com"
85  validation_method = "DNS"
86
87  lifecycle {
88    create_before_destroy = true
89  }
90}
91
92# DNS records for certificate validation
93resource "aws_route53_record" "redirect_cert_validation" {
94  for_each = {
95    for dvo in aws_acm_certificate.redirect_cert.domain_validation_options : dvo.domain_name => {
96      name   = dvo.resource_record_name
97      record = dvo.resource_record_value
98      type   = dvo.resource_record_type
99    }
100  }
101
102  allow_overwrite = true
103  name            = each.value.name
104  records         = [each.value.record]
105  ttl             = 60
106  type            = each.value.type
107  zone_id         = aws_route53_zone.primary.zone_id
108}
109
110# Certificate validation
111resource "aws_acm_certificate_validation" "redirect_cert_validation" {
112  provider = aws.us-east-1
113
114  certificate_arn         = aws_acm_certificate.redirect_cert.arn
115  validation_record_fqdns = [for record in aws_route53_record.redirect_cert_validation : record.fqdn]
116}
117
118# CloudFront Function for implementing redirect logic
119resource "aws_cloudfront_function" "redirects" {
120  name    = "oma-domain-redirects"
121  runtime = "cloudfront-js-2.0"
122  comment = "redirects"
123  publish = true
124  code    = <<EOF
125function handler(event) {
126  // Implement a 307 temporary redirect to the target domain
127  var response = {
128    statusCode: 307,
129    statusDescription: "Temporary Redirect",
130    headers: {
131      location: { value: "https://stage.example.com" },
132    },
133  };
134  return response;
135}
136EOF
137}

Key Benefits

  1. Cost-Effective: CloudFront Functions are extremely cost-effective compared to Lambda@Edge
  2. Low Latency: The redirect happens at the edge, providing minimal latency for users
  3. Secure: All traffic is encrypted using SSL/TLS certificates
  4. Scalable: CloudFront automatically handles scaling based on traffic
  5. Maintainable: Infrastructure as code makes it easy to version and maintain

Customisation Options

The CloudFront Function can be customised to implement various redirect patterns:

Conclusion

This Terraform configuration provides a robust foundation for implementing domain-level redirects using AWS CloudFront. The solution is scalable, secure, and cost-effective, making it suitable for both small and large-scale applications.

Remember to replace example.com with your actual domain name and adjust the CloudFront Function logic to match your specific redirect requirements.