Packet Filter. Установка, настройка, грабли.

Ноябрь 2nd, 2012

Предисловие

Pácket Fílter (PF) — файрвол, разрабатываемый в рамках проекта OpenBSD. Обладает высокой скоростью работы, удобством в конфигурировании и большими возможностями, включая поддержку IPv6. На данный момент используется, помимо OpenBSD, также в NetBSD и FreeBSD, а также основанных на этих трёх MirOS BSD, DesktopBSD, pfSense и других. PF был портирован на Microsoft Windows и лёг в основу файрвола Core Force. © Википедия

Об особенностях pf можно почитать здесь:

http://www.openbsd.org/faq/pf/

http://ru.wikipedia.org/wiki/Packet_Filter

Сборка ядра

Итак, приступаем. Файрвол у нас будет "ядерный", поэтому в файл-конфиг ядра дописываем строчки:

# поддержка файрвола и  логов
device pf
device pflog
# секция для шейпинга трафика 
options         ALTQ
options         ALTQ_CBQ
options         ALTQ_RED       
options         ALTQ_RIO       
options         ALTQ_HFSC      
options         ALTQ_PRIQ    
options         ALTQ_NOPCC

Также, лучше убрать все упоминания о ipfw и nat (IPFIREWALL, IPDIVERT и тд). Если оставить, могут возникнуть грабли.

Собираем/устанавливаем ядро:

# cd /usr/src
# make buildworld KERNCONF=YOUR_KERNEL
# make installworld KERNCONF=YOUR_KERNEL

Создаём нужны нам файлы:

# touch /etc/pf.conf
# touch /etc/full_internet
# touch /etc/mail_servers

Файл /etc/full_internet имеет вид:

192.168.аа.хх
192.168.аа.уу

и т.д.

Сюда мы вносим компьютеры, которым необходим полный доступ в Интернет, как правило, это ноутбуки руководства, которые мы предварительно резервим в DHCP.

Файл /etc/mail_servers содержит список разрешённых серверов электронной почты. У меня имеет такой вид:

81.9.3.234
85.249.225.4
85.249.225.3
85.249.255.210
85.249.255.195
85.249.229.114
78.138.154.163
81.9.106.34
81.9.110.226
85.26.134.203
83.149.4.130
217.170.93.179
85.249.224.242
85.249.227.4
81.222.207.34
213.150.83.58
83.149.4.226
93.157.149.154
85.26.134.210
83.149.4.154
83.149.7.42
83.149.17.20
83.149.18.242
78.138.154.155

В файл лучше вносить ip-адреса, а не DNS-имена. Иначе после ребута у нас правила не загрузятся, т.к. эти самые имена не смогут отрезолвиться.

pf.conf

Теперь приступаем к редактированию конфига pf:

# mcedit /etc/pf.conf

Далее идёт содержание файла с подробными комментариями:

# Для начала записываем нужные нам в дальнейшем скаляры (переменные), списки и таблицы 
# внешний интерфейс 
ext_if = "rl0"
# внутренний интерфейс
int_if = "em0"
# loopback-интерфейс
lo_if = "lo0"
# таблица с адресами компьютеров, с которых мы выпускаем в интернеты всё
table <full> persist file "/etc/full_internet"
# Вверенная вам подсеть
local_network = "192.168.xx.0/24"
# таблица разрешённых почтовых серверов
table <mail_servers> persist file "/etc/mail_servers"
# разрешаемые типы icmp-пакетов
icmp_types="{ 0, 8, 10, 11}"
# интерфейсы входящего vpn
vpn_if = "{ ng0, ng1, ng2, ng3, ng4}"
# порты, на которые может коннектиться squid
squid_ports = "{ 80, 443, 888, 8080}"
# компьютеры, на которых разрешаем icq/jabber
table <allow_icq> { 192.168.50.44, 192.168.50.55, 192.168.50.35}
# список компьютеров, для которых будет применяться шейпинг трафика
table <shape_users> { 192.168.50.76}
# список, кому разрешаем исходящий VPN
table <vpn_access> {192.168.50.0/24}
# правило поведения для файрвола при срабатывании правила block, при return для отброшенных пакетов TCP отсылается                  
# пакет TCP RST, для прочих ICMP Unreachable. Если вписать drop – пакеты отбрасываются молча
set block-policy return
# Нормализация трафика, подробно читаем здесь
# http://91.192.71.196/BSDCert/BSDA-course/apcs02.html#pf-scrubbing
scrub in all 
# далее идут правила NAT и шейпинг
# собственно шейпинг трафика. Почему закоменчено – в конце материала. 
# Сначала задаём канал и его ширину. Дальше создаём очереди, очередь standart – обязательна, 
#туда идёт всё, что не попадает в другие очереди. Очередь inet_in – собственно и есть урезанная #часть канала
#altq on $int_if cbq bandwidth 2000Kb queue { standart, inet_in }
#queue standart bandwidth 80% priority 2 cbq (default)
#queue inet_in bandwidth 20% priority 6 cbq (borrow)
# NAT
nat on $ext_if from !($ext_if) to any -> ($ext_if:0)
# форвадинг http-трафика на прокси-сервер. Теперь юзерам можно не вписывать в браузер #прокси-сервер. Однако не   
# забываем, что форвадится только http, всё остальное будет резаться файрволом
rdr on $int_if proto tcp from !<full> to any port 80 -> 127.0.0.1 port 3128
# Основная секция с правилами нашего файрвола
# Политика по-умолчанию, отбрасываем всё, что не попало в ниже идущие правила
block log all
# Защита от спуфинга (подмены адресов в IP-пакетах)
antispoof for $ext_if
# разрешаем прохождение трафика на интерфейсах vpn-демона
pass on $vpn_if
# разрешаем локальный трафик
pass on $int_if
# разрешаем loopback-трафик
pass on $lo_if
# разрешаем пользователю root ходить в интернеты, здесь и далее опции:
# quick – если пакет удовлетворяет правилу с такой опцией, то дальше по списку правил он не
# идёт, а отбрасывается назад в Интернет или же пускается во внутрь, в зависимости от правила.
# keep state – разрешаем трафик в обе стороны. Начиная с 6-ой ветки FreeBSD, в pf все правила
# по дефолту – keep state, однако считается хорошим тоном этот самый keep state дописывать.
# Если хотите, чтобы правило действовало в одну сторону, вместо keep state пишите no state  
pass out quick on $ext_if inet from $ext_if to any keep state user root
# Конструкцией tag “A” помечаем пакеты, которые будем выпускать в интернет
pass in on $int_if inet from <full> to any keep state tag "A"
# Разрешаем входящие вышеперечисленные типы icmp-пакетов
pass quick on $ext_if inet proto icmp all icmp-type $icmp_types
# Разрешаем входящий ssh, порт соответственно пишем, на котором у вас висит sshd
pass quick on $ext_if inet proto tcp from any to any port { ssh 11122 } keep state
# разрешаем сервис NTP (Network Time Protocol)
pass quick on $ext_if inet proto udp from any to any port 123 keep state
# разрешаем запросы DNS. Для передачи зоны нужно открыть 53/tcp, вместо udp пишем { tcp, udp}
pass out quick on $ext_if inet proto udp from any to any port 53 keep state
# Выпускаем наружу прокси-сервер
pass out quick on $ext_if proto tcp from $ext_if to any port $squid_ports keep state user squid
# разрешаем отправку почты в мир
pass quick on $ext_if proto tcp from any to any port { 25, 465} keep state user postfix
# разрешаем клиентам коннектиться к нам извне для приёма-отправки почты
pass in quick on $ext_if proto tcp from any to $ext_if port { 25, 465, 993, 995} keep state
# разрешаем клиентам локальной сети пользоваться списком почтовых серверов 
pass in quick on $int_if proto tcp from $local_network to <mail_servers> port { 25, 110, 143, 465, 993, 995} keep state tag "A"
# помечаем пакеты клиента Комита Налогоплательщик, вместо 192.168.50.50 пишем нужный адрес
pass in quick on $int_if proto tcp from 192.168.50.50 to 213.182.169.34 port { 50025, 50110} keep state tag "A"
# помечаем пакеты icq/jabber/skype
pass in quick on $int_if proto { tcp, udp} from $local_network to any port { 5190, 5222, 2042, 26086} keep state tag   "A"
# Банк-клиенты
pass in on $int_if inet proto udp from $local_network to any port 87 keep state tag "A"
pass in on $int_if inet proto tcp from $local_network to any port 1024 keep state tag "A"
pass in on $int_if inet proto tcp from $local_network to 212.5.81.77 port 10747 tag “A” 
# Выпускаем Citrix-клиента (для Subaru обязательно)
pass in on $int_if proto { tcp, udp} from $local_network to any port { 1494, 1604, 2512, 2513, 2598, 2897} tag "A"
# входящий/исходящий VPN
pass in quick on $ext_if proto tcp from any to $ext_if port 1723 keep state
pass out quick on $ext_if inet proto tcp from $ext_if to any port 1723 keep state
pass quick on $ext_if inet proto gre from any to any keep state
# Режем скорость выбранным компьютерам, почему правило закоменчено - чуть ниже
#pass out on $int_if from any to <shape_users> queue inet_in
# выпускаем наружу отмеченные пакеты
pass out on $ext_if inet from $ext_if to any keep state tagged "A"

Почему закоменчены строчки с шейпингом трафика? Дело в том, что сия конструкция работает нормально в FreeBSD 8.1, а вот в FreeBSD 7.1 начинались какие-то непонятные тормоза. Пока что не разобрался в чём дело. Поэтому в семёрке шейпинг выключил.

В самом конце правим /etc/rc.conf:

pf_enable="YES"
pf_rules="/etc/pf.conf"
pf_flags=""
pflog_enable="YES"
pflog_logfile="/var/log/pflog"
pflog_flags=""

В принципе, почти всё.

Решаем проблему с ненатищимся GRE

Но у pf есть один минус – он не умеет по-человечески натить gre. Для этих целей можно использовать pptp-прокси (/usr/ports/net/frickin, правда, проект судя по всему заброшен, ды и поражает отсутствие какой-либо документации), а можно и сторонний NAT, конкретно – ipfw_nat.

Правим /etc/rc.conf , добавляем в самый конец:

firewall_enable=”YES”
firewall_script=”/etc/ipfw_nat”

Правим /boot/loader.conf :

ipfw_nat_load=”YES”

Вообще, правильней было бы дописать в /etc/rc.conf:
firewall_nat_enable=”YES”
но при таком раскладе у меня в семёрке FreeBSD NAT не поднимается, приходится пинать вручную. В восьмёрке же всё нормально.

Далее:

# touch /etc/ipfw_nat
# chmod +x /etc/ipfw_nat
# cat /etc/ipfw_nat
#!/bin/sh
fw="/sbin/ipfw -q "
$fw flush
$fw nat 1 config if rl0
$fw add 101 nat 1 gre from any to any in via rl0
$fw add 104 nat 1 gre from any to any out via rl0
$fw add allow all from any to any 

rl0 в скрипте - это внешний интерфейс, ставим соответственно свой.

Таким образом, весь gre-трафик у нас натит ipfw+nat, всё остальное идёт мимо него, на pf.

Избавиться от комментов и пустых строк /etc/pf.conf, получив при этом только полезный конфиг можно так:

# cat /etc/pf.conf | grep –v # | sed ‘/^$/d’ > /etc/pf.conf.new

Все манипуляции с файрволом проводятся утилитой pfctl. Вот основные:

# pfctl –s rules   - просмотр списка загруженных правил
# pfctl –nf /etc/pf.conf    - проверка файла конфига на ошибки, если на stdout ничего не вернулось – значит всё ОК
# pfctl –F all –f /etc/pf.conf         -   очищаем список правил и загружаем по новой из конфига (оборвёт текущую  сессию!!!)
# pfctl –f /etc/pf.conf - подгружает список правил из файл-конфига, при этом не трогает поднятые сессии если в новом конфиге для таких сессий есть разрешающие правила

Очень рекомендую к прочтению man pfctl.

Мануал хорошо сформирован, всё в целом понятно, а возможностей у pfctl очень много

Проверяем правильность файла /etc/pf.conf

#pfctl -nf /etc/pf.conf

Если в конфиге что-то не так - на stdout вывалится сообщение об ошибке с номером строки. Правим, проверяем по-новой. Как только ничего не вываливается - значит, всё хорошо, ребутимся.

Логи

Packet Filter пишет логи в бинарном виде, для просмотра пользуемся tcpdump’ом:

# tcpdump –nettttqr /var/log/pflog 

Также не забываем настроить ротацию логов:

# mcedit /etc/newsyslog.conf

Добавляем туда примерно следующее:

/var/log/pflog                          644  10     100  *     JB    /var/run/pflogd.pid

Иногда возникает необходимость просмотреть все логи файрвола, которые имеются. Можно воспользоваться таким вот скриптом, чтобы не парсить каждый файл руками.

Содержимое файла pflog.sh

#!/usr/local/bin/bash
# число файлов лога, указанное в /etc/newsyslog.conf
number=10
for arg in $@
 do
  case $arg in
   -h*)
    host=${arg:2}
   ;;
   -p*)
    port=${arg:2}
   ;;
  esac
done
parse_logs ()
{
 i=0
  while (( i <= $number)); do
   bzcat -f /var/log/pflog.${i}.bz2 | tcpdump -nettttqr - 2>&1
   ((i++))
 done
tcpdump -nettttqr /var/log/pflog
}
parse_logs_ports ()
{
 i=0
  while (( i <= $number)); do
   bzcat -f /var/log/pflog.${i}.bz2 | tcpdump -nettttqr - port $port 2>&1
   ((i++))
  done
 tcpdump -nettttqr /var/log/pflog port $port
}
parse_logs_hosts ()
 {
  i=0
    while (( i <= $number)); do
    bzcat -f /var/log/pflog.${i}.bz2 | tcpdump -nettttqr - host $host 2>&1
   ((i++))
 done
 tcpdump -nettttqr /var/log/pflog host $host
}
parse_logs_hosts_ports ()
 {
  i=0
  while (( i <= $number)); do
  bzcat -f /var/log/pflog.${i}.bz2 | tcpdump -nettttqr - host $host and port $port 2>&1
   ((i++))
    done
    tcpdump -nettttqr /var/log/pflog host $host and port $port
}
if [ "$host" != "" -a "$port" != "" ];  then
 parse_logs_hosts_ports
  elif [ "$host" != "" -a "$port" = ""   ]; then
   parse_logs_hosts
    elif [ "$host" = "" -a "$port" != "" ]; then
     parse_logs_ports
      else
       parse_logs
fi
    

В самом начале переменной number нужно присвоить значение, равное значению числа файлов лога в /etc/newsyslog.conf.

Вызов скрипта такой:

./pflog.sh –h192.168.50.99 –p123 , где опция h задаёт искомый хост, p – порт. 

Входные данные можно комбинировать (искать или по хосту, или по порту), можно просто запускать ./pflog.sh, тогда скрипт вывалит всё содержимое логов.

Полезные ссылки:


Смотрите также: