AWS Startups Blog

Building a Serverless Dynamic DNS System with AWS

By Sean Greathouse, Solutions Architect, AWS


Modern workers are mobile and connected, and they expect to have access to the tools they need from anywhere. When people need to access systems on their network, from the internet, they need to know the public IP address of the network. Early-stage startups, small businesses, and home networks often have dynamic public IP addresses that can change without notice. Because of this changing address, you can’t reliably access systems on these networks from the outside. Dynamic DNS systems solve this problem by running a software agent inside your network to keep a DNS record updated with your public IP address. As long as the DNS record is up to date, you can find your network.

In this post, we describe how to build your own dynamic DNS system with a small script and several AWS services. There are other systems that provide similar solutions; however, building a serverless system using nothing but AWS services and a few lines of code is simple, cost-effective, and an example of how to build your own serverless solutions on AWS.

AWS Services We Use in Our Dynamic DNS system

In the next sections, we show you how to use the following AWS services to build a dynamic DNS microservice:

  • AWS Lambda allows you to run Python, Java, or Node.js code without having to manage the underlying server. Your code is always ready to run, but you are charged only per execution, in 100 ms increments. Lambda can manage other AWS services through the AWS SDKs.
    Lambda functions can be triggered on a schedule, by Amazon API Gateway, or by events from other AWS services.
  • Amazon API Gateway is a managed service that helps you build a public API front end for backend services running on Amazon EC2, Lambda, or any other web application.
  • Amazon Route 53 is a managed DNS service that allows you to register and host domains and DNS zones from a global network of DNS servers. As with all AWS services, Route 53 can be managed through APIs.
  • Amazon S3 is highly available, highly durable object storage service. It’s often used for static asstets like images, but is also a good place to store configuration or other information for serverless and stateless systems.

Logical Flow of our Dynamic DNS System

The following illustration shows how a client finds its own IP address by making an API request to a service built with API Gateway and Lambda.

API request to a service built with API Gateway and Lambda

Now that the client knows its public IP, it makes another request to our service to set a DNS record. Lambda consults a configuration file in S3 to validate the request. If the check passes, Lambda then sets the DNS entry in Route 53 via an API call. Now the network’s current IP is in public DNS and can be found by a standard DNS query.

Lambda then sets the DNS entry in Route 53 via an API call

Benefits of Dynamic DNS with AWS Lambda and Amazon Route 53

There are other Dynamic DNS systems available, here are some of the advantages of our system.

  • Easy to set up. There is a sample client along with all the code, config, and instructions to set this up in your own AWS account.
  • Thin to no client. It takes only three commands to update the API. You can write your own client in most languages and run it on platforms including Windows, Linux, OS X, Raspberry Pi, Chrome OS and DD-WRT / Tomato USB router firmware.
  • Supports an arbitrary number of clients, hostnames, and domains.
  • Inexpensive. $1 — $2/mo. Route 53 zones each cost $0.50/month, 250,000 DNS queries cost $0.01, and 10,000 Lambda requests to update DNS cost under $0.01.
  • Serverless microservice. The code runs in Lambda, so there is none of the expense or maintenance associated with running your own Linux host.
  • Secure. Granular permissions allow only authorized clients to update their own hostname. Clients can update the system only from the address that is being added to DNS.
  • Only minor changes required to your current DNS setup. You can leave your primary example.com zone with your current DNS provider and use a secondary dynamic.example.com zone in AWS.
  • Open sourced code and expandable architecture. Add your own features to the system, or stay tuned for a follow-up post. We plan to show you how to open access from your network or roaming laptop into your Amazon EC2 instances using an enhanced version of the system.

Prerequisites

  • An Amazon Web Services (AWS) account. New accounts are eligible for the AWS Free Tier.
  • A domain you own, hosted on Amazon Route 53 or another provider. You can register domains through Route 53 for as little as $10/year.

Building a Dynamic DNS System in Your Own AWS Account

