Skip to content

Commit

Permalink
feat(terraform): redirects naked domain to www (#1093)
Browse files Browse the repository at this point in the history
Closes: #1093
  • Loading branch information
dgrebb committed Dec 23, 2023
1 parent 5860640 commit 302e6e5
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 58 deletions.
31 changes: 27 additions & 4 deletions _tf/modules/cdn/cdn.tf
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,22 @@ resource "aws_cloudfront_origin_access_identity" "this" {

resource "aws_cloudfront_function" "subdir" {
name = "subdir-index-${var.dashed_domain}"
runtime = "cloudfront-js-1.0"
runtime = "cloudfront-js-2.0"
comment = "Redirect subdirectory root to index.html"
publish = true
code = file("${path.module}/ref/subdir-index.js")
}

resource "aws_cloudfront_function" "redirect" {
count = var.redirect ? 1 : 0
name = "naked-redirect-${var.dashed_domain}"
runtime = "cloudfront-js-2.0"
comment = "Redirect naked domain to www"
publish = true
code = file("${path.module}/ref/naked-redirect.js")
}


resource "aws_cloudfront_distribution" "this" {
comment = var.domain
price_class = "PriceClass_100"
Expand Down Expand Up @@ -70,9 +80,22 @@ resource "aws_cloudfront_distribution" "this" {
]
}

function_association {
event_type = "viewer-request"
function_arn = aws_cloudfront_function.subdir.arn
# Replaced by naked-redirect below, when redirect is true
dynamic "function_association" {
for_each = var.redirect ? [] : [1]
content {
event_type = "viewer-request"
function_arn = aws_cloudfront_function.subdir.arn
}
}

# Test this on STG by first setting redirect = true on `www_cdn` in main.tf
dynamic "function_association" {
for_each = var.redirect ? [1] : []
content {
event_type = "viewer-request"
function_arn = aws_cloudfront_function.redirect[0].arn
}
}

compress = true
Expand Down
1 change: 1 addition & 0 deletions _tf/modules/cdn/inputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ variable "bucket" {}
variable "log_enabled" {}
variable "log_bucket" {}
variable "cert" {}
variable "redirect" {}
77 changes: 77 additions & 0 deletions _tf/modules/cdn/ref/naked-redirect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* @description: Handler entry point.
* - Note AWS CloudFront Functions use a modified ECMAScript 5.1 compatible runtime and NOT NodeJS.
* - Use var, not const or let
* - Use string concatenation and not template literals
* - Beware that the CloudFront Functions Console editor and test environment do NOT mimic CloudFront 100%
* @date 2022-01-26
* @param {object} event: A CloudFront Function event (expecting a Viewer Request event)
* @return {object}: A CloudFront response or request object (depends whether conditions allow pass through or 301 redirect)
*/
function handler(event) {
var request = event.request;
var queryString = queryStringObjectToString(request.querystring);
var uri = request.uri;

// Copy request.headers since it is read-only and we want to return a modified set of headers
var headers = JSON.parse(JSON.stringify(request.headers));
var host = headers.host.value;

// Check to see if we have a properly formed or naked domain request
if (host.startsWith("www.")) {
// All good, nothing to change, pass on the request as is

// Check whether the URI is missing a file name.
if (uri.endsWith("/")) {
request.uri += "index.html";
}
// Check whether the URI is missing a file extension.
else if (!uri.includes(".")) {
request.uri += "/index.html";
}

return request;
} else {
// Prefix the host with the missing www.
host = "www." + host;
// Construct the url with assumed/forced https://www. prefix plus, path and any query string parameters
var url = "https://" + host + uri + queryString;
// Append the new 301 location header
headers.location = { value: url };
// Remove the original host header to avoid a circular reference of never ending redirects
delete headers.host;

// Return a CloudFront specific response object that includes original path, query string, (most) headers and cookies
return {
statusCode: 301,
statusDescription: "Moved Permanently",
headers,
cookies: request.cookies,
};
}
}

/**
* @description: Converts an event.request.querystring object back to a query string
* @date 2022-01-26
* @param {object} queryStringObject: Query string object as provided by event.request
* @return {string}: A normalised query string in the form ?key1=valn&key2=val2...
*/
function queryStringObjectToString(queryStringObject) {
// Convert the query string object to an array of entries and reduce to a single string
return (
Object.entries(queryStringObject)
.reduce((p, q) => {
if (!q[1].multiValue) {
// Process a single key/value property
return (p += q[0] + "=" + q[1].value + "&");
} else {
// Process multiValue properties. E.g arrays
return (p +=
q[1].multiValue.map((v) => q[0] + "=" + v.value).join("&") + "&");
}
}, "?")
// Remove the trailing ampersand
.slice(0, -1)
);
}
56 changes: 2 additions & 54 deletions _tf/modules/network/cdn-dns/cdn-dns.tf
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ resource "aws_route53_record" "this" {
}

resource "aws_route53_record" "wildcard_validation" {
provider = aws.acm_provider
provider = aws.acm_provider

for_each = {
for dvo in aws_acm_certificate.wildcard.domain_validation_options : dvo.domain_name => {
Expand All @@ -53,60 +53,8 @@ resource "aws_route53_record" "wildcard_validation" {
}

resource "aws_acm_certificate_validation" "wildcard" {
provider = aws.acm_provider
provider = aws.acm_provider
certificate_arn = aws_acm_certificate.wildcard.arn

validation_record_fqdns = [for record in aws_route53_record.wildcard_validation : record.fqdn]
}


# resource "aws_route53_record" "this" {
# allow_overwrite = true
# name = var.domain
# type = "A"
# zone_id = var.zone.zone_id
# alias {
# name = var.distribution.domain_name
# zone_id = var.distribution.hosted_zone_id
# evaluate_target_health = false
# }
# }

# resource "aws_acm_certificate" "this" {
# # CloudFront Certs MUST be in us-east-1
# provider = aws.acm_provider
# domain_name = var.domain
# validation_method = "DNS"

# lifecycle {
# create_before_destroy = true
# }

# tags = {
# Name = var.domain
# }
# }

# resource "aws_route53_record" "validation" {
# for_each = {
# for dvo in aws_acm_certificate.this.domain_validation_options : dvo.domain_name => {
# name = dvo.resource_record_name
# record = dvo.resource_record_value
# type = dvo.resource_record_type
# }
# }

# allow_overwrite = true
# name = each.value.name
# records = [each.value.record]
# ttl = 60
# type = each.value.type
# zone_id = var.zone.zone_id
# }

# resource "aws_acm_certificate_validation" "this" {
# provider = aws.acm_provider
# certificate_arn = aws_acm_certificate.this.arn

# validation_record_fqdns = [for record in aws_route53_record.validation : record.fqdn]
# }
1 change: 1 addition & 0 deletions _tf/modules/network/inputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ variable "www_cdn" {}
variable "uploads_cdn" {}
variable "www_record_overwrite" {}
variable "reports_cdn" {}
variable "redirect" {}
4 changes: 4 additions & 0 deletions _tf/prd/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module "www_cdn" {
log_enabled = true
log_bucket = module.www_cdn_bucket.log_bucket
cert = module.network.wildcard_cert
redirect = true
}

module "uploads_cdn" {
Expand All @@ -24,6 +25,7 @@ module "uploads_cdn" {
log_enabled = true
log_bucket = module.uploads_cdn_bucket.log_bucket
cert = module.network.uploads_cert
redirect = false
}

module "containers" {
Expand Down Expand Up @@ -71,6 +73,7 @@ module "network" {
uploads_cdn = module.uploads_cdn.cf_distribution
reports_cdn = module.reports_cdn.cf_distribution
www_record_overwrite = true
redirect = false
}

module "scaling" {
Expand Down Expand Up @@ -128,6 +131,7 @@ module "reports_cdn" {
log_enabled = false
log_bucket = false
cert = module.network.reports_cert
redirect = false
}

module "reports_bucket" {
Expand Down
4 changes: 4 additions & 0 deletions _tf/stg/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ module "www_cdn" {
log_enabled = true
log_bucket = module.www_cdn_bucket.log_bucket
cert = module.network.wildcard_cert
redirect = false
}

module "uploads_cdn" {
Expand All @@ -30,6 +31,7 @@ module "uploads_cdn" {
log_enabled = true
log_bucket = module.uploads_cdn_bucket.log_bucket
cert = module.network.uploads_cert
redirect = false
}

module "containers" {
Expand Down Expand Up @@ -77,6 +79,7 @@ module "network" {
uploads_cdn = module.uploads_cdn.cf_distribution
reports_cdn = module.reports_cdn.cf_distribution
www_record_overwrite = true
redirect = false
}

module "scaling" {
Expand Down Expand Up @@ -135,6 +138,7 @@ module "reports_cdn" {
log_enabled = false
log_bucket = false
cert = module.network.reports_cert
redirect = false
}

module "reports_bucket" {
Expand Down

0 comments on commit 302e6e5

Please sign in to comment.