Базовая настройка Nftables в Linux с помощью Bash-скрипта
Приветствую!

Автоматизируем настройку nftables в Linux с помощью универсального Bash-скрипта, используя базовый набор правил для фильтрации сетевого трафика.

Предлагаемый в этой статье скрипт nftables.sh создан под давним впечатлением от статьи на serveradmin.ru, посвященной настройке iptables.

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

Подразумевается, что вы имеете некоторое представление о том, что такое Netfilter и как работают инструменты для его настройки, такие как iptables, nftables, ufw, firewald.

Если вы ранее работали с iptables, но про nftables только слышали: рекомендую полезную вводную статью моего коллеги по цеху: 🔗 Настройка фаервола - Nftables.

Предисловие

Я, как и многие другие администраторы Linux систем, немного “проспал” тот момент, когда популярные дистрибутивы перешли на использование nftables в качестве бэкенда по умолчанию для управления сетевым экраном.

Да-да, если вы используете iptables:

BASH
sudo iptables -L -n
Нажмите, чтобы развернуть и увидеть больше

То с высокой долей вероятности ваши правила уже переведены в nftables:

BASH
sudo nft list ruleset
Нажмите, чтобы развернуть и увидеть больше

Разработчики постарались сделать переход максимально плавным и создали инструмент для совместимости правил iptables с nftables. На данный момент многие дистрибутивы для команды iptables делают симлинк, указывающий на специальный бинарник nftables.

Например, в Linux Mint Debian Edition 7 команда iptables - это символическая ссылка на утилиту xtables-nft-multi.

Определить конечный файл симлинков можно такой страшной командой:

BASH
current="$(command -v iptables)"; while [[ -L "$current" ]]; do target=$(readlink "$current"); echo "$current -> $target"; if [[ "$target" == /* ]]; then current="$target"; else current="$(dirname "$current")/$target"; fi; done; echo "Final: $current"
Нажмите, чтобы развернуть и увидеть больше

Сделано это также и для того, чтобы не ломать работу многих сетевых программ в Linux, которые до сих пор используют iptables. Например, фаервол ufw или Docker:

Немного про особенности nftables

Чаще всего при прочтении материалов по nftables вы увидите следующую схему:

Она визуализирует работу сетевого экрана Linux - Netfilter и пути прохождения пакетов через него.

Сегодня учить всю схему не нужно, говорить мы будем лишь про семейства IPv4, IPv6 и соответственно IP layer и Application layer. Сюда входят следующие хуки (точки входа, см. схему) фильтрации: prerouting, input, output, forward, postrouting.

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

И ключевые различия и нововведения nftables, в сравнении с iptables:

Вроде озвучил всё, что хотел. Тема фаервола в Linux очень большая. Трудно охватить всё сразу. Не будем раздувать статью, переходим к скрипту и его описанию.

Скрипт настройки nftables

Цель написания подобного скрипта: иметь под рукой универсальный инструмент быстрой настройки локального фаервола Linux, с возможностью простой кастомизации набора правил фильтрации под различные нужды. Данный набор составлен в соответствии с принципом “Всё запрещено, кроме явно разрешённого”. Отлично подойдет для защиты хостов, имеющих публичный IP с работающими на нём сервисами.

В скрипте определяется массив NFT_RULES, который содержит команды-правила для nftables. При необходимости отредактируйте/измените/добавьте нужные правила (соблюдая синтаксис и экранируя двойные кавычки внутри правил).

При запуске, скрипт создает временный файл с набором правил в формате nft (его командный вариант), после чего работает уже с ним: проверяет синтаксис/применяет правила. Также у скрипта предусмотрены флаги для бэкапа текущего файла конфигурации (который задаётся в переменной NFT_CONFIG) и сохранения текущего набора правил (nft -s list table inet filter) в этот файл конфигурации (перезаписывая его).

Запускается скрипт от имени root с одним или несколькими параметрами:

Скрипт призван автоматизировать полную настройку фаервола: создание таблиц, наборов адресов, цепочек и правил фильтрации, включая обработку исключений для интерфейсов контейнеров, виртуализации и VPN. + немного NAT (перенаправление порта и маскарадинг) для примера.

BASH
nvim ./nftables.sh
Нажмите, чтобы развернуть и увидеть больше
nftables.sh
  1#!/usr/bin/env bash
  2
  3# Script security parameters
  4set -Eeuo pipefail
  5
  6# Explicit PATH definition
  7export PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin"
  8
  9# =============================================================
 10# ========== BEGINNING OF USER CONFIGURATION SECTION ==========
 11
 12NFT="$(command -v nft)"
 13NFT_CONFIG="/etc/nftables.conf"
 14NFT_RULES=(
 15# =====================
 16#     TABLES & SETS
 17# =====================
 18# "flush ruleset"
 19"add table inet filter"
 20"flush table inet filter"
 21"add table inet nat"
 22"flush table inet nat"
 23
 24"add set inet filter lan4 { type ipv4_addr; flags interval; elements = { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16 } }"
 25"add set inet filter lan6 { type ipv6_addr; flags interval; elements = { fd00::/8, fe80::/10 } }"
 26"add set inet filter trusted { type ipv4_addr; elements = { 123.34.56.78 } }"
 27
 28# =====================
 29#        CHAINS
 30# =====================
 31"add chain inet filter input { type filter hook input priority 0; policy drop; }"
 32"add chain inet filter forward { type filter hook forward priority 50; policy drop; }"
 33"add chain inet filter output { type filter hook output priority -200; policy accept; }"
 34"add chain inet nat prerouting { type nat hook prerouting priority dstnat; policy accept; }"
 35"add chain inet nat postrouting { type nat hook postrouting priority srcnat; policy accept; }"
 36
 37"add chain inet filter input_wan"
 38"add chain inet filter input_lan"
 39"add chain inet filter log_drop"
 40
 41# =====================
 42#      INPUT BASE
 43# =====================
 44"add rule inet filter input ct state invalid drop comment \"Drop invalid connections\""
 45"add rule inet filter input ct state { established, related } accept comment \"Allow established connections\""
 46"add rule inet filter input iif lo accept comment \"Allow loopback\""
 47"add rule inet filter input ip saddr @trusted accept comment \"Allow trusted IPs\""
 48
 49# ICMP
 50"add rule inet filter input meta l4proto icmp icmp type { echo-request, destination-unreachable, time-exceeded } limit rate 10/second accept comment \"ICMP rate limited\""
 51"add rule inet filter input meta l4proto ipv6-icmp icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, mld-listener-query, mld-listener-report, mld-listener-reduction, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, mld2-listener-report } accept comment \"Necessary IPv6 ICMP\""
 52
 53# UDP traceroute
 54"add rule inet filter input_wan udp dport 33434-33534 reject comment \"Allow UDP traceroute\""
 55
 56# LAN/WAN
 57"add rule inet filter input ip saddr @lan4 jump input_lan comment \"LAN IPv4 processing\""
 58"add rule inet filter input ip6 saddr @lan6 jump input_lan comment \"LAN IPv6 processing\""
 59"add rule inet filter input ip saddr != @lan4 jump input_wan comment \"WAN IPv4 processing\""
 60"add rule inet filter input ip6 saddr != @lan6 jump input_wan comment \"WAN IPv6 processing\""
 61"add rule inet filter input jump log_drop comment \"Default drop\""
 62
 63# =====================
 64#       INPUT LAN
 65# =====================
 66# Allow all
 67"add rule inet filter input_lan meta l4proto { tcp, udp } accept comment \"Allow all TCP/UDP from LAN\""
 68# Or allow selected and drop others
 69# "add rule inet filter input_lan tcp dport { 80, 443 } accept comment \"Allowed TCP ports from LAN\""
 70# "add rule inet filter input_lan udp dport { 53, 123 } accept comment \"Allowed UDP ports from LAN\""
 71# "add rule inet filter input_lan ct state new jump log_drop comment \"Drop all from LAN with log\""
 72
 73# =====================
 74#       INPUT WAN
 75# =====================
 76"add rule inet filter input_wan tcp dport 22 accept comment \"Allow SSH from WAN\"" 
 77"add rule inet filter input_wan tcp dport { 80, 443 } accept comment \"Allowed TCP ports from WAN\""
 78"add rule inet filter input_wan udp dport { 53, 123 } accept comment \"Allowed UDP ports from WAN\""
 79"add rule inet filter input_wan iifname \"eth0\" tcp dport 443 accept comment \"DNAT: 443->43443\""
 80"add rule inet filter input_wan iifname \"eth0\" ct status dnat tcp dport 43443 accept comment \"DNAT: 443->43443\""
 81"add rule inet filter input_wan ct state new jump log_drop comment \"Drop all from WAN\""
 82
 83# =====================
 84#        FORWARD
 85# =====================
 86"add rule inet filter forward ct state established,related accept"
 87"add rule inet filter forward iifname \"cni*\" accept comment \"Allow K8s forward in\""
 88"add rule inet filter forward oifname \"cni*\" accept comment \"Allow K8s forward out\""
 89"add rule inet filter forward iifname \"flannel.*\" accept comment \"Allow K8s forward in\""
 90"add rule inet filter forward oifname \"flannel.*\" accept comment \"Allow K8s forward out\""
 91"add rule inet filter forward iifname \"vxlan.calico\" accept comment \"Allow K8s forward in\""
 92"add rule inet filter forward oifname \"vxlan.calico\" accept comment \"Allow K8s forward out\""
 93"add rule inet filter forward iifname \"br-*\" accept comment \"Allow Docker forward in\""
 94"add rule inet filter forward oifname \"br-*\" accept comment \"Allow Docker forward out\""
 95"add rule inet filter forward iifname \"virbr*\" accept comment \"Allow VMs forward in\""
 96"add rule inet filter forward oifname \"virbr*\" accept comment \"Allow VMs forward out\""
 97"add rule inet filter forward iifname \"tun*\" accept comment \"Allow OC forward in\""
 98"add rule inet filter forward oifname \"tun*\" accept comment \"Allow OC forward out\""
 99"add rule inet filter forward iifname \"wg*\" accept comment \"Allow WG forward in\""
100"add rule inet filter forward oifname \"wg*\" accept comment \"Allow WG forward out\""
101"add rule inet filter forward ct state new jump log_drop comment \"Drop all forward\""
102
103# =====================
104#      LOG & DROP
105# =====================
106"add rule inet filter log_drop limit rate 5/second log prefix \"NFT-DROP: \" flags all counter comment \"Drop logging\""
107# "add rule inet filter input meta l4proto tcp reject with tcp reset comment \"Reject TCP\""
108# "add rule inet filter input meta l4proto udp reject comment \"Reject UDP\""
109# "add rule inet filter input counter reject with icmpx type port-unreachable comment \"Reject other protocols\""
110# "add rule inet filter input pkttype host limit rate 5/second counter reject with icmpx type admin-prohibited comment \"Protection from port scanning\""
111"add rule inet filter log_drop drop comment \"Drop all\""
112
113# =====================
114#         NAT
115# =====================
116"add rule inet nat prerouting iifname \"eth0\" tcp dport 443 redirect to 43443 comment \"DNAT: 443->43443\""
117# "add rule inet nat prerouting iifname \"eth0\" tcp dport 443 dnat to :43443 comment \"DNAT:  443->43443 \""
118# "add rule inet nat postrouting oifname != lo masquerade comment \"SNAT: NAT processing for all\""
119# "add rule inet nat postrouting oifname \"eth0\" masquerade comment \"SNAT: NAT procesing for eth0\""
120"add rule inet nat postrouting oifname \"tun*\" masquerade comment \"SNAT: NAT procesing for VPN\""
121)
122
123# ========== END OF USER CONFIGURATION SECTION ==========
124# =======================================================
125
126SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd -P)
127SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"
128SCRIPT_TMP=$(mktemp "${SCRIPT_DIR}"/"${SCRIPT_NAME}"_XXXXXXXX)
129
130cleanup() {
131    trap - SIGINT SIGTERM SIGHUP SIGQUIT ERR EXIT
132
133    rm -f "$SCRIPT_TMP"
134}
135
136trap cleanup SIGINT SIGTERM SIGHUP SIGQUIT ERR EXIT
137
138
139usage() {
140    cat <<EOF
141Usage: $SCRIPT_NAME OPTIONS
142
143Description:
144  Script to configure the firewall via nftables.
145
146Options:
147  -h, --help            Show this help message and exit
148  -a, --apply           *Apply new rules from this script
149  -s, --save            *Save current ruleset to /etc/nftables.conf
150  -b, --backup          *Create a backup of /etc/nftables.conf
151  -c, --check           *Dry-run mode (check syntax without applying changes)
152
153Note:
154  One of the * options is required.
155
156Examples:
157  nftables.sh -a
158  nftables.sh --check --apply
159  nftables.sh --apply --save --backup
160EOF
161}
162
163
164parse_params() {
165    APPLY=0 SAVE=0 BACKUP=0 CHECK=0
166
167    while [[ $# -gt 0 ]]; do
168      case $1 in
169        -h|--help) usage; exit 0 ;;
170        -a|--apply) APPLY=1 ;;
171        -s|--save) SAVE=1 ;;
172        -b|--backup) BACKUP=1 ;;
173        -c|--check) CHECK=1 ;;
174        *) usage; exit 1 ;;
175      esac
176      shift
177    done
178
179    (( APPLY || SAVE || BACKUP || CHECK )) || { usage; exit 1; }
180
181    # echo "apply=$APPLY save=$SAVE backup=$BACKUP"
182}
183
184
185save_current_rules() {
186    echo -e "#!${NFT} -f\n\nflush ruleset\n\n$($NFT -s list table inet filter)" > "$NFT_CONFIG"
187    chmod 644 "$NFT_CONFIG"
188	echo "Rules successfully saved at $NFT_CONFIG"
189}
190
191
192backup_current_config() {
193    local datetime
194    datetime="$(date +%Y-%m-%d_%H-%M-%S)"
195
196	if [[ -f "$NFT_CONFIG" ]]; then
197		cp "${NFT_CONFIG}"{,_"${datetime}"}
198		echo "Backup created: ${NFT_CONFIG}_${datetime}"
199	fi
200}
201
202
203# =====================
204#   Main script flow
205# =====================
206parse_params "$@"
207
208# Check for root privileges
209if [[ $EUID -ne 0 ]]; then
210    echo "Please run as root"
211    exit 1
212fi
213
214if (( BACKUP )); then backup_current_config; fi
215
216if (( CHECK )); then
217    for rule in "${NFT_RULES[@]}"; do
218        echo "$rule" >> "$SCRIPT_TMP"
219    done
220
221    "$NFT" -c -f "$SCRIPT_TMP"|| { echo "Syntax - error"; exit 1; }
222    cat "$SCRIPT_TMP"
223    echo -e "-----------\nSyntax - ok"
224fi
225
226if (( APPLY )); then
227    for rule in "${NFT_RULES[@]}"; do
228        echo "$rule" >> "$SCRIPT_TMP"
229    done
230
231    if ! (( CHECK )); then
232        "$NFT" -c -f "$SCRIPT_TMP" || { echo "Syntax - error"; exit 1; }
233    fi
234
235    "$NFT" -f "$SCRIPT_TMP" && echo "Rules applied"
236fi
237
238if (( SAVE )); then save_current_rules; fi
Нажмите, чтобы развернуть и увидеть больше

Разбор правил по порядку:

Таблицы и наборы (sets)

BASH
add table inet filter
Нажмите, чтобы развернуть и увидеть больше

Создает таблицу filter для фильтрации пакетов. Имя таблицы произвольное.

BASH
flush table inet filter
Нажмите, чтобы развернуть и увидеть больше

Очищает таблицу filter перед добавлением новых правил.

BASH
add table inet nat
Нажмите, чтобы развернуть и увидеть больше

Создает таблицу nat для NAT-трансляции.

BASH
flush table inet nat
Нажмите, чтобы развернуть и увидеть больше

Очищает таблицу nat.

BASH
add set inet filter lan4 { type ipv4_addr; flags interval; elements = { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16 } }
Нажмите, чтобы развернуть и увидеть больше

Создает набор IPv4-сетей LAN.

BASH
add set inet filter lan6 { type ipv6_addr; flags interval; elements = { fd00::/8, fe80::/10 } }
Нажмите, чтобы развернуть и увидеть больше

Создает набор IPv6-сетей LAN.

BASH
add set inet filter trusted { type ipv4_addr; elements = { 123.34.56.78 } }
Нажмите, чтобы развернуть и увидеть больше

Определяет список доверенных IP-адресов.

Цепочки (chains)

BASH
add chain inet filter input { type filter hook input priority 0; policy drop; }
Нажмите, чтобы развернуть и увидеть больше

Создает цепочку input с политикой DROP по умолчанию.

BASH
add chain inet filter forward { type filter hook forward priority 50; policy drop; }
Нажмите, чтобы развернуть и увидеть больше

Создает цепочку forward с политикой DROP.

BASH
add chain inet filter output { type filter hook output priority -200; policy accept; }
Нажмите, чтобы развернуть и увидеть больше

Создает цепочку output, разрешающую исходящие соединения.

BASH
add chain inet nat prerouting { type nat hook prerouting priority dstnat; policy accept; }
Нажмите, чтобы развернуть и увидеть больше

Создает цепочку prerouting для входящего NAT (DNAT).

BASH
add chain inet nat postrouting { type nat hook postrouting priority srcnat; policy accept; }
Нажмите, чтобы развернуть и увидеть больше

Создает цепочку postrouting для исходящего NAT (SNAT).

BASH
add chain inet filter input_wan
add chain inet filter input_lan
add chain inet filter log_drop
Нажмите, чтобы развернуть и увидеть больше

Создает пользовательские цепочки для разделения трафика и логирования.

Базовые правила INPUT

BASH
add rule inet filter input ct state invalid drop
Нажмите, чтобы развернуть и увидеть больше

Отбрасывает некорректные соединения.

BASH
add rule inet filter input ct state { established, related } accept
Нажмите, чтобы развернуть и увидеть больше

Разрешает установленные и связанные соединения (механизм contrack).

BASH
add rule inet filter input iif lo accept
Нажмите, чтобы развернуть и увидеть больше

Разрешает локальный трафик (loopback).

BASH
add rule inet filter input ip saddr @trusted accept
Нажмите, чтобы развернуть и увидеть больше

Сразу разрешает доступ доверенным IP.

ICMP

BASH
add rule inet filter input meta l4proto icmp icmp type { echo-request, destination-unreachable, time-exceeded } limit rate 10/second accept
Нажмите, чтобы развернуть и увидеть больше

Разрешает основные типы ICMP IPv4 с ограничением по скорости (небольшая защита от флуда и DoS атак).

BASH
add rule inet filter input meta l4proto ipv6-icmp icmpv6 type { ... } accept
Нажмите, чтобы развернуть и увидеть больше

Разрешает необходимые ICMPv6-пакеты (ND, MLD и др.).

За блок про ICMP спасибо пользователю Comm из Вороньего чата.

Traceroute

BASH
add rule inet filter input_wan udp dport 33434-33534 reject
Нажмите, чтобы развернуть и увидеть больше

Разрешает UDP-traceroute (через reject уведомления).

Разделение LAN/WAN

BASH
add rule inet filter input ip saddr @lan4 jump input_lan
add rule inet filter input ip6 saddr @lan6 jump input_lan
Нажмите, чтобы развернуть и увидеть больше

Передает трафик из LAN в цепочку input_lan.

BASH
add rule inet filter input ip saddr != @lan4 jump input_wan
add rule inet filter input ip6 saddr != @lan6 jump input_wan
Нажмите, чтобы развернуть и увидеть больше

Передает внешний трафик в цепочку input_wan.

BASH
add rule inet filter input jump log_drop
Нажмите, чтобы развернуть и увидеть больше

Передает оставшийся трафик на логирование и блокировку.

Цепочка INPUT LAN

BASH
add rule inet filter input_lan meta l4proto { tcp, udp } accept
Нажмите, чтобы развернуть и увидеть больше

Разрешает весь TCP/UDP-трафик из LAN.

Цепочка INPUT WAN

BASH
add rule inet filter input_wan tcp dport 22 limit rate 20/minute accept
Нажмите, чтобы развернуть и увидеть больше

Разрешает SSH.

BASH
add rule inet filter input_wan tcp dport { 80, 443 } accept
Нажмите, чтобы развернуть и увидеть больше

Разрешает HTTP(S).

BASH
add rule inet filter input_wan udp dport { 53, 123 } accept
Нажмите, чтобы развернуть и увидеть больше

Разрешает DNS и NTP.

BASH
add rule inet filter input_wan iifname "eth0" tcp dport 443 accept
add rule inet filter input_wan iifname "eth0" ct status dnat tcp dport 43443 accept
Нажмите, чтобы развернуть и увидеть больше

Обрабатывает DNAT-переадресацию порта 443 на порт 43443 - в хуке input. Для корректной работы требует также правило в хуке forward (см. ниже).

BASH
add rule inet filter input_wan ct state new jump logdrop
Нажмите, чтобы развернуть и увидеть больше

Блокирует новые неразрешенные соединения из WAN.

Цепочка FORWARD

BASH
add rule inet filter forward ct state established,related accept
Нажмите, чтобы развернуть и увидеть больше

Разрешает установленные соединения.

BASH
add rule inet filter forward iifname "cni*" accept
add rule inet filter forward oifname "cni*" accept
Нажмите, чтобы развернуть и увидеть больше

Разрешает маршрутизацию для Kubernetes.

BASH
add rule inet filter forward iifname "flannel.*" accept
add rule inet filter forward oifname "flannel.*" accept
Нажмите, чтобы развернуть и увидеть больше

Разрешает трафик через Flannel.

BASH
add rule inet filter forward iifname "vxlan.calico" accept
add rule inet filter forward oifname "vxlan.calico" accept
Нажмите, чтобы развернуть и увидеть больше

Разрешает трафик Calico.

BASH
add rule inet filter forward iifname "br-*" accept
add rule inet filter forward oifname "br-*" accept
Нажмите, чтобы развернуть и увидеть больше

Разрешает Docker-сети.

BASH
add rule inet filter forward iifname "virbr*" accept
add rule inet filter forward oifname "virbr*" accept
Нажмите, чтобы развернуть и увидеть больше

Разрешает трафик виртуальных машин.

BASH
add rule inet filter forward iifname "tun*" accept
add rule inet filter forward oifname "tun*" accept

add rule inet filter forward iifname "wg*" accept
add rule inet filter forward oifname "wg*" accept
Нажмите, чтобы развернуть и увидеть больше

Разрешает трафик VPN (OpenConnect, WireGuard).

BASH
add rule inet filter forward ct state new jump log_drop
Нажмите, чтобы развернуть и увидеть больше

Блокирует неизвестный форвард.

Логирование и дроп

BASH
add rule inet filter log_drop limit rate 5/second log prefix "NFT-DROP:" flags all counter
Нажмите, чтобы развернуть и увидеть больше

Включает логирование отброшенных пакетов с префиксом “NFT-DROP:”, ограничение скорости логирования до 5 пакетов в секунду и подсчет количества отброшенных пакетов. flags all указывает логировать все флаги пакетов.

BASH
add rule inet filter log_drop drop
Нажмите, чтобы развернуть и увидеть больше

Окончательно блокирует весь оставшийся трафик.

NAT

BASH
add rule inet nat prerouting iifname "eth0" tcp dport 443 redirect to 43443
Нажмите, чтобы развернуть и увидеть больше

Переадресует порт 443 на 43443 (DNAT) - для работы требует разрешающее правило в хуке input (см. выше).

BASH
add rule inet nat postrouting oifname "tun*" masquerade
Нажмите, чтобы развернуть и увидеть больше

Выполняет SNAT (маскарадинг) для VPN-интерфейсов.

Короткое резюме правил:

  1. Скрипт создает изолированные таблицы filter и nat, полностью сбрасывая старые правила;
  2. Входящие соединения разрешаются только из доверенных сетей, LAN или для конкретных портов WAN;
  3. Применяется строгая политика по умолчанию: drop для входящих и форвардных цепочек;
  4. Реализовано ограничение для ICMP;
  5. Разрешается маршрутизация для Docker, KVM, Kubernetes и VPN (WireGuard, OpenConnect);
  6. Все неразрешенные пакеты логируются и блокируются;
  7. Настроен NAT для переадресации и маскарадинга VPN (интерфейсы tun*).

Демонстрация работы скрипта nftables.sh

Делаем скрипт исполняемым:

BASH
chmod +x ./nftables.sh

./nftables.sh --help
Нажмите, чтобы развернуть и увидеть больше

Проверяем синтаксис правил:

BASH
sudo ./nftables.sh -c
Нажмите, чтобы развернуть и увидеть больше

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

BASH
sudo ./nftables.sh -b
Нажмите, чтобы развернуть и увидеть больше

Применяем правила:

BASH
sudo ./nftables.sh -a
Нажмите, чтобы развернуть и увидеть больше

Смотрим список всех правил nftables:

BASH
sudo nft list ruleset
Нажмите, чтобы развернуть и увидеть больше

Проверяем текущую сессию, не дропнуло ли наш SSH:

BASH
w

date
Нажмите, чтобы развернуть и увидеть больше

Теперь открываем соседнюю вкладку и проверяем, что можем подключиться к серверу:

Пока всё ок. Проверяем работу фаервола дальше.

ICMP - выполняем с клиентской машины:

BASH
ping -c3 nftables.r4ven.me
Нажмите, чтобы развернуть и увидеть больше

Трассировка (UDP):

BASH
traceroute nftables.r4ven.me
Нажмите, чтобы развернуть и увидеть больше

Всё проходит.

Теперь проверим блокировку ICMP свише 10 пакетов в секунду.

В наборе правил есть одно для логирования: оно пишет события в системный журнал.

На сервере смотрим drop журнал с фильтрацией по ICMP:

BASH
sudo journalctl -k -f -g 'NFT-DROP' -g 'ICMP'
Нажмите, чтобы развернуть и увидеть больше

На клиенте запускаем 20 паралельных ping:

BASH
seq 20 | xargs -n1 -P20 sh -c 'ping -c5 nftables.r4ven.me'
Нажмите, чтобы развернуть и увидеть больше

В логе будут видны сообщения о дропах:

А у клиента будут видны потери пакетов:

Для следующих тестов на клиенте и на сервере установим утилиту для работы с сетевыми соединениями - socat:

BASH
sudo apt update && sudo apt install -y socat
Нажмите, чтобы развернуть и увидеть больше

Теперь с её помощью на хосте с настроенным фаерволом запустим TCP сервер на 443 порту, доступ к которому мы разрешили при настройке фаервола.

На сервере запускаем процесс, который при получении TCP пакета отправит pong:

BASH
sudo socat -v TCP-LISTEN:443,fork SYSTEM:"echo 'pong'"
Нажмите, чтобы развернуть и увидеть больше

С клиента отправляем тестовый запрос:

BASH
echo "ping" | socat - TCP:nftables.r4ven.me:443
Нажмите, чтобы развернуть и увидеть больше

Работает. Теперь проверим какой нибудь не открытый порт.

На сервере:

BASH
sudo socat -v TCP-LISTEN:444,fork SYSTEM:"echo 'pong'"
Нажмите, чтобы развернуть и увидеть больше

На клиенте:

BASH
echo "ping" | socat - TCP:nftables.r4ven.me:444
Нажмите, чтобы развернуть и увидеть больше

Как видим, соединение не устанавливается.

Смотрим дроп лог nftables:

BASH
sudo journalctl -k -f -g 'NFT-DROP' -g '12.34.56.78'
Нажмите, чтобы развернуть и увидеть больше

Где 12.34.56.78 - ваш внешний IP из-под которого вы выполняете клиентское подключение.

Отлично, фаервол работает ожидаемо😅.

Теперь проверим работу перенаправления портов. В наших правилах настраивался редирект 443 порта в хуке prerouting на порт 43443 (и разрешающее правило в хуке input).

На сервере слушаем 43443 TCP порт:

BASH
sudo socat -v TCP-LISTEN:43443,fork SYSTEM:"echo 'pong'"
Нажмите, чтобы развернуть и увидеть больше

На клиенте отправляем запрос на 443 TCP порт:

BASH
echo "ping" | socat - TCP:nftables.r4ven.me:443
Нажмите, чтобы развернуть и увидеть больше

Работет!

Подобным образом проверим работу UDP. В наших правилах мы открывали 123 порт.

Выполняем на сервере:

BASH
sudo socat -v UDP-RECVFROM:123,fork SYSTEM:"echo 'pong'"
Нажмите, чтобы развернуть и увидеть больше

А на клиенте:

BASH
echo "ping" | socat - UDP:nftables.r4ven.me:123
Нажмите, чтобы развернуть и увидеть больше

Всё хорошо. Также проверим drop неразрешённых UDP:

На сервере:

BASH
sudo socat -v UDP-RECVFROM:12345,fork SYSTEM:"echo 'pong'"
Нажмите, чтобы развернуть и увидеть больше

На клиенте:

BASH
echo "ping" | socat - UDP:nftables.r4ven.me:12345
Нажмите, чтобы развернуть и увидеть больше

Пусто. Зато при просмотре журнала:

BASH
sudo journalctl -k -f -g 'NFT-DROP' -g '12345'
Нажмите, чтобы развернуть и увидеть больше

Видим соответствующие записи:

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

BASH
sudo ./nftables.sh -s

cat /etc/nftables.conf
Нажмите, чтобы развернуть и увидеть больше

Настройка автостарта nftables при запуске OS

В состав пакета nftables идёт юнит сервиса Systemd, который по сути запускает команду с применение правил из основного файла конфигурации: nft -f /etc/nftables.conf (в deb-based дистрибутивах, в rpm-based - /etc/sysconfig/nftables.conf).

Включаем автозапуск:

BASH
sudo systemctl enable --now nftables

sudo systemctl status nftables

sudo nft list ruleset
Нажмите, чтобы развернуть и увидеть больше

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

BASH
sudo reboot
Нажмите, чтобы развернуть и увидеть больше

Полезные команды nftables

BASH
# Показать весь набор правил
sudo nft list ruleset

# Показать все таблицы
sudo nft list tables

# Показать содержимое таблицы filter
sudo nft list table inet filter

# Показать цепочку input таблицы filter
sudo nft list chain inet filter input

# Показать правила с handle - порядковые номера правил (для замены/удаления)
sudo nft -a list ruleset

# Очистить все правила
sudo nft flush ruleset

# Очистить только таблицу filter (ОСТОРОЖНО! Не удаляет политики по умолчанию)
sudo nft flush table inet filter

# Удалить таблицу filter
sudo nft delete table inet filter

# Добавить правило (пример)
sudo nft add rule inet filter input tcp dport 22 accept

# Показать цепочку с handle
sudo nft -a list chain inet filter input

# Удалить правило по handle
sudo nft delete rule inet filter input handle 15

# Заменить правило по handle
sudo nft replace rule inet filter input handle 15 tcp dport 2222 accept

# Загрузить конфиг из файла
sudo nft -f /etc/nftables.conf

# Проверить синтаксис без применения
sudo nft -c -f /etc/nftables.conf

# Сохранить текущие правила в конфиг
sudo nft -s list ruleset > /etc/nftables.conf

# Отслеживать изменения в реальном времени
sudo nft monitor

# Трассировка прохождения пакетов
sudo nft monitor trace

# Подробный отладочный вывод при загрузке
sudo nft --debug=netlink -f myrules.nft

# Просмотре журнала nftables
sudo journalctl -k -f -g 'NFT-DROP'
Нажмите, чтобы развернуть и увидеть больше

Послесловие

Данный скрипт я протестировал на нескольких локальных и публичных (с внешним IP) серверах. Пока полёт нормальный, но возможно где-то могут быть недочёты (я не сетевик😑). Еще раз напомню, что все действия вы делаете на свой страх и риск. Если у вас остались вопросы по теме, то можете задать их в нашем Вороньем чате. В свободное время я постараюсь вам ответить👨‍💻.

Спасибо, что читаете!

Используемые материалы

Авторские права

Автор: Иван Чёрный

Ссылка: https://r4ven.me/networking/bazovaya-nastroyka-nftables-v-linux-s-pomoshchyu-bash-skripta/

Лицензия: CC BY-NC-SA 4.0

Использование материалов блога разрешается при условии: указания авторства/источника, некоммерческого использования и сохранения лицензии.

Начать поиск

Введите ключевые слова для поиска статей

↑↓
ESC
⌘K Горячая клавиша