Как управлять разрешениями в Docker?

DockerBeginner
Практиковаться сейчас

Введение

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

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

Понимание стандартных разрешений Docker

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

Проверка стандартного пользователя в Docker

По умолчанию Docker запускает процессы внутри контейнеров от имени пользователя root. Это может привести к потенциальным проблемам безопасности, если контейнер будет взломан. Чтобы увидеть это в действии, создадим простой контейнер и проверим, от имени какого пользователя запускаются процессы.

Сначала убедитесь, что вы находитесь в директории проекта:

cd ~/project

Теперь запустим базовый контейнер Ubuntu и проверим текущего пользователя:

docker run -it --rm ubuntu:22.04 whoami

Вы должны увидеть следующий вывод:

root

Это подтверждает, что Docker по умолчанию использует пользователя root. Теперь проверим идентификатор пользователя (UID) и идентификатор группы (GID):

docker run -it --rm ubuntu:22.04 id

Вывод должен быть похож на следующий:

uid=0(root) gid=0(root) groups=0(root)

uid=0 и gid=0 указывают, что контейнер запускается от имени пользователя и группы root, которые имеют полный доступ ко всем ресурсам в контейнере.

Исследование разрешений файлов внутри контейнера

Рассмотрим, как работают разрешения файлов внутри контейнера Docker. Создадим простой файл внутри контейнера и проверим его разрешения.

Сначала создадим контейнер, который будет работать в фоновом режиме:

docker run -d --name permissions-demo ubuntu:22.04 sleep 3600

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

docker exec permissions-demo touch /test-file
docker exec permissions-demo ls -l /test-file

Вы должны увидеть вывод, похожий на следующий:

-rw-r--r-- 1 root root 0 May 15 12:34 /test-file

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

Создадим также директорию и проверим ее разрешения:

docker exec permissions-demo mkdir /test-directory
docker exec permissions-demo ls -ld /test-directory

Вывод должен быть похож на следующий:

drwxr-xr-x 2 root root 4096 May 15 12:35 /test-directory

Снова видим, что директория принадлежит пользователю и группе root.

Очистим контейнер перед переходом к следующему шагу:

docker stop permissions-demo
docker rm permissions-demo

Это демонстрирует стандартное поведение Docker в отношении разрешений пользователей. На следующем шаге мы узнаем, как создавать и использовать не-root пользователей в контейнерах Docker для повышения безопасности.

Создание и использование не-root пользователей в Docker

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

Создание Dockerfile с не-root пользователем

Создадим Dockerfile, который определит не-root пользователя и установит его в качестве стандартного пользователя для выполнения команд.

Сначала создадим новую директорию для нашего проекта:

mkdir -p ~/project/non-root-user
cd ~/project/non-root-user

Теперь создадим Dockerfile с помощью текстового редактора nano:

nano Dockerfile

Добавим следующее содержимое в Dockerfile:

FROM ubuntu:22.04

## Create a new user called 'appuser' with user ID 1000
RUN useradd -m -u 1000 appuser

## Create a directory for the application and set ownership
RUN mkdir -p /app && chown -R appuser:appuser /app

## Set the working directory to /app
WORKDIR /app

## Switch to the non-root user
USER appuser

## Create a test file
RUN touch test-file.txt

## Command to run when the container starts
CMD ["bash", "-c", "echo 'Running as user:' && whoami && echo 'File ownership:' && ls -l test-file.txt && tail -f /dev/null"]

Сохраните файл, нажав Ctrl+O, затем Enter, и выйдите из редактора с помощью Ctrl+X.

Этот Dockerfile выполняет следующие действия:

  1. Создает нового пользователя с именем appuser с UID 1000
  2. Создает директорию для приложения и делает ее владельцем пользователя appuser
  3. Устанавливает рабочую директорию в /app
  4. Переключается на не-root пользователя для последующих команд
  5. Создает тестовый файл, который будет принадлежать пользователю appuser
  6. Устанавливает команду, которая отобразит информацию о пользователе и владельце файла

Сборка и запуск контейнера с не-root пользователем

Теперь соберем Docker-образ из нашего Dockerfile:

docker build -t non-root-image .

Вы должны увидеть вывод, указывающий, что Docker собирает образ. После завершения сборки запустите контейнер из этого образа:

docker run --name non-root-container -d non-root-image

Теперь проверим вывод из нашего контейнера:

docker logs non-root-container

Вы должны увидеть вывод, похожий на следующий:

Running as user:
appuser
File ownership:
-rw-r--r-- 1 appuser appuser 0 May 15 12:45 test-file.txt

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

Тестирование разрешений не-root пользователя

Подключимся к запущенному контейнеру и исследуем разрешения нашего не-root пользователя:

docker exec -it non-root-container bash

Теперь вы должны находиться внутри контейнера как пользователь appuser. Проверим это:

whoami

Вы должны увидеть:

appuser

Проверим идентификатор пользователя и идентификатор группы:

id

Вывод должен быть похож на следующий:

uid=1000(appuser) gid=1000(appuser) groups=1000(appuser)

Теперь попробуем создать файл в корневой директории:

touch /root-test-file

Вы должны увидеть ошибку "Permission denied":

touch: cannot touch '/root-test-file': Permission denied

Это происходит потому, что не-root пользователь не имеет прав на запись в корневой директории. Однако наш пользователь может записывать в директорию /app:

touch /app/user-test-file
ls -l /app/user-test-file

Вы должны увидеть, что новый файл принадлежит appuser:

-rw-r--r-- 1 appuser appuser 0 May 15 12:50 /app/user-test-file

Выйдите из оболочки контейнера:

exit

Очистим контейнер перед переходом к следующему шагу:

docker stop non-root-container
docker rm non-root-container

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

Управление разрешениями монтирования томов

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

Понимание проблем с разрешениями монтирования томов

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

Покажем эту проблему на простом примере.

Сначала создайте новую директорию на хосте, которую мы будем монтировать в контейнер:

mkdir -p ~/project/host-data
cd ~/project/host-data

Создайте тестовый файл в этой директории:

echo "This is a test file created on the host" > host-file.txt

Проверьте владельца этого файла:

ls -l host-file.txt

Вы должны увидеть, что файл принадлежит пользователю labex (вашему текущему пользователю на хосте):

-rw-r--r-- 1 labex labex 39 May 15 13:00 host-file.txt

Теперь запустим контейнер, который монтирует эту директорию, и попробуем изменить файл:

docker run -it --rm -v ~/project/host-data:/container-data ubuntu:22.04 bash

Теперь вы находитесь внутри контейнера. Проверим владельца смонтированного файла:

ls -l /container-data/host-file.txt

Вы можете увидеть вывод, похожий на следующий:

-rw-r--r-- 1 1000 1000 39 May 15 13:00 /container-data/host-file.txt

Обратите внимание, что файл отображает числовые идентификаторы вместо имен пользователей, потому что контейнер не знает о пользователе labex с хоста.

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

echo "This is a test file created in the container" > /container-data/container-file.txt

Теперь проверим владельца этого нового файла:

ls -l /container-data/container-file.txt

Вы должны увидеть:

-rw-r--r-- 1 root root 47 May 15 13:05 /container-data/container-file.txt

Файл принадлежит пользователю root внутри контейнера (так как по умолчанию мы запускаемся от имени root).

Выйдите из контейнера:

exit

Теперь проверьте владельца обоих файлов на хосте:

ls -l ~/project/host-data/

Вы увидите, что файл, созданный изнутри контейнера, принадлежит root на хосте:

-rw-r--r-- 1 root  root  47 May 15 13:05 container-file.txt
-rw-r--r-- 1 labex labex 39 May 15 13:00 host-file.txt

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

Решение проблем с разрешениями томов

Существует несколько способов решить проблемы с разрешениями томов. Рассмотрим несколько распространенных подходов.

Подход 1: Установка пользователя в Dockerfile для совпадения с пользователем хоста

Создайте новую директорию для этого примера:

mkdir -p ~/project/volume-permissions
cd ~/project/volume-permissions

Создайте новый Dockerfile:

nano Dockerfile

Добавьте следующее содержимое:

