Управляем сетевыми политиками доступа в стиле «Network as Code». Часть 1

Привет, Хабр! Сегодня поделюсь, как мы с коллегами решали небольшую задачу по автоматизации управления списками доступов на пограничных маршрутизаторах. Исходные данные просты: 100+ маршрутизаторов, на которых необходимо поддерживать в актуальном состоянии правила NAT. Звучит несложно, но, как водится, есть свои нюансы.

02dc5c8dd5029a5e4293b9cd2c29ebda.png
  • Часть 1 — Концепция (вы находитесь тут)

  • Часть 2 — Код

Disclaimer

В силу определенных обстоятельств не могу привести полностью код реализованного решения. Все конфигурации, адреса, названия устройств, площадок и департаментов — вымышлены.

Про существующий ландшафт и задачу, которая перед нами стояла

Вернемся в недавнее прошлое, когда мы были молоды и полны сил, и нам хватало времени для ручного выполнения рутинных задач. Вот что у нас было:  

Сетевое оборудование: маршрутизаторы Cisco с IOS-XE (на самом деле, вендор не важен, конечная реализация не сильно зависит от производителя). На каждом из них настроена политика доступа в Интернет, которой мы хотим управлять. Выглядит такая политика примерно так:

# Описания сервисов

object-group service OG_DNS_PORTS
 udp eq domain
 tcp eq domain
object-group service OG_WEB_PORTS
 tcp eq http
 tcp eq 443
# Описания сетей и хостов

object-group network SOME_SERVER
 host 10.0.0.10
object-group network OG_WIFI_NETWORK
 192.168.111.0 255.255.255.0
# Списки доступа

ip access-list extended ACL_COMMON_NAT
 permit icmp any any
 permit object-group OG_DNS_PORTS any any
ip access-list extended ACL_FF_WIFI_NAT
 permit object-group OG_WEB_PORTS object-group OG_WIFI_NETWORK any
ip access-list extended ACL_TEMP_NAT
 permit ip object-group SOME_SERVER any
# Политика доступа

route-map RM_NAT permit 10
 match ip address ACL_COMMON_NAT
route-map RM_NAT permit 20
 match ip address ACL_WIFI_NAT
route-map RM_NAT permit 30
 match ip address ACL_TEMP_NAT

Разумеется, в реальности политики доступа намного сложнее и содержат в десятки раз больше правил.

 Системы учета:

  • Вся информация об устройствах хранится в Nautobot

  • Актуальные бэкапы конфигураций хранятся в корпоративной системе версий

  • Описания правил доступа в интернет (откуда, куда, номер заявки и пр.) хранятся в Excel на сетевом диске

Способ конфигурирования: на один маршрутизатор изменения вносятся руками через CLI, на несколько маршрутизаторов — используя Ansible и стандартный playbook, в котором каждый раз меняются ACL и object-groups.

 Примеры запросов на доступ со звездочкой:

  • Просят добавить специфичный доступ в интернет из сети Wi-Fi. Адреса сетей на разных площадках отличаются, значит в плейбуке надо реализовать алгоритм формирования адреса источника. Ну или все делать руками.

  • Просят открыть временный доступ в Интернет (на неделю) с конкретного хоста. Приходится создавать себе напоминание, чтобы не забыть удалить лишние ACL.

  • Чаще всего просят открыть доступ к адресам FQDN. Увы, обычные маршрутизаторы Cisco умеют работать только с IP-адресами. Приходится ставить на мониторинг A-записи DNS и при ее обновлении автоматически создавать тикет для изменения ACL. При получении тикета сетевой инженер должен добавить доступ к новому IP-адресу, а старую запись в ACL удалить.

  • На одном маршрутизаторе организован site-to-site VPN, поэтому надо запретить трансляцию определенных адресов

Это была вполне рабочая схема. Пользователи и системы мониторинга регулярно создавали заявки, сетевые инженеры с помощью Ansible изменяли ACL на маршрутизаторах и с помощью Excel вели документацию с комментариями. Но вероятность человеческой ошибки в этой схеме оставалась высокой. Ведь у сетевого инженера есть другие, более интересные задачи. Отвлекшись на которые, можно забыть описать доступ в общем файле, не удалить устаревшие правила или проигнорировать тикет от системы мониторинга. А значит, напрашивается необходимость максимально автоматизировать процесс и избавить сетевой отдел еще от одной рутины.

Так уж вышло, что готового решения для наших условий не существует. Имей мы на площадках маршрутизаторы/МСЭ, изначально построенные для централизованного управления, этой статьи бы не было. Но имеем то, что имеем, так что автоматизацию решено было делать свою собственную.

В процессе автоматизации необходимо было определить и реализовать несколько важных аспектов:

  • Место хранения и формат исходных данных.

  • Изменение исходных данных при наступлении определенных событий.

  • Доставка изменений политик на маршрутизаторы.

В первой части статьи расскажу про выбранную архитектуру: в чем состоит концепция, где и в каком виде было решено хранить исходные данные для формирования политик.

