Naked Domain Nightmare!
Since the release of AWS Elastic Load Balancer (ELB) in 2009, system administrators have struggled with the fundamentals of Internet: zone apex and DNS.
If you are not that familiar with Domain Name System, let's start by looking at the internals of domain names:
Fully qualified domain name www.mydomain.fi consists of Top level Domain (TLD) .fi, second level domain mydomain and subdomain www. Zone apex aka naked domain (aka root domain) is the second level domain without any sublevel domain defined, in our example mydomain.fi.
So what's all this fuzz about? Due to the long history of DNS and the evolution of the Internet, there lies a problem in the DNS RFC (RFC1033). RFC requires zone apex to be an A record and not for example CNAME. A record points to a IP address, CNAME points to a fully qualified domain name.
AWS Elastic Load Balancers (Classic and Application) are referred with FQDNs, for example mydomain.eu-west-1.elb.amazonaws.com. Due to the limitation described above, it is impossible to forward traffic to zone apex mydomain.fi into ELB directly using traditional DNS. Dexterous system administrators have resolved this from running single EC2 instances just to host NGINX redirects to full blows HA architecture with chain of load balancers to provide a IP address for the zone apex A record.
Route 53 (DNSaaS in AWS) offers ALIAS record to tacle this, but it is only available to zones hosted in Route 53 service.
In November 2018 AWS announced a new service call Global Accelerator. In a nutshell it is a Static Anycast IP that servers as a fixed entry point to your application.
Keyword: Static IP address..
So we came up with an idea to host a simple but rigid and bullet proof HTTP/S redirect service. It would be built with high abstraction services from AWS, meaning there is slim to none maintenance overhead. No EC2 instances, no Lambdas to look after.
Design consists of a Global Accelerator piece that offers 2 static IPs to be utilized as A records in DNS entries for zone apex. As we automate everything, including automation, and we are heavy users of Terraform, here are the example code snippets for required AWS resources:
resource "aws_globalaccelerator_accelerator" "redirect" {
name = "redirect"
ip_address_type = "IPV4"
enabled = true
}
resource "aws_globalaccelerator_listener" "redirect" {
accelerator_arn = "${aws_globalaccelerator_accelerator.redirect.id}"
client_affinity = "SOURCE_IP"
protocol = "TCP"
port_range {
from_port = 80
to_port = 80
}
port_range {
from_port = 443
to_port = 443
}
}
resource "aws_globalaccelerator_endpoint_group" "redirect" {
listener_arn = "${aws_globalaccelerator_listener.redirect.id}"
endpoint_configuration {
endpoint_id = "${aws_lb.redirect.arn}"
weight = 100
}
}
Second piece is AWS Application Load Balancer with default actions that send HTTP status code 301: permanently moved with domain name including the www subdomain. We also promote HTTP to HTTPS whenever possible.
resource "aws_lb" "redirect" {
name = "redirect"
load_balancer_type = "application"
internal = false
idle_timeout = 400
subnets = ["${local.aws_subnet_public}"]
security_groups = ["${local.aws_security_group_public_www}"]
enable_http2 = true
tags {
Name = "redirect"
}
}
resource "aws_lb_listener" "redirect" {
load_balancer_arn = "${aws_lb.redirect.arn}"
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-2015-05"
certificate_arn = "${var.cert_arn}"
default_action {
type = "redirect"
redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
host = "www.mydomain.fi"
path = "/#{path}"
query = "#{query}"
}
}
After Terraform has performed an apply, Global Accelerator has reserved you 2 static IP addresses that are available for your zone apex in DNS:
mydomain.fi A 1.2.3.4
While this is not the ideal use of IPV4 address space and might not end up in the AWS Best Practices white paper, this is what we do, mitigate customer problems with nuke resilient designs!