Каков (лучший) способ управления разрешениями для общих томов Docker?

Некоторое время я играл с Docker и продолжаю находить ту же проблему при работе с постоянными данными.

Я создаю свой Dockerfile и открываю том или использую --volumes-from для монтирования папки хоста внутри моего контейнера.

Какие разрешения я должен применить к общему тому на хосте?

Я могу придумать два варианта:

  • До сих пор я предоставил всем доступ на чтение/запись, поэтому я могу писать в папку из контейнера Docker.

  • Сопоставьте пользователей с хоста в контейнере, чтобы я мог назначить более детальные разрешения. Не уверен, что это возможно, хотя и не нашел много об этом. Пока что все, что я могу сделать, это запустить контейнер от имени какого-то пользователя: docker run -i -t -user="myuser" postgres, но у этого пользователя UID, отличный от моего хоста myuser, поэтому разрешения не работают. Кроме того, я не уверен, что сопоставление пользователей будет представлять некоторую угрозу безопасности.

Есть ли другие альтернативы?

Как вы, ребята/девочки, имеете дело с этой проблемой?

Ответы

Ответ 1

ОБНОВЛЕНИЕ 2016-03-02. Начиная с Docker 1.9.0, Docker имеет имена томов, которые заменить контейнеры только для данных. Ответ ниже, а также мое связанное сообщение в блоге по-прежнему имеют значение в смысле того, как думать о данных внутри докеров, но подумайте об использовании именованных томов для реализации шаблона, описанного ниже, а не контейнеров данных.


Я считаю, что канонический способ решить это - использовать контейнеры только для данных. При таком подходе весь доступ к данным тома осуществляется через контейнеры, которые используют -volumes-from контейнер данных, поэтому хост uid/gid не имеет значения.

Например, один вариант использования, указанный в документации, представляет собой резервную копию тома данных. Для этого другой контейнер используется для резервного копирования через tar, и он также использует -volumes-from для установки тома. Поэтому я считаю, что ключевым моментом для этого является: вместо того, чтобы думать о том, как получить доступ к данным на хосте с соответствующими разрешениями, подумайте о том, как делать все, что вам нужно - резервные копии, просмотр и т.д. - через другой контейнер, Сами контейнеры должны использовать согласованные uid/gids, но им не нужно отображать что-либо на хосте, оставаясь при этом переносимым.

Это относительно новое для меня, но если у вас есть конкретный случай использования, не стесняйтесь комментировать, и я попытаюсь расширить ответ.

ОБНОВЛЕНИЕ. Для данного варианта использования комментариев вы можете иметь изображение some/graphite для запуска графита и изображение some/graphitedata в качестве контейнера данных. Таким образом, игнорируя порты и т.д., Dockerfile изображения some/graphitedata выглядит примерно так:

FROM debian:jessie
# add our user and group first to make sure their IDs get assigned consistently, regardless of other deps added later
RUN groupadd -r graphite \
  && useradd -r -g graphite graphite
RUN mkdir -p /data/graphite \
  && chown -R graphite:graphite /data/graphite
VOLUME /data/graphite
USER graphite
CMD ["echo", "Data container for graphite"]

Создайте и создайте контейнер данных:

docker build -t some/graphitedata Dockerfile
docker run --name graphitedata some/graphitedata

Файл some/graphite Dockerfile должен также получать одинаковые uid/gids, поэтому он может выглядеть примерно так:

FROM debian:jessie
# add our user and group first to make sure their IDs get assigned consistently, regardless of other deps added later
RUN groupadd -r graphite \
  && useradd -r -g graphite graphite
# ... graphite installation ...
VOLUME /data/graphite
USER graphite
CMD ["/bin/graphite"]

И он будет запускаться следующим образом:

docker run --volumes-from=graphitedata some/graphite

Итак, теперь это дает нам наш графитовый контейнер и связанный контейнер с данными только с правильным пользователем/группой (обратите внимание, что вы можете повторно использовать контейнер some/graphite для контейнера данных, а также переопределить ввод /cmd при запуске это, но наличие их как отдельных изображений IMO яснее).

Теперь скажем, вы хотите что-то изменить в папке с данными. Поэтому вместо того, чтобы связывать установку с хостом и редактировать его там, создайте новый контейнер для выполнения этой задачи. Давайте назовем его some/graphitetools. Позволяет также создать соответствующий пользователь/группу, как и изображение some/graphite.

FROM debian:jessie
# add our user and group first to make sure their IDs get assigned consistently, regardless of other deps added later
RUN groupadd -r graphite \
  && useradd -r -g graphite graphite
VOLUME /data/graphite
USER graphite
CMD ["/bin/bash"]

