Андрей Копылов, наш технический директор, любит, активно использует и пропагандирует Docker. В новой статье он рассказывает, как создать пользователей в Docker. Правильная работа с ними, почему пользователей нельзя оставлять с root правами и, как решить задачу несовпадения идентификаторов в Dockerfile.
Все процессы в контейнере будут работать из-под пользователя root, если специальным образом его не указать. Это кажется очень удобно, ведь у этого пользователя нет никаких ограничений. Именно поэтому работать под рутом неправильно с точки зрения безопасности. Если на локальном компьютере никто в здравом уме не работает с рутовыми правами, то многие запускают процессы под рутом в контейнерах.
Всегда есть баги, которые позволят зловреду выбраться из контейнера и попасть на хостовый компьютер. Предполагая худшее, мы должны обеспечить запуск процессов внутри контейнера от пользователя, который не имеет никаких прав на хостовой машине.
Создание пользователя
Создание пользователя в контейнере не отличается от его создания в линуксовых дистрибутивах. Однако для разных базовых образов команды могут различаться.
Для дистрибутивов основанных на debian в Dockerfile необходимо добавить:
RUN groupadd --gid 2000 node && useradd --uid 2000 --gid node --shell /bin/bash --create-home node
Для alpine:
RUN addgroup -g 2000 node && adduser -u 2000 -G node -s /bin/sh -D node
Запуск процессов от пользователя
Для запуска всех последующих процессов от пользователя с UID 2000 выполните:
USER 2000
Для запуска всех последующих процессов от пользователя node выполните:
USER node
Подробнее в документации.
Монтирование томов
При монтировании томов внутрь контейнера обеспечьте пользователю возможность читать и (или) писать файлы. Для этого UID (GID) пользователя в контейнере и пользователя за пределами контейнера, у которого есть соответствующие права на доступ к файлу, должны соответствовать. При этом имена пользователей значения не имеют.
Часто на линуксовом компьютере у пользователя UID и GID равны 1000. Эти идентификаторы присваиваются первому пользователю компьютера.
Узнать свои идентификаторы просто:
id
Вы получите исчерпывающую информацию о своем пользователе.
Замените 2000 из примеров на свой идентификатор и все будет в порядке.
Присвоение пользователю UID и GID
Если пользователь создан ранее, но необходимо изменить идентификаторы, то можно сделать это так:
RUN usermod -u 1000 node && groupmod -g 1000 node
Если вы используете базовый образ alpine, то нужно установить пакет shadow:
RUN apk add —no-cache shadow
Передача идентификатора пользователя внутрь контейнера при построении образа
Если ваш идентификатор и идентификаторы всех людей, которые работают над проектом, совпадают, то достаточно указать этот идентификатор в Dockerfile. Однако часто идентификаторы пользователей не совпадают.
Как осуществить желаемое не сразу понятно. Для меня это было самым сложным в процессе освоения docker. Многие пользователи docker не задумываются о том, что есть разные этапы жизни образа. Сначала образ собирается для этого используют Dockerfile. При запуске контейнера из образа Dockerfile уже не используется.
Создание пользователей должно происходить при построении образа. Это же касается и определения пользователя, из-под которого запускаются процессы. Значит, что мы каким-то образом должны передать внутрь контейнера UID (GID).
Для использования внешних переменных в Dockerfile служат директивы ENV и ARG. Подробное сравнение директив тут.
Dockerfile
ARG UID=1000
ARG GID=1000
ENV UID=${UID}
ENV GID=${GID}
RUN usermod -u $UID node && groupmod -g $GID node
Передать аргументы через docker-compose можно так:
docker-compose
build:
context: ./src/backend
args:
UID: 1000
GID: 1000
P.S. Для освоения всех премудростей docker недостаточно читать документацию или статьи. Нужно много практиковаться, нужно почувствовать docker.
Комментарии (18)
VolCh
19.04.2019 13:35Создание пользователей должно происходить при построении образа. Это же касается и определения пользователя, из-под которого запускаются процессы. Значит, что мы каким-то образом должны передать внутрь контейнера UID (GID).
Неоднозначно как-то. На этапе построения образа контейнера нет (билд-контейнеры не рассматриваем), а частая задача: есть образ с каким-то пользователем, надо запустить контейнер, обеспечив связь с пользователем хоста.
P.S. А вообще думал пост будет про https://docs.docker.com/engine/security/userns-remap/ и ко.
MMik
19.04.2019 23:37Можете дополнить статью информацией о применении gosu, и об обязательных переменных типа таких:
version: "3"
services:
app:
image: repo/app:v1
user: "${UID:?Please export UID}:${GID:?Please export GID}"
volumes:
- "vol1:/data/vol1:rw"
- "vol2:/data/vol2:ro"
ports:
- "8080:8080"
0xf0a00
21.04.2019 17:19Хабровчане, может кто нибудь подскажет как реализовать автозапуск в контейнере?
iDen
21.04.2019 19:43автозапуск чего?
- используешь ENTRYPOINT или CMD что бы запустить свою команду при запуске контейнера.
- если надо что-то навороченное, тогда заворачиваешь все это в самописный script.sh и передаешь его в ENTRYPOINT или CMD
- можно пойти дальше и использовать github.com/krallin/tini
- или github.com/Yelp/dumb-init
- или наверное еще 100500 других извращенческих методов
0xf0a00
22.04.2019 10:42ENTRYPOINT это не автозапуск, я говорю об автозапуске в классическом его понимании. Автозапуск нужен для промежуточного образа в котором будет окружение для выполнения программ, и на основе его будет собираться контейнер каждый со своей программой. Так что ENTRYPOINT будет занят.
iDen
22.04.2019 11:09не до конца понятно. но опять попробую угадать.
Может речь идет о многоуровневых билдах multi-stage builds?
Перед созданием конечного образа, запускается промежуточный контейнер в котором запускается какаято магия, подготавливающая чтото для конечного образа.0xf0a00
22.04.2019 12:11Все намного проще, нужен образ с окружением Wine для запуска windows приложений. Что бы для каждого сервиса не собирать свой wine проще его собрать в одном образе, а потом на его основе делать контейнеры подкидывая нужное ПО через copy. Автозапуск нужен для фейковых иксов.
VolCh, это ответ и на вас вопрос тоже.aak74
22.04.2019 12:51- Соберите базовый образ.
- Протэгайте его.
- Собирайте новые образы из этого базового образа.
Протэганный образ можно либо залить на docker registry, а можно и не заливать.0xf0a00
22.04.2019 13:44А еще можно почитать ветку сначала и понять что нужен автозапуск иксов в промежуточном образе т.к. в конечном Entrypoint будет занят полезным сервисом.
VolCh
22.04.2019 17:16Фраза "запуск в образе" не имеет смысла, запуск всегда в контейнере.
А в целом, делаете ENTRYPOINT типа entrypoint.sh
xserver -d // или как там иксы запускаются, лет 10 не трогал program
а в дочернем образе в докерфайле делайте что-то вроде
RUN ln -s /path/to/program program
ну или другую сотню вариаций на эту тему
0xf0a00
22.04.2019 20:03Я конечно может и не прав, но RUN и CMD выполняются 1(!) раз при создании контейнера. А ENTRYPOINT затирается если в родительском образе он был. Запуск должен быть прописан внутри самого контейнера, но там все настолько кастрированно, что моих знаний линукс не хватает что бы его реализовать.
VolCh
23.04.2019 08:50Запуск и иксов, и основной программы (отсутствующей, ну или true) в ENTRYPOINT-скрипте родительского образа. В дочерних же через RUN создаётся симлинк на основную программу.
Смысл идеи в терминах ООП — в базовом образе создаём абстрактный (или с бессмысленной бесполезной реализацией) «метод» в «классе» ентрипоинт или кмд, наряду с нужной инициализацией, а в потомках этот «метод» переопределяем.0xf0a00
23.04.2019 09:39Спасибо за совет, возможно это сработает, попробую. Просто очень хотелось отделить вайн от работающего сервиса максимально «красиво». :)
TonyLorencio
Как гарантировать, что пользователь с
UID=${UID}
не имеет никаких прав на хостовой машине?aak74
Присвоить ему UID пользователя, которого нет на хостовой машине. Например 10000. И GID такой же.
TonyLorencio
Это понятно.
Допустим, на нашем сервере есть пользователь с UID 10000, у которого есть какие-то права, но я не знаю о факте существовании такого пользователя.
Можно ли вообще гарантировать, что на любой машине, где будет использоваться этот образ, будет отсутствовать пользователь с некоторым UID/GID?
aak74
Для любой машины вряд ли. По крайней мере мне о таком способе неизвестно.