Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Однажды моей команде довелось организовывать несложную кустовую схему шифрования для компании, у которой было более 2,5 тысяч офисов продаж и около ста региональных центров. Всё техническое описание решения легко излагалось в таблице Excel размером 2 800 строк на 25 столбцов, но где было взять столько эникейщиков, которые бы настроили оборудование без ошибок?
Если бы эта история была про оборудование, не поддерживающее автоматизацию ни в каком формате, именно так нам и пришлось бы поступить: развернуть некий стенд и найти десяток студентов для тестирования корректности настроек. Мы же имели дело с криптошлюзами S-Terra, и в нашем случае всё упиралось в знание Ubuntu и автоматизации по протоколу SSH. Автоматизировать нужно было два отдельных момента: загрузку конфигурации Cisco-like и инициализацию устройств. Для этого мы решили использовать систему управления конфигурациями Ansible.
В статье я расскажу, как мы пытались скрестить Ansible с криптошлюзами S-Terra, и что из этого вышло. Надеюсь, наш опыт будет полезным тем, кто возьмётся за подобный проект на базе решения S-Terra и будет искать способ ускорить конфигурирование оборудования и свести на нет человеческий фактор.
Как мы выбирали средство автоматизации
Одним из вариантов, которые мы рассматривали, было написать собственные скрипты на основе инструментов Netmiko или Paramiko с использованием модуля TextFSM. С одной стороны, это могла быть хорошая возможность отработать на практике знания, полученные при прочтении книги Наташи Самойленко «Python для сетевых инженеров». С другой, несколько лет назад я участвовал в подобной активности и помню, как намучился тогда с обработкой вывода, парсингом и подключением по SSH. Так что в итоге мы признали этот вариант решения слишком долгим и сложным, а к тому же и плохо масштабируемым.
Альтернативным вариантом было использовать бесплатное решение Ansible. В его пользу говорило наличие различных модулей под Cisco и Linux, а также огромная база знаний. Удобным было ещё и то, что в этом случае мы могли не вливать полный файл конфигурации, а отслеживать этапы: настройка интерфейсов, маршрутизации, Crypto, мониторинга и так далее. В итоге этот вариант и выбрали.
Поскольку у Ansible не было готовых модулей под продукты S-Terra, мы решили декомпозировать задачу: а именно разбить playbook на play Cisco-like и play Linux.
Как мы автоматизировали загрузку конфигурации
Шаг 1. Формирование конфигурации
Для начала мы попробовали отдельные task для выполнения отдельных конфигураций, а позже play для выполнения конфигураций в оболочке Cisco-like. Когда мы обкатали первую заливку конфигурации, то пришли к выводу, что наиболее удобно будет делать файлы переменных для каждого устройства в hostvars. Конечно, можно было вывести пароли, переменные мониторинга и серверов инфраструктуры в groupvars, но тогда мы потеряли бы гибкость, а заказчику во многом нужны были индивидуальные настройки. В итоге файл переменных для каждой точки стал выглядеть примерно так:
#
user_sudo: 'root'
password_sudo: 'ххххх'
user_cisco: 'cscons'
password_cisco: 'csp'
#========================
user_cisco_new: 'хххххх'
password_cisco_new: 'хххххххх'
UpLinkBase: 'ethX'
UpLink: 'ethX.100'
UpLinkCisco: 'GigabitEthernet0/X.700'
MGMTLink: 'ethX.300'
MGMTLinkCisco: 'GigabitEthernet0/X.701'
DownLink: 'ethY'
DownLink0: 'ethZ'
DownLinkCisco: 'GigabitEthernet0/Y'
DownLinkCiscoZ: 'GigabitEthernet0/Z'
InterfacesCisco:
#
- name: 'GigabitEthernet0/Z'
ip: '10.10.10.10'
mask: 'XXX.XXX.XXX.XXX'
#
- name: 'GigabitEthernet0/X.700'
ip: '10.10.10.10'
mask: 'XXX.XXX.XXX.XXX'
#
- name: 'GigabitEthernet0/X.701'
ip: '10.10.10.10'
mask: 'XXX.XXX.XXX.XXX'
#
InterfacesCiscoMTU:
- name: 'GigabitEthernet0/Z'
MTU: '1600'
- name: 'GigabitEthernet0/X'
MTU: '1700'
- name: 'GigabitEthernet0/X.700'
MTU: '1700'
- name: 'GigabitEthernet0/X.701'
MTU: '1700'
#
#SNMP
SNMP_IP: 'XXX.XXX.XXX.XXX'
#Rsyslog
SyslogServerIP: 'XXX.XXX.XXX.XXX'
#NTP
NTP_server: 'XXX.XXX.XXX.XXX'
#
route:
- prefix: 0.0.0.0
mask: 0.0.0.0
next_hop: 'XXX.XXX.XXX.XXX'
- prefix: ZZZ.ZZZ.ZZZ.ZZZ
mask: ZZZ.ZZZ.ZZZ.ZZZ
next_hop: 'XXX.XXX.XXX.XXX'
#
ACLS:
- name: 'ACL_XXXX_X'
content:
- ipsrc: 'XXX.XXX.XXX.XXX'
wildmasksrc: 'XXX.XXX.XXX.XXX'
ipdst: 'XXX.XXX.XXX.XXX'
wildmaskdst: 'XXX.XXX.XXX.XXX'
#
MAPS:
- number: '1'
acl: 'ACL_XXXXX_X'
peer: 'XXX.XXX.XXX.XXX'
#
CryptoInterfaces:
- GigabitEthernet0/X.700
Каждая role использовала свою группу переменных (MAPS, ACL, route и т.д.), которая представляла собой отдельный dict и/или list в формате json. Часть переменных, например, Interfaces, использовались на всём протяжении playbook.
Шаг 2. Подготовка конфигурационных файлов
В качестве основного проектного документа мы приняли файл с расширением .xlsx. Для того, чтобы не перебирать каждый раз ячейки таблицы и не тратить на это огромное количество времени, мы разбили скрипты на три части.
Первая часть выгружала нужную информацию из .xlsx в SQL. Это было необходимо для получения выгрузки данных из строк по определенным фильтрам. Про модуль pandas, который помог бы решить эту задачу более простым путём, на момент создания скрипта я не знал.
Вторая часть скриптов проверяла SQL на ошибки: отсутствие пересечений, корректность peer и другие. Всего у нас получилось семь функций проверки. Как говорится, не ошибается тот, кто ничего не делает, и тут это правило работало на 100%.
Третья часть включала в себя основной скрипт формирования конфигурации и скрипт для формирования изменений. Первый представлял собой группу функций, формирующих переменные для print(template.format(var1,….)), которые в свою очередь являются dict и/или list в формате json в конфигурации для Ansible. Второй мы сделали, так как со временем у нас появилась потребность парсинга (textFSM) файлов Cisco-like конфига уже установленных устройств S-Terra и формирования изменений (diff_conf).
Четыре булыжника, о которые мы споткнулись по пути
Вроде всё сделали красиво, но тут нас ждал первый булыжник — уровни вложенности переменных в Ansible. Оказалось, что при использовании модуля cisco.ios.ios_acls сделать перебор переменных аналогично [списку [списков]] не получится. Пролистав N страниц в интернете и протестировав несколько вариантов, мы пришли к использованию конструкции loop + subelements.
- name: acl_config
cisco.ios.ios_acls:
config:
- afi: ipv4
acls:
- name: '{{item.0.name}}'
acl_type: extended
aces:
- grant: permit
protocol:
ip
source:
address: '{{item.1.ipsrc}}'
wildcard_bits: '{{item.1.wildmasksrc}}'
destination:
address: '{{item.1.ipdst}}'
wildcard_bits: '{{item.1.wildmaskdst}}'
loop: "{{ACLS|subelements('content')}}"
Я думаю, что боль использования модуля cisco.ios.ios_acls понимает любой сетевой инженер, который с ним работал. На мой взгляд, так неудобно сделать мог только человек, который никогда это не настраивал. Наши ощущения можно сравнить со случаем, когда решение по маршрутизации трафика в организации реализуется не на основе динамической маршрутизации, а на основе запуска файлов с расширением .bat, меняющих def route на host: администратор сделал, а на сетевом инженере сэкономили.
К сожалению, ход с конструкцией loop + subelements не решил проблему с использованием правил для /32 сетей, так как в Cisco-like их надо вводить как host, а как запустить when по внутреннему списку, я так и не придумал. Тогда я впервые вспомнил фразу из одной статьи «Ansible — это не язык программирования, это инструмент автоматизации». Пришлось потом это реализовывать отдельной role.
Второй булыжник был в том, что модули Ansible работали не совсем так, как нам хотелось бы это видеть. Модуль sudo так и не получилось запустить на Linux S-Terra, модули ACL и cisco.ios.ios_config не могли в цикле анализировать конфигурацию, удалять лишнее и дополнять недостающее (описанная выше ситуация с host). Так мы пришли к выводу, что всё нужно тестировать, причём очень внимательно и скрупулёзно.
Третий булыжник ждал нас в ситуации перехода с одной учётки на другую, когда настроили что-то под root в Linux и теперь то же самое надо сделать под cscons в Cisco-like, а для этого нужно поменять переменные внутри одного playbook. Тут, конечно, всё оказалось гораздо проще, спасибо статье, из которой я вообще очень много почерпнул для общего понимания работы системы. После второго прочтения я всё-таки понял, почему playbook, а не play. Где же здесь book? Ведь переменные используются в пределах одного play, и если их нужно переопросить для SSH-сессии, то есть снова забрать значения из vars, то необходимо сделать новый play. На самом деле до сих пор не понимаю, где и как настраиваются параметры SSH-сессии и почему нельзя было сделать в ansible.conf список параметров, аналогичный Netmiko, но это я просто чего-то не понимаю.
Четвертый булыжник был уже на этапе настройки. При заливке S-Terra с большим количеством MAP, задаваемых в используемом нами формате, Ansible лавинообразно начинал потреблять RAM. Как он умудрился 50-килобайтовый текстовый файлик конфига превратить в 1Гбайт RAM, я не знаю. Но это был очень неожиданный сюрприз. Пришлось выделять 8Гбайт памяти. Наверное, это тоже можно было как-то решить, но я не придумал, что именно надо вбить в Google для поиска решения проблемы.
Впрочем, на этом булыжники закончились и началось довольно приятное сосуществование: конфигурации получились понятными и читаемыми, а все названия стандартизованными. Всплывающие ошибки были достаточно читаемы и связаны с ошибками скрипта создания конфигураций. К тому же мы сэкономили кучу времени и нервов при загрузке custom-скриптов SNMP, ведь за этим бы последовало многочисленное копирование файлов, запуск большого количества скриптов, выполнение проверок и конфигурирование cron. В итоге мы подготовили к установке почти три тысячи устройств.
Как мы автоматизировали инициализацию устройств
Когда я слышу слово «инициализация», мне тут же вспоминается любимый лозунг безопасников «Инженер должен … страдать». Потому как на ПАК серии ниже 3 000 нужно руками вбивать случайную последовательность для генерации Гаммы. Поклонники популярного в начале 2000-ых способа обучения слепому набору текста «СОЛО на клавиатуре» были бы в восторге от такого решения.
Что мы сделали: на S-Terra G-7000 создали нужное количество gamma и загрузили их на SCP-сервер. Далее получили лицензии в текстовом формате от вендора S-Terra и написали скрипт на Python для управления криптошлюзами через COM-порт.
Тут мы столкнулись только с одной проблемой — статикой при переключении USB-COM конвертера. Из-за неё у нас появлялись дополнительные символы при вводе пароля учётки Administrator.
Что по итогам
В процессе запуска на объектах мы также сформировали группу plays и roles для сбора данных о состоянии устройств S-Terra, проверки прохождения пакетов по шифрованным каналам, доступности инфраструктуры заказчика, сбора lic параметров и S/N. Такое управление сетью оказалось довольно удобным.
Если изначально за день нам удавалось настроить два-четыре устройства, и при этом было достаточно много ошибок, то по итогам автоматизации мы ускорили настройку конфигурации криптошлюзов S-Terra серий G-100, G-1000 и G-2000 до 30 устройств в день, а серий G-3000 и G-7000 — до 15 устройств в день. В случае с последними основное ограничение было только в распаковывании больших коробок и настройке «Аккорда».
Автор: Олег Рябикин, руководитель группы сетевой безопасности «Solar Интеграция» компании «Ростелеком-Солар»