Skip to content

Instantly share code, notes, and snippets.

@phybros
Last active February 12, 2024 00:07
Show Gist options
  • Star 87 You must be signed in to star a gist
  • Fork 33 You must be signed in to fork a gist
  • Save phybros/827aa561a44032dd1556 to your computer and use it in GitHub Desktop.
Save phybros/827aa561a44032dd1556 to your computer and use it in GitHub Desktop.
BASH Script to keep Route53 updated with your current external IP address
#!/bin/bash
# (optional) You might need to set your PATH variable at the top here
# depending on how you run this script
#PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Hosted Zone ID e.g. BJBK35SKMM9OE
ZONEID="enter zone id here"
# The CNAME you want to update e.g. hello.example.com
RECORDSET="enter cname here"
# More advanced options below
# The Time-To-Live of this recordset
TTL=300
# Change this if you want
COMMENT="Auto updating @ `date`"
# Change to AAAA if using an IPv6 address
TYPE="A"
# Get the external IP address from OpenDNS (more reliable than other providers)
IP=`dig +short myip.opendns.com @resolver1.opendns.com`
function valid_ip()
{
local ip=$1
local stat=1
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
OIFS=$IFS
IFS='.'
ip=($ip)
IFS=$OIFS
[[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \
&& ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]
stat=$?
fi
return $stat
}
# Get current dir
# (from http://stackoverflow.com/a/246128/920350)
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
LOGFILE="$DIR/update-route53.log"
IPFILE="$DIR/update-route53.ip"
if ! valid_ip $IP; then
echo "Invalid IP address: $IP" >> "$LOGFILE"
exit 1
fi
# Check if the IP has changed
if [ ! -f "$IPFILE" ]
then
touch "$IPFILE"
fi
if grep -Fxq "$IP" "$IPFILE"; then
# code if found
echo "IP is still $IP. Exiting" >> "$LOGFILE"
exit 0
else
echo "IP has changed to $IP" >> "$LOGFILE"
# Fill a temp file with valid JSON
TMPFILE=$(mktemp /tmp/temporary-file.XXXXXXXX)
cat > ${TMPFILE} << EOF
{
"Comment":"$COMMENT",
"Changes":[
{
"Action":"UPSERT",
"ResourceRecordSet":{
"ResourceRecords":[
{
"Value":"$IP"
}
],
"Name":"$RECORDSET",
"Type":"$TYPE",
"TTL":$TTL
}
}
]
}
EOF
# Update the Hosted Zone record
aws route53 change-resource-record-sets \
--hosted-zone-id $ZONEID \
--change-batch file://"$TMPFILE" >> "$LOGFILE"
echo "" >> "$LOGFILE"
# Clean up
rm $TMPFILE
fi
# All Done - cache the IP address for next time
echo "$IP" > "$IPFILE"
@fbouliane
Copy link

Having my linux OS in french, I had this error

'ascii' codec can't encode character u'\xfb' in position 27: ordinal not in range(128)

Turns out the comment containing the date was "Août" (august in french) and since my host is a french ubuntu 14.04, it generates a non-utf8 char for the û character.

Please either pull my fix here : https://gist.github.com/felixbouliane/1f0b1d9de56243887338 or find another way to provide ascii-only char to aws client.

@ghomem
Copy link

ghomem commented Oct 18, 2015

AWS has an internal API that cam be used to fetch the IP. You can use:

IP=wget -qO- http://instance-data/latest/meta-data/public-ipv4

Thanks for the nice script!

@phybros
Copy link
Author

phybros commented Jun 21, 2016

Updated with the latest version from my blog post: https://willwarren.com/2014/07/03/roll-dynamic-dns-service-using-amazon-route53

@dominic-p
Copy link

Thanks for the very helpful script. I tweaked the aws command to give me a consistent format in my log file. It took me a while to get the --query parameter right, so I thought I would share it here in case it helps someone.

aws route53 change-resource-record-sets \
        --hosted-zone-id $ZONEID \
        --change-batch file://"$TMPFILE" \
        --query '[ChangeInfo.Comment, ChangeInfo.Id, ChangeInfo.Status, ChangeInfo.SubmittedAt]' \
        --output text >> "$LOGFILE"

This gives you a single line of plain text (no JSON formatting) output from the command in your log file.

@skwashua
Copy link

Thanks for this! I ended up using curl -4sS "http://checkip.amazonaws.com" for my implementation. Running from Raspberry Pi Zero

@cterrykenzan
Copy link

I've adapted your gist for use in a Jenkins job - we've got a big instance that doesn't need to run all the time, so Jenkins shuts it down after hours. It then starts it up in the morning and runs this to update the DNS record so our staff has seamless access to it. https://gist.github.com/cterrykenzan/7fefc975851a80327323e1290e6a3c57

@Manuel5cc
Copy link

Executing this on a Raspberry pi with raspbian I got this error:

24: ./update-route53.sh Syntax error "(" unexpected

@mattdoesinfosec
Copy link

@Manuel5cc if you are using bash at the head of your script make sure you are executing the script in a bash shell.

@ELLIOTTCABLE
Copy link

ELLIOTTCABLE commented Apr 30, 2017

I've merged some work from a few forks (@rosston's, @d90's, and @dominic-p's modification in the comments above); and added support for the awscli --profile flag (ideal for use with IAM users). My fork is here:

https://gist.github.com/ELLIOTTCABLE/071fef84be5adc46c11eee53db77fc23

While on the topic, here's the “managed policy” you should use for an IAM user that's specifically created to operate as your DynDNS client:

  • Route 53: ListResourceRecordSets and Route 53: ChangeResourceRecordSets against arn:aws:route53:::hostedzone/ZB83EXAMPLEZONEID for every Zone ID your script will be running against,
  • Route 53: GetChange against arn:aws:route53:::change/*

If you implement this as an IAM user, then the access-key/secret-key stored on your dynamic-DNS-updating-machine can't be used to screw with the rest of your account, if compromised.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment