Вариант развёртывания Linux систем на базе Puppet 4. Часть I: сеть и сетефой фильтр (cfnetwork + cffirehol)

от автора


Вкратце:

  1. cfnetworkPuppet API для полной настройки сети и фильтра через ресурсы Puppet. Идеально дружит с Hiera и потенциально другими "data providers" в концепции Puppet.
  2. cffirehol — "meta-provider" конкретной реализации настройки фильтра для cfnetwork на базе замечательного генератора FireHOL
  3. Пока поддерживаются только Debian 8+ (Jessie и выше) и Ubuntu 12.04+ (Trusty и выше)


Лирическое вступление: так уж сложилось, что автор крайне параноидален на тему контроля развёрнутых систем и автоматизации. Долгие годы копился опыт встречаемых проблем и относительно местечковых решений. После ухода с предыдущего места работы стало понятно, что осязаемого багажа в сфере администрирования в общем-то и нет. Впрочем, то, что было, тащить с собой дальше особо и не хотелось. Так родился новый велосипед. А теперь к делу.

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

Чем не устраивают существующие решения?

  • Слабая интеграция конфигурации сети и сетевого фильтра — дополнительная возня с (пере-)конфигурацией и высокий риск ошибок; сложности в создания plug&play модулей отдельных сервисов, которые дружат с системой безопасности.
  • Отсутствие наглядности для аудита — конфигурация либо не лаконична и размазана по многим файлам, либо вообще существует в нечитаемом для человека виде.
  • Излишне низкоуровневая конфигурация фильтра без абстракций — те же проблемы, что и пунктом выше. Можно сравнить с написанием веб-странички на ассемблере.
  • Отсутствие "интеллектуальности" установок по умолчанию — для некоторых топорность является преимуществом, но не для автора.
  • Настройку сетевого стека вообще обходят стороной — ванильные настройки ядра далеко не всегда то, что вы хотите видеть на боевой системе даже до начала тестирования и оптимизации, не говоря уже о безопасности.

Общая концепция настройки сети и фильтра

Никаких новых теорий — обыденность из разных мест.

  1. Каждый логических сетевой интерфейс имеет уникальное значимое имя вроде world, dmz, office и т.д. local зарезервировано для loopback интерфейса.
  2. Настройки стандартных логических интерфейсов задаются и доступны генератору правил фильтра.
  3. Поддержка особого типа интерфейса any — генератор фильтра должен быть достаточно интеллектуальным чтобы не плодить лишние правила там, где они никогда не сработают. К примеру, если для исходящих или входящих задан список разрешённых адресов, то правила не должны добавиться на интерфейсы, где такие соединения не предполагаются конфигурацией сети в принципе.
  4. Вместо прямого указания портов, используется ассоциативное имя, за которым может скрываться целый набор портов и протоколов.
  5. Временная донастройка сети и фильтра должна легко производится на целевой машине без централизованного управления при восстановлении после сбоев.
  6. Достаточная сетевая безопасность должна быть достигнута ещё до включения AppArmor или SeLinux.
  7. Динамическая защита должна быть реализована отдельно, но интерфейс черных списков задаётся на этом уровне.

Выбор технологий

  • 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.
  • Удобства для использования Hiera.
    Все значения, кроме ifaces имеют lookup_options: { merge: hash } (документация).
    • 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 сети белого списка
    • blacklist4 — индивидуальные IPv4 адреса чёрного списка
    • 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/firehol/firehol.conf

