├── pictures ├── READ.me ├── NetBox_funet.png ├── NetBox_New_IP.png └── NetBox_customfields.png ├── netbox_custom_date.csv ├── netbox_custom_datetime.csv ├── README.md ├── netbox-ipam.py └── netbox-scan.py /pictures/READ.me: -------------------------------------------------------------------------------- 1 | Screenshots from the NetBox 2 | -------------------------------------------------------------------------------- /pictures/NetBox_funet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrleinonen/netbox-ipam/HEAD/pictures/NetBox_funet.png -------------------------------------------------------------------------------- /pictures/NetBox_New_IP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrleinonen/netbox-ipam/HEAD/pictures/NetBox_New_IP.png -------------------------------------------------------------------------------- /pictures/NetBox_customfields.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrleinonen/netbox-ipam/HEAD/pictures/NetBox_customfields.png -------------------------------------------------------------------------------- /netbox_custom_date.csv: -------------------------------------------------------------------------------- 1 | name,label,content_types,type,required,description,default,ui_visibility,weight,filter_logic 2 | dns_enabled,DNS,ipam.ipaddress,boolean,False,"Update and resolve DNS-name automatically.",false,read-write,100,loose 3 | first_seen,First seen,ipam.ipaddress,Date,false,"When the IP-address is first seen in ping.",,read-only,100,loose 4 | host_alive,Alive,ipam.ipaddress,boolean,false,"True if IP-address is online, False if IP-address is offline based on ping results.",,read-only,100,loose 5 | last_seen,Last seen,ipam.ipaddress,Date,false,"When the IP-address is last pinged successfully.",,read-only,100,loose 6 | seen_enabled,Scan,ipam.ipaddress,boolean,false,"True if IP-address is on ping process, False if not.",false,read-write,100,loose -------------------------------------------------------------------------------- /netbox_custom_datetime.csv: -------------------------------------------------------------------------------- 1 | name,label,content_types,type,required,description,default,ui_visibility,weight,filter_logic,cloneable,search_weight 2 | dns_enabled,DNS,ipam.ipaddress,boolean,False,"Update and resolve DNS-name automatically.",false,read-write,100,loose,true,1000 3 | first_seen,First seen,ipam.ipaddress,datetime,false,"When the IP-address is first seen in ping.",,read-only,100,loose,false,1000 4 | host_alive,Alive,ipam.ipaddress,boolean,false,"True if IP-address is online, False if IP-address is offline based on ping results.",,read-only,100,loose,false,1000 5 | last_seen,Last seen,ipam.ipaddress,datetime,false,"When the IP-address is last pinged successfully.",,read-only,100,loose,false,1000 6 | seen_enabled,Scan,ipam.ipaddress,boolean,false,"True if IP-address is on ping process, False if not.",false,read-write,100,loose,true,1000 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netbox-ipam 2 | 3 | Python script to collect IP-addresses from NetBox and check if IP is up 4 | and making reverse dns-lookup against IP's. Script automatically 5 | updates entry's DNS record. 6 | 7 | ![Example output](https://github.com/hrleinonen/netbox-ipam/blob/main/pictures/NetBox_funet.png) 8 | 9 | Managing IP addresses is great, but maintaining documentation is a pain. Someone deletes the device but doesn't delete its data. DNS records are also not always clear. I coded this script to alleviate these problems. 10 | 11 | The script searches the NetBox API for IP addresses that are marked with "alive" status and ping them, after that it updates the IP address information about whether the IP is up or not and when it was last up. The script also updates the information because the IP has been up for the first time. 12 | 13 | The script also performs a reverse DNS query for IP addresses and updates the information in the DNS field. 14 | 15 | You must create custom_fields in NetBox, please check netbox_custom_date.csv and netbox_custom_datetime.cvs files. If your NetBox version supports datetime format in custom fields, then use netbox_custom_datetime.cvs file. 16 | 17 | API-needs readwrite perssion in NetBox. 18 | 19 | Screenshot from Customization > Custom Fields (Import netbox_custom_datetime.cvs should be something like this): 20 | 21 | ![Example output](https://github.com/hrleinonen/netbox-ipam/blob/main/pictures/NetBox_customfields.png) 22 | 23 | Screenshot from new IP. 24 | 25 | ![Example output](https://github.com/hrleinonen/netbox-ipam/blob/main/pictures/NetBox_New_IP.png) 26 | -------------------------------------------------------------------------------- /netbox-ipam.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Python script to collect IP-addresses from NetBox and check if IP is up 4 | # and making reverse dns-lookup against IP's. Script automatically 5 | # updates entry's DNS record. 6 | # 7 | # Tested NetBox versions v3.3.4 (date_time must be False) and v3.5.6 8 | # 9 | # Script requires custom_fields in NetBox, you can download cvs files here: 10 | # 11 | # Author: Ville Leinonen 12 | # Versio: 0.5 13 | # 14 | import urllib3 15 | import json 16 | import time 17 | import requests 18 | import dns.resolver, dns.reversename 19 | from netbox import NetBox 20 | from pythonping import ping 21 | 22 | # Change if you want also resolve dead hosts (True/False). 23 | resolve_dead = True 24 | 25 | # Date and time supported in custom fields (True/False). 26 | date_time = False 27 | 28 | # NetBox API-token 29 | token = "" 30 | 31 | # NetBox connection settings 32 | netbox_url = "" # NetBox URL or IP eg. netbox.acme.dom 33 | netbox_port = "" # NetBox port eg. 8000 34 | netbox_ssl = True # True if https is enabled, else False. 35 | 36 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 37 | 38 | headers = { 39 | 'Content-Type': 'application/json', 40 | 'Accept': 'application/json', 41 | 'Authorization': 'Token ' + token + '' 42 | } 43 | 44 | netbox = NetBox(host=netbox_url, port=netbox_port, use_ssl=netbox_ssl, auth_token=token) 45 | 46 | t = time.localtime() 47 | 48 | json_data = netbox.ipam.get_ip_addresses() 49 | 50 | for ipam_data in json_data: 51 | 52 | if date_time: 53 | current_time = time.strftime("%Y-%m-%d %H:%M:%S", t) 54 | else: 55 | current_time = time.strftime("%Y-%m-%d", t) 56 | 57 | id = ipam_data['id'] # Get entry id 58 | address = ipam_data['address'] # Get IP-address 59 | is_active = ipam_data['status']['value'] # Check if IP-address is active or dhcp 60 | address_url = ipam_data['url'] # Get device id 61 | seen_enabled = ipam_data['custom_fields']['seen_enabled'] # Check if ping is enabled 62 | dns_enabled = ipam_data['custom_fields']['dns_enabled'] # Check if DNS-lookup is enabled 63 | first_seen = ipam_data['custom_fields']['first_seen'] # Get first seen value 64 | 65 | address = address.split('/') 66 | 67 | if is_active == "active" and seen_enabled == True: 68 | 69 | # Do the ping against host 70 | response_list = ping(address[0], count=3, timeout=2) 71 | 72 | # If host id down 73 | if str(response_list.rtt_avg_ms) == "2000.0": 74 | seen_data = { "custom_fields": { 75 | "host_alive" : False 76 | }} 77 | 78 | host_up = False 79 | 80 | # If host is up 81 | else: 82 | # If host is not checked before and it is online 83 | if not first_seen: 84 | seen_data = { "custom_fields": { 85 | "first_seen" : current_time, 86 | "host_alive" : True, 87 | "last_seen" : current_time 88 | }} 89 | else: 90 | seen_data = { "custom_fields": { 91 | "host_alive" : True, 92 | "last_seen" : current_time 93 | }} 94 | 95 | host_up = True 96 | 97 | r = requests.patch(address_url, headers=headers, json=seen_data, verify=False) 98 | 99 | # Check if DNS-entry is automaticly updated. 100 | if dns_enabled == True: 101 | 102 | if resolve_dead: 103 | 104 | addrs = dns.reversename.from_address(address[0]) 105 | 106 | try: 107 | dns_name = str(dns.resolver.resolve(addrs,"PTR")[0]) 108 | if dns_name[-1] == '.': 109 | dns_name = dns_name.rstrip(dns_name[-1]) 110 | 111 | dns_data = { 112 | "dns_name" : str(dns_name) 113 | } 114 | r = requests.patch(address_url, headers=headers, json=dns_data, verify=False) 115 | except: 116 | pass 117 | 118 | elif not resolve_dead and host_up: 119 | 120 | addrs = dns.reversename.from_address(address[0]) 121 | 122 | try: 123 | dns_name = str(dns.resolver.resolve(addrs,"PTR")[0]) 124 | if dns_name[-1] == '.': 125 | dns_name = dns_name.rstrip(dns_name[-1]) 126 | 127 | dns_data = { 128 | "dns_name" : str(dns_name) 129 | } 130 | r = requests.patch(address_url, headers=headers, json=dns_data) 131 | except: 132 | pass 133 | -------------------------------------------------------------------------------- /netbox-scan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Python script to scan IP-address ranges and add active IP's NetBox. 4 | # Script also make reverse dns-lookup against IP's. Script automatically 5 | # updates entry's DNS record. 6 | # 7 | # Tested NetBox version v3.5.6 8 | # 9 | # 10 | # Usage 1: netbox-scan.py --networks 192.168.0.0/24 --url http://127.0.0.1:8000 --api asdasdasf12222 11 | # Usage 2: netbox-scan.py --networks 192.168.0.0/24,10.20.0.0/23 --url http://127.0.0.1:8000 --api asdasdasf12222 12 | # Usage, do reserve DNS request and update IP-object): netbox-scan.py --networks 192.168.0.0/24 --url http://127.0.0.1:8000 --api asdasdasf12222 --dns 13 | # Usage, write logfile and show output: netbox-scan.py --networks 192.168.0.0/24 --url http://127.0.0.1:8000 --api asdasdasf12222 --log --out 14 | # 15 | # Author: Ville Leinonen 16 | # Version: 0.2 17 | # 18 | import urllib3 19 | import time 20 | import requests 21 | import dns.resolver, dns.reversename 22 | from ping3 import ping 23 | import argparse 24 | import ipaddress 25 | 26 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 27 | 28 | parser = argparse.ArgumentParser() 29 | parser.add_argument('--networks', type=str, nargs='+', required=True) 30 | parser.add_argument('--url', type=str, required=True) 31 | parser.add_argument('--api', type=str, required=True) 32 | parser.add_argument('--dns', action='store_true') 33 | parser.add_argument('--log', action='store_true') 34 | parser.add_argument('--out', action='store_true') 35 | 36 | args = parser.parse_args() 37 | 38 | networks = args.networks 39 | url = args.url 40 | token = args.api 41 | dns_resolve = args.dns 42 | log = args.log 43 | out = args.out 44 | 45 | headers = { 46 | 'Content-Type': 'application/json', 47 | 'Accept': 'application/json', 48 | 'Authorization': 'Token ' + token + '' 49 | } 50 | 51 | # Get all ip's for duplicate check 52 | try: 53 | r = requests.get(str(url) + "/api/ipam/ip-addresses", headers=headers, verify=False) 54 | except: 55 | print("Error to connect {}.".format(url)) 56 | exit() 57 | 58 | json_data = r.json() 59 | 60 | ip_array = [] 61 | 62 | for json_items in json_data['results']: 63 | for key, val in json_items.items(): 64 | if key == "address": 65 | ip = val 66 | ip = ip.split('/') 67 | 68 | ip_array.append(ip[0]) 69 | 70 | for network in networks: 71 | network = network.replace(',','') 72 | netmask = network.split('/') 73 | netmask = netmask[1] 74 | 75 | ips = ipaddress.ip_network(network, strict=False) 76 | for ip in ips: 77 | 78 | t = time.localtime() 79 | 80 | current_time = time.strftime("%Y-%m-%d %H:%M:%S", t) 81 | 82 | ip_mask = str(ip) + "/" + str(netmask) 83 | 84 | ip_mask = ip_mask.strip() 85 | 86 | # Do the ping against host 87 | try: 88 | ip_response = ping(str(ip), timeout=1) 89 | 90 | if ip_response is not None: 91 | if str(ip) not in ip_array: 92 | comment = str("Created automatically " + current_time + ".") 93 | ip_data = { 94 | "address": ip_mask, 95 | "status": "active", 96 | "comments": comment 97 | } 98 | r = requests.post(str(url) + "/api/ipam/ip-addresses/", headers=headers, json=ip_data, verify=False) 99 | 100 | if log: 101 | logfile = open("netbox-scan.log", "a") 102 | logfile.write(current_time + " " + str(ip_mask) + " Added to NetBox.\n") 103 | logfile.close() 104 | 105 | resp = r.json() 106 | 107 | for resp_key, resp_val in resp.items(): 108 | if resp_key == "url": 109 | address_url = str(resp_val) 110 | 111 | # Check if DNS lookup required. 112 | if dns_resolve == True: 113 | 114 | addrs = dns.reversename.from_address(str(ip)) 115 | 116 | try: 117 | dns_name = str(dns.resolver.resolve(addrs,"PTR")[0]) 118 | if dns_name[-1] == '.': 119 | dns_name = dns_name.rstrip(dns_name[-1]) 120 | 121 | dns_data = { 122 | "dns_name" : str(dns_name) 123 | } 124 | r = requests.patch(address_url, headers=headers, json=dns_data, verify=False) 125 | 126 | if log: 127 | logfile = open("netbox-scan.log", "a") 128 | logfile.write(current_time + " " + str(ip_mask) + " DNS name " + str(dns_name) + ".\n") 129 | logfile.close() 130 | 131 | except: 132 | if log: 133 | logfile = open("netbox-scan.log", "a") 134 | logfile.write(current_time + " " + str(ip) + " no reverse in DNS.\n") 135 | logfile.close() 136 | if out: 137 | print("No reverse DNS in {}".format(ip)) 138 | 139 | else: 140 | if log: 141 | logfile = open("netbox-scan.log", "a") 142 | logfile.write(current_time + " " + str(ip) + " is allready in NetBox.\n") 143 | logfile.close() 144 | if out: 145 | print("IP {} is allready in NetBox".format(ip)) 146 | 147 | else: 148 | if log: 149 | logfile = open("netbox-scan.log", "a") 150 | logfile.write(current_time + " " + str(ip) + " is not responding.\n") 151 | logfile.close() 152 | if out: 153 | print("Address {} not responding.".format(ip_mask)) 154 | 155 | except: 156 | print("Error pinging {}".format(ip_mask)) 157 | --------------------------------------------------------------------------------