Terraforming DNS with AWS Route53
Tags: terraform, aws, dns, route53
Published: 2020-10-12 01:17:20

---
title: Terraforming DNS with AWS Route53
published: true
date: 2020-10-09 00:00:00 UTC
tags: terraform,aws,dns,route53

canonical_url: https://bitsofknowledge.net/2020/10/09/terraforming-dns-with-aws-route53/
---

This post is part of a series about Terraform and AWS. You can check the other ones at:

1. [First steps with Terraform in AWS](https://bitsofknowledge.net/2020/09/30/first-steps-with-terraform-in-aws/)
2. Terraforming DNS with AWS Route53 (this post)

---

[AWS Route53](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/Welcome.html) is a DNS service used to perform three main functions: domain registration, DNS routing, and health checking. In this post, we are going to cover how to automate the configuration of AWS Route53 as your DNS service using [Terraform](https://www.terraform.io/). If you are not familiar with Terraform, you can check my introductory post [here](https://bitsofknowledge.net/2020/09/30/first-steps-with-terraform-in-aws/).

If you want to go straight to the code, you can check it out at [aws-terraform-examples/02-terraforming-dns-with-aws-route53](https://github.com/renehernandez/aws-terraform-examples/tree/master/02-terraforming-dns-with-aws-route53).

Already back? Great! Let's dive in.

## Basic concepts [#](#basic-concepts)

Before getting into the implementation, let's briefly mention some of the essential concepts around DNS and AWS Route53

- Name servers: Servers in the DNS that help translate domain names ([www.example.com](http://www.example.com/)) into IP addresses. They can be either [DNS resolver](https://en.wikipedia.org/wiki/Domain_Name_System#DNS_resolvers) or [authoritative name servers](https://en.wikipedia.org/wiki/Domain_Name_System#Authoritative_name_server).
- Hosted zones: A container for DNS records, including information about how to route traffic for a domain ([example.com](http://example.com/)) as well as its subdomains ([sub.example.com](http://sub.example.com/)).
- DNS record: A particular entry in the hosted zone that specifies the traffic routing for the domain or subdomain.
- Time To Live (TTL): The amount of time, in seconds, that the DNS resolver should cache the values for a record before submitting another request to the authoritative name servers.

This was just a general overview of the concepts that we are going to be leveraging for our infrastructure configuration. Let's move now to configure our DNS in AWS using Terraform.

## Implementation [#](#implementation)

To fully work with the code examples in this section, it is recommended that you use a domain that you own since you would need to configure the AWS Route53 nameservers on your domain registrar settings.

### Configuring zone and nameservers [#](#configuring-zone-and-nameservers)

The first step to configure the DNS service for your domain is to create the public hosted zone, which you can declare in Terraform as shown below.

```hcl
resource "aws_route53_zone" "example" {
  name = "example.com"
}
```

As part of the creation of Route53 zones, the name server (NS) record, and the start of a zone of authority (SOA) record are automatically created by AWS. By using the `allow_overwrite` option below, Terraform is then capable of managing them in a single execution without the need for a subsequent `terraform import`.

```hcl
resource "aws_route53_record" "nameservers" {
  allow_overwrite = true
  name = "example.com"
  ttl = 3600
  type = "NS"
  zone_id = aws_route53_zone.example.zone_id
  records = aws_route53_zone.example.name_servers
}
```

After this, if you are using a domain registrar other than Route53, you will need to add the name servers associated with your zone in Route53 to the configuration settings on your domain registrar website.

### Configuring email [#](#configuring-email)

Let's go through a possible email configuration as an example. We are going to be using [ProtonMail](https://protonmail.com/) as our email server of choice.

#### Verification [#](#verification)

First, we need to set up email verification. This is required by the email service to confirm that you own the domain. In the following example, we create a [TXT record](https://en.wikipedia.org/wiki/TXT_record) to hold the verification value.

```hcl
resource "aws_route53_record" "protonmail_txt" {
  zone_id = aws_route53_zone.example.zone_id
  name = ""
  type = "TXT"
  ttl = 300
  records = ["protonmail-verification=<random_number>"]
}
```

The _TXT_ record gets associated with the top-level domain in the zone by pointing to our previously created AWS Route53 zone using the `zone_id` attribute and setting the `name` attribute to empty. This effectively makes the _TXT_ record refer to `example.com` in this scenario.

#### MX records [#](#mx-records)

The [MX record](https://en.wikipedia.org/wiki/MX_record) specifies the mail server responsible to receive your domain's email.

```hcl
resource "aws_route53_record" "protonmail_mx" {
  zone_id = aws_route53_zone.example.zone_id
  name = ""
  type = "MX"
  ttl = 1800
  records = [
    "10 mail.protonmail.ch.",
    "20 mailsec.protonmail.ch."
  ]
}
```

In _TXT_ record configuration above, we are associating two ProtonMail servers for email deliverability. A lower number means a higher priority, therefore emails will be sent first to `mail.protonmail.ch` server and `mailsec.protonmail.ch` as a fallback in case of failure. Similar to the _TXT_ record discussed before, the _MX_ record is associated with the top-level `example.com` domain.

#### Sender Policy Framework (SPF) [#](#sender-policy-framework-(spf))

It is an authentication mechanism to validate that an email coming from a particular domain is being sent by an authorized IP address. For SPF, we need to use a _TXT_ record entry specifying that the ProtonMail servers are authorized to send the emails. Since we already have a record resource representing a _TXT_ record, we will reuse it and add a new entry in its `records` attributes as shown below.

```hcl
resource "aws_route53_record" "protonmail_txt" {
  zone_id = aws_route53_zone.example.zone_id name = ""
  type = "TXT"
  ttl = 1800
  records = [
    "protonmail-verification=<random_number>",
    "v=spf1 include:_spf.protonmail.ch mx ~all"
  ]
}
```

#### DomainKeys Identified Mail (DKIM) [#](#domainkeys-identified-mail-(dkim))

It is another authentication technique that leverages cryptography to verify that email is sent by trusted servers. To manage the ProtonMail keys, we need to configure [CNAME records](https://en.wikipedia.org/wiki/CNAME_record) to hold the public encryption keys. These keys will be used by the receiving servers to validate the emails, making sure that they haven't been tampered. Below, we define three new _CNAME_ records in Terraform, one for each public encryption key provided by the email service.

```hcl
resource "aws_route53_record" "protonmail_dkim_1" {
  zone_id = aws_route53_zone.example.zone_id
  name = "protonmail._domainkey"
  type = "CNAME"
  ttl = 1800
  records = ["domain_key1"]
}

resource "aws_route53_record" "protonmail_dkim_2" {
  zone_id = aws_route53_zone.example.zone_id
  name = "protonmail2._domainkey"
  type = "CNAME"
  ttl = 1800
  records = ["<domain_key2>"]
}

resource "aws_route53_record" "protonmail_dkim_3" {
  zone_id = aws_route53_zone.example.zone_id
  name = "protonmail3._domainkey"
  type = "CNAME"
  ttl = 1800
  records = ["domain_key3"]
}
```

### Gotcha [#](#gotcha)

While reading several online guides, it is common to see the usage of `@` as a hostname to refer to the top-level domain (in this post `example.com`). In AWS Route53, that doesn't work and instead, it is necessary to set the `name` attribute as empty to point to the root domain in the zone where the record is being added.

## Resources [#](#resources)

Below, it is a condensed list of all the resources mentioned throughout the post as well as a few others I consider may be of interest to deepen your knowledge.

**DNS:**

- [DNS Overview](https://en.wikipedia.org/wiki/Domain_Name_System)
- [MX record](https://en.wikipedia.org/wiki/MX_record)
- [CNAME record](https://en.wikipedia.org/wiki/CNAME_record)
- [Amazon Route53 DNS concepts](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/route-53-concepts.html)

**AWS Route53:**

- [Overview](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/Welcome.html)
- [Concepts](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/route-53-concepts.html)

**Terraform:**

- [AWS Route53 record](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone)
- [AWS Route53 record](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record)

**Email:**

- [Sender Policy Framework (SPF)](https://en.wikipedia.org/wiki/Sender_Policy_Framework)
- [DomainKeys Identified Mail (DKIM)](https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail)
- [ProtonMail Antispoofing](https://protonmail.com/support/knowledge-base/anti-spoofing/)

## Conclusions [#](#conclusions)

Let's briefly recap what we discussed in this post. First, we looked at some of the basic concepts around AWS Route53 and DNS. Next, we looked at how to attach a domain to AWS Route53 by using public hosted zone records and the nameservers. Afterward, we went through the steps of configuring an email service on our domain using different DNS records such as _MX_, _TXT_ and _CNAME_. Finally, I analyzed a common gotcha regarding the usage of `@` as the hostname and listed common resources mentioned throughout the post.

Thank you so much for reading this post. Hope you enjoyed reading it as much as I did writing it. See you soon and stay tuned for more!!