Добавлен перевод статьи "Dynamic Users with systemd"

This commit is contained in:
nnz1024
2017-10-26 14:40:05 +03:00
parent 0ceaa79e43
commit fb2a8ab3cd

685
s4a.tex
View File

@@ -18,6 +18,8 @@ pdfauthor={Lennart Poettering, Sergey Ptashnick}}
%\setcounter{tocdepth}{1} % А почему бы и нет? %\setcounter{tocdepth}{1} % А почему бы и нет?
% Несколько сокращений % Несколько сокращений
\newcommand{\sectiona}[1]{\section*{#1}\addcontentsline{toc}{section}{#1}} \newcommand{\sectiona}[1]{\section*{#1}\addcontentsline{toc}{section}{#1}}
\newcommand{\subsectiona}[1]{\subsection*{#1}%
\addcontentsline{toc}{subsection}{#1}}
\newcommand{\hreftt}[2]{\href{#1}{\texttt{#2}}} \newcommand{\hreftt}[2]{\href{#1}{\texttt{#2}}}
\newenvironment{caveat}[1][]{\smallskip\par\textbf{Предупреждение#1: }}% \newenvironment{caveat}[1][]{\smallskip\par\textbf{Предупреждение#1: }}%
{\smallskip\par} {\smallskip\par}
@@ -26,6 +28,8 @@ pdfauthor={Lennart Poettering, Sergey Ptashnick}}
\newcommand{\qna}[1]{\medskip\par\textbf{Вопрос: #1}\nopagebreak\par Ответ:} \newcommand{\qna}[1]{\medskip\par\textbf{Вопрос: #1}\nopagebreak\par Ответ:}
\newcommand\yousaywtf[1]{\emph{#1}} \newcommand\yousaywtf[1]{\emph{#1}}
\newcommand\yousaywtfsk[1]{\yousaywtf{#1}\medskip\par} \newcommand\yousaywtfsk[1]{\yousaywtf{#1}\medskip\par}
\newcommand\llquote{\texorpdfstring{<<}{"}}
\newcommand\rrquote{\texorpdfstring{>>}{"}}
% Настройка макета страницы % Настройка макета страницы
\setlength{\hoffset}{-1.5cm} \setlength{\hoffset}{-1.5cm}
\addtolength{\textwidth}{2cm} \addtolength{\textwidth}{2cm}
@@ -2762,6 +2766,7 @@ PrivateNetwork=yes
котором настраивается только интерфейс обратной петли. котором настраивается только интерфейс обратной петли.
\subsection{Предоставление службам независимых каталогов \texttt{/tmp}} \subsection{Предоставление службам независимых каталогов \texttt{/tmp}}
\label{sec:privatetmp}
Еще одна простая, но мощная опция настройки служб~--- +PrivateTmp=+: Еще одна простая, но мощная опция настройки служб~--- +PrivateTmp=+:
\begin{Verbatim} \begin{Verbatim}
@@ -5234,7 +5239,7 @@ RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
(network namespace), иерархию монтирования (mount namespace, также использовался (network namespace), иерархию монтирования (mount namespace, также использовался
термин filesystem namespace), иерархию контрольных групп (cgroup namespace), термин filesystem namespace), иерархию контрольных групп (cgroup namespace),
очереди сообщений POSIX и интерфейсы SysV IPC (IPC namespace), имя хоста (UTS очереди сообщений POSIX и интерфейсы SysV IPC (IPC namespace), имя хоста (UTS
namespace), независимые списки индентификаторов процессов (PID namespace) и namespace), независимые списки идентификаторов процессов (PID namespace) и
пользователей/групп (user namespace). Лежат в основе систем контейнерной пользователей/групп (user namespace). Лежат в основе систем контейнерной
изоляции, таких как LXC и Docker. Также широко используются в systemd для изоляции, таких как LXC и Docker. Также широко используются в systemd для
обеспечения безопасности~--- см. главы \ref{sec:chroots} и обеспечения безопасности~--- см. главы \ref{sec:chroots} и
@@ -5262,6 +5267,684 @@ IPC)\footnote{Прим. перев.: Как и в предыдущем случ
директивы в поставляемые по умолчанию юнит-файлы, так как эти люди лучше знают, директивы в поставляемые по умолчанию юнит-файлы, так как эти люди лучше знают,
какие именно привилегии минимально необходимы для функционирования их демонов. какие именно привилегии минимально необходимы для функционирования их демонов.
\sectiona{Динамические пользователи}
\emph{Коротко о главном: теперь вы можете настроить systemd таким образом, чтобы
он автоматически выделял новый идентификатор пользователя (UID) для службы при
ее запуске, и автоматически освобождал его при остановке службы. Такой подход
абсолютно безопасен и может также применяться в сочетании с другими технологями
systemd, в частности, одноразовыми службами (transient services), службами с
активацией через сокет и экземплярами служб.}
В выпуске
\href{https://lists.freedesktop.org/archives/systemd-devel/2017-October/039589.html}%
{systemd 235}, помимо прочих улучшений, значительно расширена поддержка
\emph{динамических пользователей} (базовая функциональность для которой
появилась еще в версии 232). Это мощная и удобная, но пока малоизвестная
технология, и в данной статье я попытаюсь исправить последний недостаток,
рассказав о ней более подробно.
Понятие \emph{пользователя} является одной из ключевых концепций безопасности
POSIX-совместимых операционных систем. Большинство созданных впоследствии
механизмов обеспечения безопасности (SELinux и другие системы мандатного
контроля доступа, capabilities, пространства имен пользователей и т.д.) так или
иначе связаны или взаимодействуют с концепцией пользователя. Даже если вы хотите
пересобрать ядро Linux, полностью отключив все возможные механизмы безопасности,
от пользователей вы, скорее всего, не~избавитесь.
Изначально, понятие пользователя было введено при создании многопользовательских
систем, рассчитанных на одновременную работу нескольких
пользователей-\emph{людей}, обеспечивая взаимную изоляцию и разделение ресурсов.
Однако, большинство современных UNIX-систем используют концепцию пользователей
иначе: несмотря на то, что реальный пользователь у них может быть только один
(или вообще ни одного!), в их списках пользователей (то есть файлах
+/etc/passwd+), записей намного больше. В наше время, большинство пользователей
типичной UNIX-системы являются \emph{системными}~--- они соответствуют
не~сидящим перед компьютером живым людям, а наборам полномочий (security
identity), с которыми запускаются системные службы (т.е. программы). Хотя
традиционные многопользовательские системы постепенно теряют свою актуальность,
лежащее в их основе понятие <<пользователь>> стало одним из краеугольных камней
в технологиях защиты UNIX-систем. Большинство служб в современной системе
работает от имени своего пользователем, которому даны минимально возможные
полномочия.
Создатели ОС Android хорошо понимали значимость концепции пользователя для
безопасности системы, и пошли еще дальше: в Android пользователи создаются
не~только для системных служб, но и для каждого графического приложения, что
позволяет обеспечить разделение ресурсов и защиту процессов разных приложений
друг от друга.
Тем не~менее, в традиционных Linux-системах концепции пользователей пока
уделяется меньше внимания. Несмотря на то, что она являтся ключевой технологией
обеспечения безопасности UNIX-системы, механизмы создания пользователей и
управления ими пока не~обладают достаточной гибкостью. В большинстве случаев,
если вы устанавливаете службу, которая выполняется от своего пользователя,
работу по созданию учетных записей берут на себя установочные скрипты RPM или
DEB-пакетов. После этого, созданный пользователь останется в системе уже
навсегда, даже если вы удалите соответствующий пакет. Большинство дистрибутивов
Linux используют для идентификаторов системных пользователей диапазон от 0 до
1000~--- не~так уж и много. Следовательно, создание пользователей является
<<дорогой>> операцией: количество идентификаторов ограничено, и не~существует
механизма для их автоматического освобождения после использования. Если вы
будете активно использовать концепцию системных пользователей, вы рано или
поздно исчерпаете доступный лимит.
У вас может возникнуть вопрос: почему системные пользователи не~удаляются при
удалении создавшего их пакета (как минимум, в большинстве дистрибутивов)?
Причиной этому является одно из важных свойств концепции пользователя (его можно
даже назвать \emph{ошибкой дизайна}): идентификаторы пользователей
сохраняются в атрибутах файлов (и некоторых других объектов, в частности,
элементов IPC). Если служба, работающая из-под отдельного системного
пользователя, создаст где-либо новый файл, то даже после остановки службы и
удаления ее пакета, числовой идентификатор ее пользователя (UID) останется в
метаданных этого файла. Если бы наша система удаляла системных пользователей
вместе с их пакетами, то рано или поздно этот идентификатор заняла бы
какая-нибудь другая служба. И это уже можно считать уязвимостью, так как файл с
данными одной службы становится доступен для другой. Как следствие, дистрибутивы
склонны избегать повторного использования UID, и каждый созданный системный
пользователь остается в системе навсегда.
Выше мы рассматривали сложившуюся к настоящему моменту ситуацию. А теперь
посмотрим, что нового вносит наша концепция динамических пользователей, и какие
проблемы она может решить.
\subsectiona{Что такое \llquote{}динамический пользователь\rrquote{}?}
Реализованный в systemd механизм динамических пользователей нацелен на упрощение
и удешевление операций создания пользователей <<на лету>>, и в перспективе
позволит значительно расширить область примнения концепции пользователей в
целом.
При создании или редактировании service-файла вашей службы, вы можете включить
для нее механизм динамических пользователей, добавив в секцию +[Service]+
директиву
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#DynamicUser=}%
{DynamicUser=yes}. После этого, при каждом запуске данной службы, для нее будет
на лету создаваться системный пользователь. При остановке службы он будет
автоматически удаляться. UID такого пользователя выбирается из диапазона
61184--65519 (при этом производится автоматическая проверка, исключающая
использование UID, занятых кем-либо еще).
Вы спросите, как же в таком случае решается вышеописанная проблема привязки
идентификаторов пользователей к файлам? Существуют как минимум два очевидных
подхода к ее решению:
\begin{enumerate}
\item Запретить службе создавать файлы, каталоги и объекты IPC.
\item Автоматически удалять созданные службой файлы, каталоги и объекты
IPC при ее остановке.
\end{enumerate}
systemd реализует одновременно обе стратегии, но применяет их к различным
областям рабочего окружения службы. А именно:
\begin{enumerate}
\item Установка +DynamicUser=yes+ также применяет директивы
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#ProtectSystem=}%
{ProtectSystem=strict} и
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#ProtectHome=}%
{ProtectHome=read-only}. В результате служба теряет возможность
что-либо писать практически во все каталоги системы, за
исключением специальных файловых систем (+/dev+, +/proc+ и
+/sys+) и временных каталогов (+/tmp+ и +/var/tmp+). (Кстати,
включать эти опции имеет смысл даже для служб, которые
не~используют режим +DynamicUser=yes+, так как это сильно
снижает возможный ущерб при взломе службы.)
\item Установка +DynamicUser=yes+ автоматически применяет директиву
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#PrivateTmp=}%
{PrivateTmp=yes}. В результате для этой службы создаются свои
собственные, изолированные от других служб экземпляры каталогов
+/tmp+ и +/var/tmp+\footnote{Прим. перев.: Технически это
реализовано следующим образом: в системных +/tmp+ и +/var/tmp+
создаются подкаталоги со специальными именами (построенными на
основе имени службы и (псевдо)случайных последовательностей
символов), принадлежащие руту и имеющие права доступа 0700 (это
граничные каталоги~--- принцип их работы описан в данной статье
ниже), а внутри них создаются подкаталоги +tmp+ с правами 0777.
Для самой службы создается пространство имен монтирования (mount
namespace), в котором эти подкаталоги bind-монтируются в +/tmp+
и +/var/tmp+ соответственно. См. также раздел
\ref{sec:privatetmp}.}, причем их жизненный цикл привязан к
жизненному циклу службы: при остановке службы удаляется
не~только ее пользователь, но и ее временные каталоги. (Опять же
замечу, что эту директиву имеет смысл применять и без
+DynamicUser=yes+, так как она тоже повышает безопасность вашей
системы.)
\item Установка +DynamicUser=yes+ автоматически применяет директиву
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#RemoveIPC=}%
{RemoveIPC=yes}, которая обеспечивает автоматическое удаление
объектов межпроцессного взаимодействия (IPC) SysV и POSIX (общая
память, очереди сообщений, семафоры), принадлежащих службе, при
ее остановке. Как следствие, жизненный цикл IPC-объектов также
привязан к жизненному циклу службы и ее пользователя. (И снова:
эту директиву имеет смысл применять и для обычных служб!)
\end{enumerate}
Использование этих четырех опций позволяет практически полностью изолировать
службу с динамическим пользователем от основной системы. Она не~может создавать
файлы или каталоги где-либо, кроме +/tmp+ и +/var/tmp+, где они будут удалены
при ее остановке, как и созданные ею объекты IPC. Таким образом, проблема
владения файлами, каталогами и объектами IPC, успешно решена.
Если вам нужно немного приоткрыть <<железный занавес>> и дать службе возможность
взаимодействовать с другими программами, это можно сделать при помощи параметра
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#RuntimeDirectory=}%
{RuntimeDirectory=}. Подкаталог с указанным в этом параметре именем создается в
каталоге +/run+ при запуске службы, принадлежит ее пользователю, и тоже
автоматически удаляется при ее остановке. Однако, при этом он открыт для доступа
других программ, так что служба может размещать в нем различные интерфейсные
объекты (например, UNIX-сокеты), жизненный цикл которых также будет привязан к
жизненному циклу службы. Например, если вы зададите для своей службы
+RuntimeDirectory=foobar+, то увидите, что при ее запуске создается каталог
+/run/foobar+, а при ее остановке он удаляется. (Как и другие описанные здесь
директивы, +RuntimeDirectory=+ прекрасно может работать и без +DynamicUser=+,
предоставляя для вашей службы автоматически создаваемый и удаляемый каталог с
правильным владельцем.)
\subsectiona{Долговременное хранение данных}
Вышеописанный способ изоляции службы, хотя и может оказаться полезен в некоторых
случаях, имеет одно существенное ограничение: служба не~может сохранить
какие-либо данные так, чтобы получить к ним доступ при последующих запусках.
Почти все системное дерево каталогов доступно ей только для чтения, а все
доступные на запись каталоги удаляются при остановке и перезапуске.
В выпуске systemd 235 это ограничение было снято: мы добавили три новых
опции~---
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#RuntimeDirectory=}%
{StateDirectory=}, +LogsDirectory=+ и +CacheDirectory=+. По большей части они
действуют аналогично вышеописанной опции +RuntimeDirectory=+, но создают
подкаталоги не~в +/run+, а в +/var/lib+, +/var/log+ и +/var/cache+
соответственно. Но самое главное их отличие состоит в том, что эти подкаталоги
\emph{не~удаляются} при остановке службы, что позволяет использовать их для
долговременного хранения данных.
Очевидный вопрос: как при этом решается вышеописанная проблема с динамическими
идентификаторами пользователей?
Для решения этой задачи мы обратились к опыту систем контейнерной изоляции.
При работе с контейнерами возникает схожая проблема: контейнеры и хост
используют частично или полностью перекрывающиеся наборы числовых идентификаторов
пользователей, а значит, пользователи хоста могут получить доступ к
файлам контейнера, которые принадлежат пользователям с таким же UID~--- а ведь
это может быть совсем другой пользователь, никак не~связанный со своим хостовым
<<двойником>> (кроме случаев, когда используются пространства имен
пользователей\footnote{Прим. перев.: В таких случаях используется механизм
отображения UID (user-remap), когда пользователям, работающим с user
namespaces (например, +dockremap+ в Docker), выдаются диапазоны UID хоста (см.
\hreftt{http://man7.org/linux/man-pages/man5/subuid.5.html}{/etc/subuid}), на
которые отображаются UID гостевой системы. Это решает проблему пересечения UID
хоста и гостя. Без присвоения такого диапазона гость будет иметь только один
UID (в пространстве гостя он может быть любым, даже нулевым), который для хоста
его файловой системы) будет виден как UID пользователя контейнера.}).
Особенно неприятно, если это происходит с исполняемым файлом, имеющим
+suid+-бит~--- тогда речь идет уже не~просто о доступе к чужим данным, а о
потенциальной возможности повышения привилегий. Чтобы предотвратить подобные
ситуации, системы управления контейнерами обычно помещают корневые системы
гостей в специальный \emph{граничный} каталог, имеющий ограниченные права
доступа (обычно: права 0700, владелец +root:root+). При этом,
непривилигерованные пользователи хоста уже не~могут добраться до файлов,
принадлежащих их контейнерным <<двойникам>>, просто потому, что не~имеют доступа
ко всему, что лежит в граничном каталоге. В UNIX, чтобы получить доступ к файлу,
вы должны иметь доступ ко всем каталогам по пути к нему, начиная с корневого.
Как это работает для наших служб с динамическими пользователями? Предположим,
что вы указали +StateDirectory=foobar+, но \emph{не~включали} режим
+DynamicUser=+. В момент запуска такой службы, для нее будет создан каталог
+/var/lib/foobar+, принадлежащий пользователю, от которого запускается
служба\footnote{Прим. перев.: Если режим +DynamicUser=+ отключен, пользователь
службы определяется директивой
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#User=}%
{User=}. Более подробно ее роль при включенном и выключенном режиме
динамических пользователей рассмотрена \hyperref[itm:setuser]{ниже}.}. Этот
каталог и его содержимое \emph{не~удаляются} после завершения службы. Если же мы
включим для нашей службы режим +DynamicUser=+, алгоритм станет немного
сложнее~--- теперь +/var/lib/foobar+ уже не~каталог, а принадлежащая руту
символьная ссылка на каталог +/var/lib/private/foobar+, который принадлежит
динамическому пользователю, выделенному для службы. Каталог +/var/lib/private+
играет роль граничного: он принадлежит +root:root+, и имеет права 0700. При
остановке службы, и символьная ссылка, и каталог, на который она указывает,
не~удаляются. Каталог продолжает принадлежать теперь уже освобожденному
идентификатору пользователя, однако граничный каталог не~позволит получить к
нему доступ ни пользователям хоста, ни другим службам, получившим тот же UID.
Следующий вопрос: если граничный каталог столь успешно защищает подкаталог с
данными службы, то как сама служба сможет получить к ним доступ? Для этого,
служба запускается в специально модифицированном пространстве имен точек
монтирования (mount namespace): помимо уже упомянутых хитростей с монтированием
+/tmp+ и +/var/tmp+\footnote{Прим. перев.: Строго говоря, <<хитростей>> там
гораздо больше~--- упомянутые выше директивы +ProtectSystem=strict+ и
+ProtectHome=read-only+ предполагают перемонтирование целого ряда каталогов,
включая корневой. Но к теме это действительно не~относится.}, добавляется
+tmpfs+, смонтированная в +/var/lib/private+ и доступная только на чтение, с
правами, разрешающими чтение из этого каталога (0755). Внутри этой файловой
системы создан каталог +foobar+, в который bind-монтируется каталог
+/var/lib/private/foobar+ с хоста. В результате и для хоста, и для службы
содержимое этого каталога находится по одному и тому же пути, но при этом <<с
точки зрения>> службы каталог +/var/lib/private+ уже не~является
ограничительным, и в нем отсутствуют подкаталоги, принадлежащие другим службам,
что обеспечивает полную изоляцию служб друг от друга. Символьная сслыка
+/var/lib/foobar+ позволяет не~задумываться о том, используется граничный
каталог или нет: и при включенном, и при выключенном режиме динамических
пользователей, каталог с данными программы доступен по указанному пути.
Назревает очередной вопрос. Предположим, что мы запустили службу от динамического
пользователя и с настроенным каталогом для хранения данных. Она получила
некоторый UID (назовем его $X$), которому принадлежит этот каталог. Потом мы
перезапустили службу, и она получила новый UID $Y\neq X$. Что тогда произойдет?
Неужели каталог и его содержимое будут все еще принадлжать UID $X$, и наша
служба уже не~сможет получить к ним доступ? Конечно же, нет~--- systemd
рекурсивно поменяет владельца для каталога и его содержимого.
Разумеется, операция рекурсивной смены владельца (+chown()+) для дерева
каталогов может оказаться весьма затратной (хотя, по моему личному опыту, для
большинства служб это не~так критично, как кажется на первый взгляд), и поэтому
мы ввели две оптимизации, сводящие к минимуму вероятность такой операции.
Во-первых, systemd начинает подбор подходящего UID с некоторого значения,
полученного путем хеширования имени службы. Таким образом, если имя службы
не~поменялось, то при следующем запуске она, скорее всего, получит тот же UID. В
результате, необходимость в +chown()+ отпадает (разумеется, после
соответствующих проверок). Во-вторых, если указанный каталог уже существует, и
его владельцем является неиспользуемый UID из динамического диапазона, то службе
присваивается именно этот UID, что также увеличивает шансы избежать +chown()+.
(На самом деле, выделенный сейчас диапазон на четыре с лишним тысячи UID не~так
уж и велик, и при активном применении динамических пользователей рано или поздно
появятся ситуации, когда обойтись без +chown()+ уже не~удастся.)
Директивы +CacheDirectory=+ и +LogsDirectory=+ работают по аналогии со
+StateDirectory=+. Единственное их отличие состоит в том, что они управляют
подкаталогами в +/var/cache+ и +/var/log+, и используют граничные каталоги
+/var/cache/private+ и +/var/log/private+ соответственно.
\subsectiona{Примеры}
Итак, мы ознакомились с теорией. Попробуем теперь посмотреть, как все это
работает на практике. Простой пример:
\begin{Verbatim}
# cat > /etc/systemd/system/dynamic-user-test.service <<EOF
[Service]
ExecStart=/usr/bin/sleep 4711
DynamicUser=yes
EOF
# systemctl daemon-reload
# systemctl start dynamic-user-test
# systemctl status dynamic-user-test
dynamic-user-test.service
Loaded: loaded (/etc/systemd/system/dynamic-user-test.service; static; vendor preset: disabled)
Active: active (running) since Fri 2017-10-06 13:12:25 CEST; 3s ago
Main PID: 2967 (sleep)
Tasks: 1 (limit: 4915)
CGroup: /system.slice/dynamic-user-test.service
└─2967 /usr/bin/sleep 4711
Okt 06 13:12:25 sigma systemd[1]: Started dynamic-user-test.service.
# ps -e -o pid,comm,user | grep 2967
2967 sleep dynamic-user-test
# id dynamic-user-test
uid=64642(dynamic-user-test) gid=64642(dynamic-user-test) groups=64642(dynamic-user-test)
# systemctl stop dynamic-user-test
# id dynamic-user-test
id: dynamic-user-test: no such user
\end{Verbatim}
Мы создали юнит-файл службы с включенным режимом +DynamicUser=+, запустили эту
службу, убедились, что она работает нормально, запросили данные о пользователе,
от которого она запущена (в нашем случае его имя совпадает с именем службы~---
systemd использует его автоматически, если оно соответствует синтаксису имени
пользователя, и вы не~указали явно другого имени), остановили службу и
убедились, что такого пользователя больше не~существует.
Уже неплохо, правда? Следующий шаг~--- повторим то же самое с интерактивной
\emph{одноразовой} службой. Для тех, кто не~очень хорошо разбирается в тонкостях
systemd: одноразовая служба (transient service) создается и запускается <<на
лету>>, без каких-либо конфигурационных файлов~--- например, с помощью программы
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd-run.html}%
{systemd-run}, которую можно запустить непосредственно из командной оболочки.
Короче: запуск службы без предварительного создания юнит-файла.
\begin{Verbatim}
# systemd-run --pty --property=DynamicUser=yes --property=StateDirectory=wuff /bin/sh
Running as unit: run-u15750.service
Press ^] three times within 1s to disconnect TTY.
sh-4.4$ id
uid=63122(run-u15750) gid=63122(run-u15750) groups=63122(run-u15750) context=system_u:system_r:initrc_t:s0
sh-4.4$ ls -al /var/lib/private/
total 0
drwxr-xr-x. 3 root root 60 6. Okt 13:21 .
drwxr-xr-x. 1 root root 852 6. Okt 13:21 ..
drwxr-xr-x. 1 run-u15750 run-u15750 8 6. Okt 13:22 wuff
sh-4.4$ ls -ld /var/lib/wuff
lrwxrwxrwx. 1 root root 12 6. Okt 13:21 /var/lib/wuff -> private/wuff
sh-4.4$ ls -ld /var/lib/wuff/
drwxr-xr-x. 1 run-u15750 run-u15750 0 6. Okt 13:21 /var/lib/wuff/
sh-4.4$ echo hello > /var/lib/wuff/test
sh-4.4$ exit
exit
# id run-u15750
id: run-u15750: no such user
# ls -al /var/lib/private
total 0
drwx------. 1 root root 66 6. Okt 13:21 .
drwxr-xr-x. 1 root root 852 6. Okt 13:21 ..
drwxr-xr-x. 1 63122 63122 8 6. Okt 13:22 wuff
# ls -ld /var/lib/wuff
lrwxrwxrwx. 1 root root 12 6. Okt 13:21 /var/lib/wuff -> private/wuff
# ls -ld /var/lib/wuff/
drwxr-xr-x. 1 63122 63122 8 6. Okt 13:22 /var/lib/wuff/
# cat /var/lib/wuff/test
hello
\end{Verbatim}
В приведенном примере, мы запускаем интерактивную оболочку +/bin/sh+ как
одноразовую службу +run-u15750.service+ (+systemd-run+ выбрал это имя
автоматически, так как мы не~указали имя службы явно\footnote{Прим. перев.: Это
можно было бы сделать параметром +systemd-run+ +--unit=+.}) под динамическим
пользователем, имя которого, как и в предыдущем примере, унаследовано от имени
службы. Так как мы задали +StateDirectory=wuff+, то каталог для долговременного
хранения данных нашей службы должен быть доступен под именем +/var/lib/wuff+. В
интерактивной оболочке, запущенной в рамках службы, команда +ls+ показывает
граничный каталог +/var/lib/private+ и его содержимое, а также символьную ссылку
+/var/lib/wuff+, указывающую на его подкаталог +wuff+. Наконец, перед
завершением оболочки, мы создаем там тестовый файл. Вернувшись в нашу исходную
оболочку, мы проверяем, существует ли еще пользователь, выделенный для нашей
службы~--- нет, он автоматически удален в момент завершения службы (оболочки).
При помощи аналогичных команд +ls+ мы снова проверяем каталог долговременного
хранения данных службы, на этот раз с хоста. Видим мы почти то же самое, с
двумя исключениями: во-первых, пользователь и группа, владеющие каталогом и его
содержимым, отображаются уже в виде числовых идентификатов, а не~имен. Это
обусловлено тем, что пользователь (то есть ассоциация числового идентификатора с
некоторым именем) был удален в момент завершения службы. Во-вторых, отличаются
права доступа к граничному каталогу: 0755 (читать могут все) изнутри службы, и
0700 (читать может только владелец, т.е. рут)~--- с хоста.
А теперь попробуем запустить еще одну одноразовую службу, указав ей тот же
каталог с данными:
\begin{Verbatim}
# systemd-run --pty --property=DynamicUser=yes --property=StateDirectory=wuff /bin/sh
Running as unit: run-u16087.service
Press ^] three times within 1s to disconnect TTY.
sh-4.4$ cat /var/lib/wuff/test
hello
sh-4.4$ ls -al /var/lib/wuff/
total 4
drwxr-xr-x. 1 run-u16087 run-u16087 8 6. Okt 13:22 .
drwxr-xr-x. 3 root root 60 6. Okt 15:42 ..
-rw-r--r--. 1 run-u16087 run-u16087 6 6. Okt 13:22 test
sh-4.4$ id
uid=63122(run-u16087) gid=63122(run-u16087) groups=63122(run-u16087) context=system_u:system_r:initrc_t:s0
sh-4.4$ exit
exit
\end{Verbatim}
Как видим, +systemd-run+ сгенерировал для этой службы уже другое имя, которое
перешло и к ее пользователю, однако числовой идентификатор пользователя остался
прежним~--- systemd подхватил UID владельца каталога с данными (предварительно
убедившись, что он больше никем не~используется). Этим иллюстрируется
вышеописанная оптимизация алгоритма выбора UID (цикл выбора идентификатора
начинается с UID владельца существующего каталога с данными): рекурсивного
выполнения +chown()+ удалось избежать.
Надеюсь, вышеприведенные примеры помогли вам разобраться и в самой идее, и в
особенностях ее реализации.
\subsectiona{Практическое применение}
Итак, мы рассмотрели, как включить механизм динамических пользователей для
юнита, и разобрались, как он реализован. Самое время попытаться понять, где и
для чего он может применяться.
\begin{itemize}
\item Одно из главных достоинств технологии динамического выделения
UID~--- возможность запускать службы с урезаннмыми привилегиями
(то есть, от отдельного пользователя), не~оставляя в системе
никаких артефактов. Системный пользователь создается и
используется, но после применения автоматически удаляется, и
повторное применение его UID не~несет никаких рисков для
безопасности. Мы можем одной командой запускать одноразовые
службы для выполнения каких-либо операций, изолируя их под
отдельным идентификатором пользователя, без необходимости
вручную создавать и удалять учетную запись, и не~расходуя
доступные UID попусту.
\item Во многих случаях, запуск службы уже не~требует предварительной
подготовки со стороны пакетного менджера. Другими словами,
большинство операций +useradd+/+mkdir+/+chown+/+chmod+,
выполняемых пост-инсталляционными скриптами пакетов, а также
дополнительные конфигурационные файлы в
\hreftt{https://www.freedesktop.org/software/systemd/man/sysusers.d.html}%
{sysusers.d} и
\hreftt{https://www.freedesktop.org/software/systemd/man/tmpfiles.d.html}%
{tmpfiles.d} становятся необязательными, так как эти операции
выполняются автоматически благодаря директивам +DynamicUser=+ и
+StateDirectory=+/+CacheDirectory=+/+LogsDirectory=+, причем
не~при установке/удалении пакета, а непосредственно при
запуске/остановке службы.
\item Сочетание технологий динамических пользователей и одноразовых
служб предоставляет простой механизм изоляции приложений.
Например, предположим, что мы не~доверяем бинарнику +sort+.
Мы можем поместить его в простую и надежную песочницу
динамического пользователя при помощи +systemd-run+, и при этом
сохранить возможность подключать ее через каналы (pipelines) к
другим программам. Простенький пример конвейера, второй элемент
которого запущен от динамического пользователя (который
уничтожается после завершения конвейера):
\begin{Verbatim}
# cat some-file.txt | systemd-run --pipe --property=DynamicUser=1 sort -u | \
grep -i foobar > some-other-file.txt
\end{Verbatim}
\item Сочетая технологию динамических пользователей и экземпляров
служб\footnote{Прим перев.: Более подробно работа с экземплярами
служб рассмотрена в главе~\ref{sec:instances}.}, можно получить
гибкой и полностью автоматический механизм управления
идентификаторами пользователей. Допустим, мы создаем шаблон
службы +/etc/systemd/system/foobard@.service+:
\begin{Verbatim}
[Service]
ExecStart=/usr/bin/myfoobarserviced
DynamicUser=1
StateDirectory=foobar/%i
\end{Verbatim}
Теперь предположим, что вы запускаете экземпляр этой службы
для одного из своих клиентов:
\begin{Verbatim}
# systemctl enable foobard@customerxyz.service --now
\end{Verbatim}
Готово! (Надеюсь, понятно, что эту операцию можно повторять
многократно, подставляя каждый раз вместо +customerxyz+
идентификаторы различных клиентов.)
\item Сочетая технологии динамических пользователей и сокет-активации,
вы легко можете получить систему, где каждое входящее соединение
обслуживается экземпляром процесса, работающим в песочнице
динамически выделенного UID\footnote{Прим перев.: Здесь идет
речь о сокет-активации <<в стиле inetd>>, когда на каждое
соединение создается экземпляр службы. Более подробно она
обсуждается в главе~\ref{sec:inetd}.}. Пример конфигурации
сокета +waldo.socket+:
\begin{Verbatim}
[Socket]
ListenStream=2048
Accept=yes
\end{Verbatim}
И соответствующего ему шаблона службы +waldo@.service+:
\begin{Verbatim}
[Service]
ExecStart=-/usr/bin/myservicebinary
DynamicUser=yes
\end{Verbatim}
В результате, systemd будет слушать TCP-порт 2048, и на каждое
входящее соединение создавать новый экземпляр +waldo@.service+,
каждый раз с новым идентификатором пользователя, обеспечивающим
его изоляцию от остальных экземпляров.
\item Динамическое выделение пользователей хорошо сочетается с
<<системами без состояния>> (state-less systems), то есть
системами, которые запускаются с пустыми каталогами +/etc+ и
+/var+. Динамическим выделение UID и директивы
+StateDirectory=+, +CacheDirectory=+, +LogsDirectory=+ и
+RuntimeDirectory=+ позволяет автоматических создавать
пользователя и необходимые службе каталоги непосредственно перед
ее запуском.
\end{itemize}
Динамическое выделение пользователей~--- масштабная и глобальная концепция, и ее
применение, разумеется, не~ограничивается приведенным списком. Этот список~---
всего лишь попытка разбудить ваше воображение, дать начальный импульс к
размышлениям.
\subsectiona{Рекомендации сопровождающим пакетов}
Я уверен, что для значительной доли служб, поставляемых в современных
дистрибутивах, опции +DynamicUser=+, +StateDirectory=+ и т.д., могут оказаться
весьма полезны. Во многих случаях они позволят вообще отказаться от +post-inst+
скриптов, а также конфигурационных файлов в +sysusers.d+ и +tmpfiles.d+,
объединив все необходимые настройки непосредственно в юнит-файле. Так что, если
вы сопровождаете какой-либо пакет со службой, пожалуйста, рассмотрите
возможность использования этих директив. Тем не~менее, существует ряд ситуаций,
когда данные директивы неэффективны или неприменимы:
\begin{enumerate}
\item Службы, которым нужно писать куда-либо за пределами разрешенного
списка каталогов (+/run/<package>+, +/var/lib/<package>+,
+/var/cache/<package>+, +/var/log/<package>+, +/var/tmp+, +/tmp+,
+/dev/shm+) не~совместимы с описанным подходом. Например, демон,
обновляющий систему~--- ему, как минимум, необходим доступ на
запись в +/usr+.
\item Службы, которые управляют набором процессов, запущенных от
различных пользователей, например, некоторые SMTP-серверы. Если
ваша служба построена по принципу \emph{суперсервера}, то
управление идентификаторами пользователей для своих процессов
она должна осуществлять сама~--- systemd не~должен в это
вмешиваться.
\item Службы, запускаемые от рута, и вообще требующие расширенных
привилегий.
\item Службы, котороые должны запускаться в пространстве имен
монтирования хоста (например, если служба должна создавать точки
монтирования, видимые для всей системы). Как уже упоминалось
выше, +DynamicUser=+ задействует механизмы +ProtectSystem=+,
+PrivateTmp=+ и т.д., которые основаны на запуске службы в
отдельном пространстве имен монтирования.
\item В вашем дистрибутиве пока нет свежих версий systemd: 232
(поддержка +DynamicUser=+) или 235 (поддержка +StateDirectory=+
и аналогичных ей опций).
\item Правила создания пакетов для вашего дистрибутива не~разрешают
подобный подход. Уточните эти моменты в правилах и, при
необходимости, обсудите данный вопрос в рассылке вашего
дистрибутива.
\end{enumerate}
\subsectiona{Дополнительные замечания}
Еще несколько замечаний, непосредственно относящихся к обсуждаемой теме:
\begin{enumerate}
\item Обратите внимание, что процесс выделения и удаления динамических
пользователей никак не~затрагивает +/etc/passwd+. Добавление
пользователя в базу данных оусществляется при помощи NSS-модуля
glibc
\hreftt{https://www.freedesktop.org/software/systemd/man/nss-systemd.html}%
{nss-systemd}\footnote{Прим. перев.: Разумеется, чтобы
преобразование <<имя-идентификатор>> для пользователя и его
группы работало корректно, это модуль должен быть указан в
строках +passwd:+ и +group:+ файла +/etc/nsswitch.conf+. Примеры
приведены на странице руководства модуля.}, и эта информация
никогда не~попадает на диск.
\item В традиционных UNIX-системах, демоны сбрасывают привилегии с рута
до обычного пользователя самостоятельно, в то время как механизм
динамических пользователей предполагает, что этим должен
заниматься systemd. В версии systemd 235 добавлена возможность
совместить механизм динамических пользователей с самостоятельным
сбросом привилегий процессом службы. Для этого, включите опцию
+DynamicUser=+, а в опции
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#User=}%
{User=} укажите имя пользователя, в которого ваша служба
перевоплотится (+setuid()+) после инициализации. В результате, в
момент запуска службы systemd создаст динамического пользователя
с указанным именем. Далее, в директиве
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.service.html\#ExecStart=}%
{ExecStart=} непосредственно перед командой запуска укажите
символ <<+!+>>. После этого, пользователь для службы будет
создаваться, но запускаться она будет от рута~--- systemd
будет считать, что служба сама сбросит полномочия в ходе
инициализации. Например: +ExecStart=!/usr/bin/mydaemond+. При
этом, регистрация соответствия имени и идентификатора
пользователя в базе данных производится, как и прежде, при
запуске службы, и поэтому процесс демона сможет без проблем
преобразововать имя пользователя в UID.
\item У вас может возникнуть вопрос: почему для динамического выделения
UID выбран именно диапазон 61184--65519 (в шестнадцатеричной
записи 0xEF00--0xFFEF)? Он был выбран потому, что большинство
дистрибутивов (например, Fedora) используют для обычных
пользователей идентификаторы ниже 60000, и мы не~хотим
переступать эту границу. Также мы делаем небольшой отступ от
65535, так как некоторые UID вблизи этого значения имеют
специальное значение (65535 часто трактуется как
<<некорректный>> или <<отсутствующий>> UID, так как является
представлением числа $-1$ в 16-битном целом типе; 65534 обычно
соответствует пользователю +nobody+, и некоторые подсистемы ядра
отображают в это значение <<посторонние>>
идентификаторы\footnote{Прим. перев.: Например, изнутри
пространства имен пользователей, все пользователи хоста, кроме
создателя пространства, выглядят как +nobody+.}). И наконец, мы
не~хотим выходить за пределы 16-битного целого типа. Даже с
распространением технологии пространств имен пользователей,
контейнерам все равно не~нужен весь диапазон значений,
предоставляемый 32-битным целым типом, который используется в
ядре Linux для UID. Там не~менее, очевидно, что контейнеры
должны поддерживать весь 16-битный диапазон~--- как минимум,
из-за +nobody+. (Если честно, я считаю выделение 64 тысяч
идентификаторов на контейнер оптимальным вариантом: верхние 16
бит из 32-битного UID можно использовать как идентификатор
контейнера, в том время как нижние будут соответствовать
идентификатору пользователя в этом контейнере\ldots{} Надеюсь,
вы не~потеряли нить рассуждений.) И, не~дожидаясь вашего
вопроса: пока нет никакого способа изменить этот диапазон~---
его границы заданы в исходном коде. Но когда-нибудь мы
обязательно добавим соответствующие настройки.
\item Вы можете поинтересоваться, что произойдет, если вы уже
используете идентификаторы из диапазона 61184--65519 для других
целей? systemd должен обработать такую ситуацию корректно, если
эти идентификаторы зарегистрированы в базе даных пользователей:
выбрав UID, systemd проверят, не~используется ли он кем-то, и
если он занят, выбирает другой~--- до тех пор, пока не~найдет
свободный. Проверка производится прежде всего при помощи
функций NSS. Также просматриваются списки объектов IPC, и
их владельцы проверяются на совпадение с нашим кандидатом. Таким
образом, systemd избегает использования UID, занятых кем-то еще.
Тем не~менее, это сокращает набор доступных идентификаторов, и в
худшем случае выделение пользователя может завершиться ошибкой
из-за отсутствия свободных UID в рабочем диапазоне.
\item Если имя для выделяемого пользователя не~указано явно, systemd
пытается вывести его из имени службы. Однако, далеко не~каждое
корректное имя службы является также корректным именем
пользователя, и чтобы обойти это, используется случайное имя.
Возможно, вам будет удобнее задать имя пользователя вручную~---
используйте для этого директиву +User=+.
\item \label{itm:setuser}
Если вы используете +User=+ в сочетании с +DynamicUser=on+, но
пользователь с указанным именем уже существует, то для службы
будет использован именно он, а механизм динамического выделения
пользователя для этой службы автоматически отключится. Таким
образом, упрощается переход между статическими и динамическими
пользователями: вы указываете нужное вам имя в +User=+, и пока
этот пользователь существует в системной базе, система будет
использовать его, и лишь при отутствии такого пользователя он
будет создаваться в динамическом режиме. Также это может быть
полезно в других ситуациях, например, чтобы подготовить службы,
использующие динамических пользователей, к возможности перехода
на статические UID, скажем, чтобы применить к ним квоты файловой
системы.
\item systemd всегда выделяет вместе с пользователем еще и группу, с
тем же самым значением идентификатора (UID = GID).
\item Если бы ядро Linux имело механизм наподобие +shiftfs+, то есть
способ смонтировать существующий каталог куда-либо с подменой
UID/GID по некоторому правилу, задаваемому при монтировании, это
значительно упростило бы реализацию работы +StateDirectory=+ в
сочетании с +DynamicUser=+, в частности, позволив отказаться от
рекурсивной смены владельца, и просто монтировать каталог с
хоста в пространство имен гостя, подменив владельца каталога на
UID/GID службы. Однако я не~питаю больших надежд на подобный
вариант, так как все работы в этой области сейчас завязаны на
пространство имен пользователей~--- механизм, который
\emph{никак не~используется} в обсуждаемой технологии (есть
мнение, что он создает гораздо больше проблем, чем решает, хотя
вы можете с этим и не~согласиться).
\end{enumerate}
На сегодня все!
\appendix \appendix
\section{FAQ (часто задаваемые вопросы)\sfnote{Перевод статьи \section{FAQ (часто задаваемые вопросы)\sfnote{Перевод статьи