aboutsummaryrefslogtreecommitdiffstats
path: root/examples/tg19/dns_auth
diff options
context:
space:
mode:
authorOle Mathias Aa. Heggem <olemathias.aa.heggem@gmail.com>2019-11-11 22:14:22 +0100
committerOle Mathias Aa. Heggem <olemathias.aa.heggem@gmail.com>2019-11-11 22:14:22 +0100
commitef8122013956d09127c25285fde92b39c4e8af55 (patch)
tree34ce1266ae41d86e9378f481c1afb1e00be9c90c /examples/tg19/dns_auth
parent1f190fed23d84fa00559653e868e6be5ff6451aa (diff)
A little TG19 scripts and config
Diffstat (limited to 'examples/tg19/dns_auth')
-rw-r--r--examples/tg19/dns_auth/cozy/pdns.conf34
-rw-r--r--examples/tg19/dns_auth/huor/pdns.conf34
-rw-r--r--examples/tg19/dns_auth/tools/config.ini20
-rw-r--r--examples/tg19/dns_auth/tools/make_dns_gondul.py81
-rw-r--r--examples/tg19/dns_auth/tools/make_dns_netbox.py20
-rwxr-xr-xexamples/tg19/dns_auth/tools/make_reverse_zones.py76
-rw-r--r--examples/tg19/dns_auth/tools/notify-all.sh3
-rw-r--r--examples/tg19/dns_auth/tools/pdns.py119
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]))