Last active
October 5, 2022 03:55
-
-
Save 22phuber/20bba64b2ffca42595d3dece454c74ab to your computer and use it in GitHub Desktop.
Dynamic DNS update for AWS Route 35
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# Refactored by: Patrick Huber (22phuber@gmail.com) | |
# Description: Digs the current external Ipv4 and compares it to a Ipv4 locally stored in a file. | |
# If the ips differ, aws cli is used to update a route 53 dns record with the new Ipv4. | |
# This script only supports Ipv4. | |
# Original script source: | |
# - https://willwarren.com/2014/07/03/roll-dynamic-dns-service-using-amazon-route53/ | |
# Dependencies: | |
# - awscli # install AWS cli: https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html | |
# - dig | |
# - mktemp | |
# - grep | |
# | |
set -e | |
set -o pipefail | |
# INITIALIZE | |
{ | |
echo "[$(date)] Starting \"${BASH_SOURCE[0]}\"" \ | |
&& trap finish EXIT \ | |
&& trap 'Error in line: ${LINENO}' ERR \ | |
&& cd "$(dirname "${BASH_SOURCE[0]}")"; | |
} || exit 1 | |
# VARIABLES | |
# AWS profile | |
aws_profile='default' | |
# AWS cli path | |
aws_binary_path='/usr/local/bin' | |
# set new PATH for AWS binary | |
if [[ ! ${PATH} == *"${aws_binary_path}"* ]]; then | |
PATH=${PATH}:${aws_binary_path} | |
fi | |
# ipfile (caches last runs ip) | |
declare -r IPFILE="update-route53.ip" | |
# Hosted Zone ID e.g. BJBK35SKMM9OE | |
declare -r ZONEID="<your zone id>" | |
# The recordset you want to update e.g. example.com or www.example.com | |
declare -r RECORDSET="<you recordset>" | |
# The Time-To-Live of this recordset | |
declare -r TTL=300 | |
# Use type A when using IPv4 address | |
declare -r TYPE="A" | |
# Change this if you want | |
COMMENT="Auto updating @ $(date)" | |
# FUNCTIONS | |
# desc: prints the date and "script finished" text | |
# usage: checkAwsCli | |
function finish () { | |
echo -e "[$(date)] Script finished\n" | |
} | |
# desc: check if aws cli is present | |
# usage: checkAwsCli | |
# returns: | |
# 0 => if aws cli exists | |
# 1 => if aws cli could not be found | |
function checkAwsCli () { | |
if [[ -x "$(command -v aws)" ]]; then # True if file exists and is executable. | |
return 0 | |
else | |
echo "Function [${FUNCNAME[0]}]: AWS cli is missing." | |
return 1 | |
fi | |
} | |
# desc: simple validation for a given ipv4 address | |
# checks: | |
# - correct ip parts | |
# - part values between 0 and 255 (inclusive) | |
# usage: validate_ipv4 "<ipv4>" | |
# returns: | |
# 0 => if validation was successful | |
# 1 => if validation has failed | |
function validate_ipv4() { | |
if [[ $# -lt 1 ]] ; then | |
echo "Function [${FUNCNAME[0]}]: Wrong argument count. Nothing validated" | |
return 1 | |
fi | |
local __ip="${1}" | |
declare -a __ip_arr=${__ip//./ } | |
if [[ ${__ip} =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then | |
for ip_part in ${__ip_arr[@]}; do | |
if [[ ! ${ip_part} -le 255 ]]; then return 1; fi | |
done | |
return 0 | |
fi | |
return 1 | |
} | |
# desc: chcek if given string is present in the given file | |
# usage: find_string_in_file "<string>" file | |
# returns: | |
# 0 => if string has been found | |
# 1 => if string has NOT been found | |
function find_string_in_file() { | |
if [[ $# -lt 2 ]] ; then | |
echo "Function [${FUNCNAME[0]}]: Wrong argument count. Nothing done" | |
return 1 | |
fi | |
local __string_to_find="${1}" | |
shift 1 | |
local __file="${1}" | |
grep -Fxq "${__string_to_find}" "${__file}" && return 0 | |
return 1 | |
} | |
# desc: caches given ipv4 in given file | |
# usage: cache_ipv4 "<ipv4>" file | |
# 0 => if caching was successful | |
# 1 => if caching failed | |
function cache_ipv4 () { | |
if [[ $# -lt 2 ]] ; then | |
echo "Function [${FUNCNAME[0]}]: Wrong argument count. Nothing cached" | |
return 1 | |
fi | |
local __ip="${1}" | |
shift 1 | |
local __file="${1}" | |
validate_ipv4 "${__ip}" \ | |
&& echo "${__ip}" > "${__file}" \ | |
&& find_string_in_file "${__ip}" "${__file}" \ | |
&& return 0 | |
return 1 | |
} | |
# desc: update route 53 record | |
# usage: update_route53_record "<zoneid>" "<ipv4>" "<recordset>" "<type>" "<ttl>" "<comment>" "<aws-profile>" | |
# 0 => if route 53 record update was successful | |
# 1 => if route 53 record update failed | |
function update_route53_record () { | |
if [[ $# -lt 7 ]] ; then | |
echo "Function [${FUNCNAME[0]}]: Wrong argument count. Nothing updated" | |
return 1 | |
fi | |
local __zoneid="${1}"; shift | |
local __ip="${1}"; shift | |
local __recordset="${1}"; shift | |
local __type="${1}"; shift | |
local __ttl="${1}"; shift | |
local __comment="${1}"; shift | |
local __aws_profile="${1}"; | |
local __tmpFile; | |
__tmpFile=$(mktemp /var/tmp/aws_route53_change-resource-record-set.json.XXXXXXXX) | |
# trap temp file deletion on RETURN signal | |
trap 'rm -rf "${__tmpFile}"' RETURN | |
# heredoc section | |
cat << EOF > "${__tmpFile}" | |
{ | |
"Comment":"${__comment}", | |
"Changes":[ | |
{ | |
"Action":"UPSERT", | |
"ResourceRecordSet":{ | |
"ResourceRecords":[ | |
{ | |
"Value":"${__ip}" | |
} | |
], | |
"Name":"${__recordset}", | |
"Type":"${__type}", | |
"TTL":${__ttl} | |
} | |
} | |
] | |
} | |
EOF | |
# Update the route 53 record | |
aws --profile "${__aws_profile}" route53 change-resource-record-sets \ | |
--hosted-zone-id "${__zoneid}" \ | |
--change-batch "file://${__tmpFile}" \ | |
&& return 0 | |
return 1 | |
} | |
# MAIN | |
checkAwsCli || exit 1 | |
# Get the external IP address | |
# https://unix.stackexchange.com/questions/22615/how-can-i-get-my-external-ip-address-in-a-shell-script | |
# example1: dig @resolver1.opendns.com A myip.opendns.com +short -4 | |
# example2: dig @ns1-1.akamaitech.net ANY whoami.akamai.net +short -4 | |
# example3: dig @ns1.google.com TXT o-o.myaddr.l.google.com +short -4 | sed -e 's#"##g' | |
IPv4=$(dig @resolver1.opendns.com A myip.opendns.com +short -4) | |
{ | |
# validate ipv4 | |
validate_ipv4 "${IPv4}" \ | |
&& touch "${IPFILE}"; # touching ipfile (creates it, if it does not exist) | |
} || exit 1 | |
# Check current ip with previously save ip from the ipfile | |
if (find_string_in_file "${IPv4}" "${IPFILE}" ); then | |
echo "External IPv4 is still the same." | |
exit 0 | |
else | |
echo "External Ipv4 has changed to ${IPv4}" | |
# Update the record when the ip has changed | |
if (update_route53_record "${ZONEID}" "${IPv4}" "${RECORDSET}" "${TYPE}" "${TTL}" "${COMMENT}" "${aws_profile}" ); then | |
echo "AWS Route 53 record \"${RECORDSET}\" with type \"${TYPE}\" has been updated with value \"${IPv4}\" in zone ${ZONEID}" | |
cache_ipv4 "${IPv4}" "${IPFILE}" | |
exit 0 | |
else | |
echo "Failed to update AWS Route 53 record: ${RECORDSET}" | |
exit 1 | |
fi | |
fi | |
exit 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment