Введение
Управление разрешениями в 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 выполняет следующие действия:
- Создает нового пользователя с именем
appuserс UID 1000 - Создает директорию для приложения и делает ее владельцем пользователя appuser
- Устанавливает рабочую директорию в
/app - Переключается на не-root пользователя для последующих команд
- Создает тестовый файл, который будет принадлежать пользователю appuser
- Устанавливает команду, которая отобразит информацию о пользователе и владельце файла
Сборка и запуск контейнера с не-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:
- Не-root пользователь: Мы создали отдельного пользователя (
appuser) для запуска приложения, что снижает риск, если контейнер будет взломан. - Минимальные разрешения: Мы установили только необходимые разрешения, требуемые для работы приложения.
- Ясное владение: Все файлы и директории в контейнере имеют четко определенное владение.
- Корректная структура директорий: Мы создали отдельную структуру директорий для приложения и его данных.
Эти практики помогают обеспечить то, что даже если злоумышленник сможет использовать уязвимость в приложении, у него будет ограниченный доступ к контейнеру и хост-системе.
Реализация разрешений для томов с безопасным приложением
Добавим том к нашему безопасному приложению, чтобы показать, как правильно управлять разрешениями при использовании томов.
Сначала остановите и удалите существующий контейнер:
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-контейнерах:
- Понимание стандартных разрешений Docker: Вы изучили, что Docker-контейнеры по умолчанию запускаются от имени root и как это может привести к потенциальным рискам безопасности.
- Создание и использование не-root пользователей: Вы научились создавать пользовательских не-root пользователей в Docker-контейнерах, что является важной лучшей практикой в области безопасности.
- Управление разрешениями монтирования томов: Вы узнали, как управлять разрешениями при обмене данными между хостом и контейнерами с использованием томов, решая распространенные проблемы с разрешениями.
- Реализация лучших практик по управлению разрешениями: Вы применили все эти концепции для создания безопасного контейнера веб-приложения, соответствующего лучшим практикам по управлению разрешениями в Docker.
Применяя эти методы в своих Docker-проектах, вы можете значительно повысить безопасность и надежность своих контейнеризованных приложений. Помните, что запуск контейнеров с минимальными необходимыми привилегиями - это фундаментальный принцип безопасности, который всегда должен быть соблюден.
Основные выводы:
- Всегда используйте не-root пользователей в своих контейнерах.
- Сопоставляйте идентификаторы пользователей между хостом и контейнером при использовании томов.
- Устанавливайте правильные разрешения для файлов и директорий.
- Соблюдайте принцип минимальных привилегий.
Эти практики помогут вам создать более безопасные Docker-контейнеры и избежать распространенных проблем, связанных с разрешениями.



