Який (найкращий) спосіб управління дозволами для спільних томів Docker?


345

Я деякий час розмовляв з Докером і продовжував шукати ту саму проблему, коли мав справу зі стійкими даними.

Я створюю свою Dockerfileта виставляю томик або використовую --volumes-fromдля монтажу хостової папки всередині свого контейнера .

Які дозволи я повинен застосувати до спільного тома на хості?

Я можу придумати два варіанти:

  • Поки я дав усім доступ для читання / запису, тому я можу писати в папку з контейнера Docker.

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

Чи є інші альтернативи?

Як ви, хлопці / дівчата, вирішили це питання?



1
Можливо, вам буде цікавий цей потік, який детально обговорює цю тему: groups.google.com/forum/#!msg/docker-user/cVov44ZFg_c/…
btiernay


На даний момент команда Docker не планує реалізовувати нативне рішення для монтажу каталогів хостів як томи із заданим uid / gid. Дивіться мій коментар та відповіді на це питання: github.com/docker/docker/isissue/7198#issuecomment-230636074
Quinn Comendant

Відповіді:


168

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


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

Наприклад, один випадок використання, наведений у документації, - це резервне копіювання обсягу даних. Для цього використовується інший контейнер для резервного копіювання через tar, і він теж використовується -volumes-fromдля монтажу гучності. Тому я думаю, що ключовим моментом для grok є: замість того, щоб думати про те, як отримати доступ до даних на хості з належними дозволами, подумайте, як робити все, що вам потрібно - резервне копіювання, перегляд веб-сайтів тощо - через інший контейнер . Самі контейнери повинні використовувати послідовні 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/graphiteDockerfile також має бути однаковий 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 під час його запуску, але маючи їх як окремі зображення ІМО зрозуміліше).

Тепер скажемо, що ви хочете щось відредагувати у папці даних. Тому замість того, щоб прив’язувати том до хосту та редагувати його там, створіть новий контейнер, щоб виконати цю роботу. Давайте назвемо це 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в Dockerfile, або замість створення нового зображення просто повторно використовувати одне із існуючих (переопределення вхідної точки / 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 для запуску команди як правильного користувача. Якщо когось цікавить такий підхід, будь ласка, прокоментуйте, і я можу надати посилання на зразок, використовуючи цей підхід.


35
Боюся, що це не є рішенням, оскільки у вас буде та сама проблема з контейнерами, що містять лише дані. Зрештою, ці контейнери будуть використовувати томи, якими надається хост, тому вам потрібно буде керувати дозволами для цих спільних папок.
Xabs

2
Майте на увазі, що мені може знадобитися відредагувати папку даних з мого хоста (тобто: видалити тестовий графітовий ключ, видалити мою домашню папку тесту JIRA або оновити її за допомогою останньої резервної копії виробництва ...). Наскільки я розумію з вашого коментаря, я повинен робити такі речі, як оновлення даних JIRA через 3-й контейнер. У будь-якому випадку, які дозволи ви застосували до нової папки даних /data/newcontainer? Я припускаю, що ви запускаєте dockerяк root (чи не можна цього робити?) Також, чи є різниця в цих дозволах, якщо дані встановлюються безпосередньо в основному контейнері або через контейнер, призначений лише для даних ?
Xabs

2
Дякуємо за вашу детальну відповідь. Перевірте це, як тільки у мене з’явиться можливість. Крім того, приємні посилання як на ваш пост у блозі, так і на використання мінімальних зображень для контейнерів даних .
Xabs

3
Єдина проблема такого підходу - це дуже легко помилково видалити контейнер. Уявіть, чи трапляється це ваш контейнер даних. Я думаю (CMIIW) дані все ще будуть /var/lib/dockerдесь, але все ще величезний біль
lolski

3
"Ви можете просто використовувати контейнер даних як" довідку / обробляти "і встановити право власності / perms в іншому контейнері через chown у точці входу" ... @ Раман: Цей розділ остаточно врятував мене після численних проблем з дозволом не зрозумів. Використання сценарію вхідної точки та налаштування дозволів у цьому працює для мене. Дякуємо за ваше детальне пояснення. Це найкраще, що я знайшов в Інтернеті до цих пір.
Vanderstaaij

59

Дуже елегантне рішення можна побачити на офіційному зображенні redis та загалом у всіх офіційних образах.

Описано поетапно:

  • Створіть redis користувача / групу, перш ніж все інше

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

спершу додайте нашого користувача та групу, щоб переконатися, що їх посвідчення присвоюються послідовно, незалежно від того, які залежності будуть додані

  • Встановіть gosu за допомогою Dockerfile

gosu - це альтернатива su/ sudoдля легкого відключення від користувача root. (Redis завжди працює з redisкористувачем)

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

Налаштувавши / том даних за допомогою VOLUME /dataкоманди, тепер у нас є окремий том, який може бути або током докера, або прив’язаним до хору dir.

Конфігуруючи його як workdir ( WORKDIR /data), це каталог за замовчуванням, з якого виконуються команди.

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

Це означає, що всі виконання контейнерів будуть виконуватися через скрипт docker-entrypoint, а за замовчуванням команда, яку потрібно запустити, - redis-server.

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

Це має такий ефект

Якщо каталог / data прив’язаний до хоста, то точка входу docker підготує права користувача перед запуском redis-сервера під redisкористувачем.

Це дає вам зручність врахувати, що для запуску контейнера в будь-якій конфігурації обсягу є нульова настройка.

Звичайно, якщо вам потрібно поділити гучність між різними зображеннями, вам потрібно переконатися, що вони використовують один і той же userid / groupid, інакше останній контейнер захопить дозволи користувача з попереднього.


11
Прийнята відповідь є інформативною, але це лише привело мене вниз через тиждень шлях розчарування з дозволами на пошук цієї відповіді, яка насправді забезпечує канонічний спосіб вирішення проблеми.
m0meni

3
Дуже добре пояснено і тут: denibertovic.com/posts/handling-permissions-with-docker-volumes
kheraud

Тому? Як зробити гучність для запису з внутрішнього докера? chownце всередині ENTRYPOINTсценарію?
Герман

34

Це, мабуть, не найкращий спосіб для більшості обставин, але це ще не було згадано, тому, можливо, це комусь допоможе.

  1. Прив’язати гучність хоста кріплення

    Host folder FOOBAR is mounted in container /volume/FOOBAR

  2. Змініть сценарій запуску вашого контейнера, щоб знайти GID потрібного вам обсягу

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

  3. Переконайтеся, що ваш користувач належить до групи з цим 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пропозицією в Dockerfile (якщо я думаю, що користувач не має кореневих привілеїв). Крім того, він кричить зламати роботу ;-)