Начинаем с места хранения

Excel в качестве способа хранения данных нас категорически не устраивал, а из уже имеющихся и легкодоступных было всего два — Nautobot и Gitlab. Рассмотрим особенности каждого варианта.

Разработчики Nautobot создали целую экосистему плагинов, которые значительно расширяют функционал системы. Одним из таких плагинов является Nautobot Firewall Models. Предназначено расширение для учета политик и списков сетевого доступа L4. Вполне дружелюбный пользовательский интерфейс позволяет добавить сервисы, объекты и группы объектов, IP-адреса и FQDN, политики как для zone-based firewall, так и для организации NAT. И все это уже интегрировано в нашу IPAM-систему. Данные можно обработать внутри Nautobot с помощью скриптов, а можно получить по API и использовать любые внешние интеграции. Из минусов — отсутствие функционала ограничения правил доступа по времени.

В GitLab все проще и сложнее одновременно. В текстовых файлах можно хранить какую угодно информацию о правилах и политиках, «из коробки» есть история изменений и права доступа. Все данные легко получить из репозитория стандартными способами. Можно встроить скрипты в репозиторий и обрабатывать политики, используя GitLab CI/CD. Из минусов — текстовые файлы не очень удобны для презентации обычным пользователям.

Так как вариант с GitLab предоставлял больше свободы и гибкости, в итоге был выбран именно он. А это сразу же стало сигналом, что решать задачу мы будем в стиле «Network as Code».

Про «Network as Code»

Как известно, NaC является частным случаем концепции «Infrastructure as Code» и требует внедрения практик NetDevOps. В соответствии наследию Cisco мы для себя определяем следующие принципы «Network as Code»:

  • Данные, на основе которых генерируется конфигурация устройств, хранятся в текстовом виде в системе контроля версий. В нашем случае это репозиторий GitLab.

  • Репозиторий с данными является единой точкой истины. Изменения вносятся только в него, ручные правки на сетевых устройствах не приветствуются.

  • Должен быть организован процесс доставки целевой конфигурации на сетевые устройства. В современном мире для этого используется API (RESTCONF, NETCONF), мы же пока обойдемся стандартным CLI

Что же нам надо для реализации указанных принципов? Всего-то ничего: описать правила доступа в vendor-agnostic виде, разработать кодовую базу для конвертации правил в vendor-specific конфигурацию, реализовать CI/CD для доставки конфигурации на маршрутизаторы.

Dataflow и структура репозитория

При подготовке и деплое конфигураций на сетевые устройства необходимо определить множество моментов: кто может вносить изменения, как проверять данные, каким образом формировать команды для отправки на маршрутизаторы. С учетом неизбежной мультивендорности нет других вариантов, как разделить процесс работы с политиками на две части:

  • Vendor-agnostic — описания политик, правила заполнения данных и пользовательский интерфейс не должны зависеть от производителя;

  • Vendor-specific — подготовка команд для отправки и сам деплой зависят производителя и версии ОС на маршрутизаторе.

 В итоге диаграмма распространения политик доступа стала выглядеть следующим образом:

policy dataflow
policy dataflow

Структура папок репозитория стала выглядеть так:

┌─ansible
│ └─ все, что необходимо для отправки конфигураций на устройства
├─code
│ └─ все, что необходимо для подготовки конфигураций перед отправкой
├─intended_state
│ ├─locations
│ │ ├─ MSK-001_network_groups.yml
│ │ ├─ MSK-002_network_groups.yml
│ │ ├─ SPB-001_network_groups.yml
│ │ └─ SPB-001_rules.yml
│ ├─routers
│ │ └─ MSK-001-BRD-0_rules.yml
│ ├─ common_service_groups.yml
│ ├─ common_network_groups.yml
│ ├─ store_policies.yml
│ ├─ store_rules.yml
│ ├─ warehouse_policies.yml
│ └─ warehouse_rules.yml
└─templates
  └─cisco
    ├─ network_groups.j2
    ├─ rules.j2
    ├─ policies.j2
    └─ service_groups.j2

Папки ansible и code нам сейчас не особо интересны, они будут использоваться на последних этапах для подготовки и отправки команд на сетевые устройства. 

В папке templates будем хранить шаблоны Jinja для каждого вендора, предназначенные для генерации vendor-specific конфигураций. Для каждого типа содержимого политики предназначен отдельный шаблон. 

Папка intended_state — непосредственно политики доступа в формализованном виде. Давайте разберемся, почему в ней так много YAML-файлов, и как внутри организованы данные.

Vendor-agnostic описания правил доступа

Для удобства каждый файл отвечает только за один тип данных. Напомню, их у нас всего четыре:

  • описание сервисов,

  • описание хостов и сетей,

  • списки доступа,

  • политики доступа.

Если файлов много, как определить какие именно данные в нем хранятся и к каким сетевым объектам они относятся? Ведь политики доступа могут отличаться для разных типов локаций, для разных площадок или даже для разных маршрутизаторов на одной площадке. За это отвечает блок _metadata, который содержится в каждом файле.