FROM ubuntu:22.04

## Create a user with the same UID as the host user
RUN useradd -m -u 1000 appuser

## Create app directory and set ownership
RUN mkdir -p /app/data && chown -R appuser:appuser /app

## Set working directory
WORKDIR /app

## Switch to appuser
USER appuser

## Command to run
CMD ["bash", "-c", "echo 'I can write to the mounted volume' > /app/data/test.txt && tail -f /dev/null"]

Сохраните и выйдите из редактора.

Соберите образ:

docker build -t volume-permissions-image .

Создайте директорию на хосте для тестирования:

mkdir -p ~/project/volume-permissions/host-data

Запустите контейнер с смонтированным томом:

docker run -d --name volume-test -v ~/project/volume-permissions/host-data:/app/data volume-permissions-image

Через некоторое время проверьте владельца созданного файла на хосте:

ls -l ~/project/volume-permissions/host-data/

Вы должны увидеть, что файл принадлежит пользователю с UID 1000, который должен совпадать с UID вашего хост-пользователя:

-rw-r--r-- 1 labex labex 35 May 15 13:15 test.txt

Этот подход работает, потому что мы создали пользователя в контейнере с тем же UID, что и у хост-пользователя.

Подход 2: Использование флага --user

Другой подход - использовать флаг --user для указания идентификатора пользователя, который следует использовать при запуске контейнера.

Сначала очистите предыдущий контейнер:

docker stop volume-test
docker rm volume-test

Теперь запустите контейнер с флагом --user:

docker run -d --name user-flag-test --user "$(id -u):$(id -g)" -v ~/project/volume-permissions/host-data:/data ubuntu:22.04 bash -c "echo 'Created with --user flag' > /data/user-flag-test.txt && sleep 3600"

Эта команда запускает контейнер с вашим текущим идентификатором пользователя и идентификатором группы.

Проверьте владельца нового файла:

ls -l ~/project/volume-permissions/host-data/user-flag-test.txt

Вы должны увидеть, что файл принадлежит вашему хост-пользователю:

-rw-r--r-- 1 labex labex 23 May 15 13:20 user-flag-test.txt

Очистим все перед переходом к следующему шагу:

docker stop user-flag-test
docker rm user-flag-test

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

Реализация лучших практик по управлению разрешениями

Применим все, что мы узнали, на практике, создав более реалистичный пример Docker-контейнера, который следует лучшим практикам по управлению разрешениями. Мы создадим простой контейнер веб-приложения, соответствующий лучшим практикам безопасности в области разрешений.

Создание безопасного контейнера веб-приложения

Сначала создайте новую директорию для нашего примера:

mkdir -p ~/project/secure-app
cd ~/project/secure-app

Создадим простое веб-приложение. Сначала создайте файл app.py:

nano app.py

Добавьте следующий Python-код для создания простого веб-сервера на Flask:

from flask import Flask
import os

app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello from a secure container!'

@app.route('/whoami')
def whoami():
    return os.popen('id').read()

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

Сохраните и выйдите из редактора.

Теперь создайте файл requirements.txt для зависимостей Python:

nano requirements.txt

Добавьте следующее содержимое:

flask==2.0.1

Сохраните и выйдите из редактора.

Теперь создайте Dockerfile, соответствующий лучшим практикам по управлению разрешениями:

nano Dockerfile

Добавьте следующее содержимое:

FROM python:3.10-slim

## Create a non-root user to run the application
RUN groupadd -g 1000 appgroup \
  && useradd -u 1000 -g appgroup -s /bin/bash -m appuser

## Set working directory and create necessary directories
WORKDIR /app

## Copy requirements first to leverage Docker cache
COPY requirements.txt .

## Install dependencies as root
RUN pip install --no-cache-dir -r requirements.txt

## Copy application code
COPY app.py .

## Create a data directory that the application can write to
RUN mkdir -p /app/data \
  && chown -R appuser:appgroup /app

## Set proper permissions
RUN chmod -R 755 /app

## Switch to non-root user
USER appuser

## Expose the port the app will run on
EXPOSE 8080

## Command to run the application
CMD ["python", "app.py"]