Вы можете сделать это DRY, наследуя от some/graphite или some/graphitedata в файле Docker, или вместо создания нового изображения просто повторно используйте один из существующих (переопределяя точку входа /cmd при необходимости).

Теперь вы просто запускаете:

docker run -ti --rm --volumes-from=graphitedata some/graphitetools

а затем vi /data/graphite/whatever.txt. Это отлично работает, потому что все контейнеры имеют один и тот же графитовый пользователь с соответствующими uid/gid.

Поскольку вы никогда не монтируете /data/graphite с хоста, вам все равно, как хост uid/gid сопоставляется с uid/gid, определенным внутри контейнеров graphite и graphitetools. Теперь эти контейнеры могут быть развернуты на любом хосте, и они будут продолжать работать отлично.

В этой статье описывается, что graphitetools может иметь всевозможные полезные утилиты и скрипты, которые теперь можно развертывать переносимым образом.

ОБНОВЛЕНИЕ 2. После написания этого ответа я решил написать более полное сообщение в блоге об этом подходе. Надеюсь, это поможет.

ОБНОВЛЕНИЕ 3. Я исправил этот ответ и добавил более подробную информацию. Ранее он содержал некоторые неправильные предположения о владении и perms - право собственности обычно присваивается при создании объема, т.е. В контейнере данных, потому что именно тогда создается тома. Смотрите этот блог. Однако это не является обязательным требованием - вы можете просто использовать контейнер данных в качестве "ссылки/дескриптора" и установить права собственности /perms в другом контейнере через chown в точке входа, которая заканчивается gosu, чтобы запустить команду как правильный пользователь. Если кто-то заинтересован в этом подходе, прокомментируйте, и я могу предоставить ссылки на образец, используя этот подход.

Ответ 2

Очень элегантное решение можно увидеть на официальном redis image и вообще на всех официальных изображениях.

Описано в пошаговом процессе:

  • Создайте пользователя/группу redis перед чем-либо еще

Как видно из комментариев Dockerfile:

сначала добавьте нашего пользователя и группу, чтобы убедиться, что их идентификаторы назначаются последовательно, независимо от того, какие зависимости добавляются

  • Установите gosu с помощью Dockerfile

gosu является альтернативой su/sudo для легкого ухода от пользователя root. (Redis всегда запускается с пользователем redis)

  • Настройте громкость /data и установите его как рабочий каталог

Благодаря настройке тома /data с помощью команды VOLUME /data у нас теперь есть отдельный том, который может быть либо томом докера, либо подключенным к каталогу хоста.

Конфигурирование его как рабочего каталога (WORKDIR /data) делает его каталогом по умолчанию, из которого выполняются команды.

  • Добавьте файл docker-entrypoint и установите его как ENTRYPOINT с CMD-редис-сервером по умолчанию

Это означает, что все выполнения контейнера будут выполняться через сценарий docker-entrypoint, и по умолчанию команда, которую нужно запустить, - это redis-server.

docker-entrypoint - это скрипт, выполняющий простую функцию: измените владельца текущего каталога (/data) и перейдите с root на redis пользователя, чтобы запустить redis-server. (Если выполненная команда не является redis-сервером, она будет запущена непосредственно.)

Это имеет следующий эффект

Если каталог /data привязан к хосту, точка входа docker подготовит права пользователя перед запуском redis-server под пользователем redis.

Это дает вам легкое представление о том, что для запуска контейнера в любой конфигурации тома не требуется никаких настроек.

Конечно, если вам нужно разделить объем между разными изображениями, вам нужно убедиться, что они используют один и тот же идентификатор пользователя /groupid, в противном случае последний контейнер перехватит права пользователя из предыдущего.

Ответ 3

Это, возможно, не лучший способ для большинства обстоятельств, но он еще не упоминался, поэтому, возможно, это поможет кому-то.

  • Связать хост с хостом

    Host folder FOOBAR is mounted in container /volume/FOOBAR

  • Измените запуск вашего контейнера script, чтобы найти GID интересующего тома.

    $ TARGET_GID=$(stat -c "%g" /volume/FOOBAR)

  • Убедитесь, что ваш пользователь принадлежит к группе с этим GID (возможно, вам придется создать новую группу). В этом примере я буду притворяться, что мое программное обеспечение работает как пользователь nobody, когда внутри контейнера, поэтому я хочу, чтобы nobody принадлежал к группе с идентификатором группы, равным TARGET_GID

  EXISTS=$(cat /etc/group | grep $TARGET_GID | wc -l)

  # Create new group using target GID and add nobody user
  if [ $EXISTS == "0" ]; then
    groupadd -g $TARGET_GID tempgroup
    usermod -a -G tempgroup nobody
  else
    # GID exists, find group name and add
    GROUP=$(getent group $TARGET_GID | cut -d: -f1)
    usermod -a -G $GROUP nobody
  fi

