Введение
- Файрвол PF в ОС FreeBSD
- Фильтрация трафика PF <- Вы здесь
В прошлой статье мы разобрали, что такое вообще PF, его основные возможности и создали простенький конфиг, использующий правила фильтрации и макросы. Сегодня разберемся с метками и научимся более продвинуто фильтровать трафик по различным условиям. Спасибо пользователям, комментировавшим предыдущую статью, за проявленный интерес.
В этот раз попробуем различные варианты более продвинутой фильтрации:
- Макросы в различных местах конфигурационного файла.
- Списки — набор параметров. PF раскроет его в отдельные правила.
- Правила, ограничивающие количество подключений с одного IP, их частоту.
- Таблицы. Это список IP адресов с которыми может сравнивать правило. Возможные варианты динамического заполнения. И проверку состояния наших таблиц.
Задача будет такая:
- Разрешить доступ с доверенных IP на любой порт
- Создать ban-list с загрузкой из файла
- Разрешить доступ с crm и api нашей компании на порт 8080 и 8443
- Разрешить исходящий доступ по порам 80 и 443 к CRM и API
- Создать простейшую защиту от брут-форса на порту 22 с внесением хостов в бан-лист на час
- Создать простейшую защиту от DDOS и DOS на портах 80 и 443, с внесением хостов в другой бан-лист
Итак, приступим.
Макросы и списки
С макросами всё относительно просто. Это вставка куска строки в нужное (практически любое) место конфигурационного файла. В макросе можно держать имя интерфейса, IP адрес (или адреса), опции tcp, и так далее. IP адреса и имена интерфейса взяты из головы, все совпадения случайны.
# macros section
If="re0" # интерфейс нашего сервера
IfIp="192.169.12.10" # IP нашего сервера
permit_tcp_ports="22,53"
permit_udp_ports="53,123"
web_ports = "http,https"
После этого можно использовать их в правилах подобного вида:
pass in on $IfIp inet proto tcp from any to $IfIp port {$permit_tcp_ports}
После загрузки правила будут выглядеть так:
pfctl -sr
...
pass in on re0 inet proto tcp from any to 192.169.12.10 port = ssh flags S/SA keep state
pass in on re0 inet proto tcp from any to 192.169.12.10 port = domain flags S/SA keep state
...
Как видим, был подставлен интерфейс и порты из макроса. Затем, для каждого порта было создано отдельное правило в соответствии со списком. Были установлены значения по умолчанию. flags это tcp флаги, установленные у пакета для соответствия правилу. S/SA — это пакеты с установленным флагом SYN. То есть, если упростить, первый пакет сессии.
Keep state — означает сохранение соединения в таблице состояний или сессий. Это означает, что только первый пакет из соединения пробежит по правилам файрвола. Все остальные пакеты этого соединения файрволл пропустит при соответствии таблице состояний (state table), не проводя проверок.
Важно для оптимизации конфига помнить опцию quick. Она указывает файрволу прекратить дальнейшую обработку этого пакета, и сразу применить текущее действие. К примеру:
pass in quick on em0 from { 192.168.1.0/24 } to 192.168.1.1
Таблицы
Таблицы — это одно из мощнейших средств PF. После создания в конфиге, с ними можно творить всё, что угодно. Загружать из файла, чистить, очищать от записей, старее заданного срока, ну и, конечно, добавлять и удалять записи. И всё это на лету без перезагрузки конфигурации файрвола.
Таблицы могут быть нескольких видов:
- const — этот флаг указывает на неизменность таблицы. Пользователь не может добавлять и удалять IP "на лету"
- counters — включает ведение статистики по каждому IP в таблице
- persist — указывает ядру хранить таблицу, даже если в ней нет записей
В этой статье будут показаны примеры таблиц без флагов и с флагом persist. Остальные флаги интуитивно поняты и используются куда реже.
Простая таблица:
# администраторы, им разрешено всё
table <admins> { 173.18.12.22, 173.18.12.45 }
Для использования в правиле файрвола:
pass in quick on $If from <admins> to $IfIp
Это правило разрешит всё, что приходит от IP адресов, перечисленных в таблице, на сетевой интерфейс и IP сервера.
Таблица с загрузкой из файла:
# хосты, которые нам просто не нравятся
table <block_list> file "/etc/pf.blocklist.conf"
Используем в правиле:
block in quick on $If from <block_list> to any
Правило заблокирует любой трафик от <simple-ban> на интерфейсе $If
Сама таблица будет иметь следующий вид:
# cat /etc/pf.blocklist.conf
1.1.1.1
111.12.46.0/24
!111.12.46.17
Можно указать знак отрицания для исключения из таблицы. В данном случае будет соответствие IP 1.1.1.1, и сети 111.12.46.0/24, кроме IP 111.12.46.17. Наполнением этого файла можно заниматься вручную. Затем загрузить таблицу из файла снова:
pfctl -t block_list -T replace -f /etc/pf.blocklist.conf
3 addresses added.
В таблицу можно добавлять как IP адреса, так и доменные имена. Доменные имена разрешатся при загрузке. Для обновления адресов можно поставить загрузку таблицы в cron каждый час, к примеру. Однако, если хотя бы один адрес разрешить не удастся, обновление таблицы не удастся:
# cat /etc/pf.blocklist.conf
ya.ru
1.2.3.4
mail.ru
После загрузки можно посмотреть, что содержится в таблице:
# pfctl -t blocklist -T show
1.2.3.4
87.250.250.242
94.100.180.200
94.100.180.201
217.69.139.200
217.69.139.202
2a00:1148:db00:0:b0b0::1
2a02:6b8::2:242
Можно удобным образом добавить/удалить записи:
# pfctl -t blocklist -T add 123.12.34.5
# pfctl -t blocklist -T delete 123.12.34.5
Конечно, эти записи не попадут магически в файл, и после перезагрузки таблицы их не будет.
Важно помнить о возможности проверки соответствия IP таблице. Можно проверить и доменное имя.
# pfctl -t blocklist -T test 1.2.3.4
1/1 addresses match.
# pfctl -t blocklist -T test 1.1.1.1
0/1 addresses match.
# pfctl -t blocklist -T test ya.ru
2/2 addresses match.
# pfctl -t blocklist -T test 1.1.1.1 1.2.3.4
1/2 addresses match.
Задания в cron для обновления:
# cat /etc/crontab
...
0 * * * * root /sbin/pfctl -t block_list -T replace -f /etc/pf.blocklist.conf
...
Теперь добавим возможность ходить на корпоративные ресурсы по портам http(s) и обратно по портам 8080 и 8443. Создаём макросы со списком портов, можно использовать названия протоколов:
permit_corp_ports ="http,https" # порты для доступа к корп. ресурсам
permit_add_tcp_ports = "8080,8443" # дополнительные порты, на которых слушают сервера для внутренних нужд
Таблицу, загружаемую из файла, со списком хостов:
table <corp_res> file "/etc/pf.corpres.conf" # корпоративные ресурсы, доступ по http(s)
Для примера, в таблице будет один хост:
# cat /etc/pf.corpres.conf
example.com
И собственно правила:
pass in quick on $If proto tcp from <corp_res> to $IfIp ports { $permit_add_tcp_ports }
pass out quick on $If proto tcp from $IfIp to <corp_res> port { $permit_corp_ports }
Дополнительные параметры фильтрации
Теперь задача интереснее. Необходимо защитить наш сервер от перебора паролей ssh. Еще одна возможность PF — лимитировать количество соединений, соответствующих правилу, по различным параметрам.
Таблица:
table <block_ssh> persist # Брутфорсеры ssh. если таблица пустая, PF её удалит. Для отмены такого поведения используется ключевое слово persist
persist — держать в памяти, даже если пустая. Если не указать такое поведение, PF удалит пустую таблицу.
И правила:
block in quick on $If proto tcp from <block_ssh> to any port 22
pass in on $If proto tcp to $IfIp port { 22 } keep state (max-src-conn 10, max-src-conn-rate 3/10, overload <block_ssh> flush)
В данном случае max-src-conn 10 — только 10 одновременных соединений с одного адреса, max-src-conn-rate 3/10 — только 3 новых соединения за 10 секунд, overload <block_ssh> — добавлять в таблицу адреса источника нарушителей, flush — сбрасывать все соединения от нарушителя, созданные этим правилом.
По образу и подобию — разрешение пользователю ходить на веб-сервер.
Таблица:
table <block_web> persist # заблокированный доступ до веб
И правила:
# разрешаем входящие соединения
block in quick on $If from <block_web>
pass in on $If proto tcp to $IfIp port { $permit_tcp_ports } synproxy state (max-src-conn 100, max-src-conn-rate 20/1, overload <block_web> flush global)
Параметры этого правила надо очень аккуратно подбирать под ваш сервис. Synproxy — включает проксирование syn запросов, защиту от synflood атак. А flush global означает сброс всех соединений с источника-нарушителя, даже если они не относятся к этому правилу.
Устаревание записей в таблицах заблокированных обеспечиваются командами такого вида:
pfctl -t block_ssh -T expire 3600
Эта команда удалит все записи, старше часа из соответствующей таблицы. Команды поставим в крон:
...
0 * * * * root /sbin/pfctl -t block_ssh -T expire 3600
0 * * * * root /sbin/pfctl -t block_web -T expire 3600
...
Таким образом каждый час таблицы будут вычищаться.
Итоговая конфигурация
# cat /etc/pf.conf
# macros section
If="re0" # интерфейс нашего сервера
IfIp="192.169.12.10" # ip нашего сервера
permit_tcp_ports="22,53"
permit_udp_ports="53,123"
web_ports = "http,https"
permit_corp_ports ="http,https"
permit_add_tcp_ports = "8080,8443"
# table section
# администраторы, им разрешено всё
table <admins> { 192.168.11.0/24, 173.18.12.22, 173.18.12.45 }
# хосты, которые нам просто не нравятся, находятся тут
table <block_list> file "/etc/pf.blocklist.conf"
# корпоративные ресурсы, доступ по hppt(s)
table <corp_res> file "/etc/pf.corpres.conf"
# Брутфорсеры ssh. если таблица пустая, PF её удалит.
#Для отмены такого поведения используется ключевое слово persist
table <block_ssh> persist
# заблокированный доступ до веб
table <block_web> persist
# options section
# разрываем соединения с ответом, а не просто дропаем пакеты
set block-policy return
# пропускаем проверку на локальной петле, там фильтрация не нужна
set skip on lo0
# scrub section
scrub in all # нормализация всего входящего трафика
# Queueing section
# в данный момент пустая
# nat section
# пустая, у нас нечего транслировать
# filtering section
# запрет всего по умолчанию, помним,
# что это не означает окончание обработки пакета.
block all
# разрешаем админов и корпоративные ресурсы
# ключевое слово quick - прекращать на этом правиле обработку.
pass in quick on $If from <admins> to $IfIp
pass in quick on $If proto tcp from <corp_res> to $IfIp port { $permit_add_tcp_ports }
# разрешаем ssh с защитой от брутфорса
block in quick on $If proto tcp from <block_ssh> port 22
pass in on $If proto tcp to $IfIp port { 22 } keep state (max-src-conn 10, max-src-conn-rate 3/10, overload <block_ssh> flush)
# разрешаем Web с защитой от ботов и прочего
block in quick on $If from <block_web>
pass in on $If proto tcp to $IfIp port { $web_ports } keep state (max-src-conn 100, max-src-conn-rate 20/1, overload <block_web> flush global)
# разрешаем серверу ходить до корпоративных ресурсов
pass out quick on $If proto tcp from $IfIp to <corp_res> port { $permit_corp_ports }
# и прочее по мелочи
# разрешаем исходящие соединения udp# разрешаем исходящие соединения tcp
pass out proto udp to port { $permit_tcp_ports }
# разрешаем исходящие соединения udp
pass out proto udp to port { $permit_udp_ports }
pass out inet proto icmp # разрешаем исходящий icmp
Не забывайте комментировать ваш конфигурационный файл, и он всегда будет радовать взгляд.
Проверяем:
# pfctl -nf pf.conf
Загружаем:
# pfctl -f pf.conf
Заключение
Мы рассмотрели возможности PF в части фильтрации входящего и исходящего трафика. Познакомились с макросами и таблицами. И создали неплохой конфигурационный файл для файрвола, который способен отразить большинство типовых атак на сервер. Описанные технологии наша команда использует для создания шлюза Интернет Контроль Сервер. Протестировать ИКС можно в течение 35 дней или бесплатно использовать версию до 9 пользователей.
Ну а в следующей статье познакомимся с якорями, метками и трансляцией адресов (NAT). Как и прежде, будем рады вашим комментариям и пожеланиям!
darken99
Если добавить еще пару правил то получится конфиг, который у меня уже лет 10 работает :)
M14xa Автор
Спасибо, это хорошая оценка. Цель цикла статей — показать, как настроить типовые конфигурации файрвола в Freebsd. Самое главное — показать логику построения, ведь она сильно отличается от того, к чему привыкли администраторы Linux, использующие IPTables.