ascension

Migrate DNS zones to the GNU Name System
Log | Files | Refs | README | LICENSE

commit 5e99d6901e622deef1789c9a84862a839bfb6ffc
parent abc2641c8f0c5026a2754e3afed961bbf110dcae
Author: rexxnor <rexxnor+gnunet@brief.li>
Date:   Tue, 15 Jan 2019 15:14:27 +0100

added logic for subzones with zone cut

Diffstat:
Mascension/ascension.py | 223+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
1 file changed, 127 insertions(+), 96 deletions(-)

diff --git a/ascension/ascension.py b/ascension/ascension.py @@ -37,8 +37,9 @@ GNUNET_ZONE_CREATION_COMMAND = 'gnunet-identity' GNUNET_NAMESTORE_COMMAND = 'gnunet-namestore' GNUNET_GNS_COMMAND = 'gnunet-gns' GNUNET_ARM_COMMAND = 'gnunet-arm' +# TODO find better solution for ignoring DNSSEC record types SUPPORTED_RECORD_TYPES = [ - "A", "AAAA", "NS", "MX", "SOA", "SRV", "TXT", "CNAME" + "A", "AAAA", "NS", "MX", "SRV", "TXT", "CNAME" ] class Ascender(): @@ -72,86 +73,17 @@ class Ascender(): cls.domain, port=cls.port) - @classmethod def bootstrap_zone(cls): """ Creates the zone in gnunet """ - reverse_parsing = cls.domain.split('.')[::-1] - reverse_parsing = list(filter(None, reverse_parsing)) - counter = 0 - stop = len(reverse_parsing) - 1 - for domainpart in reverse_parsing: - pkey_lookup = sp.Popen([GNUNET_ZONE_CREATION_COMMAND, - '-d'], - stdout=sp.PIPE) - pkey_line = sp.Popen(['grep', cls.domain], - stdin=pkey_lookup.stdout, - stdout=sp.PIPE) - pkey_zone = sp.check_output(['cut', '-d', - ' ', '-f3'], - stdin=pkey_line.stdout) - pkey_zone = pkey_zone.decode().strip() - pkey_lookup.stdout.close() - pkey_line.stdout.close() - - # Create identity in GNUnet - try: - ret = sp.run([GNUNET_ZONE_CREATION_COMMAND, - '-C', domainpart], - stdout=sp.DEVNULL, - stderr=sp.DEVNULL) - logging.info("executed command: %s", " ".join(ret.args)) - except sp.CalledProcessError: - logging.info("Zone %s already exists!", domainpart) - pkey_lookup = sp.Popen([GNUNET_ZONE_CREATION_COMMAND, - '-d'], - stdout=sp.PIPE) - pkey_line = sp.Popen(['grep', cls.domain], - stdin=pkey_lookup.stdout, - stdout=sp.PIPE) - pkey_zone = sp.check_output(['cut', '-d', ' ', '-f3'], - stdin=pkey_line.stdout) - pkey_zone = pkey_zone.decode().strip() - pkey_lookup.stdout.close() - pkey_line.stdout.close() - - # If it is TLD, don't add PKEY to higher zone - if counter > 0: - result = sp.check_output([GNUNET_NAMESTORE_COMMAND, - '-D', - '-t', 'PKEY', - '-z', '%s' % domainpart, - '-n', '%s' % reverse_parsing[counter - 1] - ]) - if not "PKEY" in result.decode(): - ret = sp.run([GNUNET_NAMESTORE_COMMAND, - '-z', reverse_parsing[counter - 1], - '-a', '-n', domainpart, - '-t', 'PKEY', - '-V', pkey_zone, - '-e', 'never']) - logging.info("executed command: %s", " ".join(ret.args)) - if ret.returncode != 0: - logging.warning("failed to add record %s", domainpart) - logging.warning("failed to add %s record %s", - "PKEY", domainpart) - if counter == stop: - break - - counter += 1 - - # Create the entire zone with name - if counter != 0: - try: - ret = sp.run([GNUNET_ZONE_CREATION_COMMAND, - '-C', cls.domain], - stdout=sp.DEVNULL, - stderr=sp.DEVNULL) - logging.info("executed command: %s", " ".join(ret.args)) - except sp.CalledProcessError: - logging.info("Zone %s already exists!", cls.domain) + try: + ret = sp.run([GNUNET_ZONE_CREATION_COMMAND, + '-C', cls.domain]) + logging.info("executed command: %s", " ".join(ret.args)) + except sp.CalledProcessError: + logging.info("Zone %s already exists!", cls.domain) @classmethod def get_current_serial(cls, domain, resolver=None): @@ -174,9 +106,10 @@ class Ascender(): master_answer[0].address, domain, port=cls.port)) except dns.resolver.NoAnswer: - logging.warning("nameserver for '%s' did not answer", domain) + logging.error("nameserver for '%s' did not answer", domain) except dns.exception.FormError: - logging.warning("domain '%s' does not allow xfr requests", domain) + logging.critical("domain '%s' does not allow xfr requests", domain) + sys.exit(1) for soa_record in zone.iterate_rdatas(rdtype=dns.rdatatype.SOA): if not cls.transferns: mname = soa_record[2].mname @@ -226,6 +159,7 @@ class Ascender(): # define recordline recordline = [] label = "" + domain = None labelrecords = taskqueue.get() # break if taskqueue is empty @@ -235,9 +169,21 @@ class Ascender(): # execute thing to run on item label, listofrdatasets = labelrecords + subzones = label.split('.') + + # ignore SRV records here + if len(subzones) == 2 and subzones[0][0] == "_": + pass + elif len(subzones) > 1: + ttl = cls.get_zone_refresh_time() + cls.create_zone_hierarchy(subzones, ttl) + label = subzones[0] + subdomains = ".".join(subzones[1:]) + domain = "%s.%s" % (subdomains, cls.domain) + for rdataset in listofrdatasets: - for test in rdataset: - rdtype = dns.rdatatype.to_text(test.rdtype) + for record in rdataset: + rdtype = dns.rdatatype.to_text(record.rdtype) if rdtype not in SUPPORTED_RECORD_TYPES: continue try: @@ -245,13 +191,19 @@ class Ascender(): except AttributeError: ttl = 3600 - value = str(test) + value = str(record) + + # ignore NS for itself here + if label == '@' and rdtype == 'NS': + logging.info("ignoring NS record for itself") + continue # modify value to fit gns syntax - rdtype, value = cls.transform_to_gns_format(test, - rdtype, - cls.domain, - label) + rdtype, value, label = \ + cls.transform_to_gns_format(record, + rdtype, + cls.domain, + label) # skip record if value is none if value is None: continue @@ -263,7 +215,9 @@ class Ascender(): # add recordline to gns if len(recordline) > 1: - cls.add_recordline_to_gns(recordline, cls.domain, label) + cls.add_recordline_to_gns(recordline, + domain if domain else cls.domain, + label) taskqueue.task_done() @@ -346,9 +300,6 @@ class Ascender(): if value[-1] == ".": value = value[:-1] elif rdtype == 'NS': - if label == '@': - logging.info("ignoring NS record for itself") - return (rdtype, None) nameserver = str(record) if value[-1] == ".": value = value[:-1] @@ -380,22 +331,22 @@ class Ascender(): _, proto = str(label).split('.') except ValueError: logging.warning("could not parse SRV label %s", label) - return (rdtype, None) + return (rdtype, None, None) priority, weight, destport, target = value.split(' ') protonum = protocols.get(proto) if protonum is None: logging.warning("invalid protocol: %s", proto) - return (rdtype, None) + return (rdtype, None, None) - #pr w por target - #100 1 443 sipdir.online.lync.com. value = '%s %s %s %s %s %s %s' % ( - destport, protonum, srv, priority, weight, destport, target + destport, protonum, srv, priority, weight, destport, + "%s.%s" % (target, zonename) ) + label = target else: logging.info("Did not transform record of type: %s", rdtype) - return (rdtype, value) + return (rdtype, value, label) @classmethod def get_zone_serial(cls): @@ -420,6 +371,14 @@ class Ascender(): return soa_serial @classmethod + def get_soa_refresh_time(cls): + """ + Extracts the current serial from the current soa + """ + ttlpattern = re.compile(r'.+\s\d+,(\d+),\d+,\d+,\d+', re.M) + return re.findall(ttlpattern, cls.soa)[0] + + @classmethod def get_zone_refresh_time(cls): """ Extracts the current serial from a given zone @@ -506,6 +465,7 @@ class Ascender(): # needs to be added in any case _, ttl, rdata = record zonetuple = str(rdata).split(' ') + domain = zonename zonename = str(".".join(zonename.split('.')[:-1])) authns, owner, serial, refresh, retry, expiry, irefresh = zonetuple if authns[-1] == '.': @@ -513,7 +473,7 @@ class Ascender(): if owner[-1] == '.': owner = owner[:-1] ret = sp.run([GNUNET_NAMESTORE_COMMAND, - '-z', zonename, + '-z', domain, '-a', '-n', '@', '-t', 'SOA', '-V', "rname=%s.%s mname=%s.%s %d,%d,%d,%d,%d" @@ -543,6 +503,76 @@ class Ascender(): return True return False + @staticmethod + def create_zone_and_get_pkey(zonestring): + """ + Creates the zone in zonestring and returns pkey + :param zonestring: The label name of the zone + :returns pkey: gnunet pkey of the zone + """ + try: + ret = sp.run([GNUNET_ZONE_CREATION_COMMAND, + '-C', zonestring], + stdout=sp.DEVNULL, + stderr=sp.DEVNULL) + logging.info("executed command: %s", " ".join(ret.args)) + except sp.CalledProcessError: + logging.info("Zone %s already exists!", zonestring) + + pkey_lookup = sp.Popen([GNUNET_ZONE_CREATION_COMMAND, + '-d'], + stdout=sp.PIPE) + pkey_line = sp.Popen(['grep', zonestring], + stdin=pkey_lookup.stdout, + stdout=sp.PIPE) + pkey_zone = sp.check_output(['cut', '-d', + ' ', '-f3'], + stdin=pkey_line.stdout) + pkey_zone = pkey_zone.decode().strip() + pkey_lookup.stdout.close() + pkey_line.stdout.close() + return pkey_zone + + @staticmethod + def add_pkey_record_to_zone(pkey, domain, label, ttl): + """ + Adds the pkey of the subzone to the parent zone + :param pkey: the public key of the child zone + :param domain: the name of the parent zone + :param label: the label under which to add the pkey + """ + debug = " ".join([GNUNET_NAMESTORE_COMMAND, + '-z', domain, + '-a', '-n', label, + '-t', 'PKEY', + '-V', pkey, + '-e', "%s" % ttl]) + ret = sp.run([GNUNET_NAMESTORE_COMMAND, + '-z', domain, + '-a', '-n', label, + '-t', 'PKEY', + '-V', pkey, + '-e', "%s" % ttl]) + logging.info("executed command: %s", debug) + if ret.returncode != 0: + logging.warning("failed to add PKEY record %s to %s", + label, domain) + + + @classmethod + def create_zone_hierarchy(cls, labels, ttl): + """ + Creates the zone hierarchy in GNS for label + :param label: the split record to create zones for + """ + domain = cls.domain + # black magic that removes first element and reverses list + for label in labels[1:][::-1]: + zonelabel = "%s.%s" % (label, domain) + pkey = cls.create_zone_and_get_pkey(zonelabel) + cls.add_pkey_record_to_zone(pkey, domain, label, ttl) + domain = zonelabel + def main(): """ Initializes object and handles arguments @@ -585,6 +615,7 @@ def main(): time.sleep(retry) else: logging.info("refreshing zone in %ds", refresh) + print("refreshing zone in %ds" % refresh) time.sleep(refresh) if __name__ == '__main__':