IMPROVED: Update Route53 record sets on EC2 instance state change

Florian | Sep 23, 2019

This is a follow up on a previous post from last year which used systemd to update the DNS records on instance boot.

I am using this for bastion hosts which are only started when I need to use them stopped as soon I don’t require them anymore. But in the day-to-day usage I encountered an inconvenience and it was nagging me that it was not using IPv6.

A few days ago someone else started to use the setup and during the following discussion and feedback I decided to make a few improvements.

The previous version did not remove the DNS records on instance shutdown which could result in connecting to a random host if the public IP was reassigned to another instance. So if you tried connecting via SSH before the systemd unit had run or you did not start the instance because you thought it was already running you may get the “WARNING: REMOTEHOST IDENTIFICATION HAS CHANGED!” error.

Also the old version also did not support IPv6 addresses because it was using the EC2 public IPv4 hostname as a CNAME.

You could also use scripts and systemd units to fix those two issues but actually there is a better solution using CloudWatch Events and a Lambda function that does not require any configuration or installation on the EC2 instance. This means you could extend this functionality to any EC2 instance including Windows instances.

To avoid the warning about a changed SSH host key if the IP address was reassigned we will set IPv4 and IPv6 address for the hostname to a reserved IP address when the instance is stopped. We will use IPs that are reserved for documentation by RFC5737 (192.0.2.1) and RFC3849 (2001:db8::1) to avoid conflicts with IPs that may be in use within your network.

If you do not use IPv6 in your VPC - no problem. The Lambda function will remove the IPv6 record set if there is no IPv6 address available for the instance.

The Solution in one sentence: We will use a CloudWatch Events rule for EC2 instance state changes and trigger a Lambda function that will use a tag of the instance to update the corresponding Route53 record sets.

Let’s get started by creating an IAM service role for the Lambda function and assign the AWSOpsWorksCloudWatchLogs managed policy to it. Now attach the following policy allowing to get instance details and update Route53 record sets.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": ["ec2:DescribeInstances", "route53:ChangeResourceRecordSets"],
      "Resource": "*",
      "Effect": "Allow"
    }
  ]
}

Now create a Python 3.7 Lambda function with the role we just created and the source code that is availalbe here: https://gitlab.com/ydkn/aws-ec2-route53-update/blob/master/lambda_function.py

If all or most of your hostnames will be in the same Route53 zone you can also set the environment variable ROUTE53_ZONEID to a zone ID that will be used as a default if the EC2 instance does not set a zone ID.

After we have set up our Lambda function we create the CloudWatch Event rule with the following pattern:

{
  "detail-type": ["EC2 Instance State-change Notification"],
  "source": ["aws.ec2"],
  "detail": {
    "state": ["running", "stopped"]
  }
}

And we pass the matched event to our Lambda function. Finally, we simple set the tag ROUTE53_HOSTNAME on all EC2 instances to a FQDN that will be updated if an instance is started or stopped.

That’s it!

You can find the complete source code for this here: https://gitlab.com/ydkn/aws-ec2-route53-update

The repository also contains a Cloudformation template and a Terraform module to get you started quickly.

And to make it even more convenient, here are some Cloudformation Quick-Create links for all regions that will get you started with only 3 clicks!