diff --git a/README.md b/README.md index 50d6395..793505b 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,17 @@ To get credentials, go to this link here: https://robot.hetzner.com/preferences/ #### OVH -- Work in progress +- Supports creating, updating, and deleting DNS records using the OVH API. + +#### Credentials +To get credentials, go to this link here: https://github.com/ovh/php-ovh#supported-endpoints, pick the right endpoint and go to the right **/createApp/** application credentials page. +The application needs to have the following rights: +- GET /ip +- GET /ip/* +- POST /ip/* +- DELETE /ip/* + +Don't forget to set the right endpoint region in the config file. ## Contributing diff --git a/backend/hetzner.py b/backend/hetzner_backend.py similarity index 100% rename from backend/hetzner.py rename to backend/hetzner_backend.py diff --git a/backend/ovh.py b/backend/ovh.py deleted file mode 100644 index a45f5e7..0000000 --- a/backend/ovh.py +++ /dev/null @@ -1,44 +0,0 @@ -import configparser -import requests - - -def reverse_ip_to_standard(reverse_ip): - parts = reverse_ip.split(".") - parts = parts[:4] - standard_ip = ".".join(reversed(parts)) - - return standard_ip - - -class OvhBackend: - def __init__(self, config_file='config.ini'): - self.config_file = config_file - self.username = None - self.password = None - - self.load_credentials() - - def load_credentials(self): - config = configparser.ConfigParser() - config.read(self.config_file) - - if "OVH" not in config: - raise ValueError("OVH section not found in config.ini") - - self.username = config["OVH"].get("username") - self.password = config["OVH"].get("password") - - if not self.username or not self.password: - raise ValueError("OVH username or password not provided in config.ini") - - def update_record(self, reverse_ip, record): - # Do update record stuff here - pass - - def create_record(self, reverse_ip, record): - # Do create record stuff here - pass - - def delete_record(self, reverse_ip): - # Do delete record stuff here - pass \ No newline at end of file diff --git a/backend/ovh_backend.py b/backend/ovh_backend.py new file mode 100644 index 0000000..f3ed425 --- /dev/null +++ b/backend/ovh_backend.py @@ -0,0 +1,113 @@ +import configparser +import urllib + +import ovh +import json +import ipaddress + + +def reverse_ip_to_standard(reverse_ip): + parts = reverse_ip.split(".") + parts = parts[:4] + standard_ip = ".".join(reversed(parts)) + + return standard_ip + + +def ip_to_block(ip_addr, ip_blocks): + # Remove IPv6 blocks from the list + ip_blocks = [block for block in ip_blocks if ":" not in block] + ip_addr = ipaddress.ip_address(ip_addr) # Convert ip_addr to an IPv4Address object + for block in ip_blocks: + network = ipaddress.ip_network(block) + if ip_addr in network: + print("IP " + str(ip_addr) + " belongs to " + block) + return block + + +class OvhBackend: + def __init__(self, config_file='config.ini'): + self.client = None + self.config_file = config_file + self.load_credentials() + + def load_credentials(self): + config = configparser.ConfigParser() + config.read(self.config_file) + + if "OVH" not in config: + raise ValueError("OVH section not found in config.ini") + + endpoint = config["OVH"]["endpoint"] + application_key = config["OVH"]["application_key"] + application_secret = config["OVH"]["application_secret"] + consumer_key = config["OVH"]["consumer_key"] + + if not endpoint or not application_key or not application_secret or not consumer_key: + raise ValueError("OVH credentials not provided in config.ini") + + self.client = ovh.Client( + endpoint=endpoint, + application_key=application_key, + application_secret=application_secret, + consumer_key=consumer_key + ) + + def update_record(self, reverse_ip, record): + # Check if the record contains the necessary data + if not record or "content" not in record[0]: + print("Invalid record data. 'content' field is missing.") + return None + + domain = record[0]["content"].strip(".") + + ip_blocks = self.client.get("/ip") + ip_blocks = json.dumps(ip_blocks) + ip_blocks = json.loads(ip_blocks) + + ip = reverse_ip_to_standard(reverse_ip) + block = ip_to_block(ip, ip_blocks) + + if block is None: + print("Error: no block found for IP " + ip) + return + + print("Updating RDNS record for: " + ip + " in block " + block + " to: " + record[0]["content"]) + block = urllib.parse.quote(block, safe="") + path = "/ip/" + block + "/reverse" + try: + self.client.post(f"{path}", + ipReverse=ip, + reverse=domain, + ) + except ovh.exceptions.APIError as e: + print("Error: " + str(e)) + else: + print("Updated RDNS record for: " + ip) + return None + + def create_record(self, reverse_ip, record): + self.update_record(reverse_ip, record) + pass + + def delete_record(self, reverse_ip): + ip_blocks = self.client.get("/ip") + ip_blocks = json.dumps(ip_blocks) + ip_blocks = json.loads(ip_blocks) + + ip = reverse_ip_to_standard(reverse_ip) + block = ip_to_block(ip, ip_blocks) + + if block is None: + print("Error: no block found for ip " + ip) + return + + result = self.client.delete(f"/ip/{ip}/reverse" + block) + if result.status_code == 200: + print("Deleted RDNS record for: " + ip) + return result.json() + else: + print(f"Failed to delete rDNS data. Status code: {result.status_code}") + print(result.json()) + return None + pass diff --git a/config.ini b/config.ini index 6465895..d3fb438 100644 --- a/config.ini +++ b/config.ini @@ -4,9 +4,11 @@ api_key=x frequency=5 [Hetzner] -username=x -password=x +username=#ws+yimPZrpr +password=BTJOxx@Cfk&\&!!5 [OVH] -username=x -password=x \ No newline at end of file +endpoint=ovh-ca +application_key=x +application_secret=x +consumer_key=x \ No newline at end of file diff --git a/main.py b/main.py index 1291894..4b114e5 100644 --- a/main.py +++ b/main.py @@ -4,8 +4,8 @@ import json import configparser import schedule import time -from backend.hetzner import HetznerBackend -from backend.ovh import OvhBackend +from backend.hetzner_backend import HetznerBackend +from backend.ovh_backend import OvhBackend def compare_zone_records(saved_records, new_records, backend_type): @@ -19,8 +19,10 @@ def compare_zone_records(saved_records, new_records, backend_type): if backend_type == "hetzner": backend = HetznerBackend() + print("Using Hetzner backend") elif backend_type == "ovh": backend = OvhBackend() + print("Using OVH backend") else: print(f"Specified backend not supported: {backend_type}") return diff --git a/requirements.txt b/requirements.txt index f94c746..d08c871 100644 Binary files a/requirements.txt and b/requirements.txt differ