From cd875a8d9b71bbf3bc5e083cf25a34ca89c063c1 Mon Sep 17 00:00:00 2001 From: Benjamin Collet Date: Thu, 8 Apr 2021 20:27:54 +0200 Subject: [PATCH] first commit --- .gitignore | 2 + config.yml.dist | 3 + exporter.py | 177 ++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 3 + zonefile.jj2.dist | 21 ++++++ 5 files changed, 206 insertions(+) create mode 100644 .gitignore create mode 100644 config.yml.dist create mode 100755 exporter.py create mode 100644 requirements.txt create mode 100644 zonefile.jj2.dist diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9fbd116 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +config.yml +zonefile.jj2 diff --git a/config.yml.dist b/config.yml.dist new file mode 100644 index 0000000..7d31b45 --- /dev/null +++ b/config.yml.dist @@ -0,0 +1,3 @@ +netbox: + url: 'https://netbox.local' + token: '' diff --git a/exporter.py b/exporter.py new file mode 100755 index 0000000..18f7e22 --- /dev/null +++ b/exporter.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 + +import argparse +import ipaddress +import os +import pynetbox +import sys +import yaml +import re +from datetime import datetime +from jinja2 import Environment, FileSystemLoader + +config = os.path.join(os.path.dirname(os.path.realpath(__file__)),'config.yml') + +with open(config, 'r') as ymlfile: + cfg = yaml.load(ymlfile, Loader=yaml.FullLoader) + +templates_dir = os.path.dirname(os.path.realpath(__file__)) + +def ptr(nb, args): + device_primary = {} + vm_primary = {} + records = {} + serial = 0 + af = ipaddress.ip_network(args.prefix).version + + devices = nb.dcim.devices.filter(has_primary_ip=True) + vms = nb.virtualization.virtual_machines.filter(has_primary_ip=True) + addresses = nb.ipam.ip_addresses.filter(parent=args.prefix) + + for device in devices: + last_updated = int(datetime.timestamp(datetime.strptime(device.last_updated, '%Y-%m-%dT%H:%M:%S.%fZ'))) + if last_updated > serial: serial = last_updated + + if af == 4 and device.primary_ip4: + device_primary[device.id] = device.primary_ip4.id + + elif af == 6 and device.primary_ip6: + device_primary[device.id] = device.primary_ip6.id + + for vm in vms: + last_updated = int(datetime.timestamp(datetime.strptime(vm.last_updated, '%Y-%m-%dT%H:%M:%S.%fZ'))) + if last_updated > serial: serial = last_updated + + if af == 4 and vm.primary_ip4: + vm_primary[vm.id] = vm.primary_ip4.id + + elif af == 6 and vm.primary_ip6: + vm_primary[vm.id] = vm.primary_ip6.id + + for address in addresses: + last_updated = int(datetime.timestamp(datetime.strptime(address.last_updated, '%Y-%m-%dT%H:%M:%S.%fZ'))) + if last_updated > serial: serial = last_updated + + ip = ipaddress.ip_interface(address.address).ip + ptr = ipaddress.ip_address(ip).reverse_pointer + + if address.dns_name: + records[ptr] = [{"type":"PTR","rr":address.dns_name}] + + elif address.assigned_object_type == 'dcim.interface': + if address.id == device_primary[address.assigned_object.device.id]: + records[ptr] = [{"type":"PTR","rr":address.assigned_object.device.name}] + else: + iname = re.sub(r'[^a-z0-9]', '-',address.assigned_object.name) + records[ptr] = [{"type":"PTR","rr":".".join((iname,address.assigned_object.device.name))}] + + elif address.assigned_object_type == 'virtualization.vminterface': + if address.id == vm_primary[address.assigned_object.virtual_machine.id]: + records[ptr] = [{"type":"PTR","rr":address.assigned_object.virtual_machine.name}] + else: + iname = re.sub(r'[^a-z0-9]', '-',address.assigned_object.name) + records[ptr] = [{"type":"PTR","rr":".".join((iname,address.assigned_object.virtual_machine.name))}] + + file_loader = FileSystemLoader(templates_dir) + env = Environment(loader=file_loader) + template = env.get_template("zonefile.jj2") + output = template.render(serial=serial,records=records) + print(output) + +def dns(nb, args): + devices_id = [] + vm_id = [] + primary_ip = {} + records = {} + serial = 0 + + devices = nb.dcim.devices.filter(name__iew=args.domain) + vms = nb.virtualization.virtual_machines.filter(name__iew=args.domain) + addresses = nb.ipam.ip_addresses.all() + + for device in devices: + last_updated = int(datetime.timestamp(datetime.strptime(device.last_updated, '%Y-%m-%dT%H:%M:%S.%fZ'))) + if last_updated > serial: serial = last_updated + + devices_id.append(device.id) + + if device.primary_ip4: + primary_ip[device.primary_ip4.id] = device.name + + if device.primary_ip6: + primary_ip[device.primary_ip6.id] = device.name + + for vm in vms: + last_updated = int(datetime.timestamp(datetime.strptime(vm.last_updated, '%Y-%m-%dT%H:%M:%S.%fZ'))) + if last_updated > serial: serial = last_updated + + vm_id.append(vm.id) + + if vm.primary_ip4: + primary_ip[vm.primary_ip4.id] = vm.name + + if vm.primary_ip6: + primary_ip[vm.primary_ip6.id] = vm.name + + for address in addresses: + last_updated = int(datetime.timestamp(datetime.strptime(address.last_updated, '%Y-%m-%dT%H:%M:%S.%fZ'))) + if last_updated > serial: serial = last_updated + + ip = ipaddress.ip_interface(address.address).ip + if ipaddress.ip_address(ip).version == 4: + type = "A" + else: + type = "AAAA" + + if address.dns_name and address.dns_name.endswith(args.domain): + records[address.dns_name] = [{"type":type,"rr":ip}] + + elif address.id in primary_ip: + if primary_ip[address.id] not in records: + records[primary_ip[address.id]] = [] + + records[primary_ip[address.id]].append({"type":type,"rr":ip}) + + elif address.assigned_object_type == 'dcim.interface' and address.assigned_object.device.id in devices_id: + iname = re.sub(r'[^a-z0-9]', '-',address.assigned_object.name) + fname = ".".join((iname,address.assigned_object.device.name)) + + if fname not in records: + records[fname] = [] + records[fname].append({"type":type,"rr":ip}) + + elif address.assigned_object_type == 'virtualization.vminterface' and address.assigned_object.virtual_machine.id in vm_id: + iname = re.sub(r'[^a-z0-9]', '-',address.assigned_object.name) + fname = ".".join((iname,address.assigned_object.virtual_machine.name)) + + if fname not in records: + records[fname] = [] + records[fname].append({"type":type,"rr":ip}) + + else: + sys.exit(1) + + file_loader = FileSystemLoader(templates_dir) + env = Environment(loader=file_loader) + template = env.get_template("zonefile.jj2") + output = template.render(serial=serial,records=records) + print(output) + + +if __name__ == '__main__': + nb = pynetbox.api( + cfg['netbox']['url'], + token=cfg['netbox']['token'] + ) + + parser = argparse.ArgumentParser(description='Netbox API exporter') + subparsers = parser.add_subparsers(help='Action to perform',dest='action',required=True) + + subparser = subparsers.add_parser('ptr', help='Generate reverse DNS zone file for prefix') + subparser.add_argument('prefix', type=str, help='Prefix') + + subparser = subparsers.add_parser('dns', help='Generate DNS zone file for domain') + subparser.add_argument('domain', type=str, help='Domain') + + args = parser.parse_args() + globals()[args.action](nb, args) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e5afcaf --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pynetbox +PyYAML +jinja2 diff --git a/zonefile.jj2.dist b/zonefile.jj2.dist new file mode 100644 index 0000000..374f7e1 --- /dev/null +++ b/zonefile.jj2.dist @@ -0,0 +1,21 @@ +$TTL 86400 +@ IN SOA ns1.example.com. hostmaster.example.com. ( + {{ serial }} ; Serial + 4H ; Refresh + 1H ; Retry + 1W ; Expire + 1D ) ; Minimum TTL +@ IN NS ns1.example.com. +@ IN NS ns2.example.com. + +{% for record, rrs in records.items() -%} +{% for rr in rrs -%} +{% if not record.endswith('.') -%} +{% set record = record + "." -%} +{% endif -%} +{% if rr.type in ['PTR'] and not rr.rr.endswith('.') -%} +{% set update = rr.update({'rr':rr.rr + "."}) -%} +{% endif -%} +{{ record }} IN {{ rr.type }} {{ rr.rr }} +{% endfor -%} +{% endfor -%}