Начинающие админы часто считают, что файрвол в Linux называется iptables. Более опытные знают, что нынешняя подсистема ядра для фильтрации трафика и модификации пакетов называется NetFilter, а команда iptables всего лишь утилита для ее настройки. В этой статье я познакомлю тебя с новым инструментом, который все чаще встречается в современных дистрибутивах, — nftables.
Возможно, для тебя это будет новостью, но iptables вовсе не уникальна, есть и другие утилиты подобного рода. Собственно, настраивать ей можно только правила для пакетов IPv4 на сетевом уровне. Для IPv6 понадобится ip6tables, для канального уровня — ebtables и arptables.
Сравнение iptables Nftables
Настройка сетевых интерфейсов, маршрутов, пространств имен и прочего уже давно унифицирована в утилите ip из пакета iproute2, а зоопарк старых утилит (ifconfig, vconfig, route и прочие) поддерживается только для совместимости со старыми скриптами. Ждет ли такая же унификация межсетевой экран? До версий ядра 2.4 Linux прошел через множество реализаций межсетевых экранов в ядре и утилит для их настройки (ipfilter, ipfwadm, ipchains), но NetFilter и iptables используются уже почти двадцать лет и кажутся незыблемыми.
Как ни странно, первые попытки переосмыслить настройку МСЭ предпринимались еще в 2008 году. Проект назвали nftables, но он стал легендарным (в узких кругах) долгостроем. В 2014 году его наконец приняли в основную ветку ядра 3.13, но пользовательские утилиты какое-то время оставались почти непригодными к работе. К примеру, счетчики пакетов для правил хранились в ядре, но просмотреть их из пространства пользователя не было никакой возможности.
РЕКОМЕНДУЕМ:
Преимущества NFTables
Сейчас дело наконец меняется и дистрибутивы даже начинают использовать nftables по умолчанию. Сторонние инструменты вроде fail2ban тоже уже добавляют ее поддержку.
Несколько причин расстаться с iptables и перейти на nftables:
- унифицированный синтаксис;
- быстрая загрузка больших конфигов;
- встроенная поддержка списков адресов/портов;
- средства автоматической конвертации правил из iptables;
- вывод правил в JSON.
В этой статье мы перенесем настройки моего VPS с iptables на nftables. Нужно отметить, что использовать сразу оба инструмента на одной машине не выйдет, так что нужно планировать одномоментную миграцию.
Документацию можно найти в вики проекта.
Проблемы iptables
Хотя NetFilter вполне хорошо справлялся со своей задачей, его настройка через iptables нередко оказывалась куда сложнее, чем могла бы быть.
К примеру, встроенный синтаксис для групп адресов и сетей в нем так и не появился. Существует отдельный инструмент для этих целей — ipset, который позволяет создать группы и ссылаться на них в правилах, вроде iptables -I FORWARD -m set --match-set TrustedHosts src -j ACCEPT.
Однако само то, что группы настраиваются отдельно от правил и с помощью другой утилиты, создает много проблем. Нужно помнить два разных синтаксиса, да еще и убедиться, что настройки ipset при загрузке применяются раньше правил iptables, — это при том, что во многих дистрибутивах Linux встроенного сервиса для ipset так и нет.
Другая надоедливая проблема — нельзя указать несколько разных действий в одном правиле.
Хотя еще больше раздражает отсутствие возможности использовать одни правила для IPv4 и IPv6. В современном мире dual stack уже стал нормой на серверах, машин с одним IPv4 все меньше, а машин с одним IPv6 нет и не предвидится, в итоге админу приходится дублировать одни и те же правила в двух разных конфигах.
Кроме того, в некоторых местах синтаксис опций iptables достаточно хрупкий. Где-то можно ставить пробел после запятой, где-то нет. Где-то можно смело поменять две опции местами, где-то это вызовет ошибку. Если правила генерируются скриптом, это особенно усложняет тестирование.
Все это приводит к тому, что iptables часто используют как своеобразный низкоуровневый «язык ассемблера», который генерируется либо фронтендами вроде shorewall или firewalld, либо пользовательскими скриптами. А цель проекта nftables — в первую очередь сделать настройку правил простой и удобной для человека. Давай посмотрим, насколько это удалось.
Настройка Nftables
Автоматическая трансляция
Авторы nftables определенно учли горький опыт многих других больших миграций и написали инструменты для автоматической трансляции правил из iptables. Можно конвертировать как отдельные команды, так и файлы для iptables-restore.
К сожалению, возможности автоматической трансляции настроек ipset там нет, в первую очередь из-за совершенно разных подходов к настройке групп, ну и из-за малой популярности ipset.
Отдельные правила транслируются с помощью утилиты iptables-translate, а наборы правил из iptables-save — с помощью iptables-restore-translate. Это только для IPv4, для правил ip6tables нужно использовать ip6tables-restore-translate и далее по аналогии.
В Debian эти утилиты находятся в пакете iptables, в Fedora — в пакете iptables-nft.
Посмотрим на трансляцию отдельных правил. Команда iptables-translate — самый простой способ изучить новый синтаксис на примерах (хотя и не заменит чтения документации!).
1 2 3 4 5 |
$ iptables-translate -t nat -I POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE nft insert rule ip nat POSTROUTING oifname "eth0" ip saddr 10.0.0.0/24 counter masquerade $ iptables-translate -A INPUT -m state --state NEW -m tcp -p tcp --dport 53 -j ACCEPT -m comment --comment "DNS zone transfer" nft add rule ip filter INPUT ct state new tcp dport 53 counter accept comment \"DNS zone transfer\" |
Как видим, стиль синтаксиса больше похож на pf и другие МСЭ из систем семейства BSD.
Но! Воспользоваться этими командами на неподготовленной системе не выйдет, и вот почему: в nftables больше нет предопределенных таблиц и цепочек.
Хуки вместо цепочек
В iptables цепочки INPUT или FORWARD находятся в таблице filter по умолчанию, так же как PREROUTING и POSTROUTING в таблице nat. В nftables не существует никаких таблиц и цепочек по умолчанию. Особое значение там есть у типов таблиц и хуков, а имена таблиц и цепочек могут быть совершенно произвольными.
В целом имена хуков повторяют старые имена специальных цепочек, но в нижнем регистре. Например, input, output, forward. Скоро мы увидим их в действии.
Перенос правила
Для экономии времени мы не будем писать правила с нуля, а воспользуемся iptables-restore-translate и творчески переработаем ее вывод.
На моем VPS стоит Fedora, поэтому все команды и расположение конфигов я даю именно для этого дистрибутива. Из сервисов: SSH, веб, почта, DNS — типичный набор.
Для начала выключим и остановим старые сервисы iptables.
1 2 3 4 |
$ sudo systemctl disable iptables $ sudo systemctl stop iptables $ sudo systemctl disable ip6tables $ sudo systemctl stop ip6tables |
Теперь посмотрим на правила ( cat /etc/sysconfig/iptables).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
*filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] ## Keep state -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT ## SSH -A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT -m comment --comment "SSH" ## Email -A INPUT -m state --state NEW -m tcp -p tcp --dport 25 -j ACCEPT -m comment --comment "SMTP" -A INPUT -m state --state NEW -m tcp -p tcp --dport 465 -j ACCEPT -m comment --comment "SMTPS (SSL/TLS)" -A INPUT -m state --state NEW -m tcp -p tcp --dport 587 -j ACCEPT -m comment --comment "SMTP StartTLS" -A INPUT -m state --state NEW -m tcp -p tcp --dport 993 -j ACCEPT -m comment --comment "IMAPS" -A INPUT -m state --state NEW -m tcp -p tcp --dport 995 -j ACCEPT -m comment --comment "POP3S" ## DNS -A INPUT -m state --state NEW -m tcp -p tcp --dport 53 -j ACCEPT -m comment --comment "DNS zone transfer" -A INPUT -p udp --dport 53 -j ACCEPT -m comment --comment "DNS queries" ## HTTP -A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT -m comment --comment "HTTP" -A INPUT -m state --state NEW -m tcp -p tcp --dport 443 -j ACCEPT -m comment --comment "HTTPS" ## ICMP -A INPUT -p icmp -j ACCEPT ## Local traffic -A INPUT -i lo -j ACCEPT ## Default actions -A INPUT -j REJECT --reject-with icmp-host-prohibited -A FORWARD -j REJECT --reject-with icmp-host-prohibited COMMIT |
Кроме них, есть /etc/sysconfig/ip6tables, который отличается тремя правилами.
1 2 3 4 5 6 |
## ICMP -A INPUT -p ipv6-icmp -j ACCEPT ## Default actions -A INPUT -j REJECT --reject-with icmp6-adm-prohibited -A FORWARD -j REJECT --reject-with icmp6-adm-prohibited |
Теперь конвертируем этот конфиг в синтаксис nftables. Пиши:
1 |
$ sudo iptables-restore-translate -f /etc/sysconfig/iptables |
Результат будет следующим.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
add table ip filter add chain ip filter INPUT { type filter hook input priority 0; policy accept; } add chain ip filter FORWARD { type filter hook forward priority 0; policy accept; } add chain ip filter OUTPUT { type filter hook output priority 0; policy accept; } add rule ip filter INPUT ct state related,established counter accept add rule ip filter INPUT ct state new tcp dport 22 counter accept comment "SSH" add rule ip filter INPUT ct state new tcp dport 25 counter accept comment "SMTP" add rule ip filter INPUT ct state new tcp dport 465 counter accept comment "SMTPS (SSL/TLS)" add rule ip filter INPUT ct state new tcp dport 587 counter accept comment "SMTP StartTLS" add rule ip filter INPUT ct state new tcp dport 993 counter accept comment "IMAPS" add rule ip filter INPUT ct state new tcp dport 995 counter accept comment "POP3S" add rule ip filter INPUT ct state new tcp dport 53 counter accept comment "DNS zone transfer" add rule ip filter INPUT udp dport 53 counter accept comment "DNS queries" add rule ip filter INPUT ct state new tcp dport 80 counter accept comment "HTTP" add rule ip filter INPUT ct state new tcp dport 443 counter accept comment "HTTPS" add rule ip filter INPUT ip protocol icmp counter accept add rule ip filter INPUT iifname "lo" counter accept add rule ip filter INPUT counter reject with icmp type host-prohibited add rule ip filter FORWARD counter reject with icmp type host-prohibited |
Ключевое слово ct — это, конечно же, ConnTrack — механизм отслеживания состояния соединений в ядре Linux. Опция counter отсутствовала в исходных правилах iptables. Просто nftables не включает счетчики правил по умолчанию, и, если они тебе важны, нужно включить их этой опцией.
Существенный недостаток синтаксиса в стиле pf — он имеет свойство превращаться в плохо читаемую стену текста. К счастью, такой синтаксис обязателен только для команды nft. Для скриптов, которые загружаются с помощью nft -f, nftables поддерживает более читаемый синтаксис с фигурными скобками и возможностью писать несколько опций в одной строке через точку с запятой.
Самый простой способ конвертировать этот синтаксис в вариант со скобками — сохранить эти правила в файл (например, /tmp/rules.nft), применить их с помощью nft -f /tmp/rules.nft и просмотреть их командой nft list ruleset.
Команда nft list ruleset может заменить и iptables -L -nv, и iptables-save. Кроме того, она поддерживает вывод правил в JSON вместе со значениями счетчиков с помощью опции -j/--json — мечта авторов фронтендов для настройки.
Можно было бы просто сохранить конвертированные правила в один из файлов в /etc/nftables/ и ограничиться этим, но можно и немного реорганизовать конфиг с помощью новых фич nftables.
Размешение новых конфигов
В iptables не существует способов разделить конфиг на части, из-за чего многие люди и генерируют конфиги скриптами. Другое дело nftables — там есть опция include.
В Fedora есть файл /etc/sysconfig/nftables.conf, который читает скрипт сервиса nftables. Этот файл состоит из одних опций include, а в «/etc/nftables/ лежит набор заготовок конфигов. Мы поместим наши новые правила в файлinet-filter.nft`.
Перед этим нужно раскомментировать в файле /etc/sysconfig/nftables.conf строку include "/etc/nftables/inet-filter.nft".
В файле из поставки дистрибутива для нас определена таблица inet filter с цепочками, которые повторяют смысл таблицы filter в iptables по умолчанию.
/etc/nftables/inet-filter.nft
1 2 3 4 5 6 7 |
#!/usr/sbin/nft -f table inet filter { chain input { type filter hook input priority 0; } chain forward { type filter hook forward priority 0; } chain output { type filter hook output priority 0; } } |
Здесь filter — произвольное название таблицы, а ключевое слово inet — ее тип. Если типы ip и ip6 ограничивают таблицу правилами для IPv4 и IPv6 соответственно, то тип inet позволяет писать правила для обоих протоколов. Если все сервисы доступны по обоим протоколам, таким способом можно избежать дублирования правил почти полностью.
Названия chain input и прочие тоже чисто информационные, назначение цепочки определяет опция type, вроде type filter hook input. Соответственно, если бы у нас был NAT, нам нужна была бы таблица с опцией type nat hook prerouting вместо правил вида iptables -t nat -I PREROUTING и так далее по аналогии.
Добавления правила
Поскольку нас интересует фильтрация входящего трафика, свои правила мы будем дописывать внутрь chain input. По сути, копируем из вывода iptables-restore-translate все после add rule ip filter INPUT и вставляем внутрь chain input.
В своих старых настройках я использовал по одному правилу на каждый порт TCP или UDP. В iptables есть опция --dports, но в nftables все еще проще: можно писать порты в фигурных скобках через запятую. Этим мы и воспользуемся и объединим все правила для каждого сервиса в одно, например dport {80, 443} для HTTP(S).
Получится что-то вроде такого:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
table inet filter { chain input { type filter hook input priority 0 ct state related,established accept # Services ct state new tcp dport ssh counter accept ct state new tcp dport {25, 465, 587} counter accept comment SMTP ct state new tcp dport {993, 995} counter accept comment "IMAPS and POP3S" ct state new tcp dport {80, 443} counter accept comment "HTTP" udp dport 53 counter accept comment "DNS queries" ct state new tcp dport 53 counter accept comment "DNS zone transfers" # ICMP ip protocol icmp accept meta l4proto ipv6-icmp accept # Loopback iifname lo accept # Reject everything else meta nfproto ipv4 reject with icmp type admin-prohibited meta nfproto ipv6 reject with icmpv6 type admin-prohibited } chain forward { type filter hook forward priority filter; policy accept; meta nfproto ipv4 reject with icmp type admin-prohibited meta nfproto ipv6 reject with icmpv6 type admin-prohibited } chain output { type filter hook output priority 0; } } |
Теперь осталось запустить сервис nftables и поставить его на загрузку:
1 2 |
$ sudo systemctl start nftables $ sudo systemctl enable nftables |
Если работаешь на удаленной машине без доступа к консоли, не забудь запланировать перезагрузку ( shutdown -r +10) или сброс правил ( echo "nft flush ruleset | at now+10min").
Создание группы адресов
Правило про tcp dport 53 разрешает репликацию зон DNS на вторичные серверы. Если обычные запросы DNS выполняются через UDP, то репликация зон — через TCP.
Конечно, репликация доступна не всем подряд, а вполне определенным хостам, в моем случае это вторичные серверы Hurricane Electric: 216.218.133.2 и 2001:470:600::2. До миграции это ограничение было прописано в настройках самого BIND, но мы добавим его и в правила nftables, чтобы посмотреть на синтаксис переменных и групп.
Переменные определяются с помощью ключевого слова define, например define foo = 192.0.2.1. На них можно ссылаться в стиле shell с помощью $foo. Объявления переменных мы поместим в самое начало файла.
РЕКОМЕНДУЕМ:
Настройка файрвола на примере Микротика
Затем мы создадим две группы, одну для IPv4, другую для IPv6. Увы, использовать оба типа адресов в одной группе не выйдет, но зато проверка нахождения адреса в группе в nftables выполняется за время O(1).
Группы определяются с помощью ключевого слова set, и в них нужно указать тип. Когда они определены, в опциях правил на них можно ссылаться с помощью @setname.
С переменными и группами наш конфиг пример следующий вид:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#!/usr/sbin/nft -f define he_dns_ipv4 = 216.218.133.2 define he_dns_ipv6 = 2001:470:600::2 table inet filter { set secondary_dns_ipv4 { type ipv4_addr; elements = { $he_dns_ipv4 } } set secondary_dns_ipv6 { type ipv6_addr; elements = { $he_dns_ipv6 } } chain input { ... udp dport 53 counter accept comment "DNS queries" ct state new tcp dport 53 ip saddr @secondary_dns_ipv4 counter accept comment "DNS zone transfers" ct state new tcp dport 53 ip6 saddr @secondary_dns_ipv6 counter accept comment "DNS zone transfers" ... |
Выводы
На мой взгляд, синтаксис nftables и ее подход к работе с конфигами — это значительный прогресс по сравнению с iptables и расставаться со старыми инструментами можно без сожаления.