D7net
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
proc
/
self
/
root
/
opt
/
saltstack
/
salt
/
lib
/
python3.10
/
site-packages
/
salt
/
runners
/
Filename :
ddns.py
back
Copy
""" Dynamic DNS Runner ================== .. versionadded:: 2015.8.0 Runner to interact with DNS server and create/delete/update DNS records :codeauthor: Nitin Madhok <nmadhok@g.clemson.edu> """ import logging import os import salt.utils.files import salt.utils.json HAS_LIBS = False try: import dns.query import dns.tsigkeyring # pylint: disable=no-name-in-module import dns.update # pylint: disable=no-name-in-module HAS_LIBS = True except ImportError: HAS_LIBS = False log = logging.getLogger(__name__) def __virtual__(): """ Check if required libs (python-dns) is installed and load runner only if they are present """ if not HAS_LIBS: return False return True def _get_keyring(keyfile): keyring = None if keyfile and os.path.isfile(os.path.expanduser(keyfile)): with salt.utils.files.fopen(keyfile) as _f: keyring = dns.tsigkeyring.from_text(salt.utils.json.load(_f)) return keyring def create( zone, name, ttl, rdtype, data, keyname, keyfile, nameserver, timeout, port=53, keyalgorithm="hmac-md5", ): """ Create a DNS record. The nameserver must be an IP address and the master running this runner must have create privileges on that server. CLI Example: .. code-block:: bash salt-run ddns.create domain.com my-test-vm 3600 A 10.20.30.40 my-tsig-key /etc/salt/tsig.keyring 10.0.0.1 5 """ if zone in name: name = name.replace(zone, "").rstrip(".") fqdn = "{}.{}".format(name, zone) request = dns.message.make_query(fqdn, rdtype) answer = dns.query.udp(request, nameserver, timeout, port) rdata_value = dns.rdatatype.from_text(rdtype) rdata = dns.rdata.from_text(dns.rdataclass.IN, rdata_value, data) for rrset in answer.answer: if rdata in rrset.items: return { fqdn: "Record of type '{}' already exists with ttl of {}".format( rdtype, rrset.ttl ) } keyring = _get_keyring(keyfile) dns_update = dns.update.Update( zone, keyring=keyring, keyname=keyname, keyalgorithm=keyalgorithm ) dns_update.add(name, ttl, rdata) answer = dns.query.udp(dns_update, nameserver, timeout, port) if answer.rcode() > 0: return {fqdn: "Failed to create record of type '{}'".format(rdtype)} return {fqdn: "Created record of type '{}': {} -> {}".format(rdtype, fqdn, data)} def update( zone, name, ttl, rdtype, data, keyname, keyfile, nameserver, timeout, replace=False, port=53, keyalgorithm="hmac-md5", ): """ Replace, or update a DNS record. The nameserver must be an IP address and the master running this runner must have update privileges on that server. .. note:: If ``replace`` is set to True, all records for this name and type will first be deleted and then recreated. Default is ``replace=False``. CLI Example: .. code-block:: bash salt-run ddns.update domain.com my-test-vm 3600 A 10.20.30.40 my-tsig-key /etc/salt/tsig.keyring 10.0.0.1 5 """ if zone in name: name = name.replace(zone, "").rstrip(".") fqdn = "{}.{}".format(name, zone) request = dns.message.make_query(fqdn, rdtype) answer = dns.query.udp(request, nameserver, timeout, port) if not answer.answer: return {fqdn: "No matching DNS record(s) found"} rdata_value = dns.rdatatype.from_text(rdtype) rdata = dns.rdata.from_text(dns.rdataclass.IN, rdata_value, data) for rrset in answer.answer: if rdata in rrset.items: rr = rrset.items if ttl == rrset.ttl: if replace and (len(answer.answer) > 1 or len(rrset.items) > 1): break return { fqdn: "Record of type '{}' already present with ttl of {}".format( rdtype, ttl ) } break keyring = _get_keyring(keyfile) dns_update = dns.update.Update( zone, keyring=keyring, keyname=keyname, keyalgorithm=keyalgorithm ) dns_update.replace(name, ttl, rdata) answer = dns.query.udp(dns_update, nameserver, timeout, port) if answer.rcode() > 0: return {fqdn: "Failed to update record of type '{}'".format(rdtype)} return {fqdn: "Updated record of type '{}'".format(rdtype)} def delete( zone, name, keyname, keyfile, nameserver, timeout, rdtype=None, data=None, port=53, keyalgorithm="hmac-md5", ): """ Delete a DNS record. CLI Example: .. code-block:: bash salt-run ddns.delete domain.com my-test-vm my-tsig-key /etc/salt/tsig.keyring 10.0.0.1 5 A """ if zone in name: name = name.replace(zone, "").rstrip(".") fqdn = "{}.{}".format(name, zone) request = dns.message.make_query(fqdn, (rdtype or "ANY")) answer = dns.query.udp(request, nameserver, timeout, port) if not answer.answer: return {fqdn: "No matching DNS record(s) found"} keyring = _get_keyring(keyfile) dns_update = dns.update.Update( zone, keyring=keyring, keyname=keyname, keyalgorithm=keyalgorithm ) if rdtype: rdata_value = dns.rdatatype.from_text(rdtype) if data: rdata = dns.rdata.from_text(dns.rdataclass.IN, rdata_value, data) dns_update.delete(name, rdata) else: dns_update.delete(name, rdata_value) else: dns_update.delete(name) answer = dns.query.udp(dns_update, nameserver, timeout, port) if answer.rcode() > 0: return {fqdn: "Failed to delete DNS record(s)"} return {fqdn: "Deleted DNS record(s)"} def add_host( zone, name, ttl, ip, keyname, keyfile, nameserver, timeout, port=53, keyalgorithm="hmac-md5", ): """ Create both A and PTR (reverse) records for a host. CLI Example: .. code-block:: bash salt-run ddns.add_host domain.com my-test-vm 3600 10.20.30.40 my-tsig-key /etc/salt/tsig.keyring 10.0.0.1 5 """ res = [] if zone in name: name = name.replace(zone, "").rstrip(".") fqdn = "{}.{}".format(name, zone) ret = create( zone, name, ttl, "A", ip, keyname, keyfile, nameserver, timeout, port, keyalgorithm, ) res.append(ret[fqdn]) parts = ip.split(".")[::-1] i = len(parts) popped = [] # Iterate over possible reverse zones while i > 1: p = parts.pop(0) i -= 1 popped.append(p) zone = "{}.{}".format(".".join(parts), "in-addr.arpa.") name = ".".join(popped) rev_fqdn = "{}.{}".format(name, zone) ret = create( zone, name, ttl, "PTR", "{}.".format(fqdn), keyname, keyfile, nameserver, timeout, port, keyalgorithm, ) if "Created" in ret[rev_fqdn]: res.append(ret[rev_fqdn]) return {fqdn: res} res.append(ret[rev_fqdn]) return {fqdn: res} def delete_host( zone, name, keyname, keyfile, nameserver, timeout, port=53, keyalgorithm="hmac-md5" ): """ Delete both forward (A) and reverse (PTR) records for a host only if the forward (A) record exists. CLI Example: .. code-block:: bash salt-run ddns.delete_host domain.com my-test-vm my-tsig-key /etc/salt/tsig.keyring 10.0.0.1 5 """ res = [] if zone in name: name = name.replace(zone, "").rstrip(".") fqdn = "{}.{}".format(name, zone) request = dns.message.make_query(fqdn, "A") answer = dns.query.udp(request, nameserver, timeout, port) try: ips = [i.address for i in answer.answer[0].items] except IndexError: ips = [] ret = delete( zone, name, keyname, keyfile, nameserver, timeout, port=port, keyalgorithm=keyalgorithm, ) res.append("{} of type 'A'".format(ret[fqdn])) for ip in ips: parts = ip.split(".")[::-1] i = len(parts) popped = [] # Iterate over possible reverse zones while i > 1: p = parts.pop(0) i -= 1 popped.append(p) zone = "{}.{}".format(".".join(parts), "in-addr.arpa.") name = ".".join(popped) rev_fqdn = "{}.{}".format(name, zone) ret = delete( zone, name, keyname, keyfile, nameserver, timeout, "PTR", "{}.".format(fqdn), port, keyalgorithm, ) if "Deleted" in ret[rev_fqdn]: res.append("{} of type 'PTR'".format(ret[rev_fqdn])) return {fqdn: res} res.append(ret[rev_fqdn]) return {fqdn: res}