Dynamic DNS with AWS Lambda and DynamoDB

A cluster of Raspberry Pi Servers

Back in 2015, Amazon Web Services (AWS) published a blog post and sample code illustrating how to create a serverless dynamic DNS service using AWS Lambda and API Gateway. I loved the idea and added this to my ever-growing list of projects to tackle “one of these days.”

After deciding to self-host my blog on a Raspberry Pi, I finally got around to implementing this dynamic DNS solution in order to point blog.jasonweddington.com to the dynamic IP address from my ISP. For the most part, everything you need is in the repo accompanying the original post, but there were a few places where I found myself scratching my head. So in this post, I’ll walk through the steps required to implement the solution. Before diving in though, take a look at the AWS post describing the solution to get a sense for what we’re about to build. Note also that since the time this blog post was published, the solution on GitHub has been updated to store DNS configuration in a DynamoDB. This more recent version that used DynamoDB is what we’ll implement below.

Here are the steps required to implement your own dynamic DNS service in AWS:

  1. Clone the git repository
  2. Create a hosted zone in AWS Route 53
  3. Use CloudFormation to deploy the stack in your AWS account
  4. Add an NS record that points a subdomain to AWS Route 53 (if your main DNS is not Route 53)
  5. Create one or more records in DynamoDB for the DNS records you want to manage
  6. Test your setup with curl
  7. Set up a cron job to update the IP address(es) associated with your domain(s)

1. Clone the git repository

First we need to grab the code from GitHub. This repo includes the CloudFormation template we’ll use later to create our infrastructure in AWS.

git clone https://github.com/awslabs/route53-dynamic-dns-with-lambda.git

2. Create a hosted zone in AWS Route 53

If you do not already have a hosted zone in Route 53 for the DNS name(s) you want to manage with the dynamic DNS system, you’ll need to create one now. Navigate to the Route 53 Hosted zones (https://console.aws.amazon.com/route53/v2/hostedzones) section in the AWS console and click “Create hosted zone”:

Create hosted zone

If you plan to use Route 53 for the main domain (e.g. example.com) create a zone called example.com. If you’re only going to manage the DNS for a subdomain with this solution, name the hosted zone accordingly. For example, I’m only managing the blog.jasonweddington.com subdomain:

Hosted Zone Details

Take note of the hosted zone ID (blurred in the screenshot above) and name servers that AWS automatically assigned to your hosted zone. You’ll need these details later.

There are more details on the required Route 53 DNS setup in the repo for this project on GitHub:
https://github.com/awslabs/route53-dynamic-dns-with-lambda/blob/master/documentation/route53_setup.md.

Important: don’t use the name servers from the screenshot above, use the name servers assigned to your hosted zone.

3. Use CloudFormation to deploy the stack in your AWS account

Now we need to edit the CloudFormation template to set a couple of variables. Open route53-ddns.yml in the root of the repo you cloned in step one and set the following values:

route53ZoneName

route53ZoneName: [the name of the hosted zone you created in step 2]

Example:

route53ZoneName: 'blog.jasonweddington.com'

route53ZoneId

route53ZoneId: [the zone ID of the zone you created in step 2]

Example:

route53ZoneId: '12345'


Now we’re ready to create the CloudFormation stack. Go to the CloudFormation section in the AWS console and click “Create stack.” Upload your modified version of route53-ddns.yml and click Next:

Create a CloudFormation stack

Step through the wizard to deploy the stack. It will take a few minutes to create all of the resources. Once complete, take note of the apiKey and apiUrl in the Outputs section in CloudFormation:

Cloud Formation Outputs

4. Add an NS record that points a subdomain to AWS Route 53 (if your main DNS is not Route 53)

If the DNS for your main domain not managed in Route 53, you’ll need to create NS records with your DNS provider to allow select subdomains to be managed by Route 53 so you can use then with the dynamic DNS system.

For example, the DNS for my main domain, jasonweddington.com is managed in Google Domains. In the Google Domains DNS settings for jasonweddington.com, I’ve created NS records to indicate that the DNS for blog.jasonweddington.com is managed by Route 53:

NS records in Google Domains

These are the same name servers that were assigned to your hosted zone in step 2 above. If you’re managing the DNS for your main domain elsewhere, check their documentation for how to delegate a subdomain to Route 53. In the case of Google Domains, this is as simple as creating NS records in the “Custom resource records” section under DNS.

5. Create one or more records in DynamoDB for the DNS records you want to manage

The dynamic DNS solution stores configuration for each managed DNS record in a DynamoDB table that was created by CloudFormation. The table schema is documented here: https://github.com/awslabs/route53-dynamic-dns-with-lambda#configuration-guide. Two example records are created as templates that you can copy.

Navigate to DynamoDB and find the table that was created by CloudFormation. It will be prefixed by CloudFormation stack name, in my case the table is home-dynamic-dns-config. Select one of the example records and chose “Duplicate” from the Actions menu:

Duplicate DynamoDB Record

Edit the comment, hostname, record_type (if needed), and shared_secret fields. I recommend using a password generator to create a strong password for the shared secret:

Copying a DynamoDB Record

6. Test your setup with curl

At this point, your serverless dynamic DNS system is complete! All that’s left is to test the system, and set up a client to regularly update your DNS records(s).

In the API Reference section of the documentation, there are instructions for testing the API with curl. With curl, updating your DNS record is a three step process. Run the below commands in your terminal:

  1. Use the “IP Address Reflector” feature to find out your current public IP address:
    curl -q --ipv4 -s -H 'x-api-key: API_KEY' http://API_URL?mode=getAPI

    Response:

    {"return_message": "11.22.33.44", "return_status": "success"}
  2. Create a hash of your shared secret, current public IP address, and api key.
    mySharedSecret=SECRET /
    myPublicIP=11.22.33.44 /
    myHostname=example.com /
    echo -n "$myPublicIP$myHostname$mySharedSecret" | shasum -a 256
  3. Make an API call to update your DNS record with your current public IP address:
    curl -q --ipv4 -s -H 'x-api-key: API_KEY' "https://API_URL?mode=set&hostname=example.com&hash=HASH"

    Response:

    {"return_message": "Your IP address matches the current Route53 DNS record.", "return_status": "success"}

     

7. Set up a cron job to update the IP address(es) associated with your domain(s)

The AWS project comes with a shell script client that you can use to regularly update your DNS records. I’ve wrapped this script in a simple shell script of my own which I run every hour with cron:

dns_update.sh

#!/bin/bash
date
route53-ddns-client.sh \
--hostname example.com. \
--secret SECRET \
--url https://API_URL \
--api-key API_KEY

There are plenty resources online that explain creating cron jobs, so I won’t cover that here. For Windows users, there’s also a PowerShell client in the repo.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.