1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
|
import pynetbox
import os
from dotenv import load_dotenv
import json
from pdns import PowerDNS
import ipaddress
import subprocess
import re
import netaddr
from config.dhcp4 import base as dhcp4
from config.dhcp6 import base as dhcp6
from config.dhcp4 import fap
from config.ddns import base as ddns
from config.ddns import ddns_domain
from config.dhcp4 import subnet as subnet4
from config.dhcp6 import subnet as subnet6
# Take environment variables from .env
load_dotenv()
DOMAIN_NAME = os.environ['DOMAIN_NAME']
DOMAIN_SEARCH = os.environ['DOMAIN_SEARCH']
NAMESERVERS = os.environ['NAMESERVERS'].split()
nb = pynetbox.api(
os.getenv('NETBOX_URL'),
token=os.getenv('NETBOX_API_KEY'),
threading=True,
)
# DNS
pdns = PowerDNS(os.environ['PDNS_API_URL'], os.environ['PDNS_API_KEY'])
# Load all zones to later check if a zone already exist
zones = [zone['name'] for zone in pdns.list_zones()]
rdns_zones = pdns.search("*.arpa", 2000, "zone")
kea4_subnets = []
kea6_subnets = []
kea_ddns_domains = []
kea_rddns_domains = []
# dhcp-client
vlans = nb.ipam.vlans.filter(tag='dhcp-client')
for vlan in vlans:
vlan_domain_name = f"net-{vlan.name}.{DOMAIN_NAME}"
prefixes4 = []
prefixes6 = []
kea_ddns_domains.append(ddns_domain(vlan_domain_name))
for prefix in nb.ipam.prefixes.filter(vlan_id=vlan.id, family=4):
kea4_subnets.append(
subnet4(vlan, prefix, DOMAIN_NAME, vlan_domain_name))
prefixes4.append(prefix)
for prefix in nb.ipam.prefixes.filter(vlan_id=vlan.id, family=6):
kea6_subnets.append(
subnet6(vlan, prefix, DOMAIN_NAME, vlan_domain_name))
prefixes6.append(prefix)
if f"{vlan_domain_name}." not in zones and len(prefixes4) >= 1:
print(pdns.create_zone(f"{vlan_domain_name}.", NAMESERVERS))
print(pdns.create_zone_metadata(
f"{vlan_domain_name}.", 'TSIG-ALLOW-DNSUPDATE', 'dhcpns'))
zone_rrsets = []
for prefix in prefixes4:
network = ipaddress.ip_network(prefix)
# Network ID
zone_rrsets.append({'name': f'id-{network[0]}.{vlan_domain_name}.', 'changetype': 'replace', 'type': 'A', 'records': [
{'content': str(network[0]), 'disabled': False, 'type':'A'}], 'ttl': 900})
# Gateway
zone_rrsets.append({'name': f'gw-{network[1]}.{vlan_domain_name}.', 'changetype': 'replace', 'type': 'A', 'records': [
{'content': str(network[1]), 'disabled': False, 'type':'A'}], 'ttl': 900})
# Broadcast
zone_rrsets.append({'name': f'broadcast-{network[-1]}.{vlan_domain_name}.', 'changetype': 'replace', 'type': 'A', 'records': [
{'content': str(network[-1]), 'disabled': False, 'type':'A'}], 'ttl': 900})
# Apply zone_rrsets
pdns.set_records(vlan_domain_name, zone_rrsets)
rdns_zone = pdns.get_rdns_zone_from_ip(network[0])
rdns_rrsets = []
if rdns_zone is None:
print(f"Failed to find RDNS Zone for IP {network[0]}")
# Network ID
rdns_rrsets.append({"name": network[0].reverse_pointer + '.', "changetype": "replace", "type": "PTR", "records": [
{"content": f'net-{network[0]}.{vlan_domain_name}.', "disabled": False, "type": "PTR"}], "ttl": 900})
# Gateway
rdns_rrsets.append({"name": network[1].reverse_pointer + '.', "changetype": "replace", "type": "PTR", "records": [
{"content": f'gw-{network[1]}.{vlan_domain_name}.', "disabled": False, "type": "PTR"}], "ttl": 900})
# Broadcast
rdns_rrsets.append({"name": network[-1].reverse_pointer + '.', "changetype": "replace", "type": "PTR", "records": [
{"content": f'broadcast-{network[-1]}.{vlan_domain_name}.', "disabled": False, "type": "PTR"}], "ttl": 900})
# Apply rdns_rrsets
pdns.set_records(network[1].reverse_pointer + '.', rdns_rrsets)
# dhcp-mgmt-edge
vlans = nb.ipam.vlans.filter(tag='dhcp-mgmt-edge')
for vlan in vlans:
prefixes4 = []
for prefix in nb.ipam.prefixes.filter(vlan_id=vlan.id, family=4):
kea4_subnets.append(
fap(vlan, prefix))
for zone in rdns_zones:
kea_rddns_domains.append(ddns_domain(zone['name'][:-1]))
# Write DDNS
if os.environ['KEA_DDNS_FILE'] is not None:
with open(os.environ['KEA_DDNS_FILE'], "w") as outfile:
outfile.write(json.dumps(
{"DhcpDdns": ddns(kea_ddns_domains, kea_rddns_domains)}, indent=2))
# Write DHCPv4
if os.environ['KEA_DHCP4_FILE'] is not None:
with open(os.environ['KEA_DHCP4_FILE'], "w") as outfile:
outfile.write(json.dumps({"Dhcp4": dhcp4(kea4_subnets)}, indent=2))
# Write DHCPv6
if os.environ['KEA_DHCP6_FILE'] is not None:
with open(os.environ['KEA_DHCP6_FILE'], "w") as outfile:
outfile.write(json.dumps({"Dhcp6": dhcp6(kea6_subnets)}, indent=2))
# Test DHCPv4
try:
subprocess.check_call(['/usr/sbin/kea-dhcp4', '-t',
os.environ['KEA_DHCP4_FILE']])
except subprocess.CalledProcessError:
print("Failed to validate kea-dhcp4 config. What do we do now?")
# Test DHCPv6
try:
subprocess.check_call(['/usr/sbin/kea-dhcp6', '-t',
os.environ['KEA_DHCP6_FILE']])
except subprocess.CalledProcessError:
print("Failed to validate kea-dhcp6 config. What do we do now?")
# Reload all zones
zones = [zone['name'] for zone in pdns.list_zones()]
# Create DNS for devices
devices = nb.dcim.devices.all()
for device in devices:
if device.primary_ip4 is None or device.primary_ip6 is None:
continue
zone = "tg23.gathering.org"
# IPv4
zone_rrsets = []
if device.primary_ip4 is not None:
zone_rrsets.append({'name': f'{device.name}.{zone}.', 'changetype': 'replace', 'type': 'A', 'records': [
{'content': str(netaddr.IPNetwork(str(device.primary_ip4)).ip), 'disabled': False, 'type': 'A'}], 'ttl': 900})
# IPv6
if device.primary_ip6 is not None:
zone_rrsets.append({'name': f'{device.name}.{zone}.', 'changetype': 'replace', 'type': 'AAAA', 'records': [
{'content': str(netaddr.IPNetwork(str(device.primary_ip6)).ip), 'disabled': False, 'type': 'A'}], 'ttl': 900})
if len(zone_rrsets) > 1:
# Apply zone_rrsets
print(pdns.set_records(zone, zone_rrsets))
rdns_zone = pdns.get_rdns_zone_from_ip(
str(netaddr.IPNetwork(str(device.primary_ip4)).ip))
rdns_rrsets = []
if rdns_zone is None:
print(f"Failed to find RDNS Zone for IP")
# IPv4 RDNS
rdns_rrsets.append({"name": ipaddress.ip_address(str(netaddr.IPNetwork(str(device.primary_ip4)).ip)).reverse_pointer + '.', "changetype": "replace", "type": "PTR", "records": [
{"content": f'{device.name}.{zone}.', "disabled": False, "type": "PTR"}], "ttl": 900})
# Apply rdns_rrsets
print(pdns.set_records(rdns_zone, rdns_rrsets))
|