В прошлой заметке подготовили к работе сервер netbox для работы с ним. Сегодня реализуем костыль, который поможет держать в актуальном состоянии ipam.
При динамических изменениях в инфре, трудоемкая задача каждый раз обновлять адреса в сетевом пространстве. Особенно когда в команде несколько админов и каждый добавляем по своему.
Начинает появляться хаос, тратится больше времени на то что бы все причесать к порядку. Спустя 30 минут ручного труда, ты решаешься сесть за написание скрипта.
У нет netbox есть хорошо задокументированная API из коробки, для python есть несколько библиотек. Сисадминский грех этим не воспрользоваться.
Работать с ipam будем за счет запросов к апишке. Остается только продумать, как проверять доступность хоста по тому или иному ip. Очевидное, что приходит на ум это nmap
.
Подготовка
Получение токена
Прежде чем начать, нужно сделать несколько приготовлений. Создать отдельного пользователя, и сгенерить API-Токен для него. Поэтому переходим в админку сервиса, и создаем нового пользователя:
После перехода в админ панель, жмем на кнопку добавить пользователя. Указываем логин/пароль для пользователя, и сохраняемся.
Если сейчас попытаемся залогиниться из под этого пользователя, панелька будет вся пустая. Что бы поправить это, нужно новому пользователю выдать права.
Так как через этого пользователя будем только удалять/добавлять ip-адреса, наградим его доступом к ipam-разделу. В админке переходим в раздел Users
/Permissions
.
В окне редактора прав (permissions), указываем имя и краткое описание для этой политики. Разрешаем действия по добавлению/удалению и изменению объектов.
В поле с выбором типа объектов в списке выбираем: IPAM > IP Address
и IPAM > IP Range
Небольшой апдейт: В этом поинте нужно также выставить галочку, разрещающую просмотр (Can view), иначе не сможем парсить ранее добавленые адреса в ipam.
Спускаемся в конец страницы к полю выбора пользователей. И перетаскиваем нашего пользователя в правое окно. И сохраняемся.
Теперь остается выдать токен для пользователя, идем в раздел Users
/Tokens
. Добавляем новый токен, в окне добавления выбираем пользователя. Опционально можно указать дату истечения срока работы токена и указать IP-адреса с которым можно будет подключаться. Далее жмем сохранить.
На выходе, полученный токен сохраняем.
Установка зависимостей
Для работы с api нужно установить питоновские библиотеки, ну и интепретатор python соответственно.
Все зависимости ставяться через питоновкий пакетный менеджер pip
.
$ pip install python-nmap pynetbox
Проверять доступность хоста будем через библиотеку python-nmap
, для работы которой также нужно поставить пакет nmap
.
$ yum install -y nmap
Пишем код
Пояснения к сниппету постараюсь выстроить в той же последовательности, что и процесс выполнения команды. Подробно рассказывать про методы и переменные не буду, лишь опишу что возвращается функциями.
Импорт либ
Начинаем с импорта требуемых библиотек:
import sys
import nmap
import pynetbox
import ipaddress
import urllib3
import socket
sys
- используется для перехвата аргументов, которые передаются при запуске скрипта. По моему замыслу, я планирую передавать на вход программе адрес сети, которую нужно будет отсканить.nmap
- эта либа используется проверки доступности хоста.pynetbox
- либа с помошью которой будем искать, удалять или добавлять ip-адреса.ipaddress
- c этой библиотечкой, я смогу легко работать с сеткой. Получать длину префикса, список адресов входящих в определенный сабнетurllib3
- добавил этот пакет, я отключения варнингов для самоподписных ssl-сертификатов.socket
- этот пакет включил, для дальнейшего получения dns имени хоста
Объявление переменных
Теперь я объявляю входные переменные, тут все просто.
# General vars
netbox_url = '<NETBOX_SERVER_URL>'
netbox_api = '<NETBOX_API_TOKEN>'
nb = pynetbox.api(url=netbox_url, token=netbox_api)
# Disable SSL and SSL Warnings
nb.http_session.verify = False
urllib3.disable_warnings()
netbox_url
- эта переменная содержит урл-адрес netbox сервераnetbox_api
- здесь лежит API-токен, который мы сгенерили ранее.nb
- инициализируем экземпляр класса api, в качестве агрументов передаем url и token.nb.http_session.verify = False
- тут я отключаю проверку ssl-сертификата, так как у меня используется само подписной серт.urllib3.disable_warnings()
- и тут отключаю нотификации относительно сертификата
Основной раздел
Так скажем entry часть программы, проверяем длинну переданных аргументов программе.
if __name__ == '__main__':
if len(sys.argv) <= 1:
print("You not present network, please run: \n -> python .\\netbox_scan.py 192.168.0.0/24")
else:
app_run(sys.argv[1])
Тут же реализовано условие, если колличество аргументов меньше или равно одному, принтануть сообщение с подсказкой как правильно запускать скриптец.
Иначе запустить метод - app_run(sys.argv[1])
, в метод передаем агрумент из командной строки, то есть адрес сети.
def app_run(subnet):
ip_list = ipaddress.ip_network(subnet)
for ip in ip_list:
last_octet = str(ip).split('.')
if (int(last_octet[-1]) != 255) and (int(last_octet[-1]) != 0):
nmap_host_state = ip_scan(str(ip))
netbox_ip_id, netbox_ip = ip_check(str(ip))
netb_ipam_update(netbox_ip_id ,
str(ip),
ip_list.prefixlen,
nmap_host_state
)
В этом методе получаем список всех ip-адресов, используя либу ipaddress
и результат лежит в переменной ip_list
. Далее циклом начинаем переберать все ip входящие в сетку, которую мы указали при запуске скрипта. В этом же цикле исключаем адреса, с последним октетом .0
и .255
.
Проверка IP
Вызываем функцию ip_scan
,
def ip_scan(ip_address):
nm = nmap.PortScanner()
nm.scan(hosts=ip_address, arguments='-sP -R')
if len(nm.all_hosts()) != 0:
return nm[ip_address]['status']['state']
else:
return 'down'
Эта функция проверяем доступность хоста, и возвращает строкое значение - down
или up
. И значение помещается в переменную nmap_host_state
.
Следующим этапов, в этом же цикле вызываем функцию ip_check
,
def ip_check(ip_address):
ip_search_status = nb.ipam.ip_addresses.filter(address=ip_address)
if ip_search_status:
for item in ip_search_status:
return item.id, item.display
return 0, '0.0.0.0/0'
И данный метод ходит в нетбокс, ищет наш ip и возвращает его данные dispay_address и id. Если никаких данных в процессе не было найдено, возвращается пустые значения.
Принятие решения об IP
Теперь после того, как мы чекнули доступность хоста по указанному ip и проверили наличия этого ip в базе ipam, время принимать решение что сделать с ним дальше.
И это решение принимается уже в следующем методе netb_ipam_update
, эта функция состоит условного оператора:
def netb_ipam_update(netbox_ip_id, netbox_ip, prefix, nmap_host_state):
if (nmap_host_state == 'up') and (netbox_ip_id == 0):
netbox_add_ip(netbox_ip, prefix)
elif (nmap_host_state == 'down') and (netbox_ip_id != 0):
netbox_remove_ip(netbox_ip_id)
return True
При вызове функции передаются агрументы - id номер ip-адреса в netbox, ip-адрес в netbox, длина префикса сети и статус хоста с этим адресов.
В условии функции, такая логика. Если хост в апе, и нетбокс id номер равен нулю, значит этого хоста нет в базе. Хост новый, и принимается решение добавить его. Второе условие, если хост в дауне (недоступен), и его нетбокс id номер не равен нулю, то принимается решение об удалении этого хоста их базы. Все остальные условия просто пропускается.
Функции добавления/удаления
В зависимости выполннего условия запускается другой встроенный метод, у меня их два. Первый добавляем новый ip, второй удаляем. Функция, которая добавляет:
def netbox_add_ip(ip, prefix):
ip_dns_name = get_dns_by_host(ip)
ip_add_result = nb.ipam.ip_addresses.create(
address = f'{ip}/{prefix}',
dns_name = ip_dns_name
)
На вход этому методу передаем ip-адрес и префикс сети. Внутри метода проверяем dns имя этого ip, и далее совершаем библиотечный вызов к апи нетбокса, создаем новый объект.
Функция, которая удаляет:
def netbox_remove_ip(ip_id):
ip_search_result = nb.ipam.ip_addresses.get(id=ip_id)
if ip_search_result is not None:
ip_search_result.delete()
Этому методу передаем id номер существующего объекта, находим этот объект и посредством библиотечного вызова к апи удалем его из базы.
На этому вроде бы все, ссылка на репозиторий где лежит скриптец прикрепил в конце.
Прикручиваем скриптик
Завершающим этапом, прикрутиваем скриптец в cron или в scheduler. И думаю понятен тот момент, хост с которого запускается скрипт должен имет доступ в смежные сети.
Добавляем в cron задания:
# * * * * * user-name command to be executed
0 */4 * * * root /bin/python3.8 /opt/scripts/network_scan.py '192.168.15.0/24'
0 */8 * * * root /bin/python3.8 /opt/scripts/network_scan.py '192.168.20.0/24'
После запуска скрипта, если посмотрим в changelog netbox будет винда такая история:
Я применил скриптец на трех сетках, и как можете обратить внимание на скрине ниже, значительно выросла утилизация адресного пространства в этих сетях.