commit 57a7886ae487a5d83f737888e0bea91dab04858b Author: Jake Charman Date: Mon May 25 16:18:07 2020 +0100 Initial Commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..bc11a50 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Cloudflare Updater +Python script to update Cloudflare DNS records when a server's IP address changes + +## Installation: +Get the script and make it executable: + + git clone https://github.com/jcharman/Cloudflare-Updater + chmod +x ./updateCloudflare.py +Copy the example config file into place: + + cp ./updateCloudflare.conf.example updateCloudflare.conf +Edit the file with values for your setup. + +Run the script: + + ./updateCloudflare.py + +## Usage +This script is designed to be run by cron periodically. On each run it will get the server's current IP, check it against the IP last time the script ran and update Cloudflare if necessary. If the script has not been run before it will get the current IP address from Cloudflare. + +NOTE: This script WILL NOT detect manual changes to cloudflare and will only update the IP when it detects that the server's IP address has changed. To force the script to check the current IP in Cloudflare, delete the updateCloudflare.lastip file. \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1470b6a --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +tldextract==2.2.2 \ No newline at end of file diff --git a/updateCloudflare.conf.example b/updateCloudflare.conf.example new file mode 100644 index 0000000..fb8a66c --- /dev/null +++ b/updateCloudflare.conf.example @@ -0,0 +1,6 @@ +# Email to authenticate against cloudlflare +email=your@email.address +# Cloudflare API key. +apiKey=aaabbbcccdddeeefffggghhhiiijjjkkklllm +# Host to be updated. +host=subdomain.domain.tld \ No newline at end of file diff --git a/updateCloudflare.py b/updateCloudflare.py new file mode 100755 index 0000000..885ee7d --- /dev/null +++ b/updateCloudflare.py @@ -0,0 +1,145 @@ +#!/usr/bin/python + +try: + import requests + import json + import tempfile + import tldextract + import os +except ModuleNotFoundError: + print("Missing module, see requirements.txt") + exit(1) + +def getZone(email, apiKey, host): + listZones = requests.get(f"https://api.cloudflare.com/client/v4/zones/", + headers={"X-Auth-Email": f"{email}","X-Auth-Key": f"{apiKey}","Content-Type": "application/json"}).json() + + if listZones["success"] != True: + print("Could not get Zone ID from Cloudflare. Errors were: " + str(listZones["errors"])) + exit(1) + + # Extract the root domain from the full host. + extractedHost = tldextract.extract(host) + domain = (extractedHost.domain + "." + extractedHost.suffix) + + for zone in listZones["result"]: + if zone["name"] == domain: + return zone["id"] + + print("Could not find a Zone ID for the specified domain.") + exit(1) + +def storeIP(ip): + # Store the given IP in the lastip file. + file = open(scriptDir + 'updateCloudflare.lastip', 'w+') + file.write(ip) + +def checkCloudflare(zone, email, apiKey, host): + # Get all the records in the zone. + allRecords = requests.get(f"https://api.cloudflare.com/client/v4/zones/{zone}/dns_records", + headers={"X-Auth-Email": f"{email}","X-Auth-Key": f"{apiKey}","Content-Type": "application/json"}).json() + + # Search for the defined zone in the list and get it's IP. + for currentRecord in allRecords["result"]: + if currentRecord["name"] == host: + recordIP = currentRecord["content"] + print(f"IP of {host} is " + recordIP) + return(recordIP) + +def updateCloudflare(zone, email, apiKey, host, ip): + # Get all records for the given zone. + allRecords = requests.get(f"https://api.cloudflare.com/client/v4/zones/{zone}/dns_records", + headers={"X-Auth-Email": f"{email}","X-Auth-Key": f"{apiKey}","Content-Type": "application/json"}).json() + + # Find the record for the domain we want to work on. + for currentRecord in allRecords["result"]: + if currentRecord["name"] == host: + recordID = currentRecord["id"] + print(f"ID for {host} is " + recordID) + + # Update our record with the new IP. + result = requests.patch(f"https://api.cloudflare.com/client/v4/zones/{zone}/dns_records/" + recordID, + headers={"X-Auth-Email": f"{email}","X-Auth-Key": f"{apiKey}","Content-Type": "application/json"}, + data='{"content":"%s"}' % ip).json() + + # Check if Cloudflare returned any errors. + if result["success"] == True: + print("Cloudflare was successfully updated") + else: + print("Updating failed, errors were: " + str(result["errors"])) + +print( + ''' + ------------------------------ + Cloudflare updater + www.jakecharman.co.uk + ------------------------------ + ''' +) + +# Get the directory of the script. +scriptDir = os.path.dirname(os.path.realpath(__file__)) + "/" + +# Read in parameters from the config file. +try: + configFile = open(scriptDir + "updateCloudflare.conf", "r") +except FileNotFoundError: + print("Configuration file does not exist.") + exit(1) + +configLines = configFile.readlines() +for line in configLines: + if line[0] == "#": + continue + # Remove any spaces from the line then split it to an array on the = sign. + splitLn = line.replace(" ", "").strip().split('#')[0].split("=") + + # Pull in the known config lines. + if (splitLn[0] == "email"): + authEmail = splitLn[1] + elif (splitLn[0] == "apiKey"): + apiKey = splitLn[1] + elif (splitLn[0] == "host"): + hostToUpdate = splitLn[1] + else: + # Error out on an unknown config line. + print("Unknown config: " + splitLn[0]) + exit(1) + +# Get the Zone ID for the given hostname. +zoneID = getZone(authEmail, apiKey, hostToUpdate) + +# Open the temp file. +try: + file = open(scriptDir + 'updateCloudflare.lastip', 'r') + lastip = file.read() +except FileNotFoundError: + lastip = "" + +if lastip == "": + storedIP = False + print("No stored IP... checking Cloudflare API") + cloudflareIP = checkCloudflare(zoneID, authEmail, apiKey, hostToUpdate) +else: + storedIP = lastip + print("Stored IP is " + lastip) + +# Get our external IP +ip = requests.get('https://api.ipify.org').text +print("Our current IP is " + ip) +if storedIP: + if ip == storedIP: + print("IP has not changed since last run... Exiting") + exit(0) + else: + print("IP has changed since last run... Updating Cloudflare") + storeIP(ip) + updateCloudflare(zoneID, authEmail, apiKey, hostToUpdate, ip) +else: + storeIP(ip) + if ip == cloudflareIP: + print("Cloudflare matches our current IP... Exiting") + exit(0) + else: + print("Cloudflare IP does not match our current IP... Updating Cloudflare.") + updateCloudflare(zoneID, authEmail, apiKey, hostToUpdate, ip) \ No newline at end of file