# intended_state/store_policies.yml
_metadata:
  description: Политика NAT для магазинов
  schema: policy
  filter:
    location_type:
     - store
  weight: 1000
  is_active: true
...

Поле schema определяет тип данных. В секции filter мы можем указать тип локации (например, магазин или склад), конкретные площадки или список hostname маршрутизаторов. А если какому-то маршрутизатору соответствует несколько файлов с данными? В таком случае для разрешения конфликта более приоритетным будут данные из файла, в котором вес (weight) наиболее высокий.

Далее в файле располагаются правила или описания в соответствии с указанной схемой. Ниже показан пример, как в четырех файлах описать простенькую политику доступа, в которой для Wi-Fi сети (172.18.100.0/24) открыты порты http/https, а всем остальным — только ICMP и DNS:

# intended_state/store_policies.yml
...
name: RM_NAT
type: route-map
lines:
  10:
    rule: ACL_COMMON_NAT
    action: permit
  20:
    rule: ACL_WIFI_NAT
    action: permit
# intended_state/store_rules.yml
...
rules:
  ACL_COMMON_NAT:
    lines:
      -
        action: permit
        source: any
        destination: any
        services:
          protocol: icmp
      -
        action: permit
        source: any
        destination: any
        services:
          group: OG_DNS_PORTS
  ACL_WIFI_NAT:
    lines:
      -
        action: permit
        source: OG_WIFI_NETWORK
        source_type: group
        destination: any
        services:
          group: OG_WEB_STANDARD_PORTS
# intended_state/common_network_groups.yml
...
network_groups:
  OG_WIFI_NETWORK:
    networks:
      - 172.18.100.0 255.255.255.0
# intended_state/common_service_groups.yml
...
service_groups:
  OG_WEB_STANDARD_PORTS:
    tcp:
      - eq 80
      - eq 443
  OG_DNS_PORTS:
    udp:
      - eq 53
    tcp:
      - eq 53

Мы уже знаем, что, используя более высокий вес, мы можем переопределить правила или всю политику для конкретной площадки. А если необходимо не заменить, а дополнить правила? Для такой задачи предусмотрена директива extend. Укажем в следующем примере, что для площадки SPB-001 нам надо добавить в правило ACL_WIFI_NAT разрешение трафика для группы OG_SPECIFIC_SERVER:

# intended_state/locations/SPB-001_rules.yml
_metadata:
  description: Правила NAT для SPB-001
  schema: rules
  filter:  
    locations:
      - SPB-001
  weight: 9000
  is_active: true
rules:
  ACL_WIFI_NAT:
    extend: true
    lines:
      -
        action: permit
        source: any
        destination: OG_SPECIFIC_SERVER
        destination_type: group
        services:
          group: OG_SPECIFIC_PORTS

Дополнительные данные для автоматизации

Согласно нашему dataflow, источником изменений в YAML-файлах может быть не только человек, но и внешний скрипт. Добавим в схему данных директивы, позволяющие внешним скриптам реагировать на наступление событий двух типов:

  • истечение срока действия правила,

  • изменение IP-адреса в DNS.

В следующем примере создано правило ACL_TEMP_NAT с одной позицией, действующей до конца 2024 года. Теперь легко организовать автоматическую проверку данных и удаление истекшей позиции из файла:

# intended_state/store_rules.yml
...
rules:
  ACL_TEMP_NAT:
    lines:
      -
        action: permit
        destination: OG_SPECIFIC_SERVER
        destination_type: group
        destination: any
        services:
          protocol: ip
        expiry: 2024-12-31

Для слежения за изменениями записей в DNS нам необходимо хранить FQDN-имя в описаниях хостов. Использовать для этого имя object-group не очень удобно, так что добавим соответствующий параметр в описание хоста. Теперь можно просто сравнить вывод nslookup и список hosts в файле и в случае несоответствия внести правки. Пример ниже показывает, как привязать адрес хоста к его имени:

# intended_state/locations/SPB-001_network_groups.yml
...
OG_SPECIFIC_SERVER:
    fqdn: someserver.ru
    hosts:
      - 10.0.0.10

Что дальше?

К этому моменту мы:

  • создали репозиторий в GitLab

  • создали структуру файлов, содержащих всю необходимую информацию для формирования политик доступа

  • предусмотрели данные, нужные нам для автоматизации изменения политик

Теперь нам нужен был код, который обеспечит этап deploy:

  1. проверит внесенные данные (мы же помним, что их вносит человек, способный на ошибки?),

  2. на основе этих данных сформирует ожидаемую конфигурацию для каждого устройства,

  3. если новая конфигурация не соответствует текущей, вычислит разницу,

  4. отправит на маршрутизатор команды, необходимые для приведения конфигурации устройства к ожидаемому виду.

И, конечно, внешние скрипты, которые удалят временные правила или поменяют IP-адреса при обновлении DNS.

Обо всей этой кодовой базе читайте в следующей части. Stay tuned…

© Habrahabr.ru