Skip to content

Instantly share code, notes, and snippets.

@craeckor
Last active March 25, 2024 20:49
Show Gist options
  • Save craeckor/06dc9a40596b640ddd4ff4b5049c167d to your computer and use it in GitHub Desktop.
Save craeckor/06dc9a40596b640ddd4ff4b5049c167d to your computer and use it in GitHub Desktop.
#!/bin/bash
#
# Cloudflare DNS-Records updater script V1.4
# Made by craeckor
#
# This script updates your Cloudflare DNS-Records.
# Requirements: bash, jq, curl and awk.
# API-Token (Not API-Key) is required.
# Only works for one specific TLD (Top Level Domain) per script. (E.g example.com) Multiple subdomains are supported.
# Only works for A-records, AAAA-records are not supported yet.
# This script logs everything under /var/log/dns-updater, except if there where no changes made.
# This script respects privacy and hides the API-Token and your IP.
# API-Token permission: Zone.DNS.Write (Edit)
#
# Docs: Create API token: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/
# Docs: Cloudflare API: https://developers.cloudflare.com/api/
# Docs: API token permissions: https://developers.cloudflare.com/fundamentals/api/reference/permissions/
#
# Get current date and time for naming logs
date=$(date +"%Y-%m-%d_%H-%M-%S")
# Get creation time of the log
create_log=$(date +"%Y-%m-%d %H:%M:%S")
# Function to get current date and time in log format
log_date() {
echo "$(date +"%Y-%m-%d %H:%M:%S")"
}
# Function to get current date and time for log messages
get_date() {
echo "$(date +"%Y-%m-%d_%H-%M-%S") - Cloudflare-DNS-Updater: "
}
# Check if log directory exists, if not, create it
if [ ! -d "/var/log/dns-updater" ]; then
mkdir -p /var/log/dns-updater
fi
# Define log file path
log="/var/log/dns-updater/dns-updater_$date.log"
# Cloudflare API token
api_token="CLOUDFLARE_API_KEY"
# List of subdomains to update
subdomains=("subdomain1.example.org" "subdomain2.example.org" "subdomain3.example.org" "...")
# Extract first subdomain and its top-level domain
first_domain="${subdomains[0]}"
tld=$(echo "$first_domain" | awk -F'.' '{print $(NF-1)"."$NF}')
# Counter for counting updated DNS records
counter=0
# Get public IP address
ip=$(curl https://api.ipify.org -s)
# Mask API token for logging
first_chars="${api_token:0:5}"
last_chars="${api_token: -5}"
middle_chars="*****"
masked_token="${first_chars}${middle_chars}${last_chars}"
# Mask IP address for logging
IFS="." read -r -a ip_sections <<< "$ip"
ip_sections[1]="***"
ip_sections[2]="***"
masked_ip=$(IFS="."; echo "${ip_sections[*]}")
# Log start details
echo "$(get_date)Create Log" >> $log
echo "Start-Date: $create_log" >> $log
echo "Script-Path: $0" >> $log
echo -e "\n" | tee -a $log
echo "$(get_date)API-Token: $masked_token" >> $log
echo "$(get_date)Subdomains: ${domains[*]}" >> $log
echo "$(get_date)Domain: $tld" >> $log
echo "$(get_date)IP: $masked_ip" >> $log
echo -e "\n" | tee -a $log
# Validate API token
token_validation=$(curl -s --request GET \
--url https://api.cloudflare.com/client/v4/user/tokens/verify \
--header "Authorization: Bearer $api_token" \
--header "Content-Type: application/json")
valid_token=$(echo $token_validation | jq -r '.success')
# Log token validation result
echo "$(get_date)Token Validation: $valid_token" >> $log
# If token is not valid, exit with error message
if [ "$valid_token" = true ]; then
echo "$(get_date)API-Token OK" | tee -a $log
else
echo "$(get_date)API-Token is not valid. Generate a valid one at https://dash.cloudflare.com/profile/api-tokens." | tee -a $log
echo "$(get_date)Docs: https://developers.cloudflare.com/fundamentals/api/get-started/create-token" | tee -a $log
echo "$(get_date)Exit with Error Code 1..." | tee -a $log
exit 1
fi
# Get Zone ID
zone_data=$(curl -s --request GET \
--url https://api.cloudflare.com/client/v4/zones?name=$tld \
--header "Authorization: Bearer $api_token" \
--header "Content-Type: application/json")
zone_id=$(echo $zone_data | jq -r '.result[].id')
# Log Zone ID
echo "$(get_date)Zone Data: $zone_id" >> $log
# If Zone ID is empty, exit with error message
if [ "$zone_id" = "" ];then
echo "$(get_date)No read and write permission for Zone $tld is granted or Zone $tld doesn't exist. Edit API-Token at https://dash.cloudflare.com/profile/api-tokens and give it Edit permission on Zone.DNS and specify the $tld Zone." | tee -a $log
echo "$(get_date)Docs: https://developers.cloudflare.com/fundamentals/api/reference/permissions/" | tee -a $log
echo "$(get_date)Exit with Error-Code 1..." | tee -a $log
exit 1
fi
# Generate a temporary subdomain for permission validation
temp_subdomain=$(date +%s | sha256sum | base64 | head -c 33 ; echo)
# Validate permission for adding DNS record
permission_validation=$(curl -s --request POST \
--url https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records \
--header "Authorization: Bearer $api_token" \
--header "Content-Type: application/json" \
--data "{
\"content\": \"254.254.254.254\",
\"name\": \"$temp_subdomain.$tld\",
\"proxied\": false,
\"type\": \"A\",
\"comment\": \"API-Token verification record\",
\"ttl\": 1
}")
valid_permission=$(echo $permission_validation | jq -r '.success')
# Log permission validation result
echo "$(get_date)Permission Validation: $valid_permission" >> $log
# If permission is not valid, exit with error message
if [ "$valid_permission" = true ]; then
validation_record_id=$(echo $permission_validation | jq -r '.result.id')
delete_validation=$(curl -s --request DELETE \
--url https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$validation_record_id \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $api_token")
delete_id=$(echo $delete_validation | jq -r '.result.id')
echo "$(get_date)Temporary Validation Record Name: $temp_subdomain.$tld" >> $log
echo "$(get_date)Temporary Validation Record ID: $validation_record_id" >> $log
echo "$(get_date)Temporary Validation Record Deletion: Done" >> $log
else
echo "$(get_date)No write permission for Zone $tld is granted. Edit API-Token at https://dash.cloudflare.com/profile/api-tokens and change Read to Edit at Zone.DNS." | tee -a $log
echo "$(get_date)Docs: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/" | tee -a $log
echo "$(get_date)Exit with Error-Code 1..." | tee -a $log
exit 1
fi
# Loop through each subdomain to update DNS records
for subdomain in "${subdomains[@]}"; do
echo -e "\n" | tee -a $log
subdomain_data=$(curl -s --request GET \
--url https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?name=$subdomain \
--header "Authorization: Bearer $api_token" \
--header "Content-Type: application/json")
subdomain_id=$(echo $subdomain_data | jq -r '.result[].id')
subdomain_ip=$(echo $subdomain_data | jq -r '.result[].content')
# If subdomain record doesn't exist, log it
if [ "$subdomain_id" = "" ]; then
echo "$(get_date)Record for $subdomain doesn't exist." | tee -a $log
else
# If subdomain IP is the same as current IP, log no changes
if [ "$subdomain_ip" = "$ip" ]; then
echo "$(get_date)DNS-Record Name: $subdomain" | tee -a $log
echo "$(get_date)DNS-Record ID: $subdomain_id" >> $log
echo "$(get_date)DNS-Record: No content change detected" | tee -a $log
else
# If subdomain IP differs, update the DNS record
subdomain_proxy=$(echo $subdomain_data | jq -r '.result[].proxied')
subdomain_ttl=$(echo $subdomain_data | jq -r '.result[].ttl')
record_update=$(curl -s --request PUT \
--url https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$subdomain_id \
--header "Authorization: Bearer $api_token" \
--header "Content-Type: application/json" \
--data "{
\"content\": \"$ip\",
\"name\": \"$subdomain\",
\"proxied\": $subdomain_proxy,
\"type\": \"A\",
\"ttl\": $subdomain_ttl
}")
echo "$(get_date)DNS-Record Name: $subdomain" | tee -a $log
echo "$(get_date)DNS-Record ID: $subdomain_id" >> $log
echo "$(get_date)DNS-Record Proxy: $subdomain_proxy" >> $log
echo "$(get_date)DNS-Record TTL: $subdomain_ttl" >> $log
echo "$(get_date)DNS-Record updated successfully" | tee -a $log
counter=$((counter + 1))
fi
fi
done
# Log end details
echo -e "\n" | tee -a $log
echo "End-Date: $(log_date)" >> $log
echo "$(get_date)End Log" >> $log
# If no DNS records were updated, delete the log
if [ $counter = 0 ]; then
rm -f $log
echo "Log got deleted, since nothing changed."
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment