diff --git a/README.md b/README.md index a846920..7301391 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ 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 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_. diff --git a/dnscatz/catz2nsd.py b/dnscatz/catz2nsd.py index 65404ae..82d5b44 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): @@ -223,20 +223,42 @@ 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)) + 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"): + 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: @@ -249,7 +271,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 +341,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 diff --git a/dnscatz/zones2catz.py b/dnscatz/zones2catz.py index e96ed12..1e16a99 100644 --- a/dnscatz/zones2catz.py +++ b/dnscatz/zones2catz.py @@ -52,11 +52,17 @@ def generate_catalog_zone( 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(str(zonelist), "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}"') if zonelist: with open(zonelist, mode="r") as csv_file: