From 3fec2ecfabf70cfc5a27f02832f26e48ef3a8974 Mon Sep 17 00:00:00 2001 From: simonl169 <98774388+simonl169@users.noreply.github.com> Date: Fri, 22 Mar 2024 21:59:53 +0100 Subject: [PATCH] Change DynDNS Provider to Cloudflare (#7) * Changed DynDNS registrar to Cloudflare Updated some functions Rewrite to better add multiple domains New Logo * Updated deps * Updated Readme.md * Update README.md Added section on how to obtain Cloudflare credentials and IDs --- .dockerignore | 2 +- .gitignore | 2 +- README.md | 33 ++++++---- config.example.json | 18 +++-- dns_functions.py | 132 ++++++++++++++++++++++++++++--------- docker-compose.example.yml | 3 +- main.py | 58 ++-------------- requirements.txt | 8 +-- 8 files changed, 150 insertions(+), 106 deletions(-) diff --git a/.dockerignore b/.dockerignore index bf70036..6318e95 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,4 @@ -environment +venv __pycache__ .idea .gitignore diff --git a/.gitignore b/.gitignore index 37835e9..0be2412 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ config.json docker-compose.yml -environment +venv __pycache__ .idea diff --git a/README.md b/README.md index c98503a..d347066 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,29 @@ -![GitHub release (with filter)](https://img.shields.io/github/v/release/simonl169/dns-owl) ![GitHub all releases](https://img.shields.io/github/downloads/simonl169/dns-owl/total) -![Static Badge](https://img.shields.io/badge/Python-3.11-blue) - - - - - # dns-owl -This is a little Python script that updates your IP to the DynDNS servers of Strato +This is a little Python script that updates your IP to the DynDNS servers of Cloudflare # Use -You can deploy this container on your server and update your public IP to the Strato DynDNS Servers. You can specify the update intervall in the environment variables using standard cron notation. - -In the config.json you can enter the domains you want to update by a comma-separated list +You can deploy this container on your server and update your public IP to the Cloudflare DynDNS Servers. You can specify the update intervall in the environment variables using standard cron notation. + +In the config.json you can enter the domains you want to update by a comma-separated list. +New for Cloudflare: you have to add your Zone ID, Record IDs and API Key, as well as the Mail to access the Cloudflare API and Update your records. + +# Record ID and Zone ID, API Key +You can get your Zone ID and API Key form your Cloudflare Profile. If you are unsure, refer to the Cloudflare docs. +With Zone ID, Key and Mail, you can run +~~~ +curl --request GET \ + --url https://api.cloudflare.com/client/v4/zones/{YOUR_ZONE_ID}/dns_records \ + --header 'Content-Type: application/json' \ + --header 'X-Auth-Email: name.lastname@domain.com' \ + --header 'X-Auth-Key: {YOUR_API_KEY}' +~~~ +The result contains a list of your subdomains for this record and their corresponding RECORD_ID. # Careful The script waits the specified interval before doing its first update. If you chose a long one (eg. 1 day) it takes as much time for the first update. During this, nothing is written to the log, so it can seems as the container is hung up, which is not the case. +I try to remove this and have it run an update right at the start of the container. I will try to make the logs more verbose in future versions. + +# Strato +Support for Strato is deprecated, since I no longer host my domains there and cannot test any changes. +With the next update I will tr to make the script supporting both. diff --git a/config.example.json b/config.example.json index 391a517..e51c7f1 100644 --- a/config.example.json +++ b/config.example.json @@ -1,7 +1,17 @@ { + + "ZONE_ID": "your_zone_id", + "USER_EMAIL": "name.lastname@mail.com", + "API_KEY": "your_CF_API_key", "domains": [ - "domain1.example.com", - "domain2.example.com", - "domain3.example.com" + { + "RECORD_ID": "record_key_1", + "RECORD_NAME": "sub1.domain.com" + }, + { + "RECORD_ID": "record_key_2", + "RECORD_NAME": "sub2.domain.com" + } ] -} \ No newline at end of file + +} diff --git a/dns_functions.py b/dns_functions.py index 5cb7ae2..e7d3d2f 100644 --- a/dns_functions.py +++ b/dns_functions.py @@ -1,21 +1,31 @@ import requests import pydig +import json -def get_current_public_ip(): - ip = requests.get('https://ident.me') - print('My public IP address is: {}'.format(ip.text)) - return ip.text +def load_config(filename: str): + with open(filename, 'r') as f: + data = json.load(f) + return data -def resolve_current_server_ip(url): - resolver = pydig.Resolver(nameservers=['8.8.8.8']) + +def get_current_public_ip() -> str: + ip_address = requests.get('https://ident.me') + print(f'\tMy public IP address is: {ip_address.text}') + return ip_address.text + + +def resolve_current_server_ip(url, nameservers='8.8.8.8') -> [str, str]: + nameservers = [nameservers] + resolver = pydig.Resolver(nameservers=nameservers) local_ip = pydig.query(url, 'A') public_ip = resolver.query(url, 'A') print(f'\tLocal IP address for {url} is: {local_ip[0]}') print(f'\tPublic IP address for {url} is: {public_ip[0]}') return local_ip[0], public_ip[0] -def compare_ip(ip1, ip2): + +def compare_ip(ip1, ip2) -> bool: print('\tComparing addresses:') print(f'\tIP address 1: {ip1}') print(f'\tIP address 2: {ip2}') @@ -24,32 +34,92 @@ def compare_ip(ip1, ip2): else: return False -def update_dns_ip(url, password): - - update_url = 'https://dyndns.strato.com/nic/update?hostname=' + url + '&thisipv4=1' - #print(update_url) - r = requests.get(update_url, auth=(url, password)) - #print(r.status_code) - #print(r.headers) - #print(r.text) - if 'nochg' in r.text: - print('\tNo update needed!') - print(f'\tFeedback from Strato: {r.text}') - elif 'good' in r.text: - print('\tSuccess!') - print(f'\tFeedback from Strato: {r.text}') - elif 'badauth' in r.text: - print('\tBadauth!') - print('\tYou provided a wrong password or the subdomain does not exist') - elif 'abuse' in r.text: - print('\tAbuse! You probaby update too often') - print(f'\tFeedback from Strato: {r.text}') - else: - print('\tSome other error') - print(f'\tFeedback from Strato: {r.text}') +def set_ip(cloudflare, domain, current_ip: str): + """ + sets the ip in via cloudflare api + """ + zone_id = cloudflare['ZONE_ID'] + api_key = cloudflare['API_KEY'] + user_email = cloudflare['USER_EMAIL'] + + record_id = domain['RECORD_ID'] + record_name = domain['RECORD_NAME'] + + print(f"\tCloudflare Zone ID is: {zone_id}") + print(f"\tCloudflare API Key is: {api_key}") + print(f"\tRecord ID is: {record_id}") + print(f"\tRecord Name is: {record_name}") + + url = ( + "https://api.cloudflare.com/client/v4/zones/%(zone_id)s/dns_records/%(record_id)s" + % {"zone_id": zone_id, "record_id": record_id} + ) + + headers = { + "X-Auth-Email": user_email, + "X-Auth-Key": api_key, + "Content-Type": "application/json", + } + + payload = {"type": "A", "name": record_name, "content": current_ip} + response = requests.put(url, headers=headers, data=json.dumps(payload)) + # print(response.status_code) + # print(response.json()['success']) + + return response + + +def print_owl(): + print(r""" + # _____ _ _ _____ ____ __ __ _ ___ ___ + # | __ \ | \ | | / ____| / __ \\ \ / /| | / _ \ |__ \ + # | | | || \| || (___ | | | |\ \ /\ / / | | __ __| | | | ) | + # | | | || . ` | \___ \ | | | | \ \/ \/ / | | \ \ / /| | | | / / + # | |__| || |\ | ____) | | |__| | \ /\ / | |____ \ V / | |_| |_ / /_ + # |_____/ |_| \_||_____/ \____/ \/ \/ |______| \_/ \___/(_)|____| + + + __________-------____ ____-------__________ + \------____-------___--__---------__--___-------____------/ + \//////// / / / / / \ _-------_ / \ \ \ \ \ \\\\\\\\/ + \////-/-/------/_/_| /___ ___\ |_\_\------\-\-\\\\/ + --//// / / / //|| (O)\ /(O) ||\\ \ \ \ \\\\-- + ---__/ // /| \_ /V\ _/ |\ \\ \__--- + -// / /\_ ------- _/\ \ \\- + \_/_/ /\---------/\ \_\_/ + ----\ | /---- + | -|- | + / | \ + ---- \___| + + # by Simon169 + """) + + +def update_all_ip(current_ip): + data = load_config('config.json') + + cf = data + + for domain in data['domains']: + print(f"{'':#<40}") + print(f"\tUpdating DynDNS IP for domain: {domain['RECORD_NAME']}...") + response = set_ip(cf, domain, current_ip) + + if response.json()['success']: + print(f"\tIP was set successfully!") + else: + print(f"\tThere was an error, see below for more details") + print(f"\tResponse code was: {response.status_code}") + + print('\tDone!') + print(f"{'':#<40}") if __name__ == '__main__': + print_owl() + print(f"{'':#<40}") + ip = get_current_public_ip() - print('Test') + update_all_ip(ip) \ No newline at end of file diff --git a/docker-compose.example.yml b/docker-compose.example.yml index 9926c7b..1b51831 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -1,9 +1,8 @@ version: '3.8' services: dns-owl: - image: ghcr.io/simonl169/dns-owl:v0.1.8 + image: ghcr.io/simonl169/dns-owl:v0.2 environment: - - STRATO_DYNDNS_PASSWORD=yourpw - TZ=Europe/Berlin - CRONVARS2=*/1 * * * * volumes: diff --git a/main.py b/main.py index 5b4ed61..7cf33fd 100644 --- a/main.py +++ b/main.py @@ -1,55 +1,9 @@ -# This is a sample Python script. +import dns_functions as dns -# Press Shift+F10 to execute it or replace it with your code. -# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings. +if __name__ == "__main__": -import requests -import json -import os -import datetime -from subprocess import call - - -from dns_functions import * - -def dns_main(): - print('###############-------------------------------------') - print(f'Current Time: {datetime.datetime.now().strftime("%d/%m/%Y %H:%M:%S")}') - print('Getting current public IP address...') - public_server_ip = get_current_public_ip() - print('###############-------------------------------------') - print('Reading config.json...') - with open('config.json', 'r') as f: - data = json.load(f) - - print('###############-------------------------------------') - for url in data['domains']: - print(f'\tChecking IP address for domain {url}') - local_dns_ip, public_dns_ip = resolve_current_server_ip(url) - print('\tCompare to current public IP address...') - - decide = compare_ip(public_server_ip, public_dns_ip) - - if not decide: - print('\tDetecting different DNS IP, update needed') - password = os.environ.get('STRATO_DYNDNS_PASSWORD', 'default') - update_dns_ip(url, password) - else: - print('\tIP has not changed, no further action') - - print('###############-------------------------------------') - - -# Press the green button in the gutter to run the script. -if __name__ == '__main__': - - print('Running DNS Owl\n') - print(' {o,o} ') - print('./)_)') - print(' ""\n') - print('by Simon169\n') - - dns_main() - - print('done! Next repetition set by crontab variables!') + dns.print_owl() + print(f"{'':#<40}") + ip = dns.get_current_public_ip() + dns.update_all_ip(ip) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index adf9111..d14b9a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -certifi==2023.5.7 -charset-normalizer==3.1.0 -idna==3.4 +certifi==2024.2.2 +charset-normalizer==3.3.2 +idna==3.6 pydig==0.4.0 requests==2.31.0 -urllib3==2.0.3 +urllib3==2.2.1