Мне это нравится, потому что я могу легко изменять групповые разрешения на своих томах хоста и знаю, что эти обновленные разрешения применяются в контейнере докера. Это происходит без каких-либо разрешений или прав собственности на мои папки/файлы хоста, что делает меня счастливым.

Мне это не нравится, потому что он не предполагает опасности добавлять себя к произвольным группам внутри контейнера, которые используют GID, который вы хотите. Он не может использоваться с предложением USER в файле Docker (если у этого пользователя нет привилегий root). Кроме того, он кричит о взломе, -)

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

Ответ 4

Хорошо, теперь это отслежено на докеровской проблеме # 7198

В настоящее время я имею дело с этим, используя ваш второй вариант:

Сопоставьте пользователей из хоста в контейнер

Dockerfile

#=======
# Users
#=======
# TODO: Idk how to fix hardcoding uid & gid, specifics to docker host machine
RUN (adduser --system --uid=1000 --gid=1000 \
        --home /home/myguestuser --shell /bin/bash myguestuser)

CLI

# DIR_HOST and DIR_GUEST belongs to uid:gid 1000:1000
docker run -d -v ${DIR_HOST}:${DIR_GUEST} elgalu/myservice:latest

ОБНОВЛЕНИЕ Я сейчас более склонен к Hamy ответить p >

Ответ 6

То же, что и вы, я искал способ сопоставления пользователей/групп с хостом на контейнеры докеров, и это самый короткий способ, который я нашел до сих пор:

  version: "3"
    services:
      my-service:
        .....
        volumes:
          # take uid/gid lists from host
          - /etc/passwd:/etc/passwd:ro
          - /etc/group:/etc/group:ro
          # mount config folder
          - path-to-my-configs/my-service:/etc/my-service:ro
        .....

Это выдержка из моего docker-compose.yml.

Идея состоит в том, чтобы монтировать (в режиме только для чтения) список пользователей/групп из хоста в контейнер, таким образом, после запуска контейнера он будет иметь одинаковые идентификаторы uid- > username (а также для групп) с хост. Теперь вы можете настроить параметры пользователя/группы для своей службы внутри контейнера, как если бы она работала на вашей хост-системе.

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

Ответ 7

Здесь используется подход, который все еще использует контейнер только для данных, но не требует его синхронизации с контейнером приложения (с точки зрения наличия того же uid/gid).

Предположительно, вы хотите запустить какое-то приложение в контейнере как не-root $USER без оболочки входа.

В файле Docker:

RUN useradd -s /bin/false myuser

# Set environment variables
ENV VOLUME_ROOT /data
ENV USER myuser

...

ENTRYPOINT ["./entrypoint.sh"]

Затем в entrypoint.sh:

chown -R $USER:$USER $VOLUME_ROOT
su -s /bin/bash - $USER -c "cd $repo/build; [email protected]"

Ответ 8

Для обеспечения безопасности и изменения корневого каталога для докер-контейнера хост-докеры попробуйте использовать --uidmap и --private-uids options

https://github.com/docker/docker/pull/4572#issuecomment-38400893

Также вы можете удалить несколько возможностей (--cap-drop) в контейнере докеров для обеспечения безопасности

http://opensource.com/business/14/9/security-for-docker

Поддержка UPDATE должна появиться в docker > 1.7.0

UPDATE Версия 1.10.0 (2016-02-04) добавить --userns-remap флаг https://github.com/docker/docker/blob/master/CHANGELOG.md#security-2

Ответ 9

Базовое изображение

Используйте это изображение: https://hub.docker.com/r/reduardo7/docker-host-user

или

Важно: это разрушает переносимость контейнера по хостам.

1) init.sh

#!/bin/bash

if ! getent passwd $DOCKDEV_USER_NAME > /dev/null
  then
    echo "Creating user $DOCKDEV_USER_NAME:$DOCKDEV_GROUP_NAME"
    groupadd --gid $DOCKDEV_GROUP_ID -r $DOCKDEV_GROUP_NAME
    useradd --system --uid=$DOCKDEV_USER_ID --gid=$DOCKDEV_GROUP_ID \
        --home-dir /home --password $DOCKDEV_USER_NAME $DOCKDEV_USER_NAME
    usermod -a -G sudo $DOCKDEV_USER_NAME
    chown -R $DOCKDEV_USER_NAME:$DOCKDEV_GROUP_NAME /home
  fi

