diff options
author | Ole Mathias Aa. Heggem <olemathias.aa.heggem@gmail.com> | 2019-11-11 22:14:22 +0100 |
---|---|---|
committer | Ole Mathias Aa. Heggem <olemathias.aa.heggem@gmail.com> | 2019-11-11 22:14:22 +0100 |
commit | ef8122013956d09127c25285fde92b39c4e8af55 (patch) | |
tree | 34ce1266ae41d86e9378f481c1afb1e00be9c90c /examples/tg19/dns_auth | |
parent | 1f190fed23d84fa00559653e868e6be5ff6451aa (diff) |
A little TG19 scripts and config
Diffstat (limited to 'examples/tg19/dns_auth')
-rw-r--r-- | examples/tg19/dns_auth/cozy/pdns.conf | 34 | ||||
-rw-r--r-- | examples/tg19/dns_auth/huor/pdns.conf | 34 | ||||
-rw-r--r-- | examples/tg19/dns_auth/tools/config.ini | 20 | ||||
-rw-r--r-- | examples/tg19/dns_auth/tools/make_dns_gondul.py | 81 | ||||
-rw-r--r-- | examples/tg19/dns_auth/tools/make_dns_netbox.py | 20 | ||||
-rwxr-xr-x | examples/tg19/dns_auth/tools/make_reverse_zones.py | 76 | ||||
-rw-r--r-- | examples/tg19/dns_auth/tools/notify-all.sh | 3 | ||||
-rw-r--r-- | examples/tg19/dns_auth/tools/pdns.py | 119 |
8 files changed, 387 insertions, 0 deletions
diff --git a/examples/tg19/dns_auth/cozy/pdns.conf b/examples/tg19/dns_auth/cozy/pdns.conf new file mode 100644 index 0000000..33e2a53 --- /dev/null +++ b/examples/tg19/dns_auth/cozy/pdns.conf @@ -0,0 +1,34 @@ +setgid=pdns +setuid=pdns +socket-dir=/var/run +version-string=powerdns +config-dir=/etc/powerdns +include-dir=/etc/powerdns/pdns.d +master=yes +supermaster=yes +enable-lua-records=yes + +# Listen address +local-address=185.110.149.2,127.0.0.1 +local-ipv6=2a06:5841:a:103::2,::1 +local-port=53 + +# Default SOA +default-soa-name=ns1.infra.gathering.org +default-soa-mail=tg19tech-net.gathering.org + +# API +api=yes +api-key=<SECRET> +webserver=yes +webserver-port=8081 +webserver-allow-from=127.0.0.1,::1 + +# DNS UPDATE +dnsupdate=yes +allow-dnsupdate-from=127.0.0.1/32,::1/128 +forward-dnsupdate=no + +# AXFR +disable-axfr=no +allow-axfr-ips=127.0.0.1/32, 134.90.150.178/32, ::1/128, 2a02:20c8:1930::178/128 diff --git a/examples/tg19/dns_auth/huor/pdns.conf b/examples/tg19/dns_auth/huor/pdns.conf new file mode 100644 index 0000000..ad1b44c --- /dev/null +++ b/examples/tg19/dns_auth/huor/pdns.conf @@ -0,0 +1,34 @@ +setgid=pdns +setuid=pdns +socket-dir=/var/run +version-string=powerdns +config-dir=/etc/powerdns +include-dir=/etc/powerdns/pdns.d +slave=yes +supermaster=yes +enable-lua-records=yes + +# Listen address +local-address=134.90.150.178,127.0.0.1 +local-ipv6=2A02:20C8:1930::178,::1 +local-port=53 + +# Default SOA +default-soa-name=ns1.infra.gathering.org +default-soa-mail=tg19tech-net.gathering.org + +# API +api=yes +api-key=<SECRET> +webserver=yes +webserver-port=8081 +webserver-allow-from=127.0.0.1,::1 + +# DNS UPDATE +dnsupdate=no +allow-dnsupdate-from=127.0.0.1/32,::1/128 +forward-dnsupdate=yes + +# AXFR +disable-axfr=no +allow-axfr-ips=127.0.0.1/32,185.110.149.2/32,37.191.191.134/32,::1/128,2a06:5841:a:103::2/128 diff --git a/examples/tg19/dns_auth/tools/config.ini b/examples/tg19/dns_auth/tools/config.ini new file mode 100644 index 0000000..b32aed8 --- /dev/null +++ b/examples/tg19/dns_auth/tools/config.ini @@ -0,0 +1,20 @@ +[EVENT] +name = The Gathering 2019 +short = tg19 +domain = tg19.gathering.org +lol_domain = tg.lol +netbox_url = https://netbox.infra.gathering.org +netbox_api_key = <REMOVED> + +[DNS] +pri_ipv4 = 88.92.18.2 +pri_ipv6 = 2a06:5841:a:103::2 +sec_ipv4 = 134.90.150.178 +sec_ipv6 = 2a02:20c8:1930::178 + +api_url = http://localhost:8081/api/v1 +api_key = <REMOVED> + +[DHCP] +pri_ipv4 = 88.92.18.2 +pri_ipv6 = 2a06:5841:a:103::2 diff --git a/examples/tg19/dns_auth/tools/make_dns_gondul.py b/examples/tg19/dns_auth/tools/make_dns_gondul.py new file mode 100644 index 0000000..ad9fa57 --- /dev/null +++ b/examples/tg19/dns_auth/tools/make_dns_gondul.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +import json +import requests +import os +from requests.auth import HTTPBasicAuth +from pdns import PowerDNS + +import configparser +import netaddr + +config = configparser.ConfigParser() +config.read('config.ini') + +# TODO read from config.ini +GONDUL_URL = 'https://gondul.tg19.gathering.org' +GONDUL_USER = 'tech' +GONDUL_PASSWORD = '<Removed>' +nameservers = ['ns1.infra.gathering.org.', 'ns2.infra.gathering.org.'] + + +pdns = PowerDNS(config['DNS']['api_url'], config['DNS']['api_key']) + +# Load all zones to later check if a zone already exist +zones = [] +pdns_zones = pdns.list_zones() +for zone in pdns_zones: + zones.append(zone['name']) + +r = requests.get('{}/api/read/networks'.format(GONDUL_URL), auth=HTTPBasicAuth(GONDUL_USER, GONDUL_PASSWORD)) + +networks = r.json()['networks'] + +for network in networks: + zone = '{}.tg19.gathering.org.'.format(network) + if zone not in zones: + pdns.create_zone(zone, nameservers) + pdns.create_zone_metadata(zone, 'TSIG-ALLOW-DNSUPDATE', 'dhcp_updater') + record = {'content': networks[network]['gw4'], 'disabled': False, 'type':'A', 'set-ptr': True} + rrset4 = {'name': 'gw.{}'.format(zone), 'changetype': 'replace', 'type':'A', 'records': [record], 'ttl': 900} + record = {'content': networks[network]['gw6'], 'disabled': False, 'type':'AAAA', 'set-ptr': True} + rrset6 = {'name': 'gw.{}'.format(zone), 'changetype': 'replace', 'type':'AAAA', 'records': [record], 'ttl': 900} + print(pdns.set_zone_records(zone, [rrset4, rrset6])) + + +r = requests.get('{}/api/read/switches-management'.format(GONDUL_URL), auth=HTTPBasicAuth(GONDUL_USER, GONDUL_PASSWORD)) + +switches = r.json()['switches'] + +main_zone = 'tg19.gathering.org.' + +lol_rrsets = [] + +for switch in switches: + rrsets = [] + zone = '{}.{}'.format(switch, main_zone) + name = zone + if zone not in zones: + zone = main_zone + name = '{}.{}'.format(switch, zone) + + if switches[switch]['mgmt_v4_addr'] is not None and switches[switch]['mgmt_v4_addr'] != '': + record = {'content': switches[switch]['mgmt_v4_addr'], 'disabled': False, 'type':'A', 'set-ptr': True} + rrsets.append({'name': name, 'changetype': 'replace', 'type':'A', 'records': [record], 'ttl': 900}) + if switches[switch]['mgmt_v6_addr'] is not None and switches[switch]['mgmt_v6_addr'] != '': + record = {'content': switches[switch]['mgmt_v6_addr'], 'disabled': False, 'type':'AAAA', 'set-ptr': True} + rrsets.append({'name': name, 'changetype': 'replace', 'type':'AAAA', 'records': [record], 'ttl': 900}) + print(pdns.set_zone_records(zone, rrsets).text) + print(zone, rrsets) + + zone = 'tg.lol.' + name = '{}.{}'.format(switch, zone) + if switches[switch]['mgmt_v4_addr'] is not None and switches[switch]['mgmt_v4_addr'] != '': + record = {'content': switches[switch]['mgmt_v4_addr'], 'disabled': False, 'type':'A'} + lol_rrsets.append({'name': name, 'changetype': 'replace', 'type':'A', 'records': [record], 'ttl': 900}) + if switches[switch]['mgmt_v6_addr'] is not None and switches[switch]['mgmt_v6_addr'] != '': + record = {'content': switches[switch]['mgmt_v6_addr'], 'disabled': False, 'type':'AAAA'} + lol_rrsets.append({'name': name, 'changetype': 'replace', 'type':'AAAA', 'records': [record], 'ttl': 900}) + +print(lol_rrsets) +print(pdns.set_zone_records('tg.lol.', lol_rrsets).text) diff --git a/examples/tg19/dns_auth/tools/make_dns_netbox.py b/examples/tg19/dns_auth/tools/make_dns_netbox.py new file mode 100644 index 0000000..9e99544 --- /dev/null +++ b/examples/tg19/dns_auth/tools/make_dns_netbox.py @@ -0,0 +1,20 @@ +import pynetbox +from pdns import PowerDNS +import configparser +import netaddr + +config = configparser.ConfigParser() +config.read('config.ini') + +nb = pynetbox.api(config['EVENT']['netbox_url'], token=config['EVENT']['netbox_api_key']) +pdns = PowerDNS(config['DNS']['api_url'], config['DNS']['api_key']) + +#devices = nb.dcim.devices.all() +#for device in devices: +# if device.site.name == 'Floor': +# continue +# pdns.create_netbox_device_record(device, config['EVENT']['domain'], config['EVENT']['lol_domain']) + +vms = nb.virtualization.virtual_machines.all() +for vm in vms: + pdns.create_netbox_device_record(vm, config['EVENT']['domain'], config['EVENT']['lol_domain']) diff --git a/examples/tg19/dns_auth/tools/make_reverse_zones.py b/examples/tg19/dns_auth/tools/make_reverse_zones.py new file mode 100755 index 0000000..ec05ec0 --- /dev/null +++ b/examples/tg19/dns_auth/tools/make_reverse_zones.py @@ -0,0 +1,76 @@ +#!/usr/bin/python3 + +import argparse +import ipaddress +import re +import json + +from pdns import PowerDNS + +def main(): + parser = argparse.ArgumentParser(description='Make reverse zones') + parser.add_argument('--key', help='PowerDNS Web API key', required=True) + parser.add_argument('--host', help='PowerDNS Web API url. Default: http://127.0.0.1:8081/api/v1', default='http://127.0.0.1:8081/api/v1') + parser.add_argument('nets', nargs='*', help="Example: ./make_reverse_zones.py --key PDNSAPIKEY 2a06:5840::/29 185.110.148.0/22 88.92.0.0/17") + args = parser.parse_args() + + nameservers = ['ns1.infra.gathering.org.', 'ns2.infra.gathering.org.'] + + # Connect to powerdns api + pdns = PowerDNS(args.host,args.key) + + if len(args.nets) < 1: + print("Argument with block is required. Example: ./make_reverse_zones.py 2a06:5840::/29 185.110.148.0/22 88.92.0.0/17") + exit(1) + + # Load all zones to later check if a zone already exist. + zones = [] + pdns_zones = pdns.list_zones() + for zone in pdns_zones: + zones.append(zone['name']) + + # Loop all nets in args + for net in args.nets: + block = ipaddress.ip_network(net) + + # IPv4 - Split the network up in /24 blocks + if block.version == 4 and block.prefixlen <= 24: + blocks = list(block.subnets(new_prefix=24)) + for bl in blocks: + net_id = ipaddress.ip_network(bl).network_address + p = re.compile('(.*)\.(.*)\.(.*)\.(.*)') + m = p.match(str(net_id)) + ip4_arpa = '{}.{}.{}.in-addr.arpa.'.format(m.group(3),m.group(2),m.group(1)) + if ip4_arpa not in zones: + print("Creating zone {}".format(ip4_arpa)) + pdns.create_zone(ip4_arpa, nameservers) + else: + print(pdns.get_zone_metadata(ip4_arpa)) + pdns.create_zone_metadata(ip4_arpa, 'TSIG-ALLOW-DNSUPDATE', 'dhcpdupdate') + #print("{} already exists, skipping.".format(ip4_arpa)) + + elif block.version == 4: + print("{} can't be smaller then /24 (bigger number)".format(net)) + exit(1) + + # IPv6 - Split the network up in /32 blocks + if block.version == 6 and block.prefixlen <= 32: + blocks = list(block.subnets(new_prefix=32)) + for bl in blocks: + reverse = ipaddress.ip_network((bl)[0]).reverse_pointer + # Hardcoded to /32, will need to be modified if we need smaller nets then /32 (bigger number) + p = re.compile('8\.2\.1\.\/\.(0\.0\.0\.0\.0\.0\.0\.0\.0\.0\.0\.0\.0\.0\.0\.0\.0\.0\.0\.0\.0\.0\.0\.0\.)(.*ip6.arpa)$') + m = p.match(str(reverse)) + ip6_arpa = '{}.'.format(m.group(2)) + if ip6_arpa not in zones: + print("Creating zone {}".format(ip6_arpa)) + pdns.create_zone(ip6_arpa, nameservers) + else: + print(pdns.get_zone_metadata(ip6_arpa)) + pdns.create_zone_metadata(ip6_arpa, 'TSIG-ALLOW-DNSUPDATE', 'dhcpdupdate') + elif block.version == 6: + print("{} can't be smaller then /32 (bigger number)".format(net)) + exit(1) + +if __name__ == "__main__": + main() diff --git a/examples/tg19/dns_auth/tools/notify-all.sh b/examples/tg19/dns_auth/tools/notify-all.sh new file mode 100644 index 0000000..6b878ac --- /dev/null +++ b/examples/tg19/dns_auth/tools/notify-all.sh @@ -0,0 +1,3 @@ +for zone in $(pdnsutil list-all-zones); do + pdns_control notify $zone +done diff --git a/examples/tg19/dns_auth/tools/pdns.py b/examples/tg19/dns_auth/tools/pdns.py new file mode 100644 index 0000000..dbe7f18 --- /dev/null +++ b/examples/tg19/dns_auth/tools/pdns.py @@ -0,0 +1,119 @@ +import requests +import json +import re +import netaddr + +class PowerDNS: + def __init__(self, base_url, apikey, server = 'localhost'): + self.base_url = base_url + self.apikey = apikey + self.server = server + + def _query(self, uri, method, kwargs = None): + headers = { + 'X-API-Key': self.apikey, + 'Accept': 'application/json' + } + + if method == "GET": + request = requests.get(self.base_url+uri, headers=headers) + elif method == "POST": + request = requests.post(self.base_url+uri, headers=headers, json=kwargs) + elif method == "PUT": + request = requests.put(self.base_url+uri, headers=headers, json=kwargs) + elif method == "PATCH": + request = requests.patch(self.base_url+uri, headers=headers, json=kwargs) + elif method == "DELETE": + request = requests.delete(self.base_url+uri, headers=headers) + + return request + + def list_zones(self): + return self._query("/servers/%s/zones" % (self.server), "GET").json() + + def get_zone(self, domain): + return self._query("/servers/%s/zones/%s" % (self.server, domain), "GET").json() + + def get_zone_metadata(self, domain): + return self._query("/servers/%s/zones/%s/metadata" % (self.server, domain), "GET").json() + + def create_zone_metadata(self, domain, kind, content): + return self._query("/servers/%s/zones/%s/metadata" % (self.server, domain), "POST", { + 'kind': kind, + 'metadata': [content] + }) + + def create_zone(self, domain, nameservers, kind = 'Master'): + return self._query("/servers/%s/zones" % (self.server), "POST", { + 'kind': kind, + 'nameservers': nameservers, + 'name': domain + }) + + def delete_zone(self, domain): + return self._query("/servers/%s/zones/%s." % (self.server, domain), "DELETE") + + def set_zone_records(self, domain, rrsets): + """ + changetype Must be REPLACE or DELETE. + With DELETE, all existing RRs matching name and type will be deleted, incl. all comments. + With REPLACE: when records is present, all existing RRs matching name and type will be deleted, and then new records given in records will be created. + If no records are left, any existing comments will be deleted as well. + When comments is present, all existing comments for the RRs matching name and type will be deleted, and then new comments given in comments will be created. + rrsets example: + [{ + 'type': 'A', + 'name': 'mail.example.com', + 'changetype': 'delete' + }, + { + 'type': 'MX', + 'name': 'example.com', + 'changetype': 'replace', + 'records': [{'content': '0 example.com', + 'disabled': False, + 'name': 'example.com', + 'ttl': 600, + 'type': 'MX'}], + }] + """ + return self._query("/servers/%s/zones/%s" % (self.server, domain), "PATCH", { + 'rrsets': rrsets + }) + + def create_netbox_device_record(self, device, zone, lol_zone = None): + r = re.search('^([A-Za-z1-9]*)\.([A-Za-z1-9]*)$', device.name) + if r is not None: + device_name = r.group(1) + zone = "{}.{}.".format(r.group(2), zone) + lol_zone = "{}.{}.".format(r.group(2), lol_zone) + elif re.search('^([A-Za-z1-9]*) \(([A-Za-z1-9 -\/]*)\)', device.name) is not None: + zone = "{}.".format(zone) + lol_zone = "{}.".format(lol_zone) + device_name = re.search('^([A-Za-z1-9]*) \(([A-Za-z1-9 -\/]*)\)', device.name).group(1) + else: + zone = "{}.".format(zone) + lol_zone = "{}.".format(lol_zone) + device_name = device.name + fqdn = "{}.{}".format(device_name, zone) + lol_fqdn = "{}.{}".format(device_name, lol_zone) + + if device.primary_ip4 is not None: + record = {'content': str(netaddr.IPNetwork(str(device.primary_ip4)).ip), 'disabled': False, 'type':'A', 'set-ptr': True} + rrset = {'name': fqdn, 'changetype': 'replace', 'type':'A', 'records': [record], 'ttl': 900} + print(self.set_zone_records(zone, [rrset])) + print(rrset) + if lol_zone is not None: + record = {'content': str(netaddr.IPNetwork(str(device.primary_ip4)).ip), 'disabled': False, 'type':'A'} + rrset = {'name': lol_fqdn, 'changetype': 'replace', 'type':'A', 'records': [record], 'ttl': 900} + print(self.set_zone_records(lol_zone, [rrset]).text) + + if device.primary_ip6 is not None: + record = {'content': str(netaddr.IPNetwork(str(device.primary_ip6)).ip), 'disabled': False, 'type':'AAAA', 'set-ptr': True} + rrset = {'name': fqdn, 'changetype': 'replace', 'type':'AAAA', 'records': [record], 'ttl': 900} + print(self.set_zone_records(zone, [rrset])) + print(rrset) + if lol_zone is not None: + record = {'content': str(netaddr.IPNetwork(str(device.primary_ip6)).ip), 'disabled': False, 'type':'AAAA'} + rrset = {'name': lol_fqdn, 'changetype': 'replace', 'type':'AAAA', 'records': [record], 'ttl': 900} + print(self.set_zone_records(lol_zone, [rrset])) |