* Add Cloudflare to DDNS Providers List
@ 2025-02-16 19:46 Bernhard B.
0 siblings, 0 replies; only message in thread
From: Bernhard B. @ 2025-02-16 19:46 UTC (permalink / raw)
To: ddns
[-- Attachment #1: Type: text/plain, Size: 311 bytes --]
Hello,
please find attached a patch for adding Cloudflare to the list of the
DDNS Providers. I've tested the changeset with a fresh IpFire
installation inside QEMU and a custom domain that I own. Everything
seems to be working fine.
Please let me know if you want anything changed.
Best Regards,
Bernhard
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Added-Cloudflare-to-DDNS-providers.patch --]
[-- Type: text/x-patch, Size: 6052 bytes --]
From f24f37560d4f538d70af69231cabaa0d0d730ee7 Mon Sep 17 00:00:00 2001
From: Bernhard B <bernhard@liftingcoder.com>
Date: Sun, 16 Feb 2025 19:20:21 +0100
Subject: [PATCH] Added Cloudflare to DDNS providers
This changesets adds Cloudflare to the list of DDNS providers.
DNS Record Update Workflow:
* first, the zone id for the given domain name is fetched
-> if no zone id can be obtained, then either the provided token
is wrong or doesn't have the necessary permissions.
* next, all existing records in that zone are fetched
-> if a type A record with the hostname is found, that one is
updated.
-> if no type A record is found, a new DNS record is created.
In order to implement the necessary changes, the 'send_request method' in
system.py was extended. In particular:
* PATCH support was added
* support for token based authentication was added
---
src/ddns/providers.py | 85 +++++++++++++++++++++++++++++++++++++++++++
src/ddns/system.py | 10 +++--
2 files changed, 92 insertions(+), 3 deletions(-)
diff --git a/src/ddns/providers.py b/src/ddns/providers.py
index 59f9665..4d28685 100644
--- a/src/ddns/providers.py
+++ b/src/ddns/providers.py
@@ -2017,3 +2017,88 @@ class DDNSProviderInfomaniak(DDNSProtocolDynDNS2, DDNSProvider):
# https://www.infomaniak.com/de/support/faq/2376/dyndns-aktualisieren-eines-dynamischen-dns-uber-die-api
url = "https://infomaniak.com/nic/update"
+
+class DDNSProviderCloudflare(DDNSProvider):
+ handle = "cloudflare.com"
+ name = "cloudflare.com"
+ website = "https://cloudflare.com/"
+ protocols = ("ipv4",)
+
+ url = "https://api.cloudflare.com/client/v4/zones"
+ can_remove_records = False
+ supports_token_auth = True
+
+ # https://developers.cloudflare.com/api/resources/zones/methods/list/
+ def _get_zone_id(self):
+ url = "https://api.cloudflare.com/client/v4/zones"
+ result = self.send_request(url, method="GET", token=self.token)
+ if result.code == 200:
+ try:
+ data = json.loads(result.read().decode(result.info().get_param('charset') or 'utf-8'))
+ for elem in data["result"]:
+ if self.hostname.endswith(elem["name"]):
+ return elem["id"]
+ except KeyError:
+ return None
+ return None
+
+ # https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/list/
+ def _get_dns_record_id(self, zone_id):
+ url = f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records"
+ result = self.send_request(url, method="GET", token=self.token)
+ if result.code == 200:
+ data = json.loads(result.read().decode(result.info().get_param('charset') or 'utf-8'))
+ for elem in data["result"]:
+ if elem["name"] == self.hostname and elem["type"] == "A":
+ return elem["id"]
+ return None
+
+ # https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/edit/
+ def _update_dns_record(self, zone_id, dns_record_id, ip_address):
+ payload = {
+ "content": ip_address
+ }
+ url = f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records/{dns_record_id}"
+ result = self.send_request(url, data=json.dumps(payload).encode("utf-8"), method="PATCH", token=self.token)
+ if result.code == 200:
+ data = json.loads(result.read().decode(result.info().get_param('charset') or 'utf-8'))
+ if data["success"]:
+ return True
+ return False
+
+ # https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/create/
+ def _create_dns_record(self, zone_id, ip_address):
+ url = f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records"
+ payload = {
+ "comment": "DNS Record created by IpFire",
+ "proxied": False,
+ "type": "A",
+ "ttl": 60
+ }
+
+ payload["content"] = f"{ip_address}"
+ payload["name"] = f"{self.hostname}"
+ result = self.send_request(url, data=json.dumps(payload).encode("utf-8"), method="POST", token=self.token)
+ if result.code == 200:
+ data = json.loads(result.read().decode(result.info().get_param('charset') or 'utf-8'))
+ if data["success"]:
+ return True
+ return False
+
+ def update_protocol(self, proto):
+ # retrieve ip
+ ip_address = self.get_address(proto)
+
+ zone_id = self._get_zone_id()
+ if zone_id is not None:
+ # Check if there already exists a DNS Record. If so, update the existing one.
+ # Otherwise, create a new one.
+ dns_record_id = self._get_dns_record_id(zone_id)
+ if dns_record_id is not None:
+ if not self._update_dns_record(zone_id, dns_record_id, ip_address):
+ raise DDNSUpdateError
+ else:
+ if not self._create_dns_record(zone_id, ip_address):
+ raise DDNSUpdateError
+ else:
+ raise DDNSAuthenticationError
diff --git a/src/ddns/system.py b/src/ddns/system.py
index 48c9a8f..3ca95b9 100644
--- a/src/ddns/system.py
+++ b/src/ddns/system.py
@@ -121,8 +121,10 @@ class DDNSSystem(object):
return self._guess_external_ip_address(url, **kwargs)
- def send_request(self, url, method="GET", data=None, username=None, password=None, timeout=30):
- assert method in ("GET", "POST")
+ def send_request(self, url, method="GET", data=None, username=None, password=None, timeout=30, token=None):
+ assert method in ("GET", "POST", "PATCH")
+ if token is not None and ((username is not None) or (password is not None)):
+ raise AsssertionError("Please use either password based authentication or token based authentication - but not both!")
# Add all arguments in the data dict to the URL and escape them properly.
if method == "GET" and data:
@@ -138,11 +140,13 @@ class DDNSSystem(object):
if data:
logger.debug(" data: %s" % data)
- req = urllib.request.Request(url, data=data)
+ req = urllib.request.Request(url, data=data, method=method)
if username and password:
basic_auth_header = self._make_basic_auth_header(username, password)
req.add_header("Authorization", "Basic %s" % basic_auth_header.decode())
+ elif token is not None:
+ req.add_header("Authorization", f"Bearer {token}")
# Set the user agent.
req.add_header("User-Agent", self.USER_AGENT)
--
2.39.5
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2025-02-16 19:46 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-02-16 19:46 Add Cloudflare to DDNS Providers List Bernhard B.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox