From e0cb72d6931b57e9709b7dfb58c0db2d1249dfef Mon Sep 17 00:00:00 2001 From: leccg Date: Wed, 17 Dec 2025 11:27:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=B3=BB=E7=BB=9F=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E5=89=8D=E7=9A=84DNS=E8=AE=B0=E5=BD=95=E6=B8=85?= =?UTF-8?q?=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/nsupdate_delete_system_pre.py | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 ctdeployer/modules/nsupdate_delete_system_pre.py diff --git a/ctdeployer/modules/nsupdate_delete_system_pre.py b/ctdeployer/modules/nsupdate_delete_system_pre.py new file mode 100644 index 0000000..85e6a44 --- /dev/null +++ b/ctdeployer/modules/nsupdate_delete_system_pre.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +# +# Adrian Brzezinski +# License: GPLv2+ +# +# Replace (or remove) records in DNS zone for systems created (or removed) by Ctdeployer +# + +# DNS toolkit for Python +# - python-dnspython (Debian) +# - python-dns (RH/CentOS) + +from builtins import str +import dns.query +import dns.tsigkeyring +import dns.update +import dns.resolver +import time + +from ctdeployer.cexceptions import CX + +logf = None + + +def nslog(msg): + """ + Log a message to the logger. + + :param msg: The message to log. + """ + if logf is not None: + logf.write(msg) + + +def register(): + """ + This method is the obligatory Ctdeployer registration hook. + + :return: The trigger name or an empty string. + :rtype: str + """ + if __name__ == "ctdeployer.modules.nsupdate_add_system_post": + return "/var/lib/ctdeployer/triggers/add/system/post/*" + elif __name__ == "ctdeployer.modules.nsupdate_delete_system_pre": + return "/var/lib/ctdeployer/triggers/delete/system/pre/*" + else: + return '' + + +def run(api, args, logger): + """ + This method executes the trigger, meaning in this case that it updates the dns configuration. + + :param api: The api to read metadata from. + :param args: Metadata to log. + :param logger: The logger to audit the action with. + :return: "0" on success or a skipped task. If the task failed or problems occurred then an exception is raised. + """ + global logf + + action = None + if __name__ == "ctdeployer.modules.nsupdate_add_system_post": + action = "replace" + elif __name__ == "ctdeployer.modules.nsupdate_delete_system_pre": + action = "delete" + else: + return 0 + + settings = api.settings() + + if not str(settings.nsupdate_enabled).lower() in ["1", "yes", "y", "true"]: + return 0 + + # Read our settings + if str(settings.nsupdate_log) is not None: + logf = open(str(settings.nsupdate_log), "a+") + nslog(">> starting %s %s\n" % (__name__, args)) + + if str(settings.nsupdate_tsig_key) is not None: + keyring = dns.tsigkeyring.from_text({ + str(settings.nsupdate_tsig_key[0]): str(settings.nsupdate_tsig_key[1]) + }) + else: + keyring = None + + if str(settings.nsupdate_tsig_algorithm) is not None: + keyring_algo = str(settings.nsupdate_tsig_algorithm) + else: + keyring_algo = "HMAC-MD5.SIG-ALG.REG.INT" + # nslog( " algo %s, key %s : %s \n" % (keyring_algo, str(settings.nsupdate_tsig_key[0]), + # str(settings.nsupdate_tsig_key[1])) ) + + # get information about this system + system = api.find_system(args[0]) + + # process all interfaces and perform dynamic update for those with --dns-name + for (name, interface) in list(system.interfaces.items()): + host = interface["dns_name"] + host_ip = interface["ip_address"] + + if not system.is_management_supported(cidr_ok=False): + continue + if not host: + continue + if host.find(".") == -1: + continue + + domain = ".".join(host.split(".")[1:]) # get domain from host name + host = host.split(".")[0] # strip domain + + nslog("processing interface %s : %s\n" % (name, interface)) + nslog("lookup for '%s' domain master nameserver... " % domain) + + # get master nameserver ip address + answers = dns.resolver.query(domain + '.', dns.rdatatype.SOA) + soa_mname = answers[0].mname + soa_mname_ip = None + + for rrset in answers.response.additional: + if rrset.name == soa_mname: + soa_mname_ip = str(rrset.items[0].address) + + if soa_mname_ip is None: + ip = dns.resolver.query(soa_mname, "A") + for answer in ip: + soa_mname_ip = answer.to_text() + + nslog("%s [%s]\n" % (soa_mname, soa_mname_ip)) + nslog("%s dns record for %s.%s [%s] .. " % (action, host, domain, host_ip)) + + # try to update zone with new record + update = dns.update.Update(domain + '.', keyring=keyring, keyalgorithm=keyring_algo) + + if action == "replace": + update.replace(host, 3600, dns.rdatatype.A, host_ip) + update.replace(host, 3600, dns.rdatatype.TXT, '"ctdeployer (date: %s)"' % (time.strftime("%c"))) + else: + update.delete(host, dns.rdatatype.A, host_ip) + update.delete(host, dns.rdatatype.TXT) + + try: + response = dns.query.tcp(update, soa_mname_ip) + rcode_txt = dns.rcode.to_text(response.rcode()) + except dns.tsig.PeerBadKey: + nslog("failed (refused key)\n>> done\n") + logf.close() + + raise CX("nsupdate failed, server '%s' refusing our key" % soa_mname) + + nslog('response code: %s\n' % rcode_txt) + + # notice user about update failure + if response.rcode() != dns.rcode.NOERROR: + nslog('>> done\n') + logf.close() + + raise CX("nsupdate failed (response: %s, name: %s.%s, ip %s, name server %s)" + % (rcode_txt, host, domain, host_ip, soa_mname)) + + nslog('>> done\n') + logf.close() + return 0 -- Gitee