Особенность отображения UID/GID пользователя в rootless podman контейнере на пространство имён хоста
Привет, Хабр!
Давеча я приметил в Интернете, что коллеги отображают UID/GID своих сервисов в rootless podman контейнерах, начиная с круглого числа, например --uidmap=0:1000000:65536 для первого, --uidmap=0:2000000:65536 для другого, --uidmap=0:3000000:65536 для третьего и т.д. В общем-то, в этом нет ничего предосудительного, ведь задача таким образом решается без пересечения пространства имён. Но и причина, по которой так делают, тоже понятна — перестраховаться. Давайте разберёмся, как отображать UID-в-UID в rootless podman:
Пусть по условию задачи сервис в контейнере не запустится, если смонтированная в него хостовая структура папок принадлежит не тем хостовым UID/GID, на которые отображаются контейнерные UID/GID. Для примера возьмём сервис DNS прокси unbound из этого проекта.
В данном образе процесс unbound внутри контейнера принадлежит UID 9898. Допустим, файлы /etc/subuid и /etc/subgid предоставляют нашему пользователю 512 блоков по 65536 идентификаторов:
myuser:100000:65536myuser:165536:33488896
Это равносильно такой записи:
myuser:100000:33554432
Но мы разбиваем на 2 диапазона для того, чтобы далее увидеть, заступаем мы на следующий диапазон при запуске контейнера или нет.
Для одного и того же имени пользователя (myuser) podman использует все доступные диапазоны, как один.
Структура файлов
/etc/sub{u,g}idтакова:имя:индекс:количество. Индекс есть первый элемент из количества (запрошенных идентификаторов). Смешать диапазоны, к счастью, не получится — newuidmap выдаст invalid internal status при запуске контейнера. Оставлять бреши в промежутках можно, так как podman их всё равно сольёт в один. Podman кеширует эти значения, но обновить кеш после изменения в этих файлах можно командойpodman system migrate.
Допустим, вы создали в домашней папке структуру папок для нашего примера:
mkdir -p unbound3/{etc,log,dns} && cp /usr/share/dns/* unbound3/dns && cp podmaniac/unbound/docker_files/* unbound3/etc
Вопрос: какому хостовому UID/GID должны принадлежать файлы и папки (за исключением root.hints, который обязательно принадлежит хостовому 0:0), чтобы наш контейнер запустился?
Допустим, мы создаём и запускаем контейнер такой командой (проброс портов опущен для краткости):
podman run \ --rm -it \ --cap-drop=ALL \ --cap-add=NET_BIND_SERVICE \ --cap-add=SETUID \ --cap-add=SETGID \ --security-opt=no-new-privileges \ --log-driver=journald \ --name=unbound3 \ -v $HOME/unbound3/etc:/usr/local/etc/unbound:rw \ -v $HOME/unbound3/dns:/opt/unbound/dns:rw \ -v $HOME/unbound3/log:/opt/unbound/log:rw \ --tmpfs /tmp:rw,noexec,nosuid,size=64m \ --tmpfs /run:rw,noexec,nosuid,size=16m \ --tmpfs /var/run,rw,noexec,nosuid,size=16m \ localhost/unbound3
Это пространство имён по умолчанию. В нём root контейнера (uid 0) отображается на идентификатор хостового пользователя системы (у меня это 1000). Остальные же, начиная с uid 1, отображаются на все доступные идентификаторы в /etc/sub{u,g}id из всех диапазонов и становятся доступными пространству имён данного контейнера.
Посмотрим актуальное отображение командой:
$ cat /proc/$(podman inspect -f '{{.State.Pid}}' unbound3)/uid_map 0 1000 1 1 100000 65536 65537 165536 33488896
Видим 3 отображения:
-
начиная с root (0) контейнера включительно, отображаем на хостовые, начиная с 1000 включительно последовательно 1 штуку
-
начиная с uid 1 контейнера включительно, отображаем на хостовые, начиная с 100000 включительно последовательно 65536 штук
-
начиная с uid 65537 контейнера включительно, отображаем на хостовые, начиная с 165536 включительно последовательно 33488896 штук
То есть пользователь в контейнере с UID 1 это пользователь с UID 100000 на хосте, UID 2 контейнера это UID 100001 хоста, UID 9898 контейнера это 109897 хоста. Поэтому ответ: файлы и папки должны принадлежать пользователю с UID 109897.
По умолчанию в контейнер отображаются все подчинённые (т.е. указанные в
/etc/sub{u,g}idдля данного пользователя) идентификаторы. Вы можете запустить несколько таких контейнеров, изменяя название контейнера (--name=unbound4,--name=unbound5, т.д.) и, если нужно, хостовые порты (-p 20053:53/tcp,-p 30053:53/tcp, т.д.), и для всех этих контейнеров хостовые UID/GID будут одинаковыми, т.е. пространство подчинённых имён по умолчанию одно на всех.
Допустим, теперь мы хотим выделять контейнеру лишь ограниченное количество идентификаторов в количестве 65536 штук. Для этого к вышеуказанной команде podman run добавьте аргументы:
--uidmap=0:0:65536 \ --gidmap=0:0:65536 \
структура значения данных аргументов
индекс_в_контейнере:индекс_промежуточный:количество. UID это индекс. С данными аргументами вывод командыpodman inspect unbound3 | jq '.[0].HostConfig.IDMappings'больше неnull, и отображение больше не прямое, а через промежуточные идентификаторы. По индексу 0 промежуточных идентификаторов всегда находится хостовый UID пользователяmyuser(т.е. 1000). Т.е. промежуточный UID всегда лежит вне диапазона, который вы задаёте аргументами uidmap/gidmap, а именно: на один индекс младше. Если в файлах/etc/sub{u,g}idдля пользователяmyuserмы начинаем с 100000, как в нашем случае, то это означает отображение хостового UID 100000 на промежуточный UID 1, и далее уже по порядку.
Отображение:
$ cat /proc/$(podman inspect -f '{{.State.Pid}}' unbound3)/uid_map 0 1000 1 1 100000 65535
Как видим, UID 1 это UID 100000 на хосте, UID 2 это UID 100001, UID 9898 это UID 109897 хоста. Поэтому ответ: аналогично 109897.
Допустим, мы хотим избежать использования промежуточного UID 0. Для этого поменяем значения аргументов:
--uidmap=0:1:65536 \ --gidmap=0:1:65536 \
И это даст нам самое красивое отображение:
$ cat /proc/$(podman inspect -f '{{.State.Pid}}' unbound3)/uid_map 0 100000 65536
Мы не используем промежуточный UID 0. Теперь пользователь с UID 0 в контейнере это пользователь с UID 100000 на хосте, UID 1 контейнера это UID 100001 хоста, UID 9898 контейнера это 109898 хоста. Поэтому ответ: файлы и папки, кроме root.hints, должны принадлежать пользователю с UID 109898. Самое приятное здесь то, что мы не заступаем на диапазон из 65536 идентификаторов следующего контейнера.
Убедимся в этом, идя от противного: запустите первый контейнер с аргументами:
--uidmap=0:2:65536 \ --gidmap=0:2:65536 \ --name=unbound3
а второй с аргументами:
--uidmap=0:65537:65536 \ --gidmap=0:65537:65536 \ --name=unbound4 \
Порты между контейнерами, если они всё же пробрасываются, должны различаться.
Сравните их отображения:
$ cat /proc/$(podman inspect -f '{{.State.Pid}}' unbound3)/uid_map 0 100001 65535 65535 165536 1
и
$ cat /proc/$(podman inspect -f '{{.State.Pid}}' unbound4)/uid_map 0 165536 65536
Как видите, пользователь с UID 65535 в первом контейнере является рутом (0) во втором контейнере в рамках пространства имён хоста.
В завершение рассмотрим ещё один способ: аргумент --userns=keep-id:uid=9898,gid=9898 вместо аргументов uidmap и gidmap. С данным аргументом podman смещает взаимное отображение промежуточных и хостовых UID от 0 до заданного (9898) включительно влево, и тогда выпавшее значение хостового UID по индексу 0 промежуточного (а мы помним, что это UID хостового пользователя 1000) становится на место промежуточного с UID 9898. То есть диапазон в /etc/sub{u,g}id разрывается надвое заданным UID, а заданный UID отображается на UID вашего пользователя в системе. Поэтому в данном случае ответ: папки должны принадлежать хостовому пользователю, который выполняет команду podman run, т.е. 1000.
Вот так выглядит отображение в данном последнем случае:
$ cat /proc/$(podman inspect -f '{{.State.Pid}}' unbound3)/uid_map 0 100000 9898 9898 1000 1 9899 109898 55638 65537 165536 33554432
Как видите, он отображает все доступные в /etc/sub{u,g}id идентификаторы в контейнер, что делает невозможным избежать пересечения пространства имён между контейнерами.
На этом рассмотрение особенностей вычисления хостовых UID, соответствующих контейнерным, в rootless podman версии 4.3.1, закончен.
ссылка на оригинал статьи https://habr.com/ru/articles/1068092/