Якщо ви хочете бути хардкор, ви, очевидно, можете розширити це багатьма способами - наприклад, пошук усіх груп на будь-яких підфілях, кількох томах тощо.


4
Це націлено на читання файлів із змонтованого тома? Я шукаю рішення для запису файлів, не володіючи ними іншим користувачем, ніж той, хто створив контейнер docker.
ThorSummoner

Я використовую такий підхід з 15 серпня. Все було гаразд. Просто дозволи на файли, створені всередині контейнера, були різними. Обидва користувачі (всередині та зовні контейнера) мають право власності на свої файли, але обидва мали доступ до читання, оскільки вони належали до тієї ж групи, створеної цим рішенням. Проблема почалася, коли випадок використання наклав доступ на запис для загальних файлів. Більша проблема полягала в тому, що спільний об'єм мав файли git (його том для тестування вихідних файлів розробників у тому ж виробничому контексті). Git почав попереджати про проблему доступу до спільного коду.
Юсер

Я думаю, що краще використовувати греп для $TARGET_GIDвикористання grep ':$TARGET_GID:', інакше якщо контейнер має, наприклад, gid 10001, а ваш хост - 1000, ця перевірка пройде, але вона не повинна.
robhudson

16

Гаразд, це вже відстежується у випуску докера №7198

Поки що я маю справу з цим, використовуючи ваш другий варіант:

Мапуйте користувачів із хоста в контейнер

Докерфайл

#=======
# 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

ОНОВЛЕННЯ На даний момент я більше схильний до відповіді Хемі


1
використовуйте команду id -u <username>, id -g <username>, id -G <username>щоб отримати ім'я користувача та ідентифікатор групи користувача , визначеного замість
lolski


15
Це знищує портативність контейнерів для хостів.
Раман

2
Докерська проблема №7198 дійшла висновку, що вони не реалізуватимуть нативного рішення для цього. Дивіться мої коментарі відповіді на github.com/docker/docker/isissue/7198#issuecomment-230636074
Квінн Комендант