sudo -u $DOCKDEV_USER_NAME bash

2) Dockerfile

FROM ubuntu:latest
# Volumes
    VOLUME ["/home/data"]
# Copy Files
    COPY /home/data/init.sh /home
# Init
    RUN chmod a+x /home/init.sh

3) run.sh

#!/bin/bash

DOCKDEV_VARIABLES=(\
  DOCKDEV_USER_NAME=$USERNAME\
  DOCKDEV_USER_ID=$UID\
  DOCKDEV_GROUP_NAME=$(id -g -n $USERNAME)\
  DOCKDEV_GROUP_ID=$(id -g $USERNAME)\
)

cmd="docker run"

if [ ! -z "${DOCKDEV_VARIABLES}" ]; then
  for v in ${DOCKDEV_VARIABLES[@]}; do
    cmd="${cmd} -e ${v}"
  done
fi

# /home/usr/data contains init.sh
$cmd -v /home/usr/data:/home/data -i -t my-image /home/init.sh

4) Постройте с помощью docker

4) Запустите!

sh run.sh

Ответ 10

Мой подход заключается в том, чтобы обнаружить текущий UID/GID, затем создать такого пользователя/группу внутри контейнера и выполнить скрипт под ним. В результате все файлы, которые он создаст, будут соответствовать пользователю на хосте (который является сценарием):

# get location of this script no matter what your current folder is, this might break between shells so make sure you run bash
LOCAL_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

# get current IDs
USER_ID=$(id -u)
GROUP_ID=$(id -g)

echo "Mount $LOCAL_DIR into docker, and match the host IDs ($USER_ID:$GROUP_ID) inside the container."

docker run -v $LOCAL_DIR:/host_mount -i debian:9.4-slim bash -c "set -euo pipefail && groupadd -r -g $GROUP_ID lowprivgroup && useradd -u $USER_ID lowprivuser -g $GROUP_ID && cd /host_mount && su -c ./runMyScriptAsRegularUser.sh lowprivuser"

Ответ 11

Чтобы разделить папку между хостом Docker и контейнером Docker, попробуйте команду ниже

$ docker run -v "$(pwd):$(pwd)" -i -t ubuntu

Флаг -v монтирует текущий рабочий каталог в контейнер. Если каталог хоста подключенного тома тома не существует, Docker автоматически создаст этот каталог на хосте для вас,

Однако здесь есть две проблемы:

  1. Вы не можете выполнять запись на том, смонтированный на томе, если вы были пользователем без полномочий root, поскольку общий файл будет принадлежать другому пользователю хоста,
  2. Вы не должны запускать процесс внутри ваших контейнеров с правами root, но даже если вы работаете как пользователь с жестким кодом, он все равно не будет соответствовать пользователю на вашем ноутбуке /Jenkins,

Решение:

Контейнер: создайте пользователя с именем "testuser", по умолчанию идентификатор пользователя будет начинаться с 1000,

Ведущий: создайте группу, скажем "testgroup" с идентификатором группы 1000, и добавьте каталог в новую группу (testgroup

Ответ 12

В моем конкретном случае я пытался создать мой пакет node с образцом docker node, чтобы мне не пришлось устанавливать npm на сервере развертывания. Он работал хорошо, за пределами контейнера и на главном компьютере, я попытался переместить файл в каталог node_modules, который создал образ docker node, которому было отказано в разрешениях, поскольку оно принадлежало root. Я понял, что могу обойти это, скопировав каталог из контейнера на хост-машину. Через docker docs...

Файлы, скопированные на локальный компьютер, создаются с помощью UID: GID пользователь, который вызвал команду docker cp.

Это код bash, который я использовал для изменения права собственности на каталог, созданный контейнером docker и внутри него.

NODE_IMAGE=node_builder
docker run -v $(pwd)/build:/build -w="/build" --name $NODE_IMAGE node:6-slim npm i --production
# node_modules is owned by root, so we need to copy it out 
docker cp $NODE_IMAGE:/build/node_modules build/lambda 
# you might have issues trying to remove the directory "node_modules" within the shared volume "build", because it is owned by root, so remove the image and its volumes
docker rm -vf $NODE_IMAGE || true

При необходимости вы можете удалить каталог со второго контейнера докеров.

docker run -v $(pwd)/build:/build -w="/build" --name $RMR_IMAGE node:6-slim rm -r node_modules

Ответ 13

Если вы используете Docker Compose, запустите контейнер в превалированном режиме:

wordpress:
    image: wordpress:4.5.3
    restart: always
    ports:
      - 8084:80
    privileged: true