Docker, запуск процесса из под текущего пользователя

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

Это статья является вольным переводом Juan Treminio
Running Docker Containers as Current Host User, автора https://puphpet.com/ — генератора настроек для vagrant.

Начало

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

Композер загрузить библиотеку и создаст файлы в текущей директории

Видно, что файлы созданные контейнером принадлежат пользователю root, потому что демон Docker-а запускается root пользователем. В этом можно убедиться введя команду 

Хорошо, контейнер запущен из root, так может просто запустить контейнер из под текущего пользователя? В Docker есть даже специальный ключ для этого

Задача выполнена, верно?

Верно… отчасти

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

Что если в контейнере запущен процесс PHP-FPM у которого должны быть права на создание процесс файла в /var/run/php-fpm.pid или запись сессий в папку /var/lib/php/sessions? Ясно дело что этот процесс не получит доступа к ним,  ведь контейнер запущен с ИД текущего пользователя

Может создать контейнер из под пользователя www-data?

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

ведь пользователь 33:33 не может создать файлы в папке zhandauletov

Синхронизация файла /etc/passwd тоже не поможет, не в именах дело, а в User ID

Что за грабли такие с этими пользователями?

Процитирую из книги Using Docker — Использование Docker

Ядро Linux использует идентификаторы пользователей UID и идентификаторы групп GID для идентификации пользователей и определения их прав доступа. Преобразование числовых идентификаторов UID и GID в символьные идентификаторы выполняется операционной системой в пространстве пользователя. Поэтому идентификаторы пользователей UID в контейнере совпадают с аналогичными UID на хосте, но пользователи и группы, созданные внутри контейнера, не передаются на хост. Из-за этого возникает побочный эффект – может возникать беспорядок в правах доступа, а одни и те же файлы могут принадлежать одному пользователю внутри контейнера и другому пользователю вне контейнера

То есть, могут быть пользователи zhandauletov и на хосте, и в контейнере. Но на хосте у этого пользователи UID будет 1001, в контейнере 1002 и это разные пользователи.

Пространства имен для DOCkER

Более подробно это описано в статье https://www.jujens.eu/posts/en/2017/Jul/02/docker-userns-remap/

Суть в том что пространство zhandauletov на хосте  маппится с пространством root в контейнере.

То есть ИД 1001:1001 zhandauletov будет равен ИД 0:0 root и все созданные файлы 0:0 рута в контейнере создаются на хосте с правами 1001:1001

Вроде бы все прекрасно, но не забывайте, что в контейнере скорее всего будет пользователь отличный от рута. Например, www-data c 33:33 в контейнере будет создавать файлы с правами 1033:1033 на хосте, в ведь такого пользователя нет на хосте, это может создать потенциальные ошибки с доступом.

Итак, что же на самом деле работает?

Единственным решением является заново создать нужного пользователя в контейнере с нужным UID

Это команда сначала проверяет наличие пользователя перед удалением. Полная команда выглядит так

Для начала давайте удалим пользователя и его группу — 1. getent passwd, getent  group получение информации о пользователе из базы данных passwd, получение группы из базы group соответственно

2 и 3 добавление новых пользователя и группы

4 создание домашней директории для него

5 — смена владельца, те папки которые принадлежали 33:33 теперь принадлежат 1001:1001

6 — все последующие команды запускаются из под пользователя 1001:1001

Теперь все работает, но хорошо бы еще избавиться от захардкоженных данных 1001

Динамическая передача ИД

В этом поможет ключ —build-arg при создании образа

А докерфайле нужно убрать весь харкод

Протестируем, запустим контейнер и проверим созданные им файлы

Тест прав доступа к защищенному каталогу

А если я хочу использовать docker-compose?

Перенос в docker compose выглядит так:

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

Или можно явно указать в файле .env переменные окружения. Файл .env должен быть на одном уровне с docker-compose.yml

 

Здесь использована возможность докера работать с переменными окружения
https://docs.docker.com/compose/environment-variables/
А синтаксис переменных в docker-compose.yml описан здесь
https://docs.docker.com/compose/compose-file/#variable-substitution

Осталось лишь запустить

Вот, теперь добавление пользователей работает и через docker-compose

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

Что ж, вот так вот создали переносимый код для безопасного запуска процессов в контейнере. И безопасность повысили, и разработчиков Docker уважили с их «никогда не запускайте контейнеры из под рута, хо-хо-хо» и от потенциальных проблем с правами доступа избавились.

3 комментария

  1. Алексей Кулагин
    ·

    А что мешает, в Dockerfile прописать

    RUN usermod -u ${USER_ID} www-data && groupmod -g ${GROUP_ID} www-data

    Зачем удалять, и по новой создавать пользователя www-data? Сменить ему при сборке права, и всё

    Ответить
    1. Alma Z
      ·

      Спасибо Алексей за подсказку. Сегодня вечером попробую протестировать

      Ответить

  2. ·

    Как это все проделать для alpine? Аналоги всех команд нашел, но
    chown: unrecognized option: from=33:33 (хотя наверно у меня 82:82, потому что это текущие ID для www-data)

    Ответить

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *