Nov 8, 2017
Users of Amazon Web Services are
used to having Route53 available to provide DNS records within
their clouds. However, if you’re a government contractor or an
agency, your deployments likely live on an Amazon GovCloud environment. And on that
environment, Route53 is not yet available. So does that mean you
should forego deploying any services that need custom DNS records
on GovCloud? Of course not!
We will show how to circumvent
this restriction by deploying our own DNS server inside a GovCloud
VPC in a way that makes it easy to update and manage zone
records.
Pre-requisites
Terraform
AWS command-line tools — i.e. aws-cli
Amazon GovCloud credentials configured for aws-cli
Git for version control
Terraform is used to ensure
reproducible environments between engineers. You should read more
how it and other technologies help streamline operations
productivity here.
We assume you will be using a terminal emulator
under a UNIX or UNIX-like operating system. Your GovCloud account
must have enough availability in its EC2 quota for at least one
more instance. If you never had to deal with quotas before, you
likely will not exceed your quota with the extra instance and can
ignore this point. Examples preceded by $ means the line is supposed to be typed and run from your terminal emulator:
Initial setup
We will create an empty environment for deployment, and add fpco-terraform-aws,
which includes the module you will need. FP complete created and
open-sourced fpco-terraform-aws as a collection of modules that
ease managing resources in an AWS cloud. As you will see below, a
few of its modules are used, and they considerably shorten the
amount of code you need to build your environment. We will need a
git repository to bring in fpco-terraform-aws, and it’s also good practice to always version
your infrastructure. This way, not only you but your team can
collaborate in developing and operating it:
Add the usual AWS credentials and your cloud environment variables to main.tf.
Now we need to create a VPC and add our DNS to it. Include the
following:
This is our basic VPC, with
still only Amazon DNS available, and no Route53 in sight. It
already allows us access to the internet, but has no private parts.
This should be enough for our scenario, but in a real setting, you
will want to protect your resources with private subnets and
additional machinery. Next, we will add an EC2 instance with BIND
pre-installed, which will receive our configuration
changes.
Creating a BIND config
We will now add BIND. Create a file named dns.tf and add it alongside main.tf in the repo, with the following contents:
You will also need a few other parameters on the dns module, and security groups for
ports used by BIND, as well as internet access. Adding these is
easy, and it’s a good exercise to fill in the gaps before deploying
the examples we’re giving here.
As we can see from the snippet above, the dns module expects to receive at least named.conf.options, named.conf.local and a folder of pre-generated zone
records files. For BIND options, we want to ensure that it forwards
requests for which we are not authoritative servers upstream to
Amazon’s DNS on the VPC. The following is an example of how you
could accomplish that with data/dns/templates/named.conf.options:
You will need to double check allow-query on a real environment. The IP under forwarders is a fixed address always available
inside AWS VPCs. This ensures other names other than those under
our own management can be resolved.
Adding your zones
You will then need to tell BIND which zones we respond to authoritatively. This happens under both named.conf.options and the DNS resource records (RRs) files. Let’s start with named.conf.options first:
Great. Now, whenever a query comes for yourdomain.com,
BIND will look for it locally and return whatever it has under its
RRs. This solves the Route53 part of being able to answer for DNS
records for ourselves. It still does not solve the dynamic updates
part that the Route53 API provides us. For that, we will change the
RRs directly. Whenever that happens and we run Terraform, BIND will
read back those changes, achieving the same functionality. Here is
an example of data/dns/templates/db_records/db.yourdomain.com:
RRs and directives for DNS
follow a straightforward format set by RFC 1035 alongside a few
later amendments. The snippet above is a text representation of
this format used by BIND. Whenever you would have wanted to call
the Route53 API to add a record on your behalf, you will instead
add the resource record directly on those files and get Terraform
to apply the changes.
Testing
Once you have terraform applyed the configuration
above — alongside the security groups and other changes not shown —
you should SSH into the host and test that it’s capable of
resolving queries to your own zone, as well as to other
zones:
Both queries should return an
IP. If the first doesn’t, then you cannot resolve names using the
Amazon-provided DNS. If the second fails, there is an error in your
RRs. Make sure that BIND is running on your instance by
using systemctl status bind9 and checking
your system journal. You can later turn this into a test suite and
add an alarm whenever some names fail resolving, prompting a DevOps
engineer to verify the situation.
Finally, we need to change who provides DNS for the VPC by changing the vpc module parameters:
From that point on, other resources will start
resolving using the BIND instance. Use the templates as your
interface to DNS, and terraform apply to keep in sync.
Where to go next
The previous configuration works
just fine, but has quite a few things missing to make it into a
production-grade environment. For a start, it has only a public
subnet, meaning your DNS server is exposed to the entire internet.
Without further security, it could be used for attacks on this and
other environments. Secondly, if the instance fails, there is some
time until auto-recovery kicks in, time during which the entire VPC
remains unable to resolve names. There are various strategies to
mitigate both issues, but they are beyond the scope for this
article. Lastly, you might have sensitive data that needs to live
in the BIND server. For those cases, you should take a look
at another of our blog posts.
In case you need further support with GovCloud
to ensure important government-sensitive data is kept reliable,
available and manageable, get in touch with us.
Related Articles
Intro to DevOps on GovCloud
Containerizing a legacy application: an overview
Manage Secrets on AWS with Credstash and Terraform