I’ve been making some enhancements to my home server recently. For the last installment of this series, see my last post about the move to Route53, using LetsEncrypt.
In this post, I’ll show how I set up a simple dynamic DNS updater service using AWS Lambda. This allows us to update a DNS record in Route53 with the latest IP address with any value we want.
Dynamic DNS With Route53
This is the source code of my lambda. All of the configuration in the environment variables. It accepts HTTP get requests, and updates the DNS record for the given domain with the given IP. It also checks that the domain is in the list of allowed domains/subdomains, and requires a secret API key to prevent abuse.
import { Route53Client, ChangeResourceRecordSetsCommand } from "@aws-sdk/client-route-53";
const HOSTED_ZONE_ID = process.env.HOSTED_ZONE_ID
const ALLOWED_RECORDS = JSON.parse(process.env.ALLOWED_RECORDS)
const client = new Route53Client({ region: 'us-east-1' });
export const handler = async (event) => {
// only allow known API KEY
if (event.queryStringParameters?.api_key !== process.env.API_SECRET) {
return {
statusCode: 403,
body: "Forbidden"
}
}
// get args from query
const dns_record_name = event.queryStringParameters?.domain
const new_ip = event.queryStringParameters?.ip
// check that this record is allowed to be edited
if (!ALLOWED_RECORDS.includes(dns_record_name)) {
return {
statusCode: 403,
body: "Update of " + dns_record_name + " Forbidden"
}
}
// issue change request
const command = new ChangeResourceRecordSetsCommand({
HostedZoneId: HOSTED_ZONE_ID,
ChangeBatch: {
Changes : [{
Action: 'UPSERT',
ResourceRecordSet: {
Name: dns_record_name,
Type: 'A',
ResourceRecords: [
{
Value: new_ip
}
],
TTL: 30
}
}]
}
});
try {
const aws_response = await client.send(command);
// send back AWS response
return {
statusCode: 200,
body: JSON.stringify(aws_response),
};
} catch (err) {
console.error('Error setting DNS', err)
return {
statusCode: 500,
body: 'Internal Server Error'
}
}
};
Environment Variables
HOSTED_ZONE_ID
- The ID of the hosted zone in Route53, find this in the AWS consoleALLOWED_RECORDS
- A JSON array of the DNS records you want to allow to be updated.- Example Value:
["home.example.com."]
- Example Value:
API_SECRET
- A secret key to prevent unauthorized updates, randomly generate this using openssl or your password manager
Lambda URL
I updated the lambda to be called publicly via the web with no authentication, since it handles authentication via a custom API key implementation.
Lambda Execution Role
I then attached this policy to the lambda’s execution role to allow it to modify my hosted zone. Edit to include your hosted zone’s ARN:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowModifyingRecordsInHostedZone",
"Effect": "Allow",
"Action": "route53:ChangeResourceRecordSets",
// TODO: EDIT THIS TO YOUR HOSTED ZONE'S ARN:
"Resource": "arn:aws:route53:::hostedzone/YOURHOSTEDZONEIDHERE"
}
]
}
Executing the Lambda
I can now call this lambda from my home server with a simple curl command. I have a cron job that runs on my router every 5 minutes to update the DNS record with the current IP address.
https://YOUR_FUNCTION.lambda-url.us-east-1.on.aws?api_key=YOUR_KEY&domain=myhome.example.com.&ip=INSERT_IP_HERE
Mine pulls the public ip address from the wan interface of my router. You can also use a service like https://www.ipify.org/ to get your public IP address programmatically.
Since this is a simple HTTP GET request, you could call this from a multitude of other services, scripts, or tools to update your DNS record.