cfnetwork — Puppet API для полной настройки сети и фильтра через ресурсы Puppet. Идеально дружит с Hiera и потенциально другими "data providers" в концепции Puppet.
cffirehol — "meta-provider" конкретной реализации настройки фильтра для cfnetwork на базе замечательного генератора FireHOL
Пока поддерживаются только Debian 8+ (Jessie и выше) и Ubuntu 14.04+ (Trusty и выше) Лирическое вступление:так уж сложилось, что автор крайне параноидален на тему контроля развёрнутых систем и автоматизации. Долгие годы копился опыт встречаемых проблем и относительно местечковых решений. После ухода с предыдущего места работы стало понятно, что осязаемого багажа в сфере администрирования в общем-то и нет. Впрочем, то, что было, тащить с собой дальше особо и не хотелось. Так родился новый велосипед. А теперь к делу.
Примечание: По тексту целенаправленно используется фильтр или сетевой фильтр вместо другого "русского" термина брандмауэр, которое насилует неокрепший детский слух, глаза и мозг автора.
Чем не устраивают существующие решения?
Слабая интеграция конфигурации сети и сетевого фильтра — дополнительная возня с (пере-)конфигурацией и высокий риск ошибок; сложности в создания plug&play модулей отдельных сервисов, которые дружат с системой безопасности.
Отсутствие наглядности для аудита — конфигурация либо не лаконична и размазана по многим файлам, либо вообще существует в нечитаемом для человека виде.
Излишне низкоуровневая конфигурация фильтра без абстракций — те же проблемы, что и пунктом выше. Можно сравнить с написанием веб-странички на ассемблере.
Отсутствие "интеллектуальности" установок по умолчанию — для некоторых топорность является преимуществом, но не для автора.
Настройку сетевого стека вообще обходят стороной — ванильные настройки ядра далеко не всегда то, что вы хотите видеть на боевой системе даже до начала тестирования и оптимизации, не говоря уже о безопасности.
Общая концепция настройки сети и фильтра
Никаких новых теорий — обыденность из разных мест.
Каждый логических сетевой интерфейс имеет уникальное значимое имя вроде world, dmz, office и т.д. local зарезервировано для loopback интерфейса.
Настройки стандартных логических интерфейсов задаются и доступны генератору правил фильтра.
Поддержка особого типа интерфейса any — генератор фильтра должен быть достаточно интеллектуальным чтобы не плодить лишние правила там, где они никогда не сработают. К примеру, если для исходящих или входящих задан список разрешённых адресов, то правила не должны добавиться на интерфейсы, где такие соединения не предполагаются конфигурацией сети в принципе.
Вместо прямого указания портов, используется ассоциативное имя, за которым может скрываться целый набор портов и протоколов.
Временная донастройка сети и фильтра должна легко производится на целевой машине без централизованного управления при восстановлении после сбоев.
Достаточная сетевая безопасность должна быть достигнута ещё до включения AppArmor или SeLinux.
Динамическая защита должна быть реализована отдельно, но интерфейс черных списков задаётся на этом уровне.
Выбор технологий
Puppet 4 + Puppet DB + Hiera — автор честно пытался привить себе любовь хотя бы к одному из Ansible и Chef, но четвёртая версия Puppet взяла своё. Хотя, Ansible выглядит интересным для периодических задач по содержанию систем и изначальному развёртывания Puppet.
Ruby — по сути предопределённый выбор для расширений Puppet. К слову, автору пришлось изучить этот ЯП в ходе проекта, о чём совершенно не жалеет.
FireHOL — это первый сторонний генератор iptables, которому автор смог доверить свой сетевой фильтр за более чем 10 лет активного администрирования серверов. Все остальные генераторы субъективно меркнут.
Что получилось
Сам интерфейс состоит из основного класса cfnetwork и набора типов cfnetwork::* для задания настроек сети и сетевого фильтра. Есть возможность задавать все настройки программно через Puppet DSL или же через поставщика данных вроде Hiera.
Краткое описание API с неполным списком параметров. С полным можно ознакомиться на английском языке.
класс cfnetwork
main — настройки типа cfnetwork::iface для основного интерфейса.
dns — список DNS серверов или специальные значения:
'$recurse' — поставить локальный сервер.
'$serve' — то же самое, но и ещё и обслуживать клиентов на $service_face.
is_router — выполняет ли эта машина функцию сетевого маршрутизатора.
optimize_10gbe — подогнать настройки TCP по умолчанию для максимальной производительности соединений через 10+Gbit интерфейсы вместо публичных "интернетных" с ориентировочной задержкой в 50-100ms.
ifaces — набор конфигураций второстепенных интерфейсов типа cfnetwork::iface .
describe_services — набор описаний ресурсов типа cfnetwork::describe_service (описание сервисов).
service_ports — набор * cfnetwork::service_port (входящие соединения).
client_ports — набор * cfnetwork::client_ports (исходящие соединения).
dnat_ports — набор * cfnetwork::dnat_ports.
router_ports — набор * cfnetwork::router_ports.
тип cfnetwork::iface — конфигурация интерфейса.
title — ассоциативный идентификатор, который будет использоваться в других ресурсах.
device — системный сетевой интерфейс.
address — основной адрес IPv4/IPv6 вместе с маской сети в формате "address/cidr".
extra_addresses — дополнительные адреса в таком же формате.
extra_routes — дополнительные настройки маршрутизации (тоже важно для генератора фильтра).
gateway — подразумевает маршут по умолчанию, что используется в генераторе фильтра.
force_public = auto — крайне важная настройка для фильтра:
По умолчанию, если $address принадлежит 10/8, 172.16/12 или 192.168/16, то false, иначе true.
Если true:
Автоматически добавляет SNAT или MASQUERADE для исходящих соединений.
Автоматически включает TCP SYNPROXY для входящих соединений, включая DNAT.
Ставит политику DROP вместо REJECT по умолчанию.
Ограничивает входящие ping до 1/сек. через hashlimit для одного IP.
Устанавливает глобальный чёрный список входящих IP, за исключение особого белого списка.
тип cfnetwork::describe_service — описание сервиса (протоколов и портов).
title — название ресурса используется по всех названиях портов.
server — список серверных портов в формате proto/portnum. Пример: [ 'tcp/80', 'tcp/443' ].
тип cfnetwork::client_port — описание исходящего соединений.
Терминология взята из FireHOL..
title = '<iface>:<service>[:<tag>]'
src, dst, user, group, comment
тип cfnetwork::service_port — описание входящего соединений.
title = '<iface>:<service>[:<tag>]'
src, dst, comment
тип cfnetwork::router_port — описание разрешённого маршрутизируемого соединения.
title = '<iface>/<outface>:<service>[:<tag>]'
src, dst, comment
тип cfnetwork::dnat_port — описание одновременно машрутизируемого соединения и трансляции адреса назначения
title = '<iface>/<outface>:<service>[:<tag>]'
src, dst, comment
to_dst — адрес перенаправления (IPv4 и IPv6)
to_port — порт перенаправления (не обязательно)
Описание унифицированных параметров:
<iface> — название ассоциированного ресурса cfnetwork::iface или же:
'local' — как уже сказано выше — только локальный трафик, но учитывайте, что трафик на СВОЙ же внешний IP тоже идёт через local!
'any' — специальная замысловатая логина на базе src, dst и to_dst чтобы не создавать заведомо неиспользуемые правила. При отсутствии этих параметров, добавляется на все возможные интерфейсы, где имеет смысл. (Например, local не имеет смысла в router_port)
<outface> — то же самое, но для второго интерфейса в случае с dnat_port и router_port
<service> — название описания сервиса в cfnetwork::describe_service
<tag> — необязательная часть, которая идёт в comment.
Добавлена для избежания конфликта имён ресурсов без необходимости явно использовать "virtual resources"
src, dst — списки исходящих и целевых адресов IPv4/IPv6
comment — любой однострочный комментарий (принудительно вырезаются переводы строки)
user, group — проверка пользователя и группы для исходящих соединений (настоящий параноик обязан их использовать даже для local)
класс cfnetwork::sysctl — возможность тонкой настройки сетевого стека, стандартные ключи выведены в виде параметров класса.
класс cffirehol — генератор фильтра
enable=false — нужно принудительно включить после того, как убедитесь, что конфиг фильтра соответствует ожиданиям
synproxy_public=true — флаг включения SYNPROXY на публичных интерфейсах
ip_whitelist / ip_blacklist — статические списки. ip_blacklist не следует вообще задавать тут, а нужно запихивать динамически в ipset из постоянно обновляемых баз и систем динамической защиты, но это отдельная история.
Предопределённый наборы:
whitelist4 и whitelist6 — IPv4 и IPv6 сети белого списка
blacklist4net и blacklist6net — IPv4 и IPv6 сети чёрного списка
Поскольку в Debian и Ubuntu не было достаточно свежего пакета FireHOL, и поскольку стандартные запускаются лишь ПОСЛЕ поднятия сетевых интерфейсов, пришлось сделать свои сборки .deb пакетов.
Примечание: в описании каждого Puppet модуля из серии cfxxx есть раздел "Implicitly created resources", где описываются все определяемые ресурсы сетевого фильтра.
Живой пример
Полноценное развёртывание инфраструктуры в Vagrant, используя не освещённые в этой статье модули, можно посмотреть здесь.
Для наглядности приводится конфигурация сети и фильтра маршрутизатора:
настройки Hiera
classes:
- cfnetwork
# После того, как конфиг проверен через `/sbin/firehol try`
#cffirehol::enable: true
cfnetwork::is_router: true
cfnetwork::main:
device: eth1
address: '192.168.1.30/24'
extra_addresses: '192.168.1.40/24'
gateway: '192.168.1.1'
# принудительная имитация публичного интерфейса
force_public: true
cfnetwork::ifaces:
vagrant:
device: eth0
method: dhcp
# просто доводим до сведения
extra_routes: ['10.0.1.1/25']
infradmz:
device: eth2
address: '10.10.1.254/24'
dbdmz:
device: eth3
address: '10.10.2.254/24'
webdmz:
device: eth4
address: '10.10.2.254/24'
cfnetwork::describe_services:
testdb:
server: 'tcp/1234'
cfhttp:
server:
- 'tcp/80'
- 'tcp/443'
# DNAT для входящих HTTP соединений (не лучшее решение в боевом режиме)
cfnetwork::dnat_ports:
'main/webdmz:cfhttp':
dst: '192.168.1.40'
to_dst: '10.10.2.10'
cfnetwork::router_ports:
# Разрешить локальному NTP, DNS, APT стучаться во внешний мир
'infradmz/main:cfhttp:apt':
src: 'maint.example.com'
'infradmz/main:ntp':
src: 'maint.example.com'
# Разрешить Puppet Server (r10k) скачивать модули
'infradmz/main:cfhttp:puppet': {}
# Разрешить серверам из DMZ обращаться к инфраструктурным сервисам
'any/infradmz:ntp':
src: '10.10.0.0/16'
dst: 'maint.example.com'
'any/infradmz:dns':
src: '10.10.0.0/16'
dst: 'maint.example.com'
'any/infradmz:aptproxy':
src: '10.10.0.0/16'
dst: 'maint.example.com'
'any/infradmz:puppet':
src: '10.10.0.0/16'
dst: 'puppet.example.com'
# Разрешить веб серверам обращаться к базам данных
'webdmz/dbdmz:testdb': {}
сгенерированный конфиг генератора фильтра (именно так выходит)
Да, есть небольшой процент избыточности, но оптимизацию возможно провести в последующих версиях. Например, нет смысл иметь два правила, одно из которых частных случай другого. В принципе, код генерации уже оброс множеством "интеллектуальных" условий по мере внедрения в жизнь.
Это конфигурация вступает в силу автоматически при cffirehol::enable=true!
Модуль сам не будет пытаться менять настройки сети на лету — это нужно будет делать ручками или рестартом.
/etc/network/interfaces.d/*
#
# Generated by cfnetwork::iface puppet module
#
auto lo
iface lo inet loopback
source /etc/network/interfaces.d/*
#
# Generated by cfnetwork::iface puppet module
#
auto eth3
iface eth3 inet static
address 10.10.2.254
netmask 24
up sysctl --ignore net.ipv6.conf.eth3.disable_ipv6=1
#
# Generated by cfnetwork::iface puppet module
#
auto eth2
iface eth2 inet static
address 10.10.1.254
netmask 24
up sysctl --ignore net.ipv6.conf.eth2.disable_ipv6=1
#
# Generated by cfnetwork::iface puppet module
#
auto eth1
iface eth1 inet static
address 192.168.1.30
netmask 24
gateway 192.168.1.1
dns-nameservers 10.10.1.10
dns-search example.com
up ip addr add 192.168.1.40/24 dev eth1
up sysctl --ignore net.ipv6.conf.eth1.disable_ipv6=1
#
# Generated by cfnetwork::iface puppet module
#
auto eth0
iface eth0 inet dhcp
netmask 255.255.255.0
up ip route add 10.0.1.1/25 dev eth0
up sysctl --ignore net.ipv6.conf.eth0.disable_ipv6=1
#
# Generated by cfnetwork::iface puppet module
#
auto eth4
iface eth4 inet static
address 10.10.2.254
netmask 24
up sysctl --ignore net.ipv6.conf.eth4.disable_ipv6=1
Заключение
Как видно, конфигурация сети и фильтра элементарна, чиста и лаконична, а самое главное удобна для изменений без горы магический чисел.
Пока нет длительной истории в боевом режиме. Обкатка проходит на паре реальных серверов и примерно десятке виртуалок без серьёзной нагрузки. Поэтому интересуют добровольцы, у которых разросся парк систем, а подход к администрированию ещё не успел подстроиться или же не до конца устраивает.