Добавлен перевод статьи "IP Accounting and Access Lists with systemd"

This commit is contained in:
nnz1024
2017-10-28 00:14:16 +03:00
parent fb2a8ab3cd
commit d7289115f5

601
s4a.tex
View File

@@ -2766,7 +2766,7 @@ PrivateNetwork=yes
котором настраивается только интерфейс обратной петли.
\subsection{Предоставление службам независимых каталогов \texttt{/tmp}}
\label{sec:privatetmp}
\label{ssec:privatetmp}
Еще одна простая, но мощная опция настройки служб~--- +PrivateTmp=+:
\begin{Verbatim}
@@ -3781,6 +3781,7 @@ $ journalctl /usr/sbin/vpnc /usr/sbin/dhclient
Отлично, мы нашли причину проблемы!
\subsection{Продвинутые методы выборки}
\label{ssec:metadata}
Да, это все, конечно, здорово, но попробуем подняться еще на ступеньку выше.
Чтобы понять описанные ниже приемы, нужно знать, что systemd добавляет к
@@ -5402,7 +5403,7 @@ systemd реализует одновременно обе стратегии,
Для самой службы создается пространство имен монтирования (mount
namespace), в котором эти подкаталоги bind-монтируются в +/tmp+
и +/var/tmp+ соответственно. См. также раздел
\ref{sec:privatetmp}.}, причем их жизненный цикл привязан к
\ref{ssec:privatetmp}.}, причем их жизненный цикл привязан к
жизненному циклу службы: при остановке службы удаляется
не~только ее пользователь, но и ее временные каталоги. (Опять же
замечу, что эту директиву имеет смысл применять и без
@@ -5945,6 +5946,602 @@ DynamicUser=yes
На сегодня все!
\sectiona{Учет и фильтрация IP-трафика служб}
\emph{Коротко о главном: теперь systemd может подсчитывать и фильтровать
по подсетям IP-трафик любой службы.}
В недавно вышедшем выпуске
\href{https://lists.freedesktop.org/archives/systemd-devel/2017-October/039589.html}%
{systemd 235}, помимо расширения поддержки динамических пользователей
(рассмотренной в предыдущей статье), добавлена еще одна важная возможность:
учет и фильтрация IP-трафика.
systemd и раньше поддерживал механизмы управления ресурсами, доступными юнитам:
процессорным временем, дисковым вводом-выводом, потреблением памяти, количеством
запускаемых процессов. А в версии 235 был добавлен контроль над еще одним видом
ресурсов~--- IP-трафиком.
Для этого были введены три новых директивы конфигурации юнитов:
\begin{enumerate}
\item \hreftt{https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html\#IPAccounting=}%
{IPAccounting=}~--- булева переменная, позволяющая включить
подсчет IP-трафика, принятого и полученного службой (как
количества пакетов, так и их суммарного объема в байтах).
\item \hreftt{https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html\#IPAddressAllow=ADDDRESS[/PREFIXLENGTH]\%E2\%80\%A6}%
{IPAddressDeny=} указывает <<черный>> список адресов подсетей
или хостов для службы. Весь трафик, отправляемый на эти адреса
процессами службы, а также полученный ими с этих адресов,
будет заблокирован.
\item \hreftt{https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html\#IPAddressAllow=ADDDRESS[/PREFIXLENGTH]\%E2\%80\%A6}%
{IPAddressAllow=} имеет противоположный смысл~--- процессы
службы могут обмениваться трафиком с перечисленными в этой
директиве адресами, даже если они пересекаются с тем, что задано
в +IPAddressDeny=+ (т.е. белый список имеет приоритет над
черным).
\end{enumerate}
Эти три опции предоставляют интерфейс к новой функциональности ядра Linux,
добавленной в выпуске 4.11, а именно, eBPF-хукам контрольных групп. Основную
работу берет на себя ядро, а systemd лишь обеспечивает возможность настройки
этих механизмов. Обратите внимание, что cgroup/eBPF никак не~относятся к
традиционному брандмауэру Linux~--- NetFilter/+iptables+. Вы можете использовать
любую из этих технологий, или обе сразу, или вообще не~пользоваться ни~одной из
них.
\subsectiona{Учет IP-трафика}
Давайте посмотрим, как работает учет трафика. Создадим тестовую службу
+/etc/systemd/system/ip-accounting-test.service+:
\begin{Verbatim}
[Service]
ExecStart=/usr/bin/ping 8.8.8.8
IPAccounting=yes
\end{Verbatim}
Этот простой юнит вызывает команду
\hreftt{http://man7.org/linux/man-pages/man8/ping.8.html}{ping(8)}, которая
отправляет серию ICMP эхо-запросов на IP-адрес 8.8.8.8 (это адрес DNS-сервера
Google; он используется в нашем примере потому, что его легко запомнить, он
отовсюду доступен и отвечает на эхо-запросы; вы можете использовать любой другой
адрес, отвечающий на пинги). Опция +IPAccounting=yes+ включает учет IP-трафика
для нашей службы.
Запустим нашу службу и посмотрим ее состояние:
\begin{Verbatim}
# systemctl daemon-reload
# systemctl start ip-accounting-test
# systemctl status ip-accounting-test
ip-accounting-test.service
Loaded: loaded (/etc/systemd/system/ip-accounting-test.service; static; vendor preset: disabled)
Active: active (running) since Mon 2017-10-09 18:05:47 CEST; 1s ago
Main PID: 32152 (ping)
IP: 168B in, 168B out
Tasks: 1 (limit: 4915)
CGroup: /system.slice/ip-accounting-test.service
└─32152 /usr/bin/ping 8.8.8.8
Okt 09 18:05:47 sigma systemd[1]: Started ip-accounting-test.service.
Okt 09 18:05:47 sigma ping[32152]: PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
Okt 09 18:05:47 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=1 ttl=59 time=29.2 ms
Okt 09 18:05:48 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=2 ttl=59 time=28.0 ms
\end{Verbatim}
Как видим, программа +ping+ сейчас работает. Судя по логу в конце вывода
+systemctl status+, она как раз закончила второй цикл пинга. Однако, для нас
сейчас более интересна строка выше, начинающаяся с +IP:+ и содержащая счетчики
принятых и полученных байт IP-трафика. Сайчас она показывает, что было
отправлено 168 байт, и ровно столько же принято. Это вполне предсказуемо: ICMP
эхо-запросы и эхо-ответы должны иметь одинаковый размер\footnote{Прим. перев.:
Если не~считать заголовков IP и ICMP, имеющих фиксированную длину, размер пакета
определяется только телом сообщения (payload), которое должно полностью
копироваться из запроса в ответ.}. Обратите внимание, что эта строка появляется
только для юнитов с включенной опцией +IPAccounting=+. Если она выключена,
трафик юнита не~подсчитывается\footnote{Прим. перев.: Можно заметить, что для
приведенного в примере юнита подсчитывается не~только трафик, но и количество
процессов/потоков (строка +Tasks:+). Это связано с введенным лимитом на
их количество
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html\#TasksMax=N}%
{TasksMax=4915} (+TasksMax=+ автоматически включает
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html\#TasksAccounting=}%
{TasksAccounting=}).}.
Подождем немного, и вызовем +systemctl status+ снова:
\begin{Verbatim}
# systemctl status ip-accounting-test
ip-accounting-test.service
Loaded: loaded (/etc/systemd/system/ip-accounting-test.service; static; vendor preset: disabled)
Active: active (running) since Mon 2017-10-09 18:05:47 CEST; 4min 28s ago
Main PID: 32152 (ping)
IP: 22.2K in, 22.2K out
Tasks: 1 (limit: 4915)
CGroup: /system.slice/ip-accounting-test.service
└─32152 /usr/bin/ping 8.8.8.8
Okt 09 18:10:07 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=260 ttl=59 time=27.7 ms
Okt 09 18:10:08 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=261 ttl=59 time=28.0 ms
Okt 09 18:10:09 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=262 ttl=59 time=33.8 ms
Okt 09 18:10:10 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=263 ttl=59 time=48.9 ms
Okt 09 18:10:11 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=264 ttl=59 time=27.2 ms
Okt 09 18:10:12 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=265 ttl=59 time=27.0 ms
Okt 09 18:10:13 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=266 ttl=59 time=26.8 ms
Okt 09 18:10:14 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=267 ttl=59 time=27.4 ms
Okt 09 18:10:15 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=268 ttl=59 time=29.7 ms
Okt 09 18:10:16 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=269 ttl=59 time=27.6 ms
\end{Verbatim}
Как видно из вывода, после 269 пингов счетчики достигли значения 22 килобайт.
Несмотря на то, что +systemctl status+ показывает только счетчики байт,
ведется также учет и количества пакетов. Для просмотра этих значений можно
использовать низкоуровневую команду +systemctl show+:
\begin{Verbatim}
# systemctl show ip-accounting-test -p IPIngressBytes -p IPIngressPackets \
-p IPEgressBytes -p IPEgressPackets
IPIngressBytes=37776
IPIngressPackets=449
IPEgressBytes=37776
IPEgressPackets=449
\end{Verbatim}
Разумеется, эта информация доступна также и через API D-Bus. Если вы хотите
автоматизировать обработку таких данных, использование вызовов D-Bus будет
гораздо удобнее, чем разбор вывода +systemctl show+.
Теперь остановим нашу службу:
\begin{Verbatim}
# systemctl stop ip-accounting-test
\end{Verbatim}
После остановки службы, для которой включен учет потребления ресурсов, в
системный журнал добавляется запись с итоговой суммой потребленных службой
ресурсов. Просмотреть ее можно командой +journalctl+:
\begin{Verbatim}[fontsize=\small]
# journalctl -u ip-accounting-test -n 5
-- Logs begin at Thu 2016-08-18 23:09:37 CEST, end at Mon 2017-10-09 18:17:02 CEST. --
Okt 09 18:15:50 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=603 ttl=59 time=26.9 ms
Okt 09 18:15:51 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=604 ttl=59 time=27.2 ms
Okt 09 18:15:52 sigma systemd[1]: Stopping ip-accounting-test.service...
Okt 09 18:15:52 sigma systemd[1]: Stopped ip-accounting-test.service.
Okt 09 18:15:52 sigma systemd[1]: ip-accounting-test.service: Received 49.5K IP traffic, sent 49.5K IP traffic
\end{Verbatim}
Последняя строка~--- и есть та самая запись о потребленных ресурсах. На самом
деле, эта запись является структурной\footnote{Прим. перев.: Подробнее о
структуре записей Journal можно почитать в разделе~\ref{ssec:metadata}.}, и
содержит поля метаданных, в которых приводится более полная информация:
\begin{Verbatim}[fontsize=\small]
# journalctl -u ip-accounting-test -n 1 -o verbose
-- Logs begin at Thu 2016-08-18 23:09:37 CEST, end at Mon 2017-10-09 18:18:50 CEST. --
Mon 2017-10-09 18:15:52.649028 CEST [s=89a2cc877fdf4dafb2269a7631afedad;i=14d7;b=4c7e7adcba0c45b69d612857270716d3;m=137592e75e;t=55b1f81298605;x=c3c9b57b28c9490e]
PRIORITY=6
_BOOT_ID=4c7e7adcba0c45b69d612857270716d3
_MACHINE_ID=e87bfd866aea4ae4b761aff06c9c3cb3
_HOSTNAME=sigma
SYSLOG_FACILITY=3
SYSLOG_IDENTIFIER=systemd
_UID=0
_GID=0
_TRANSPORT=journal
_PID=1
_COMM=systemd
_EXE=/usr/lib/systemd/systemd
_CAP_EFFECTIVE=3fffffffff
_SYSTEMD_CGROUP=/init.scope
_SYSTEMD_UNIT=init.scope
_SYSTEMD_SLICE=-.slice
CODE_FILE=../src/core/unit.c
_CMDLINE=/usr/lib/systemd/systemd --switched-root --system --deserialize 25
_SELINUX_CONTEXT=system_u:system_r:init_t:s0
UNIT=ip-accounting-test.service
CODE_LINE=2115
CODE_FUNC=unit_log_resources
MESSAGE_ID=ae8f7b866b0347b9af31fe1c80b127c0
INVOCATION_ID=98a6e756fa9d421d8dfc82b6df06a9c3
IP_METRIC_INGRESS_BYTES=50880
IP_METRIC_INGRESS_PACKETS=605
IP_METRIC_EGRESS_BYTES=50880
IP_METRIC_EGRESS_PACKETS=605
MESSAGE=ip-accounting-test.service: Received 49.6K IP traffic, sent 49.6K IP traffic
_SOURCE_REALTIME_TIMESTAMP=1507565752649028
\end{Verbatim}
Нас интересуют поля +IP_METRIC_INGRESS_BYTES=+, +IP_METRIC_INGRESS_PACKETS=+,
+IP_METRIC_EGRESS_BYTES=+, +IP_METRIC_EGRESS_PACKETS=+, отображающие значения
соответствующих счетчиков.
Все подобные записи имеют один и тот же идентификатор типа сообщения
(\href{https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html\#MESSAGE_ID=}%
{message ID}), при помощи которого их можно легко найти в журнале
(+ae8f7b866b0347b9af31fe1c80b127c0+). Вызовем +journalctl+ с указанием этого
идентификатора, добавив также ключ +-u+, который ограничит выборку только
записями нашей службы:
\begin{Verbatim}[fontsize=\small]
# journalctl -u ip-accounting-test MESSAGE_ID=ae8f7b866b0347b9af31fe1c80b127c0
-- Logs begin at Thu 2016-08-18 23:09:37 CEST, end at Mon 2017-10-09 18:25:27 CEST. --
Okt 09 18:15:52 sigma systemd[1]: ip-accounting-test.service: Received 49.6K IP traffic, sent 49.6K IP traffic
\end{Verbatim}
Приведенный вывод содержит пока только одну запись, так как мы запускали службу
всего один раз. Каждый последующий запуск будет добавлять новую запись.
Учет IP-трафика доступен также и для одноразовых служб, запускаемых через
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd-run.html}%
{systemd-run}, что позволяет прозрачно выполнять различные команды как службы
systemd, и подсчитывать их трафик:
\begin{Verbatim}
# systemd-run -p IPAccounting=yes --wait \
wget https://cfp.all-systems-go.io/en/ASG2017/public/schedule/2.pdf
Running as unit: run-u2761.service
Finished with result: success
Main processes terminated with: code=exited/status=0
Service runtime: 878ms
IP traffic received: 231.0K
IP traffic sent: 3.7K
\end{Verbatim}
Мы использовали \hreftt{https://linux.die.net/man/1/wget}{wget}, чтобы загрузить
\href{https://cfp.all-systems-go.io/en/ASG2017/public/schedule/2.pdf}{PDF с
расписанием второго дня} нашей любимой конференции
\href{https://all-systems-go.io/}{All Systems Go! 2017}. Эта операция
потребовала 231 килобайт входящего трафика и 4 килобайта исходящего.
Особого внимания заслуживают параметры командной строки, с которыми мы вызвали
+systemd-run+. Первый, +-p IPAccounting=yes+, включает режим учета IP-трафика
для нашей одноразовой службы (аналогично тому, как та же строка в юнит-файле
делает это для обычной службы). Второй параметр, +--wait+, приказывает
+systemd-run+ дождаться завершения работы созданной службы, после чего вывести
информацию о результатах ее работы, включая интересующую нас статистику по
IP-трафику (разумеется, только при условии, что мы включили его подсчет).
Еще интереснее получается, если применять учет IP-трафика для
\emph{интерактивных} одноразовых служб:
\begin{Verbatim}
# systemd-run -p IPAccounting=1 -t /bin/sh
Running as unit: run-u2779.service
Press ^] three times within 1s to disconnect TTY.
sh-4.4# dnf update
...
sh-4.4# dnf install firefox
...
sh-4.4# exit
Finished with result: success
Main processes terminated with: code=exited/status=0
Service runtime: 5.297s
IP traffic received: ...B
IP traffic sent: ...B
\end{Verbatim}
Мы использовали ключ +systemd-run+ +--pty+ (в краткой форме +-t+), который
открывает интерактивное псевдо-терминальное подключение к запускаемой службе~---
в нашем случае это оболочка +/bin/sh+. Таким образом, мы получаем полноценную
оболочку, с управлением фоновыми заданиями и прочими возможностями, запущенную
в рамках службы systemd. Так как для этой службы включен учет IP-трафика,
при ее завершении мы получаем суммарную статистику по входящим и исходящим
данным. (Для краткости я не~стал показывать вывод целиком, а оставил только
ключевые моменты. Если вам хочется посмотреть на полный вывод~--- попробуйте
запустить что-нибудь сами.)
Иногда бывает нужно включить учет IP-трафика для юнита, который уже запущен.
Это можно сделать командой
\begin{Verbatim}
systemctl set-property foobar.service IPAccounting=yes
\end{Verbatim}
Обратите внимание, что она не~имеет обратной силы: трафик учитывается только с
момента выполнения этой команды. Аналогичной командой (+IPAccounting=no+) учет
трафика можно отключить.
Если вы хотите подсчитывать IP-трафик сразу для всех служб, вам вовсе
не~обязательно добавлять +IPAccounting=yes+ во все юнит-файлы. Достаточно
задействовать глобальную опцию
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd-system.conf.html\#DefaultCPUAccounting=}%
{DefaultIPAccounting=} в файле +/etc/systemd/system.conf+.
\subsectiona{Фильтрация IP-трафика}
От учета трафика переходим к его фильтрации. Рассмотрим поподробнее механизм
контрольных списков IP-адресов (IP ACL), добавленный в systemd 235. Как уже
упоминалось выше, за него отвечают две директивы: +IPAddressAllow=+ и
+IPAddressDeny=+. Работают они следующим образом:
\begin{enumerate}
\item Если адрес источника входящего пакета, либо адрес назначения
исходящего пакета соответствует какому-либо из адресов хостов
или попадает в одну из подсетей, указанных в +IPAddressAllow=+,
пакет проходит свободно.
\item Если пакет не~соответствует +IPAddressAllow=+, но подпадает
под +IPAddressDeny=+ (аналогично, проверяются исходные адреса
входящих пакетов и адреса назначения исходящих), он блокируется.
\item Если пакет не~подпадает ни под одну из этих директив, он тоже
проходит свободно.
\end{enumerate}
Иными словами, +IPAddressDeny=+ является черным списком, но белый список
+IPAddressAllow=+ имеет над ним приоритет.
Посмотрим, как это работает. Поменяем предыдущий пример, чтобы получить
интерактивную оболочку, работающую в рамках одноразовой службы с настроенными
контрольными списками IP-адресов:
\begin{Verbatim}
# systemd-run -p IPAddressDeny=any -p IPAddressAllow=8.8.8.8 \
-p IPAddressAllow=127.0.0.0/8 -t /bin/sh
Running as unit: run-u2850.service
Press ^] three times within 1s to disconnect TTY.
sh-4.4# ping 8.8.8.8 -c1
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=59 time=27.9 ms
--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 27.957/27.957/27.957/0.000 ms
sh-4.4# ping 8.8.4.4 -c1
PING 8.8.4.4 (8.8.4.4) 56(84) bytes of data.
ping: sendmsg: Operation not permitted
^C
--- 8.8.4.4 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms
sh-4.4# ping 127.0.0.2 -c1
PING 127.0.0.1 (127.0.0.2) 56(84) bytes of data.
64 bytes from 127.0.0.2: icmp_seq=1 ttl=64 time=0.116 ms
--- 127.0.0.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.116/0.116/0.116/0.000 ms
sh-4.4# exit
\end{Verbatim}
Мы задали +IPAddressDeny=any+, чтобы работать по схеме белого списка: для нашей
службы разрешен обмен трафика только с теми адресами, которые перечислены в
+IPAddressAllow=+. А это, в данном примере, уже знакомый нам адрес 8.8.8.8
(указанный без маски подсети, что соответствует адресу хоста, т.е. маске +/32+),
и подсеть 127.0.0.0/8. Таким образом, служба может взаимодействовать только с
одним из DNS-серверов Google и адресами обратной петли, и больше ни с кем.
Команды, запущенные в оболочке, иллюстрируют это. Сначала мы пытаемся пинговать
8.8.8.8~--- успешно. Затем, мы пробуем пропинговать 8.8.4.4 (это другой
DNS-сервер Google, не~входящий в наш белый список), и видим ошибку <<Operation
not permitted>>. Наконец, мы пингуем адрес 127.0.0.2 (принадлежащий подсети
обратной петли), и снова успешно.
Обратите внимание на специальное значение +any+, которое мы использовали в
примере выше. Оно является сокращением для <<+0.0.0.0/0 ::/0+>>, то есть
\emph{всех} возможных адресов IPv4 и IPv6. Есть и другие сокращения. Например,
вместо +127.0.0.0/8+ мы могли бы указать более понятное обозначение +localhost+,
соответствующее <<+127.0.0.0/8 ::1/128+>>, то есть IPv4 и IPv6 подсетям обратной
петли.
Возможность настраивать контрольные списки IP-адресов независимо для каждого
юнита~--- это уже неплохо. Однако, для большего удобства, существует
возможность задавать их для целых групп юнитов, или даже для всей системы. Это
можно сделать при помощи юнитов
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.slice.html}%
{.slice} (для тех, кто не~очень хорошо разбирается в systemd: slice-юниты
предоставляют возможность организовать юниты в группы для управления системными
ресурсами\footnote{Прим. перев.: Чуть более развернутое определение:
slice-юниты соответствуют <<промежуточным>> уровням иерархии контрольных групп,
которые не~содержат непосредственно процессов, а объединяют другие +.slice+,
+.service+ и +.scope+-юниты. Последние два типа юнитов уже соответствуют
контрольным группам процессов, причем группы +.service+ создаются автоматически
при запуске служб systemd, а +.scope+~--- это специально сформированные
контрольные группы процессов, запущенных другими программами, что позволяет
<<перекидывать>> отдельные процессы в другие точки иерархии контрольных
групп. В частности, процессы, созданные в рамках пользовательских сессий, а
также деревья процессов контейнеров, автоматически перемещаются из контрольных
групп породивших их служб (например, +kdm.service+) в специальные scope-юниты
(например, +session-1.scope+).}): контрольные списки адресов юнита формируются
путем объединения списков самого юнита, а также всех slice-юнитов, в которые он
вложен.
По умолчанию, все системные службы помещаются в группу
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.special.html\#system.slice}%
{system.slice}, которая, в свою очередь, входит в корневую группу
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.special.html\#-.slice}%
{-.slice}. Соответственно, при помощи любой из этих групп можно заблокировать
\emph{все} системные службы разом. Разница состоит в том, что ограничения
+system.slice+ будут применяться только к системным службам, а +-.slice+
действует сразу на все процессы системы, включая не~только службы, но и процессы
пользовательских сеансов (объединенные в группу +user.slice+, которая находится
в корневой группе).
Воспользуемся этим:
\begin{Verbatim}
# systemctl set-property system.slice IPAddressDeny=any IPAddressAllow=localhost
# systemctl set-property apache.service IPAddressAllow=10.0.0.0/8
\end{Verbatim}
Две приведенные команды имеют очень мощный эффект: сначала мы отключаем
взаимодействие через IP (кроме обратной петли) для всех служб системы, а затем
добавляем белый список 10.0.0.0/8 (скажем, это локальная сеть вашей компании)
только для службы веб-сервера Apache.
\subsectiona{Практическое применение}
Вот несколько идей по практическому применению рассмотренных возможностей:
\begin{enumerate}
\item Механизм контрольных списков IP адресов можно рассматривать как
современную альтернативу классической технологии
\href{https://en.wikipedia.org/wiki/TCP_Wrapper}{TCP Wrapper}.
Однако, в отличие от нее, контрольные списки применяются сразу
ко всем IP-сокетам службы, и не~требуют никакой поддержки со
стороны приложения. С другой стороны, TCP Wrapper предоставляет
ряд опций, которые остутствуют в нашей схеме, в частности,
возможность указания DNS-имен вместо IP-адресов (лично я считаю
это весьма сомнительной опцией~--- выполнять сетевые операции
(разрешение имен), причем незащищенные, для того, чтобы
ограничить работу сети).
\item В некоторых аспектах наши механизмы могут заменить или хотя бы
дополнить классический брандмауэр Linux~---
NetFilter/+iptables+. На текущий момент, контрольные списки
IP-адресов systemd предоставляют гораздо меньше возможностей,
чем NetFilter, однако имеют перед ним серьезное преимущество:
они работают на уровне \emph{приложений}, а не~абстрактных
TCP/UDP-портов. Классические брандмауэры, в частности,
NetFilter, вынуждены делать предположение о принадлежности
пакетов тем или иным службам, основываясь только на номерах
портов\footnote{Прим. перев.: В ядрах Linux 2.4-2.6
существовала теоретическая возможность выборки пакетов по
идентификатору процесса (+-m owner --pid-owner PID+), однако она
была удалена в выпуске 2.6.14. Много лет спустя, в ядре 3.14 был
добавлен модуль netfilter +xt_cgroup+, позволяющий выбирать
пакеты по индексу класса, присвоенного cgroup-контроллером
+net_class+. Кстати, поддержка соответствующей опции
+NetClass=+ существовала в systemd с 227 по 229 версию, но была
удалена при миграции на
\href{https://www.kernel.org/doc/Documentation/cgroup-v2.txt}%
{cgroup v2}, где такого контроллера уже нет, зато есть
вышеописанный механизм контрольных списков.}, однако на практике
порты часто выбираются динамически. Например, клиенты
BitTorrent для передачи данных могут использовать любые удобные
им порты, что крайне затрудняет корректную выборку таких
пакетов. В случае с контрольными списками все просто: достаточно
настроить разрешения для юнита службы BitTorrent, и дело в
шляпе.
Замечу что, по большей части, сравнение NetFilter и контрольных
списков systemd~--- это сравнение теплого с мягким. Контрольные
списки ориентированы исключительно на конечные машины (клиенты и
серверы), так как работают только с локальными службами. В то
время как NetFilter отлично работает на промежуточных
маршрутизаторах, передающих чистый IP-трафик, никак
не~привязанный к их локальным процессам.
\item Контрольные списки предоставляют простой способ обеспечить
безопасность служб <<из коробки>>. Например, если вы
сопровождаете пакет службы, которая не~должна требовать доступа
к сети, добавьте в ее юнит-файл +IPAddressDeny=any+ (и, при
необходимости, +IPAddressAllow=localhost+), и она будет помещена
в песочницу, из которой не~сможет сбежать. В systemd подобные
ограничения уже введены по умолчанию для целого ряда служб,
например, для системного журнала
+systemd-journald.service+\footnote{Прим. перев.: Знаменитый
HTTP-сервер вынесен в отдельную службу
\href{https://www.freedesktop.org/software/systemd/man/systemd-journal-gatewayd.html}%
{systemd-journal-gatewayd(8)}, которая
\hreftt{https://github.com/systemd/systemd/blob/master/system-preset/90-systemd.preset}%
{отключена} по умолчанию, а в ряде дистрибутивов вообще
поставляется в отдельном опциональном пакете (в частности,
RHEL/CentOS, Debian/Ubuntu).}, менеджера логинов
+systemd-logind+ и обработчика дампов памяти
+systemd-coredump@.service+, так как мы знаем, что этим службам
ни~при каких условиях не~нужна сеть.
\item Механизм контрольных списков IP-адресов можно сочетать с
одноразовыми службами, что позволяет быстро и эффективно
изолировать произвольные команды, и даже включать их в конвееры.
Предположим, что мы не~доверяем нашему бинарнику
\hreftt{https://linux.die.net/man/1/curl}{curl} (может быть, он
модифицирован хакером, и обращается на подконтрольные ему
серверы?), но все равно хотим использовать его для загрузки
\href{http://0pointer.de/public/casync-kinvolk2017.pdf}{презентации
с моего последнего доклада по casync}, чтобы распечатать их, и
при этом быть уверенными, что он не~будет связываться ни~с кем,
кроме нужного нам сервера (а чтобы было еще интереснее и
безопаснее, включим описанный в предыдущей главе механизм
динамических пользователей):
\begin{Verbatim}
# systemd-resolve 0pointer.de
0pointer.de: 85.214.157.71
2a01:238:43ed:c300:10c3:bcf3:3266:da74
-- Information acquired via protocol DNS in 2.8ms.
-- Data is authenticated: no
# systemd-run --pipe -p IPAddressDeny=any \
-p IPAddressAllow=85.214.157.71 \
-p IPAddressAllow=2a01:238:43ed:c300:10c3:bcf3:3266:da74 \
-p DynamicUser=yes \
curl http://0pointer.de/public/casync-kinvolk2017.pdf | lp
\end{Verbatim}
\end{enumerate}
Как и в прошлой главе, в силу масштабности обсуждаемой концепции, приведенный
список применений не~претендует на полноту, а лишь является затравкой для вашего
воображения.
\subsectiona{Рекомендации сопровождающим пакетов}
Механизмы учета и контроля IP-трафика ориентированы прежде всего на системных
администраторов, а не~разработчиков. Тем не~менее, как я уже заметил выше, для
служб, которые никак и никогда не~требуют сети, целесообразно добавлять
настройку +IPAddressDeny=any+ (и опционально +IPAddressAllow=localhost+), чтобы
повысить безопасность системы сразу <<из коробки>>.
Для специализированных дистрибутивов, ориентированных на максимальную
безопасность, можно предложить более радикальный подход: добавить
+IPAddressDeny=any+ сразу в +-.slice+ или +system.slice+, чтобы администратор,
когда ему нужно выпустить в сеть какую-либо службу, вручную командовал
+systemctl set-property ... IPAddressAllow=...+. Разумеется, это вариант для
тех дистрибутивов, которые не~боятся ломать обратную совместимость.
\subsectiona{Дополнительные замечания}
И еще несколько замечаний:
\begin{enumerate}
\item Описанные механизмы учета и фильтрации IP-трафика можно совмещать
с сокет-активацией. При этом, целесообразно настраивать их
и для сокета, и для активируемой им службы, так как это
разные юниты с независимыми настройками. Обратите внимание, что
учет и фильтрация трафика, настроенные для сокет-юнита,
применяются для всех сокетов, созданных в рамках этого юнита,
включая переданные активированным службам. Как следствие, трафик
учитывается именно для юнита сокета, а не~службы. Тот факт, что
для сокетов, созданных под эгидой сокет-юнита (т.е. при
сокет-активации), и для сокетов, созданных из кода службы,
используются \emph{разные} контрольные списки, открывает весьма
интересные возможности. Например, можно настроить относительно
свободный доступ для сокета, при этом полностью запретив
IP-трафик для активируемых им служб~--- в результате эти службы
смогут взаимодейстовать с внешним миром только через переданный
им при активации сокет.
\item Учет и фильтрация IP-трафика работают только с IP-сокетами. В
частности, сокеты типа +AF_PACKET+ (так называемые <<сырые
сокеты>>) под эти ограничения не~подпадают\footnote{Прим.
перев.: Они также <<обходят>> и netfilter~--- именно поэтому
бесполезно пытаться фильтровать трафик DHCP-клиента с той же
машины.}. Поэтому целесообразно дополнять контрольные списки
опцией
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#RestrictAddressFamilies=}%
{RestrictAddressFamilies=AF\_UNIX AF\_INET AF\_INET6}.
\item Вы также можете поинтересоваться, может ли журнальная запись о
потребленных ресурсах, а также +systemd-run+ с ключом +--wait+
показывать статистику по другим видам ресурсов?
Ответ утвердительный: например, если вы зададите для юнита
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html\#CPUAccounting=}%
{CPUAccounting=yes}, то журнальная запись и +systemd-run+ будут
показывать статистику по потребленному процессорному времени. В
ближайшее время мы планируем добавить аналогичную поддержку для
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html\#IOAccounting=}%
{IOAccounting=}.
\item Обратите внимание, что учет и фильтрация IP-трафика сами могут
потреблять определенные ресурсы. Чтобы эти функции работали,
systemd вставляет в путь прохождения IP-трафика специальную
eBPF-программу. Однако, в последних версиях ядра выполнение eBPF
очень неплохо оптимизировано, и работа по оптимизации
продолжается, так что серьезных проблем с производительностью
ожидать не~стоит.
\item Учет трафика не~является иерархическим, то есть, счетчики
slice-юнита не~суммируют результаты вложенных в него юнитов. Это
одна из задач, которую мы намерены решить, однако она требует
доработки кода ядра.
\item У вас может возникнуть вопрос~--- как соотносятся механизмы
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#PrivateNetwork=}%
{PrivateNetwork=yes} и +IPAccessDeny=any+? На первый взгляд, они
работают одинаково: блокируют доступ к сети для службы. Однако,
при более пристальном рассмотрении обнаруживается ряд отличий.
Механизм +PrivateNetwork=+ реализован на основе сетевых
пространств имен ядра Linux. Он полностью изолирует от хоста
все сетевые операции службы~--- не~только обычные IP-сокеты, но
и другие виды сетевого взаимодействия (в частности, сырые
сокеты). Для этого он создает службе отдельное сетевое
пространство имен, в котором она может общаться только сама
с собой (через свой личный интерфейс обратной петли). Однако,
при использовании опции
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.unit.html\#JoinsNamespaceOf=}%
{JoinsNamespaceOf=} в ту же песочницу могут быть помещены другие
службы~--- в результате, службы из одной песочницы смогут
взаимодействовать между собой, но при этом будут изолированы от
остального мира. С другой стороны, +IPAddressAllow=+ и
+IPAccessDeny=+ действуют не~столь жестко. Прежде всего, они
работают только с IP-сокетами и IP-адресами. Кроме того, служба
с выключенным режимом +PrivateNetwork=+, но включенным
+IPAccessDeny=any+, все равно способна прочитать список сетевых
интерфейсов хоста и узнать присвоенные им адреса, хотя и
не~сможет пересылать данные по протоколу IP. В то же время, с
включенным режимом +PrivateNetwork=+ служба может видеть только
свой личный интерфейс +lo+. Короче: в зависимости от конкретной
ситуации, для изоляции службы можно использовать тот или иной
вариант, или оба сразу, или вообще ни~одного. Когда это
возможно, для максимальной безопасности лучше использовать обе
эти защиты, как это делаем мы для всех основных служб из состава
systemd.
\end{enumerate}
На этом пока все.
\newpage
\appendix
\section{FAQ (часто задаваемые вопросы)\sfnote{Перевод статьи