# This file is autogenerated by cffirehol Puppet Module # Any changes made here may be overwritten at any time version 6  # Defaults #---------------- DEFAULT_INTERFACE_POLICY="DROP" DEFAULT_ROUTER_POLICY="DROP" FIREHOL_LOG_MODE="NFLOG" FIREHOL_TRUST_LOOPBACK="0" FIREHOL_DROP_ORPHAN_TCP_ACK_FIN="1" FIREHOL_INPUT_ACTIVATION_POLICY="DROP" FIREHOL_OUTPUT_ACTIVATION_POLICY="DROP" FIREHOL_FORWARD_ACTIVATION_POLICY="DROP"  # Custom Services #---------------- server_dns_ports="tcp/53 udp/53" client_dns_ports="any"  # Use to open all TCP ports (e.g. for local) server_alltcp_ports="tcp/1:65535" client_alltcp_ports="any"  # Use to open all UDP ports (e.g. for local) server_alludp_ports="udp/1:65535" client_alludp_ports="any"  # Use to open all TCP and UDP ports (e.g. for local) server_allports_ports="udp/1:65535 tcp/1:65535" client_allports_ports="any"  server_cfhttp_ports="tcp/80 tcp/443" client_cfhttp_ports="default"  server_testdb_ports="tcp/1234" client_testdb_ports="default"  server_cfssh_ports="tcp/22" client_cfssh_ports="default"  server_smtp_ports="tcp/25" client_smtp_ports="default"  server_cfsmtp_ports="tcp/25 tcp/465 tcp/587" client_cfsmtp_ports="default"  server_puppet_ports="tcp/8140" client_puppet_ports="default"  # Setup of ipsets #---------------- ipset4 create whitelist4 hash:net ipset6 create whitelist6 hash:net ipset4 create blacklist4 hash:ip ipset4 create blacklist4net hash:net ipset6 create blacklist6net hash:net # note: hardcoded list is not expected to be large ipset4 add whitelist4 "10.0.0.0/8"  # Protection on public-facing interfaces #---------------- # main blacklist4 input inface "eth1" ipset:blacklist4net ipset:blacklist4 except src ipset:whitelist4 blacklist6 input inface "eth1" ipset:blacklist6net ipset:blacklist6 except src ipset:whitelist6 iptables -t raw -N cfunroute_main iptables -t raw -A cfunroute_main -s "10.0.0.0/8,172.16.0.0/12,224.0.0.0/4,127.0.0.1/8" -j DROP iptables -t raw -A cfunroute_main -d "10.0.0.0/8,172.16.0.0/12,224.0.0.0/4,127.0.0.1/8" -j DROP iptables -t raw -A PREROUTING -i "eth1" -j cfunroute_main # cfauth:  synproxy4 input inface main dst "192.168.1.30/24" dport "22" src "192.168.0.0/16" accept synproxy4 forward inface main dst "192.168.1.40" dport "80" dnat to "10.10.2.10" synproxy4 forward inface main dst "192.168.1.40" dport "443" dnat to "10.10.2.10" iptables -t nat -N cfpost_snat_main iptables -t nat -A cfpost_snat_main -s 192.168.1.30,192.168.1.40 -j RETURN iptables -t nat -A cfpost_snat_main -j SNAT --to-source=192.168.1.30 iptables -t nat -A POSTROUTING -o "eth1" -j cfpost_snat_main  # vagrant blacklist4 input inface "eth0" ipset:blacklist4net ipset:blacklist4 except src ipset:whitelist4 blacklist6 input inface "eth0" ipset:blacklist6net ipset:blacklist6 except src ipset:whitelist6 # cfauth:  iptables -t nat -A POSTROUTING -o "eth0" -j MASQUERADE  # Custom Headers #----------------  # NAT #---------------- dnat4 to "10.10.2.10" inface "eth1" proto "tcp" dport "80" dst "192.168.1.40" dnat4 to "10.10.2.10" inface "eth1" proto "tcp" dport "443" dst "192.168.1.40"  # Interfaces #---------------- interface "eth1" "main"     policy deny     protection bad-packets     client icmp accept     server4 ping accept with hashlimit ping upto 1/s burst 2     # cfauth:      server4 "cfssh" accept src "192.168.0.0/16"     # cfsystem:      client "http" accept uid "root"     # cfsystem:      client "https" accept uid "root"     # cfsystem:      client "ntp" accept uid "root ntpd"     # cfsystem:      client "cfsmtp" accept uid "root Debian-exim"     # cfsystem:      client "puppet" accept uid "root"     # cfnetwork:      client "dns" accept  interface "eth0" "vagrant"     policy deny     protection bad-packets     client icmp accept     server4 ping accept with hashlimit ping upto 1/s burst 2     # cfauth:      server4 "cfssh" accept src "10.0.0.0/8 192.168.0.0/16 172.16.0.0/12"     # cfsystem:      client "http" accept uid "root"     # cfsystem:      client "https" accept uid "root"     # cfsystem:      client "ntp" accept uid "root ntpd"     # cfsystem:      client "cfsmtp" accept uid "root Debian-exim"     # cfsystem:      client "puppet" accept uid "root"     # cfnetwork:      client4 "dns" accept dst "10.10.1.10"  interface "eth2" "infradmz"     policy reject     client icmp accept     server icmp accept     # cfauth:      server4 "cfssh" accept src "10.0.0.0/8"     # cfsystem:      client "http" accept uid "root"     # cfsystem:      client "https" accept uid "root"     # cfsystem:      client "ntp" accept uid "root ntpd"     # cfsystem:      client "cfsmtp" accept uid "root Debian-exim"     # cfsystem:      client "puppet" accept uid "root"     # cfnetwork:      client4 "dns" accept dst "10.10.1.10"  interface "eth3" "dbdmz"     policy reject     client icmp accept     server icmp accept     # cfauth:      server4 "cfssh" accept src "10.0.0.0/8"     # cfsystem:      client "http" accept uid "root"     # cfsystem:      client "https" accept uid "root"     # cfsystem:      client "ntp" accept uid "root ntpd"     # cfsystem:      client "cfsmtp" accept uid "root Debian-exim"     # cfsystem:      client "puppet" accept uid "root"  interface "eth4" "webdmz"     policy reject     client icmp accept     server icmp accept     # cfauth:      server4 "cfssh" accept src "10.0.0.0/8"     # cfsystem:      client "http" accept uid "root"     # cfsystem:      client "https" accept uid "root"     # cfsystem:      client "ntp" accept uid "root ntpd"     # cfsystem:      client "cfsmtp" accept uid "root Debian-exim"     # cfsystem:      client "puppet" accept uid "root"  interface "lo" "local"     policy reject     client icmp accept     server icmp accept     # cfauth:      server4 "cfssh" accept src "10.0.0.0/8 192.168.0.0/16"     # cfsystem:      client "http" accept uid "root"     # cfsystem:      client "https" accept uid "root"     # cfsystem:      client "ntp" accept uid "root ntpd"     # cfsystem:      server "smtp" accept     # cfsystem:      client "smtp" accept     # cfsystem:      client "cfsmtp" accept uid "root Debian-exim"     # cfsystem:      client "puppet" accept uid "root"  # Routers #---------------- router "main_infradmz" inface "eth1" outface "eth2"     policy drop     client icmp accept     # apt:      client4 "cfhttp" accept src "10.10.1.10"     client4 "ntp" accept src "10.10.1.10"     # puppet:      client "cfhttp" accept  router "main_webdmz" inface "eth1" outface "eth4"     policy drop     client icmp accept     server4 "cfhttp" accept dst "10.10.2.10"  router "vagrant_infradmz" inface "eth0" outface "eth2"     policy drop     client icmp accept     server4 "ntp" accept dst "10.10.1.10" src "10.10.0.0/8"     server4 "dns" accept dst "10.10.1.10" src "10.10.0.0/8"     server4 "aptproxy" accept dst "10.10.1.10" src "10.10.0.0/8"     server4 "puppet" accept dst "10.10.1.11" src "10.10.0.0/8"  router "infradmz_infradmz" inface "eth2" outface "eth2"     policy reject     server icmp accept     client icmp accept     server4 "ntp" accept dst "10.10.1.10" src "10.10.0.0/8"     server4 "dns" accept dst "10.10.1.10" src "10.10.0.0/8"     server4 "aptproxy" accept dst "10.10.1.10" src "10.10.0.0/8"     server4 "puppet" accept dst "10.10.1.11" src "10.10.0.0/8"  router "dbdmz_infradmz" inface "eth3" outface "eth2"     policy reject     server icmp accept     client icmp accept     server4 "ntp" accept dst "10.10.1.10" src "10.10.0.0/8"     server4 "dns" accept dst "10.10.1.10" src "10.10.0.0/8"     server4 "aptproxy" accept dst "10.10.1.10" src "10.10.0.0/8"     server4 "puppet" accept dst "10.10.1.11" src "10.10.0.0/8"  router "webdmz_infradmz" inface "eth4" outface "eth2"     policy reject     server icmp accept     client icmp accept     server4 "ntp" accept dst "10.10.1.10" src "10.10.0.0/8"     server4 "dns" accept dst "10.10.1.10" src "10.10.0.0/8"     server4 "aptproxy" accept dst "10.10.1.10" src "10.10.0.0/8"     server4 "puppet" accept dst "10.10.1.11" src "10.10.0.0/8"  router "webdmz_dbdmz" inface "eth4" outface "eth3"     policy reject     server icmp accept     client icmp accept     server "testdb" accept

конфигурация сети

Модуль сам не будет пытаться менять настройки сети на лету — это нужно будет делать ручками или рестартом.

/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

Заключение

Как видно, конфигурация сети и фильтра элементарна, чиста и лаконична, а самое главное удобна для изменений без горы магический чисел.

Пока нет длительной истории в боевом режиме. Обкатка проходит на паре реальных серверов и примерно десятке виртуалок без серьёзной нагрузки. Поэтому интересуют добровольцы, у которых разросся парк систем, а подход к администрированию ещё не успел подстроиться или же не до конца устраивает.

ссылка на оригинал статьи https://habrahabr.ru/post/277085/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *