Firewalls — stateful vs stateless, iptables/nftables, security groups
Firewall (сетевой экран) — это компонент, который разрешает или запрещает сетевые пакеты на основе правил. Звучит просто, но за этим определением скрывается целый класс систем разной сложности: от примитивного «пропускать только порт 80» до глубокого анализа трафика на уровне приложения с распознаванием SQL injection и XSS.
В этом уроке разберём, какие бывают firewall, в чём ключевая разница между stateless и stateful, как читать и писать правила iptables/nftables (стандарт Linux), и что такое security groups в облаке. Это базовый инструмент, который вы будете применять во всех сетевых задачах — от защиты домашнего роутера до конфигурации production VPC в AWS.
Где живёт firewall и что фильтрует
Firewall стоит на «границе» — между двумя сегментами сети с разным уровнем доверия. Типичные места:
- Edge firewall — между интернетом и вашим datacenter / VPC. Защищает всю инфраструктуру.
- Host firewall — на самом сервере (iptables, ufw, Windows Defender Firewall). Защищает один хост.
- App firewall (WAF) — перед веб-приложением, разбирает HTTP-запросы. Об этом отдельно.
- Microsegmentation — внутри datacenter, между группами серверов. Service mesh.
Принцип defense in depth: даже если один firewall обходится, есть следующий. Compromise одного web-сервера не должен дать атакующему прямой доступ к БД.
Stateless firewall — решение по каждому пакету
Простейший firewall смотрит на каждый пакет независимо и применяет правила: if src_ip == X and dst_port == Y -> allow/deny. Не помнит ничего о предыдущих пакетах. Это stateless (packet filter).
Пример правила: «пропускать TCP-пакеты с dst_port=80, всё остальное — блокировать».
Проблема: TCP — двусторонний. Запрос идёт от клиента к серверу, ответ обратно. Stateless firewall на стороне сервера, разрешающий пакеты на port 80, должен также разрешать исходящий трафик с src_port=80 — иначе ответы не дойдут до клиента.
# Простой stateless подход в iptables:
# Разрешить входящие на 80:
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
# Разрешить исходящие с 80 (ответы):
iptables -A OUTPUT -p tcp --sport 80 -j ACCEPT
# Проблема: это разрешит любому отправлять трафик с src_port=80 на наш сервер.
# Атакующий, подделав src_port=80, сможет лезть куда угодно
Stateless firewall:
- Плюсы: простой, быстрый, не требует memory для соединений.
- Минусы: грубый, требует осторожной симметричной настройки, плохо защищает от подделок source port.
Stateless подход используется в железных firewalls на гигабитных скоростях, в простых ACL на маршрутизаторах. Для большинства задач сегодня выбирают stateful.
Stateful firewall — отслеживает соединения
Stateful firewall помнит state каждого TCP-соединения. Когда видит SYN — регистрирует «новое соединение от X:p1 к Y:p2». Все последующие пакеты этого соединения автоматически разрешены, потому что они часть установленной коммуникации.
В Linux это conntrack — модуль ядра, ведущий таблицу всех соединений. Размер таблицы можно посмотреть:
# Текущее число соединений:
sudo conntrack -L | wc -l
# Лимит (можно увеличить через sysctl):
sysctl net.netfilter.nf_conntrack_max
# Hashsize -- параметр производительности:
sysctl net.netfilter.nf_conntrack_buckets
Преимущества stateful:
- Простые правила. Не нужно описывать обратное направление вручную.
- Защита от spoofing. Пакет с src_port=80 на наш сервер, не являющийся ответом, отбрасывается.
- Возможность работы с UDP в state-aware режиме (пусть UDP без connection, но conntrack отслеживает «pseudo-connection» по timeout-ам).
Минусы:
- Память. Каждое соединение — запись в таблице. Под нагрузкой (10M concurrent) нужно тюнить ядро.
- Сложнее производительности. Каждый пакет ищется в conntrack table.
В современном Linux iptables/nftables по умолчанию stateful через модуль conntrack. Stateless подход редок и применяется только в специальных случаях.
iptables — классика, до сих пор стандарт
iptables — интерфейс к netfilter (фреймворк фильтрации в Linux kernel). Создан в начале 2000-х, до сих пор повсеместно. Правила организованы в цепочки (chains) внутри таблиц (tables).
Главные tables:
- filter (default) — разрешить/запретить пакеты.
- nat — NAT, port forwarding, redirect.
- mangle — модификация заголовков (QoS, marking).
- raw — работа до conntrack.
Главные chains в filter:
- INPUT — пакеты, адресованные хосту.
- OUTPUT — пакеты, исходящие от хоста.
- FORWARD — пакеты, проходящие через хост (если он роутер).
# Посмотреть текущие правила:
sudo iptables -L -v -n
# Пример: разрешить SSH и HTTP, остальное запретить
# (приостановите если хотите воспроизвести -- может отрубить вас от SSH!)
# Политика по умолчанию -- DROP всё, что не разрешено:
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Разрешить loopback:
iptables -A INPUT -i lo -j ACCEPT
# Разрешить ESTABLISHED и RELATED (stateful magic):
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Разрешить SSH (port 22) из любого источника:
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Разрешить HTTP (80) и HTTPS (443):
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# Разрешить ICMP (ping) -- удобно для отладки:
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
Ключевые моменты:
-A INPUT— append правило в chain INPUT.-p tcp --dport 22— match для TCP-пакетов на dst port 22.-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT— разрешить пакеты, относящиеся к установленным соединениям. Это критично! Без этого правила ваши собственные ответы будут блокироваться.-P INPUT DROP— политика по умолчанию = DROP.
ВНИМАНИЕ: при настройке firewall удалённо через SSH не делайте ‘iptables -P INPUT DROP’ до того, как добавите правило для SSH! Иначе отрубите себя от сервера. Используйте ‘iptables -F’ (flush) перед политикой DROP только локально, или применяйте правила через atomic-режим (iptables-apply).
Правила сохраняются в memory до перезагрузки. Чтобы пережили reboot:
# Debian/Ubuntu:
sudo apt install iptables-persistent
sudo netfilter-persistent save
# RHEL/CentOS:
sudo iptables-save > /etc/sysconfig/iptables
sudo systemctl enable iptables
nftables — современная замена iptables
nftables — новая система фильтрации в Linux, призванная заменить iptables. Создана теми же разработчиками (netfilter team), но лучше архитектурно: единый язык для IPv4/IPv6/ARP/bridge, более выразительный синтаксис, лучшая производительность.
В Debian 11+ и RHEL 8+ nftables — default. iptables до сих пор работает, но как обёртка над nftables (iptables-nft).
Пример конфига nftables (/etc/nftables.conf):
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# Разрешить loopback
iif lo accept
# Разрешить established/related
ct state established,related accept
# SSH, HTTP, HTTPS
tcp dport { 22, 80, 443 } accept
# ICMP
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
Преимущества:
- Sets и maps. Группы IP, портов, можно делать словари (например,
tcp dport map { 80: jump web, 443: jump web, 22: jump ssh }). - IPv4/IPv6 в одной таблице. Раньше нужны были отдельные iptables и ip6tables.
- Atomic updates. Применение всего правил-файла транзакционно.
# Загрузить:
sudo nft -f /etc/nftables.conf
# Посмотреть текущие правила:
sudo nft list ruleset
# Добавить правило в существующую цепочку:
sudo nft add rule inet filter input tcp dport 8080 accept
ufw — удобный wrapper для домашнего использования
Для тех, кому iptables/nftables кажется слишком многословным, есть ufw (Uncomplicated Firewall). Это wrapper, генерирующий iptables-правила из простых команд:
# Включить и поставить default deny:
sudo ufw enable
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Разрешить порты по именам или номерам:
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Разрешить с конкретного IP:
sudo ufw allow from 10.0.0.0/24 to any port 5432
# Посмотреть правила:
sudo ufw status verbose
ufw — хороший выбор для домашнего сервера или dev-машины. В production обычно идёт прямо iptables/nftables, потому что нужны более сложные правила (rate limiting, conntrack tuning, marking).
Security groups в облаках
В AWS, GCP, Azure firewall выглядит иначе — через Security Groups (или Network Security Groups в Azure). Это виртуальные firewall, применяемые к VM или ENI (network interface).
Особенности AWS Security Groups:
- Stateful по умолчанию. Не нужно описывать обратное направление — ответы на разрешённые запросы автоматически проходят.
- Только allow rules. Нет понятия «deny — всё, что не разрешено явно, запрещено».
- Применяются к instances. Не к подсетям (для подсетей есть NACL — Network ACL).
- Можно ссылаться на другие SG. Правило «разрешить от security-group sg-web», вместо хардкода IP.
SG: web-tier
Inbound:
HTTPS (443) from 0.0.0.0/0
HTTP (80) from 0.0.0.0/0
SSH (22) from 1.2.3.4/32 # только с моего bastion IP
Outbound: всё разрешено
SG: db-tier
Inbound:
PostgreSQL (5432) from sg-web # только от web-tier serversов
Outbound: всё разрешено
Это паттерн micro-segmentation: каждый tier разрешает доступ только нужным компонентам. Если кто-то скомпрометировал web-server, он не сможет лезть в БД с других IP — только если он в SG web-tier.
GCP — похожая концепция, называется Firewall Rules. Azure — Network Security Groups. Все три stateful, allow-only.
NAT и firewall
Многие host firewalls умеют делать NAT (Network Address Translation). Это базовая функциональность домашнего роутера:
# Включить ip forwarding:
sudo sysctl -w net.ipv4.ip_forward=1
# В iptables NAT table, masquerade -- замена source IP исходящих пакетов на IP роутера:
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# Это делает интернет доступным для всех клиентов в LAN через один внешний IP
Port forwarding:
# Перенаправить входящие на порту 80 внешнего IP на внутренний 192.168.1.10:8080:
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.10:8080
# Не забыть разрешить forwarding:
iptables -A FORWARD -p tcp -d 192.168.1.10 --dport 8080 -j ACCEPT
В облаке port forwarding обычно делается через load balancer (Network Load Balancer), а не iptables.
Реальный production-firewall
В production firewall редко настраивается вручную. Обычно используется один из подходов:
- Configuration management (Ansible, Puppet, Chef, SaltStack) — описание правил декларативно, применение синхронно на все хосты.
- Cloud provider — Security Groups в Terraform/CloudFormation. Декларативное описание, ревью через PR.
- Service mesh (Istio, Linkerd) — firewall на уровне Kubernetes pods через сетевые policies.
Пример Terraform для AWS SG:
resource "aws_security_group" "web" {
name = "web-tier"
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["1.2.3.4/32"] # только bastion
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
Преимущества декларативного подхода:
- Version control. Изменения в git с историей.
- Code review. Каждое правило проходит через PR.
- Reproducibility. Можно поднять идентичную инфраструктуру в новом регионе.
- Audit. Видно, кто, когда и зачем менял правило.
Попробуй сам
Соберём минимальный firewall на iptables и проверим, что он работает. Создайте VM в Vagrant/VirtualBox или используйте dev-сервер (НЕ production, чтобы не отрубить себя).
# 1. Сохранить текущие правила (на всякий случай):
sudo iptables-save > /tmp/iptables-backup.txt
# 2. Установить чистые правила: default DROP + разрешённые порты
sudo iptables -F # flush существующие
sudo iptables -P INPUT DROP
sudo iptables -P OUTPUT ACCEPT
sudo iptables -P FORWARD DROP
# Loopback всегда:
sudo iptables -A INPUT -i lo -j ACCEPT
# Stateful: разрешить ответы на наши исходящие:
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Разрешить SSH (КРИТИЧНО если работаете удалённо):
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Разрешить HTTP/HTTPS:
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# Разрешить ICMP echo (ping):
sudo iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
# 3. Посмотрите результат:
sudo iptables -L -v -n
# 4. Проверьте, что вы доступны по http (если есть сервер):
curl -v http://your-server-ip
# 5. Попробуйте подключиться на закрытый порт (например 12345):
nc -zv your-server-ip 12345
# должно быть connection refused или timeout
# 6. Восстановить исходное (когда поэкспериментировали):
sudo iptables-restore < /tmp/iptables-backup.txt
Посмотрите conntrack table:
# Сколько активных соединений:
sudo conntrack -L 2>/dev/null | head -20
# Видно: установленные TCP, UDP "соединения", ICMP echo-reply state
# Это и есть state, на котором держится stateful firewall
iptables на практике: полный разбор PREROUTING, FORWARD, POSTROUTING цепочек