From d840ce2d67bfd0d1a39e7b678f34d11c0c15fe47 Mon Sep 17 00:00:00 2001 From: Guillaume-Jean Herbiet Date: Wed, 1 Jun 2022 13:32:21 +0200 Subject: [PATCH 1/9] zone group management in zones2catz --- dnscatz/zones2catz.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/dnscatz/zones2catz.py b/dnscatz/zones2catz.py index ef5c5a7..1806310 100644 --- a/dnscatz/zones2catz.py +++ b/dnscatz/zones2catz.py @@ -7,8 +7,8 @@ import sys import time import uuid +import csv from io import StringIO -from typing import List CATZ_VERSION = 2 @@ -23,7 +23,7 @@ DEFAULT_TTL = 0 -def generate_catalog_zone(origin: str, zones: List[str]) -> str: +def generate_catalog_zone(origin: str, zonelist: str) -> str: buf = StringIO() serial = int(time.time()) @@ -49,11 +49,17 @@ def generate_catalog_zone(origin: str, zones: List[str]) -> str: print(f"{origin} {DEFAULT_TTL} IN NS invalid.") print(f'version.{origin} {DEFAULT_TTL} IN TXT "{CATZ_VERSION}"') - for zone in zones: - if not zone.endswith("."): - zone += "." - zone_id = uuid.uuid5(uuid.NAMESPACE_DNS, zone) - print(f"{zone_id}.zones.{origin} {DEFAULT_TTL} IN PTR {zone}") + with open(zonelist, mode='r') as csv_file: + csv_reader = csv.DictReader(csv_file, fieldnames=['zone', 'group']) + for row in csv_reader: + zone = row['zone'].strip() + if not zone.endswith("."): + zone += "." + zone_id = uuid.uuid5(uuid.NAMESPACE_DNS, zone) + print(f"{zone_id}.zones.{origin} {DEFAULT_TTL} IN PTR {zone}") + if row['group']: + group = row['group'].strip() + print(f"group.{zone_id}.zones.{origin} {DEFAULT_TTL} IN TXT \"{group}\"") sys.stdout = old_stdout @@ -83,15 +89,11 @@ def main() -> None: args = parser.parse_args() - zones = set() - for z in open(args.zonelist).readlines(): - zones.add(z.rstrip()) - origin = args.origin if not origin.endswith("."): origin += "." - catalog_zone_str = generate_catalog_zone(origin=origin, zones=zones) + catalog_zone_str = generate_catalog_zone(origin=origin, zonelist=args.zonelist) if args.output: with open(args.output, "wt") as output_file: From 4a1e33150d72cdcd155e1e3293231329a33cbc85 Mon Sep 17 00:00:00 2001 From: Guillaume-Jean Herbiet Date: Wed, 1 Jun 2022 14:23:58 +0200 Subject: [PATCH 2/9] fix parsing catalog zones with properties --- dnscatz/catz2nsd.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dnscatz/catz2nsd.py b/dnscatz/catz2nsd.py index 65404ae..323a606 100644 --- a/dnscatz/catz2nsd.py +++ b/dnscatz/catz2nsd.py @@ -223,17 +223,17 @@ def get_catz_zones(catalog_zone: dns.zone.Zone) -> Set[str]: raise CatalogZoneError( f"Unsupported catalog zone version ({catz_version})" ) - elif str(k).endswith(".zones"): - rdataset = v.get_rdataset(dns.rdataclass.IN, dns.rdatatype.PTR) - if len(rdataset) != 1: - raise CatalogZoneError("Broken catalog zone (PTR)") - zones.add(str(rdataset[0]).rstrip(".")) elif str(k).startswith("group."): logging.info("Group property not supported: %s", str(k)) elif str(k).startswith("coo."): logging.info("Change of Ownership property not supported: %s", str(k)) elif str(k).startswith("serial."): logging.info("Serial property not supported: %s", str(k)) + elif str(k).endswith(".zones"): + rdataset = v.get_rdataset(dns.rdataclass.IN, dns.rdatatype.PTR) + if len(rdataset) != 1: + raise CatalogZoneError("Broken catalog zone (PTR)") + zones.add(str(rdataset[0]).rstrip(".")) return zones From 541cb6dd45701a354cb9cfdde742e9c5eb1e91ab Mon Sep 17 00:00:00 2001 From: Guillaume-Jean Herbiet Date: Wed, 1 Jun 2022 16:39:04 +0200 Subject: [PATCH 3/9] zone group management in catz2nsd --- dnscatz/catz2nsd.py | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/dnscatz/catz2nsd.py b/dnscatz/catz2nsd.py index 323a606..81cac19 100644 --- a/dnscatz/catz2nsd.py +++ b/dnscatz/catz2nsd.py @@ -212,9 +212,9 @@ def axfr( return zone -def get_catz_zones(catalog_zone: dns.zone.Zone) -> Set[str]: +def get_catz_zones(catalog_zone: dns.zone.Zone) -> Dict: """Get zones from catalog zone""" - zones = set() + zones = {} for k, v in catalog_zone.nodes.items(): if str(k) == "version": if rdataset := v.get_rdataset(dns.rdataclass.IN, dns.rdatatype.TXT): @@ -224,7 +224,14 @@ def get_catz_zones(catalog_zone: dns.zone.Zone) -> Set[str]: f"Unsupported catalog zone version ({catz_version})" ) elif str(k).startswith("group."): - logging.info("Group property not supported: %s", str(k)) + rdataset = v.get_rdataset(dns.rdataclass.IN, dns.rdatatype.TXT) + if len(rdataset) != 1: + raise CatalogZoneError("Broken catalog zone (group/TXT)") + uuid = str(k).split(".")[1] + group = str(rdataset[0]).strip("\"") + if not uuid in zones: + zones[uuid] = {} + zones[uuid]['group'] = group elif str(k).startswith("coo."): logging.info("Change of Ownership property not supported: %s", str(k)) elif str(k).startswith("serial."): @@ -233,7 +240,11 @@ def get_catz_zones(catalog_zone: dns.zone.Zone) -> Set[str]: rdataset = v.get_rdataset(dns.rdataclass.IN, dns.rdatatype.PTR) if len(rdataset) != 1: raise CatalogZoneError("Broken catalog zone (PTR)") - zones.add(str(rdataset[0]).rstrip(".")) + uuid = str(k).split(".")[0] + zone = str(rdataset[0]).rstrip(".") + if not uuid in zones: + zones[uuid] = {} + zones[uuid]['zone'] = zone return zones @@ -249,7 +260,8 @@ def ensure_unique_zones(catalog_zones: List[CatalogZone]): """Ensure zones are not defined in multiple catalogs""" zone2catalogs = defaultdict(set) for cz in catalog_zones: - for zone in cz.zones: + for uuid in cz.zones: + zone = cz.zones[uuid]['zone'] zone2catalogs[zone].add(cz.origin) errors = 0 for zone, catalogs in zone2catalogs.items(): @@ -318,15 +330,20 @@ def main() -> None: all_new_zones = set() for cz in catalog_zones: - for zone in cz.zones: + for uuid in cz.zones: + zone = cz.zones[uuid]['zone'] + if 'group' in cz.zones[uuid]: + group = cz.zones[uuid]['group'] + else: + group = cz.pattern if zone not in current_zone_patterns: - logger.info("Add zone %s (%s)", zone, cz.pattern) - nsd_control(f"addzone {zone} {cz.pattern}", args.dry_run) - elif cz.pattern != current_zone_patterns[zone]: - logger.info("Update zone %s (%s)", zone, cz.pattern) - nsd_control(f"changezone {zone} {cz.pattern}", args.dry_run) + logger.info("Add zone %s (%s)", zone, group) + nsd_control(f"addzone {zone} {group}", args.dry_run) + elif group != current_zone_patterns[zone]: + logger.info("Update zone %s (%s)", zone, group) + nsd_control(f"changezone {zone} {group}", args.dry_run) else: - logger.debug("No changes to zone %s (%s)", zone, cz.pattern) + logger.debug("No changes to zone %s (%s)", zone, group) all_new_zones.add(zone) del_zones = current_zones - all_new_zones From 183fc9c26576ded673bdfe7a2b689b13749f3be7 Mon Sep 17 00:00:00 2001 From: Guillaume-Jean Herbiet Date: Wed, 1 Jun 2022 16:48:36 +0200 Subject: [PATCH 4/9] updated documentation with group property --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 20a5237..7301391 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,11 @@ The configuration file is NSD-like as described below: algorithm: secret: +If a group property is specified for a given zone (see [section 4.4.2](https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-dns-catalog-zones#section-4.4.2) of the RFC draft), this overrides the `pattern` option defined for the corresponding catalog zone. + ## zones2catz -`zones2catz` creates a catalog zone from a text file containing one zone per line and writes its output to a file or _stdout_. +`zones2catz` creates a catalog zone from a comma-separated text file containing one zone per line (and optionally the intended group for the zone) and writes its output to a file or _stdout_. ## References From cb5ebc72aac9a117129fbc916ded9ce5a7b3f2c6 Mon Sep 17 00:00:00 2001 From: Guillaume-Jean Herbiet Date: Thu, 2 Jun 2022 10:50:49 +0200 Subject: [PATCH 5/9] lint --- dnscatz/catz2nsd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnscatz/catz2nsd.py b/dnscatz/catz2nsd.py index 81cac19..6f77bb4 100644 --- a/dnscatz/catz2nsd.py +++ b/dnscatz/catz2nsd.py @@ -229,7 +229,7 @@ def get_catz_zones(catalog_zone: dns.zone.Zone) -> Dict: raise CatalogZoneError("Broken catalog zone (group/TXT)") uuid = str(k).split(".")[1] group = str(rdataset[0]).strip("\"") - if not uuid in zones: + if uuid not in zones: zones[uuid] = {} zones[uuid]['group'] = group elif str(k).startswith("coo."): @@ -242,7 +242,7 @@ def get_catz_zones(catalog_zone: dns.zone.Zone) -> Dict: raise CatalogZoneError("Broken catalog zone (PTR)") uuid = str(k).split(".")[0] zone = str(rdataset[0]).rstrip(".") - if not uuid in zones: + if uuid not in zones: zones[uuid] = {} zones[uuid]['zone'] = zone return zones From 19024f23d49d0839d1f1cada21b85fc6c8b474c4 Mon Sep 17 00:00:00 2001 From: Guillaume-Jean Herbiet Date: Thu, 2 Jun 2022 14:29:11 +0200 Subject: [PATCH 6/9] decrease catz zone parsing function complexity --- dnscatz/catz2nsd.py | 55 ++++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/dnscatz/catz2nsd.py b/dnscatz/catz2nsd.py index 6f77bb4..fda2a2d 100644 --- a/dnscatz/catz2nsd.py +++ b/dnscatz/catz2nsd.py @@ -224,30 +224,53 @@ def get_catz_zones(catalog_zone: dns.zone.Zone) -> Dict: f"Unsupported catalog zone version ({catz_version})" ) elif str(k).startswith("group."): - rdataset = v.get_rdataset(dns.rdataclass.IN, dns.rdatatype.TXT) - if len(rdataset) != 1: - raise CatalogZoneError("Broken catalog zone (group/TXT)") - uuid = str(k).split(".")[1] - group = str(rdataset[0]).strip("\"") - if uuid not in zones: - zones[uuid] = {} - zones[uuid]['group'] = group + get_zone_property( + zones, + str(k), + v.get_rdataset(dns.rdataclass.IN, dns.rdatatype.TXT) + ) elif str(k).startswith("coo."): logging.info("Change of Ownership property not supported: %s", str(k)) elif str(k).startswith("serial."): logging.info("Serial property not supported: %s", str(k)) elif str(k).endswith(".zones"): - rdataset = v.get_rdataset(dns.rdataclass.IN, dns.rdatatype.PTR) - if len(rdataset) != 1: - raise CatalogZoneError("Broken catalog zone (PTR)") - uuid = str(k).split(".")[0] - zone = str(rdataset[0]).rstrip(".") - if uuid not in zones: - zones[uuid] = {} - zones[uuid]['zone'] = zone + get_zone( + zones, + str(k), + v.get_rdataset(dns.rdataclass.IN, dns.rdatatype.PTR) + ) return zones +def get_zone_property( + zones: Dict, + record: str, + rdataset: dns.rdataset.Rdataset +): + """Get zone property from correspindig TXT record""" + zone_property = record.split(".")[0] + uuid = record.split(".")[1] + if len(rdataset) != 1: + raise CatalogZoneError("Broken catalog zone (%s/TXT)", zone_property) + if uuid not in zones: + zones[uuid] = {} + zones[uuid][zone_property] = str(rdataset[0]).strip("\"") + + +def get_zone( + zones: Dict, + record: str, + rdataset: dns.rdataset.Rdataset +): + """Get zone from PTR record""" + uuid = record.split(".")[0] + if len(rdataset) != 1: + raise CatalogZoneError("Broken catalog zone (PTR)") + if uuid not in zones: + zones[uuid] = {} + zones[uuid]['zone'] = str(rdataset[0]).rstrip(".") + + def get_catz_version(rr) -> Optional[int]: """Get catalog zone version from TXT RR""" if rr.rdtype != dns.rdatatype.TXT: From 30249f985985f436d4723afa09119dcf82dea497 Mon Sep 17 00:00:00 2001 From: Guillaume-Jean Herbiet Date: Thu, 2 Jun 2022 15:03:34 +0200 Subject: [PATCH 7/9] lint --- dnscatz/catz2nsd.py | 30 +++++++++--------------------- dnscatz/zones2catz.py | 16 ++++++++-------- 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/dnscatz/catz2nsd.py b/dnscatz/catz2nsd.py index fda2a2d..a00ea27 100644 --- a/dnscatz/catz2nsd.py +++ b/dnscatz/catz2nsd.py @@ -225,9 +225,7 @@ def get_catz_zones(catalog_zone: dns.zone.Zone) -> Dict: ) elif str(k).startswith("group."): get_zone_property( - zones, - str(k), - v.get_rdataset(dns.rdataclass.IN, dns.rdatatype.TXT) + zones, str(k), v.get_rdataset(dns.rdataclass.IN, dns.rdatatype.TXT) ) elif str(k).startswith("coo."): logging.info("Change of Ownership property not supported: %s", str(k)) @@ -235,18 +233,12 @@ def get_catz_zones(catalog_zone: dns.zone.Zone) -> Dict: logging.info("Serial property not supported: %s", str(k)) elif str(k).endswith(".zones"): get_zone( - zones, - str(k), - v.get_rdataset(dns.rdataclass.IN, dns.rdatatype.PTR) + zones, str(k), v.get_rdataset(dns.rdataclass.IN, dns.rdatatype.PTR) ) return zones -def get_zone_property( - zones: Dict, - record: str, - rdataset: dns.rdataset.Rdataset -): +def get_zone_property(zones: Dict, record: str, rdataset: dns.rdataset.Rdataset): """Get zone property from correspindig TXT record""" zone_property = record.split(".")[0] uuid = record.split(".")[1] @@ -254,21 +246,17 @@ def get_zone_property( raise CatalogZoneError("Broken catalog zone (%s/TXT)", zone_property) if uuid not in zones: zones[uuid] = {} - zones[uuid][zone_property] = str(rdataset[0]).strip("\"") + zones[uuid][zone_property] = str(rdataset[0]).strip('"') -def get_zone( - zones: Dict, - record: str, - rdataset: dns.rdataset.Rdataset -): +def get_zone(zones: Dict, record: str, rdataset: dns.rdataset.Rdataset): """Get zone from PTR record""" uuid = record.split(".")[0] if len(rdataset) != 1: raise CatalogZoneError("Broken catalog zone (PTR)") if uuid not in zones: zones[uuid] = {} - zones[uuid]['zone'] = str(rdataset[0]).rstrip(".") + zones[uuid]["zone"] = str(rdataset[0]).rstrip(".") def get_catz_version(rr) -> Optional[int]: @@ -354,9 +342,9 @@ def main() -> None: for cz in catalog_zones: for uuid in cz.zones: - zone = cz.zones[uuid]['zone'] - if 'group' in cz.zones[uuid]: - group = cz.zones[uuid]['group'] + zone = cz.zones[uuid]["zone"] + if "group" in cz.zones[uuid]: + group = cz.zones[uuid]["group"] else: group = cz.pattern if zone not in current_zone_patterns: diff --git a/dnscatz/zones2catz.py b/dnscatz/zones2catz.py index 1806310..cf61613 100644 --- a/dnscatz/zones2catz.py +++ b/dnscatz/zones2catz.py @@ -4,10 +4,10 @@ """ import argparse +import csv import sys import time import uuid -import csv from io import StringIO CATZ_VERSION = 2 @@ -49,17 +49,17 @@ def generate_catalog_zone(origin: str, zonelist: str) -> str: print(f"{origin} {DEFAULT_TTL} IN NS invalid.") print(f'version.{origin} {DEFAULT_TTL} IN TXT "{CATZ_VERSION}"') - with open(zonelist, mode='r') as csv_file: - csv_reader = csv.DictReader(csv_file, fieldnames=['zone', 'group']) + with open(zonelist, mode="r") as csv_file: + csv_reader = csv.DictReader(csv_file, fieldnames=["zone", "group"]) for row in csv_reader: - zone = row['zone'].strip() + zone = row["zone"].strip() if not zone.endswith("."): zone += "." zone_id = uuid.uuid5(uuid.NAMESPACE_DNS, zone) print(f"{zone_id}.zones.{origin} {DEFAULT_TTL} IN PTR {zone}") - if row['group']: - group = row['group'].strip() - print(f"group.{zone_id}.zones.{origin} {DEFAULT_TTL} IN TXT \"{group}\"") + if row["group"]: + group = row["group"].strip() + print(f'group.{zone_id}.zones.{origin} {DEFAULT_TTL} IN TXT "{group}"') sys.stdout = old_stdout @@ -93,7 +93,7 @@ def main() -> None: if not origin.endswith("."): origin += "." - catalog_zone_str = generate_catalog_zone(origin=origin, zonelist=args.zonelist) + catalog_zone_str = generate_catalog_zone(origin=origin, zonelist=str(args.zonelist)) if args.output: with open(args.output, "wt") as output_file: From e056855c8dcd66b37b8e27325e39100cde986355 Mon Sep 17 00:00:00 2001 From: Guillaume-Jean Herbiet Date: Thu, 2 Jun 2022 15:33:52 +0200 Subject: [PATCH 8/9] lint --- dnscatz/catz2nsd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnscatz/catz2nsd.py b/dnscatz/catz2nsd.py index a00ea27..82d5b44 100644 --- a/dnscatz/catz2nsd.py +++ b/dnscatz/catz2nsd.py @@ -272,7 +272,7 @@ def ensure_unique_zones(catalog_zones: List[CatalogZone]): zone2catalogs = defaultdict(set) for cz in catalog_zones: for uuid in cz.zones: - zone = cz.zones[uuid]['zone'] + zone = cz.zones[uuid]["zone"] zone2catalogs[zone].add(cz.origin) errors = 0 for zone, catalogs in zone2catalogs.items(): From c98e52e68d8fac7504b639681105ddc20a273589 Mon Sep 17 00:00:00 2001 From: Guillaume-Jean Herbiet Date: Thu, 2 Jun 2022 15:45:39 +0200 Subject: [PATCH 9/9] fix type error in opening csv file inn zones2catz --- dnscatz/zones2catz.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnscatz/zones2catz.py b/dnscatz/zones2catz.py index cf61613..a426e4c 100644 --- a/dnscatz/zones2catz.py +++ b/dnscatz/zones2catz.py @@ -49,7 +49,7 @@ def generate_catalog_zone(origin: str, zonelist: str) -> str: print(f"{origin} {DEFAULT_TTL} IN NS invalid.") print(f'version.{origin} {DEFAULT_TTL} IN TXT "{CATZ_VERSION}"') - with open(zonelist, mode="r") as csv_file: + with open(str(zonelist), "r") as csv_file: csv_reader = csv.DictReader(csv_file, fieldnames=["zone", "group"]) for row in csv_reader: zone = row["zone"].strip() @@ -93,7 +93,7 @@ def main() -> None: if not origin.endswith("."): origin += "." - catalog_zone_str = generate_catalog_zone(origin=origin, zonelist=str(args.zonelist)) + catalog_zone_str = generate_catalog_zone(origin=origin, zonelist=args.zonelist) if args.output: with open(args.output, "wt") as output_file: