Настройка ipfw во FreeBSD

Некоторое время назад наводил порядок в фаерволлах на серверах FreeBSD, доставшихся в наследство от прошлых системных администраторов. Захотелось сразу продумать фаерволл таким образом, чтобы потом в нём было легко ориентироваться и было очевидно, как в него добавить новое правило. Использовался фаерволл ipfw и его таблицы. Я решил не менять фаерволл на ipf или pf, а просто последовательно преобразовывать имеющиеся правила в понятную структуру, пока меня не устроит конечный результат. Результатом и хочу поделиться, как одним из примеров того, как можно организовать фаерволл.

1. Включение фаерволла

В файле /etc/rc.conf должны быть две строчки, предписывающие включить фаерволл и использовать правила из файла /etc/firewall.conf:

firewall_enable="YES"
firewall_script="/etc/firewall.conf"

2. Написание правил

Теперь приведу упрощённый пример файла /etc/firewall.conf:

fw='/sbin/ipfw -q'
${fw} -f flush

${fw} add pass all from any to any via lo0
${fw} add deny all from any to 127.0.0.0/8
${fw} add deny all from 127.0.0.0/8 to any
${fw} add check-state
${fw} add allow ip from me to any keep-state

########## ping ##########
${fw} add allow icmp from any to me in via em0 keep-state

########## ssh ##########
table_ssh=100
${fw} table $table_ssh flush
${fw} add allow tcp from "table($table_ssh)" to me 22 in via em0 keep-state

# lan gateway
${fw} table $table_ssh add 192.168.0.1
# office computers
${fw} table $table_ssh add 192.168.0.128/25

#########################################
${fw} add deny ip from any to any

Фаерволл разрешает серверу устанавливать любые исходящие подключения. Любому внешнему сетевому узлу разрешается выполнять ICMP-запросы (среди которых могут быть не только запросы Ping). Последнее правило запрещает любой входящий трафик, который не разрешён явным образом.

В начале файла есть команда, которая очищает текущий список правил фаерволла:

${fw} -f flush

При очистке списка правил таблицы остаются нетронутыми. Поэтому внутри каждого блока, разрешающего доступ к определённому сервису на сервере, выполняется очистка соответствующей таблицы:

${fw} table $table_ssh flush

После очистки таблицы в списке правил фаерволла создаётся необходимое правило, а затем наполняется таблица, связанная с этим правилом.

Наибольший интерес представляет блок, разрешающий подключения по SSH и продолжающийся вплоть до последнего правила. Чтобы разрешить доступ к ещё одному сервису, можно скопировать блок, переименовать переменную, хранящую номер таблицы, поменять сам номер таблицы. Затем нужно заменить имя таблицы в последующих строчках, отредактировать само правило и изменить список разрешённых IP-адресов и сетей, которые будут добавлены в таблицу. Например, можно добавить блок, разрешающий доступ по HTTP:

########## http ##########
table_http=110
${fw} table $table_http flush
${fw} add allow tcp from "table($table_http)" to me 80 in via em0 keep-state

# office computers
${fw} table $table_http add 192.168.0.128/25
# zabbix server
${fw} table $table_http add 192.168.0.2

3. Применение правил

Как видно, всё очень просто. Для применения изменений нужно выполнить всего одну команду:

# /etc/rc.d/ipfw restart

Будьте внимательны, не допускайте ошибок в файле - иначе он может выполниться не до конца и можно потерять удалённый доступ к серверу. Советую первыми в список включать правила для SSH - тогда они будут отрабатывать раньше, чем интерпретатор дойдёт до строки с ошибкой и отбросит весь остаток файла. Вероятность того, что правила SSH будут применены при ошибках в других более часто редактируемых правилах, будет выше. А ещё лучше перед применением отредактированного файла изменить значение переменной fw с этого:

fw='/sbin/ipfw -q'

На вот это:

fw='/sbin/ipfw -q -n'

Команды не будут выполняться реально, будет только проверен их синтаксис. Если сообщений об ошибках не получено, то можно вернуть значение fw на исходное и снова выполнить команду, уже для реального применения изменений.

4. Вид правил в системе

Ещё одно достоинство этого фаерволла - он получается простым, т.к. в самом фаерволле есть всего несколько правил. Для наглядности приведу реальный список правил в фаерволле на одном из серверов:

# ipfw list
00100 allow ip from any to any via lo0
00200 deny ip from any to 127.0.0.0/8
00300 deny ip from 127.0.0.0/8 to any
00400 check-state
00500 allow ip from me to any keep-state
00600 allow icmp from any to me in via em0 keep-state
00700 allow tcp from table(110) to me dst-port 3306 in via em0 keep-state
00800 allow tcp from table(111) to me dst-port 10054 in via em0 keep-state
00900 allow tcp from table(112) to me dst-port 80 in via em0 keep-state
01000 allow tcp from table(116) to me dst-port 22 in via em0 keep-state
01100 allow udp from table(117) to me dst-port 69 in via em0 keep-state
01200 allow tcp from table(118) to me dst-port 48048 in via vlan32 keep-state
01300 allow udp from table(119) to me dst-port 162 in via em0
01400 deny ip from any to any
65535 deny ip from any to any

Трудно запутаться в семи существенных правилах, не так ли?

Второе достоинство фаерволла - он достаточно быстр, т.к. сравнение IP-адресов клиентов происходит с использованием таблиц, а не при помощи отдельных правил, которые просматриваются последовательно. Посмотреть текущее содержимое любой из таблиц тоже довольно просто:

# ipfw table 118 list
10.1.206.0/24 0
10.1.217.0/24 0

5. Динамически обновляемые таблицы

Если нужно сделать так, чтобы IP-адреса и сети в таблицу добавлялись динамически, каким-нибудь внешним скриптом, то достаточно не вписывать команды, очищающие таблицу и добавляющие в неё какие-то правила. Например, вот так:

########## tftp ##########
table_tftp=120
${fw} add allow tcp from "table($table_tftp)" to me 69 in via em0 keep-state

Таким образом, при применении изменённых правил, динамически добавленные в таблицу адреса не исчезнут из таблицы.

6. Динамические правила

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

# sysctl net.inet.ip.fw.dyn_count
net.inet.ip.fw.dyn_count: 656
# sysctl net.inet.ip.fw.dyn_max
net.inet.ip.fw.dyn_max: 8192

Если значение первого счётчика будет близко подбираться к значению второго, то максимальное количество динамических правил можно увеличить при помощи sysctl:

# sysctl -w net.inet.ip.fw.dyn_max=16384

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

net.inet.ip.fw.dyn_max=16384

Написать автору