Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Group property management #2

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ The configuration file is NSD-like as described below:
algorithm: <string>
secret: <base64 blob>

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

Expand Down
49 changes: 33 additions & 16 deletions dnscatz/catz2nsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps use defaultdict(dict) here to rid of the test at line 232?

for k, v in catalog_zone.nodes.items():
if str(k) == "version":
if rdataset := v.get_rdataset(dns.rdataclass.IN, dns.rdatatype.TXT):
Expand All @@ -223,17 +223,28 @@ 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))
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."):
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 not uuid in zones:
zones[uuid] = {}
zones[uuid]['zone'] = zone
return zones


Expand All @@ -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():
Expand Down Expand Up @@ -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
Expand Down
26 changes: 14 additions & 12 deletions dnscatz/zones2catz.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import sys
import time
import uuid
import csv
from io import StringIO
from typing import List

CATZ_VERSION = 2

Expand All @@ -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())

Expand All @@ -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

Expand Down Expand Up @@ -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:
Expand Down