12

Як і ви, я шукав спосіб відображення користувачів / груп від хоста до докер-контейнерів, і це найкоротший шлях, який я знайшов поки що:

  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-> ім'я користувача (як і для груп), що відповідають з хостом. Тепер ви можете налаштувати налаштування користувача / групи для вашої послуги всередині контейнера так, ніби він працював у вашій хост-системі.

Коли ви вирішите перенести контейнер на інший хост, вам просто потрібно змінити ім'я користувача у файлі конфігурації служби на те, що у вас є на цьому хості.


Це чудова відповідь, дуже проста, якщо ви хочете запускати контейнери, які обробляють файли в базовій системі, не піддаючи решту системи.
icarito

Це моя улюблена відповідь. Крім того, я побачив подібну рекомендацію в іншому місці з командою docker run, де ви переходите у своє поточне ім'я користувача / групи через, -u $( id -u $USER ):$( id -g $USER )і вам більше не доведеться турбуватися про ім’я користувача. Це добре працює для локальних середовищ розробників, де ви бажаєте генерувати файли (наприклад, бінарні файли), до яких ви читали / записували за замовчуванням.
matthewummings516

5

Ось підхід, який все ще використовує контейнер, призначений лише для даних, але не вимагає його синхронізації з контейнером додатків (з точки зору того, що має однаковий uid / gid).

Імовірно, ви хочете запустити якусь програму в контейнері як некореневий $ USER без оболонки для входу.

У Dockerfile:

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; $@"

5

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

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

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

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

Оновити підтримку UPDATEdocker > 1.7.0

ОНОВЛЕННЯ Версія 1.10.0(2016-02-04) додати --userns-remapпрапор https://github.com/docker/docker/blob/master/CHANGELOG.md#security-2


Я запускаю docker 1.3.2 build 39fa2fa (найновіший) і не бачу слідів --uidmapні --private-uidsпараметрів, ні параметрів. Схоже, що PR цього не зробив і не був об'єднаний.
Лев Галлуччі

Це не злиття в ядрі, якщо ви хочете, ви можете використовувати його як патч. Тепер можна лише обмежити деякі можливості та запустити додаток у контейнер від некористувального користувача.
umount

Червень 2015 року, і я не бачу, щоб це було об’єднано в docker 1.6.2. Ваша відповідь все ще вірна?
Лев Галлуччі

1
Питання досі відкрите. Розробник повинен додати підтримку у версії 1.7. (- опція кореня
umount

2
Здається, розробники в черговий раз перенесли випуск з цим функціоналом. Докер розробник "icecrime" кажуть "We apparently do have so some of conflicting designs between libnetwork and user namespaces ... and something we'd like to get in for 1.8.0. So don't think we're dropping this, we're definitely going to take a break after all these, and see how we need to reconsider the current design and integration of libnetwork to make this possible. Thanks!" github.com/docker/docker/pull/12648 Тому я думаю, що ми повинні почекати наступної стабільної версії.
umount

4

Мій підхід - виявити поточний 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"

3

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

Використовуйте це зображення: 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

0

У моєму конкретному випадку я намагався скласти свій пакет вузлів із зображенням докера вузла, щоб мені не довелося встановлювати npm на сервер розгортання. Це працювало добре, поки, поза зовнішньої частини контейнера та на хост-машині, я не намагався перемістити файл у каталог node_modules, який створив образ докера вузла, до якого мені було відмовлено у дозволах, оскільки він був власником 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

0

Щоб поділити папку між хостом докера та контейнером докера, спробуйте нижче команду

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

Прапор -v монтує поточний робочий каталог у контейнер. Якщо каталог хостів прив’язаного до прив'язки тома не існує, Docker автоматично створить цей каталог на хості для вас,

Однак у нас є дві проблеми:

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

Рішення:

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

Ведучий: створіть групу, яка скаже «тестова група» з ідентифікатором групи 1000, і задайте каталог новій групі (тестова група


-5

Якщо ви використовуєте Docker Compose, запустіть контейнер у пільговому режимі:

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

2
Це може полегшити монтаж томів, але .. Wordpress запущений у приватному режимі? Це жахлива ідея - це прохання піти на компроміс. wpvulndb.com/wordpresses/453
Колін Харрінгтон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.