From 9f8695dddc1b016983fae6aef95b0ec3afff247e Mon Sep 17 00:00:00 2001 From: Sebastian Lohff Date: Thu, 6 Apr 2017 11:57:22 +0200 Subject: [PATCH] dns-sync draft --- bin/dns-sync | 182 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100755 bin/dns-sync diff --git a/bin/dns-sync b/bin/dns-sync new file mode 100755 index 0000000..c82a451 --- /dev/null +++ b/bin/dns-sync @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import argparse +import os +import sys +import json +import requests + +import django + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dnmgmt.settings") +sys.path.append("..") +django.setup() + +from django.db.models import Count +from domains.models import Domain, ReverseZone + + +__VERSION__ = '0.1' + + +def _parser(): + parser = argparse.ArgumentParser( + #prog='foo', + #description='do some awesome foo', + ) + + #parser.add_argument("-p", "--port", default=2323, type=int, help="Your port") + #parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Be more verbose") + + parser.add_argument("--version", action="version", version="%(prog)s " + __VERSION__) + + return parser + +def mergeDomains(domains, zoneData): + rrAdd = [] + rrData = zoneData['rrsets'] + + for domain, rrType, records in domains: + found = False + pdnsDom = list(filter(lambda _x: _x['name'] == domain and _x['type'] == rrType, rrData)) + if len(pdnsDom) > 0: + rrSet = set(_x['content'] for _x in pdnsDom[0]['records']) + if rrSet == set(records): + found = True + + if not found: + # new domain! + rrAdd.append({ + "name": domain, + "type": rrType, + "ttl": 60*60, + "changetype": "REPLACE", + "records": [{"content": record, "disabled": False} for record in records], + }) + + return rrAdd + + +def removeOldDomains(domains, zoneData): + rrDel = [] + + for entry in zoneData['rrsets']: + # search for name/type in domain dict. if non-existtant mark for deletion + # this could be much more efficient with a dict! name: [rrset...] + if not any(entry['name'] == _x[0] and entry['type'] == _x[1] for _x in domains): + rrDel.append({ + "changetype": "DELETE", + "name": entry["name"], + "type": entry["type"], + }) + + return rrDel + +def handleNameserver(ns, servers, usedNameservers, domains): + servers.append(ns.name) + + if ns.name not in usedNameservers and (ns.glueIPv4 or ns.glueIPv6): + usedNameservers.append(ns.name) + if ns.glueIPv4: + domains.append((ns.name, "A", [ns.glueIPv4])) + if ns.glueIPv6: + domains.append((ns.name, "AAAA", [ns.glueIPv6])) + + +def main(): + parser = _parser() + args = parser.parse_args() + print(args) + + s = requests.Session() + s.headers = { + 'X-API-Key': 'bei2aequ2OBi', + 'Accept': 'application/json' + } + + print(" --> TLD dn.") + zoneData = s.get("http://potatos.portal.dn:8081/api/v1/servers/localhost/zones/dn.").json() + + domains = [] + usedNameservers = [] + qs = Domain.objects.annotate(nsCount=Count('nameservers')).filter(nsCount__gt=0).order_by("name") + + # cycle through all domains + for domain in qs: + print(" %s" % (domain.name)) + servers = [] + for ns in domain.nameservers.all(): + servers.append(ns.name) + + if ns.name not in usedNameservers and (ns.glueIPv4 or ns.glueIPv6): + # FIXME: find dns glue record duplicates in whois webif + usedNameservers.append(ns.name) + if ns.glueIPv4: + domains.append((ns.name, "A", [ns.glueIPv4])) + if ns.glueIPv6: + domains.append((ns.name, "AAAA", [ns.glueIPv6])) + + domains.append((domain.name, "NS", servers)) + + # cycle through all reverse records + qs = Domain.objects.annotate(nsCount=Count('nameservers')).filter(nsCount__gt=0).order_by("name") + for rzone in qs: + print(" %s" % (rzone.getZone(),)) + servers = [] + for ns in domain.nameservers.all(): + servers.append(ns.name) + + if ns.name not in usedNameservers and (ns.glueIPv4 or ns.glueIPv6): + # FIXME: find dns glue record duplicates in whois webif + usedNameservers.append(ns.name) + if ns.glueIPv4: + domains.append((ns.name, "A", [ns.glueIPv4])) + if ns.glueIPv6: + domains.append((ns.name, "AAAA", [ns.glueIPv6])) + + rzones.append((rzone.getZone, "PTR", servers)) + + + + newDomains = mergeDomains(domains, zoneData) + print("Add/replace", newDomains) + r = s.patch("http://potatos.portal.dn:8081/api/v1/servers/localhost/zones/dn.", data=json.dumps({'rrsets': newDomains})) + if r.status_code != 204: + raise RuntimeError("Could not update records in powerdns") + + # delete old domains (except for NS/SOA of head) + domains.extend([ + ("dn.", "NS", []), + ("dn.", "SOA", []), + ]) + + delDomains = removeOldDomains(domains, zoneData) + print("Del", delDomains) + r = s.patch("http://potatos.portal.dn:8081/api/v1/servers/localhost/zones/dn.", data=json.dumps({'rrsets': delDomains})) + if r.status_code != 204: + raise RuntimeError("Could not update records in powerdns") + + # and now for the reverse zones... + zoneData = s.get("http://potatos.portal.dn:8081/api/v1/servers/localhost/zones/dn.").json() + qs = ReverseZone.objects.annotate(nsCount=Count('nameservers')).filter(nsCount__gt=0).order_by("name") + + # cycle through all domains + for domain in qs: + print(" %s" % (domain.name)) + servers = [] + for ns in domain.nameservers.all(): + servers.append(ns.name) + + if ns.name not in usedNameservers and (ns.glueIPv4 or ns.glueIPv6): + # FIXME: find dns glue record duplicates in whois webif + usedNameservers.append(ns.name) + if ns.glueIPv4: + domains.append((ns.name, "A", [ns.glueIPv4])) + if ns.glueIPv6: + domains.append((ns.name, "AAAA", [ns.glueIPv6])) + + domains.append((domain.name, "NS", servers)) + + +if __name__ == '__main__': + main()