Сохраните и выйдите из редактора.

Соберем Docker-образ:

docker build -t secure-web-app .

Теперь запустим контейнер:

docker run -d --name secure-app -p 8080:8080 secure-web-app

Протестируем приложение. Сначала проверим, запущен ли контейнер:

docker ps

Вы должны увидеть ваш контейнер secure-app в списке. Теперь используйте curl для тестирования приложения:

curl http://localhost:8080/

Вы должны увидеть:

Hello from a secure container!

Проверим, от имени какого пользователя запущено приложение:

curl http://localhost:8080/whoami

Вы должны увидеть вывод, похожий на следующий:

uid=1000(appuser) gid=1000(appgroup) groups=1000(appgroup)

Это подтверждает, что наше приложение запущено от имени не-root пользователя appuser.

Анализ безопасности

Проанализируем улучшения безопасности в нашем Dockerfile:

  1. Не-root пользователь: Мы создали отдельного пользователя (appuser) для запуска приложения, что снижает риск, если контейнер будет взломан.
  2. Минимальные разрешения: Мы установили только необходимые разрешения, требуемые для работы приложения.
  3. Ясное владение: Все файлы и директории в контейнере имеют четко определенное владение.
  4. Корректная структура директорий: Мы создали отдельную структуру директорий для приложения и его данных.

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

Реализация разрешений для томов с безопасным приложением

Добавим том к нашему безопасному приложению, чтобы показать, как правильно управлять разрешениями при использовании томов.

Сначала остановите и удалите существующий контейнер:

docker stop secure-app
docker rm secure-app

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

mkdir -p ~/project/secure-app/host-data

Установите правильные разрешения на директорию хоста:

sudo chown 1000:1000 ~/project/secure-app/host-data

Теперь запустите контейнер с смонтированным томом:

docker run -d --name secure-app-with-volume \
  -p 8080:8080 \
  -v ~/project/secure-app/host-data:/app/data \
  secure-web-app

Подключимся к контейнеру и создадим файл в смонтированном томе:

docker exec -it secure-app-with-volume bash

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

echo "Test file created from inside the container" > /app/data/container-file.txt

Проверьте владельца файла:

ls -l /app/data/container-file.txt

Вы должны увидеть, что файл принадлежит appuser:

-rw-r--r-- 1 appuser appgroup 43 May 15 13:30 /app/data/container-file.txt

Выйдите из контейнера:

exit

Теперь проверьте владельца файла на хосте:

ls -l ~/project/secure-app/host-data/container-file.txt

Вы должны увидеть, что файл принадлежит пользователю с UID 1000 (который соответствует вашему хост-пользователю):

-rw-r--r-- 1 labex labex 43 May 15 13:30 container-file.txt

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

Очистим все перед завершением:

docker stop secure-app-with-volume
docker rm secure-app-with-volume

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

Резюме

В этом практическом занятии вы узнали важные методы управления разрешениями в Docker-контейнерах:

  1. Понимание стандартных разрешений Docker: Вы изучили, что Docker-контейнеры по умолчанию запускаются от имени root и как это может привести к потенциальным рискам безопасности.
  2. Создание и использование не-root пользователей: Вы научились создавать пользовательских не-root пользователей в Docker-контейнерах, что является важной лучшей практикой в области безопасности.
  3. Управление разрешениями монтирования томов: Вы узнали, как управлять разрешениями при обмене данными между хостом и контейнерами с использованием томов, решая распространенные проблемы с разрешениями.
  4. Реализация лучших практик по управлению разрешениями: Вы применили все эти концепции для создания безопасного контейнера веб-приложения, соответствующего лучшим практикам по управлению разрешениями в Docker.

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

Основные выводы:

  • Всегда используйте не-root пользователей в своих контейнерах.
  • Сопоставляйте идентификаторы пользователей между хостом и контейнером при использовании томов.
  • Устанавливайте правильные разрешения для файлов и директорий.
  • Соблюдайте принцип минимальных привилегий.

Эти практики помогут вам создать более безопасные Docker-контейнеры и избежать распространенных проблем, связанных с разрешениями.