OpenBSD Packet Filter (PF) — это очень функциональный фаервол, который может практически все, что может потребоваться от фаервола и даже больше. Давайте посмотрим каким образом его можно использовать в нашем офисе и какие выгоды мы получим.
Наша сеть состоит из 4х пользовательских компьютеров и шлюза с двумя интерфейсами. Имеется симметричный канал в интернет 1Мбит. На шлюзе поднят транспарентый прокси squid с системой управления SAMS. Сервисы Интернет, к которым разрешено обращаться пользователям (кроме менеджера, он отрублен от инета) локальной сети следующие :
nntp, ntp, dns, www
все остальное запрещаем. Из разрешенного траффика www заворачиваем на прокси, остальное NAT'им. Кроме того :
к одному из компьютеров требуется доступ извне, т.к. менеждер иногда работает из дома;
также и к SAMS нужно иметь доступ снаружи;
эффективно делим канал между пользователями;
есть доступ по ssh как изнутри так и снаружи;
защищаем все сервисы от атак dos, spoofing, а сервис ssh также и от bruteforce.
Схема нашей сети :
Пару слов о конфигурировании транспарентного прокси :
не забудьте собрать его с поддержкой SQUID_PF ;
к слушаемому порту добавьте параметр transparent; Например,
http_port 8080 transparent
на клиентских компьютерах ставим 192.168.0.133 в качестве основного шлюза;
Скажу сразу, что шифрованный трафик завернуть через прокси не получится (см. здесь и в [9])
Конфиг буду разбирать "по-секциям", целиком файл скачать можно тут
1) Макросы : (макросы в PF это что-то наподобие строковых переменных в классических языках программирования, в которые можно записывать несколько значений. Записать несколько значений можно путем добавления обрамляющих скобок {} в которых список значений указывается через пробел или через запятую)
int_if="re0" # внутренний интерфейс, имеет адрес 192.168.0.133 ext_if="tun0" # внешний интерфейс, динамический белый адрес (подключение по pppoe) dns_serv="153.53.53.53" # адрес нашего DNS сервера proxy_if="lo0" # интерфейс, на котором слушает прокси proxy_port="8080" # порт прокси boss="192.168.0.168" # IP адрес компьютера босса buh="192.168.0.100" # IP адрес компьютера бухгалтера admin="192.168.0.25" # IP адрес компьютера администратора WinPeak="192.168.0.60" # IP адрес компьютера менеджера allowed_icmp_types="{ echoreq, unreach }" # разрешенные типы icmp сообщений. # Остались неизменными с прошлой статьи. Всякие icmp-редиректы ффтопку. #-- # Cписок немаршрутизируемых адресов, с которых к нам # будут долбиться на внешний интерфейс, а мы их будем заботливо писать в лог. non_route_nets_inet="{ 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8, \ 169.254.0.0/16, 192.0.2.0/24, 0.0.0.0/8, 240.0.0.0/4 }"
Запись интерфейсов в виде
int_if="re0"
и
ext_if="tun0"
имеет значительное преимущество перед записью непосредственно IP адресов, как минимум в двух случаях : {a} выдается динамический адрес {b} есть необходимость получить маску, установленную на интерфейсе.
Что и имеет место быть в нашем конфиге. В первом случае, при использовании в правилах макроса
$ext_if
в правила подставиться IP адрес, динамически выданный интерфейсу tun0.
Во втором случае, мы будем использовать маску подсети интерфейса re0, для определения границ нашей локальной сети. Записывается это так
$int_if:network
Хотелось бы отметить, что физическому внешнему интерфейсу re1 в целях безопасности вообще не назначено IP адреса.
Следующая секция 2) таблицы
table <BRUTEFORCERS> persist
Таблицы в PF реализуют динамические списки значений. У нас всего одна таблица, в этот список попадают IP адреса тех, кто превысил лимит подключений в единицу времени к сервису ssh.
3) Опции
# тем, кто лезет туда куда не нужно вежливо сообщаем что нельзя set block-policy return set skip on lo0 # не проверяем на lo0 # изменяем время для состояния установленного tcp соединения, # которое по-умолчанию черезчур большое (24часа) set timeout { frag 10, tcp.established 3600 } # перед тем, как отправить фрагментированный пакет дальше # сначала дожидаемся всех его частей scrub in all
Если Вас досят — есть смысл поставить вместо "return"
set block-policy drop
Но делайте это после того, как Ваш конфиг отлажен, потому что такая политика внешне напоминает отказ сетевого оборудования и трудно определить что происходит — то ли это сеть глючит, то ли фаервол блокирует трафик.
Сколько % нужно минусовать от реальной полосы — зависит от канала. На асимметричных каналах (например, ADSL) рекомендуются значения до 10% меньше, чем реальная полоса. Лично у меня в локальной сети работало и с 2% . Боссу даем 38% ширины канала, бухгалтеру 18% и себе 15%. Приоритеты траффика у нас троих одинаковые (priority 2), когда есть свободная полоса, разрешаем занимать ее (borrow). Чем больше priority тем меньше время, которое пакеты будут пребывать в очереди. При использовании очередей cbq максимальный priority, который можно поставить равен 7. Если не указывать priority, то по умолчанию она принимается равной 1. Трафик к удаленному рабочему столу компьютера WinPeak имеет бОльший приоритет (priority 3). Кроме того, здесь я поставил метку default, согласно синтаксису очередей cbq (одна такая должна быть в любом случае). ssh траффик имеет еще больший приоритет (priority 4) и гарантированную ширину канала 8%. dns запросы идут нечасто, так что получают (priority 5) и ширину в 2%. Запросы синхронизации времени (NTP) пускаем по очереди с максимально возможным в очередях cbq приоритетом, ширина канала 2%. И последнее что хотелось бы отметить, это легковесные ACKnowledgement пакеты (еще назыаются квитанциями о получении), которые отправляются получателем для подтверждении принятых пакетов. Очень важно чтобы эти пакеты не застревали в очереди, потому что у отправителя может сработать тайм-аут и он повторно отправит пакеты, на которые не получил подтверждений о получении; priority 6 и ширина канала 5% (т.к. по этой категории пойдут пакеты от ВСЕХ пользователей и сервисов нашей сети).
5) NAT
# NAT всех разрешенных портов, кроме www
nat on $ext_if from $ext_if:network to any port { ntp, nntp, domain } -> $ext_if
# www завернем на прокси
rdr on $int_if proto tcp from $int_if:network to any port www \
-> $proxy_if port $proxy_port
# пробросим порт RDP внутрь нашей сети, на комп WinPeak (комп менеджера)
rdr on $ext_if proto tcp from any to $ext_if port rdp -> $WinPeak port rdp
# Единственное на чем хотелось бы заострить Ваше внимание # - это логичность следования секций. # Правила NAT и rdr срабатывают до правил фильтрации, # поэтому они и распологаются выше по конфигу. # И именно поэтому трафик, который пойдет от клиента по 80 # порту мы пропускаем по правилу # pass in on $int_if proto tcp from { $boss, $buh, $admin } to \ # $proxy_if port >>>> $proxy_port <<<< порт 8080! # потому что когда будет обрабатываться это правило # rdr уже сработает и подменит 80й порт 8080м # (а также подменит IP адрес получателя)
6) Правила фильтрации
# хорошее начало любых правил :-P
# Правда! (см. [7])
block all
# блокируем всяких нехороших людей
# стандартный антиспуфинг средствами pf
antispoof log quick for { lo0, $int_if, $ext_if }
# те, кто ломится на внешний интерфейс с левыми адресами
block drop in log quick on $ext_if from $non_route_nets_inet to any
# умников, которые лезут на внутренний интерфейс с любых сетей
# отличных от 192.168.0.0/24
block drop in log quick on $int_if from !$int_if:network to any
# пишем тех, кто ломится к нам на 25 порт. Облегчает работу по
# определению зараженных компов в локальной сети
block drop in log quick on { $int_if, $ext_if } proto tcp from any to any port smtp
# агаааааа, а эти подбирали пароли к ssh
block drop log quick from <BRUTEFORCERS>
# всех пишем в лог
# у меня набегает
# 10 Мб логов за 5 дней (!), на сервере которому пытаются пролезть на порт smtp
# и видимо подDoSивают. Главное не забыть настроить
# ротацию логов man newsyslog.conf
# ssh для локальных юзеров
pass in on $int_if proto tcp from $int_if:network to $int_if port ssh \
queue ( qssh, qack ) synproxy state ( max-src-conn-rate 1/60, \
overload <BRUTEFORCERS> flush global )
# и для пользователей с инета
pass in on $ext_if proto tcp from any to $ext_if port ssh \
queue ( qssh, qack ) synproxy state ( max-src-conn-rate 1/60, \
overload <BRUTEFORCERS> flush global )
# synproxy защищает наш сервис ssh от DoS атак, благодаря тому, что
# сначала САМ устанавливает соединение
# (не допуская полуоткрытых состояний),
# а потом передает установленное соединение сервису.
# Имеет немного бОльшие накладные расходы по сравнению с keep
# max-src-conn-rate numer/time interval
# позволяет применить определенные действия
# к превысившим ограничение по количеству подключений
# за единицу времени. Здесь я указал максимум 1 подключение
# за 60 секунд. Все превысившие это ограничение заносятся в таблицу
# BRUTEFORCERS и с ними разрываются (flush) установленные
# соединения по всем (global) портам. Обратитие внимание, что
# 1/60 -- очень агрессивная настройка. Не попадитесь под нее сами.
# В Вашем случае 5/300 возможно будет более актуальным.
# разрешаем входящий трафик www, который пойдет через прокси.
# Нет смысла в разделении этого трафика по очередям cbq, т.к. :
# 1. Можем зашейпить его с помощью squid delay pools
# 2. PF не сможет определить принадлежность трафика к
# к конкретному пользователю, после того как траф будет
# пропущен через squid (ведь шейпинг у нас работает на $ext_if).
pass in on $int_if proto tcp from { $boss, $buh, $admin } to \
$proxy_if port $proxy_port
# пропустим запросы синхронизации времени поступающие на
# $int_if . Помещаем такие пакеты в самую быструю очередь.
# Т.к. подтверждение о получении в UDP не высылается
# то и не указываем вторым параметром имя очереди qack.
# --
pass in on $int_if proto udp from { $boss, $buh, $admin } to \
any port ntp queue qntp keep state
# Заметьте, что вторым параметром при указании очереди
# указывается имя очереди, по которой пойдут ACK пакеты
# queue ( ИмяОчередиОсновныхПакетов, ИмяОчередиACK_Пакетов )
#--
# Для создания
# состояний UDP пакетов имеет смысл указывать только
# keep state. Остальные (modulate и synproxy) полезны
# для tcp.
# Новостные конференции USENET (nntp)
# вот тут юзеры могут и покачать, шейпим однозначно!
# от Босса
pass in on $int_if proto tcp from $boss to any port nntp \
queue ( qboss, qack ) modulate state
# от Бухгалтера
pass in on $int_if proto tcp from $buh to any port nntp \
queue ( qbuh, qack ) modulate state
# от -=Админа=-
pass in on $int_if proto tcp from $admin to any port nntp \
queue ( qadmin, qack ) modulate state
# modulate -- это почти тот же keep с тем отличием, что
# при установлении соединения будут сгенерированы
# высококачественные Initial Sequence Number, которые
# труднее угадать, соответственно труднее перехватить сессию.
# DNS запросы идут по спец очереди qdns с высоким приоритетом
# UDP не шлет ACK, qack не используем
# от Босса
pass in on $int_if proto udp from $boss to any port domain \
queue qdns keep state
# от Бухгалтера
pass in on $int_if proto udp from $buh to any port domain \
queue qdns keep state
# от -=Админа=-
pass in on $int_if proto udp from $admin to any port domain \
queue qdns keep state
# входящие соединения с компом WinPeak по порту rdp
# защищаем с помощью synproxy
pass in on $ext_if proto tcp from any to $WinPeak port rdp \
queue ( qWinPeak, qack ) synproxy state
# теоретически здесь synproxy уже не нужен
pass out on $int_if proto tcp from any to $WinPeak port rdp \
queue ( qWinPeak, qack ) modulate state
# выходящий через $ext_if ntp траффик
# здесь пропишем очередь вручную для
# пакетов, исходящих не из локальной сети, а
# непосредственно от сервера
pass out on $ext_if proto udp from $ext_if to any port ntp \
keep state queue qntp
# выходящий через $ext_if nntp траффик (tcp)
pass out on $ext_if proto tcp from $ext_if to any port nntp \
modulate state
# выходящий через $ext_if www траффик (tcp)
pass out on $ext_if proto tcp from $ext_if to any port www \
modulate state
# выходящий через $ext_if dns траффик (udp)
# здесь пропишем очередь вручную для
# пакетов, исходящих не из локальной сети, а
# непосредственно от сервера
pass out on $ext_if proto udp from $ext_if to \
$dns_serv port domain keep state queue qdns
# Возможность доступа к SAMS из внешней сети.
# Логируем,
# защищаем с помощью synproxy .
pass in log on $ext_if proto tcp from any to $ext_if \
port www synproxy state
# icmp
pass log inet proto icmp all icmp-type $allowed_icmp_types