At this point, you have enough information to start building your own copy of the system. If you want to learn more about how it works, read on. If you want to start building, visit our Git repository for illustrated instructions and all the necessary code and config.

Detailed View of How the System Works

First, the client needs to find the public IP assigned to its network. If you make a request from your network to a service on the Internet, that service sees the request coming from your external IP address.

In our system, you call the API in Get mode, and it reflects the public IP address back in the response:

https://....amazonaws.com/prod?mode=get
{“return_message”: “176.32.100.36”, “return_status”: “success”}

Behind the scenes, API Gateway converts the request to JSON, and passes the requestor’s IP address to a Python Lambda function. Lambda then sends a JSON response with the IP back to the client via API Gateway.

The following illustration shows a request to get a public IP.

request to get a public IP

The client now builds a request token by concatenating the public IP address returned from the Get request, the DNS hostname, and a shared secret. For example, if your IP address is 176.32.100.36, your hostname is host1.dyn.example.com, and your shared secret is shared_secret_1, the concatenated string will be the following:

176.32.100.36host1.dyn.example.comshared_secret_1

Next, the client generates an SHA-256 hash from the string:

echo -n 176.32.100.36host1.dyn.example.comshared_secret_1 | shasum -a 256
96772404892f24ada64bbc4b92a0949b25ccc703270b1f6a51602a1059815535

The client requests the DNS update by passing the plaintext hostname as a key and the hash as the authentication token:

https://MY_API_ID.execute-api.us-west-2.amazonaws.com/prod?mode=set&hostname=host1.dyn.example.com&hash=96772404892f24ada64bbc4b92a0949b25ccc703270b1f6a51602a1059815535

API Gateway again passes the request back to the Lambda function. The Lambda function copies its JSON configuration file from Amazon S3 using the AWS SDK for Python (Boto 3). All AWS services can be queried and modified using SDKs. In this system, interactions between Lambda, S3, and Route 53 use Boto 3, which is available natively in Lambda. Once our Lambda function loads the config from S3, it uses the hostname as a key to find the shared secret, and other config associated with that record:

{
"host1.dyn.example.com.": {
            "aws_region": "us-west-2",
            "route_53_zone_id": "MY_ZONE_ID",
            "route_53_record_ttl": 60,
            "route_53_record_type": "A",
            "shared_secret": "SHARED_SECRET_1"
       },
       "host2.dyn.example.com.": {.....

The client passed host1.dyn.example.com as the key, so Lambda reads SHARED_SECRET_1 from the config, and rebuilds the hash token using the hostname, the requestor’s IP address, and the shared secret. If the hash calculated by Lambda and the hash received from the client match, then the request is considered valid.

Once the request is validated, Lambda uses the information from the config to make an API call to Route 53 to see if the DNS hostname is already set with the client IP. If no change is necessary, Lambda responds to the client and exits:

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

If there is no record, or if the current record and the client IP do not match, Lambda makes an API call to Route 53 to set the record, responds to the client, and exits:

{“return_message”: “Your hostname record host1.dyn.example.com. has been set to 176.32.100.36”, “return_status”: “success”}

The following illustration shows the request to set the hostname.

request to set the hostname

Security Features of This System

  • All communications with API Gateway are encrypted.
  • The shared secret is never transmitted across the Internet.
  • The authentication mechanism is multi-factor because the client presents the shared secret (“something it has”) and its own public IP address (“something it is”).
  • The blast radius of leaked credentials is minimized. If a bad actor does get a copy of a hostname/shared secret pair, the only action the bad actor can take is to change the IP address associated with a single DNS record to their own current public IP.
  • The config file can be encrypted at rest via S3 server-side encryption.
  • Your AWS credentials are not used, so they cannot be leaked.

Conclusion

The Dynamic DNS system we describe in this post shows how you can create your own microservice on AWS to solve a real-world problem. We hope you find this system useful either to run your own dynamic DNS on AWS or as an example of how you can use our services to create your own solutions at any scale.

You can visit the project’s Git repository for a complete set of code, configuration, and instructions.