Docker: как запускать контейнер не от root и снизить риски

0
14
Обложка статьи: Docker: как запускать контейнер не от root и снизить риски

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

Шаг 1. Пользователь без прав в образе

FROM python:3.12-slim
RUN useradd --system --uid 10001 --no-create-home app
WORKDIR /app
COPY --chown=app:app . .
USER app
CMD ["python", "main.py"]

Если образ чужой, задайте пользователя при запуске: docker run --user 10001:10001 .... Некоторым приложениям нужны права на запись в каталоги, подготовьте их заранее.

Шаг 2. Уберите лишние привилегии при запуске

docker run -d --name app \
  --read-only --tmpfs /tmp \
  --cap-drop ALL \
  --security-opt no-new-privileges:true \
  --memory 512m --pids-limit 200 \
  myapp:1.4.2
  • --read-only запрещает запись в файловую систему контейнера, запись разрешена только в /tmp и в смонтированные тома.
  • --cap-drop ALL убирает системные возможности. Верните только нужные через --cap-add.
  • no-new-privileges не позволяет повысить права через setuid-файлы.

Если приложение не стартует, смотрите docker logs и добавляйте разрешения по одному, а не возвращайте всё сразу.

Шаг 3. Не используйте опасные режимы

  • --privileged даёт контейнеру почти полный доступ к хосту.
  • Монтирование /var/run/docker.sock внутрь контейнера равносильно root на хосте: кто управляет сокетом, тот запускает любые контейнеры.
  • --network host убирает сетевую изоляцию.

Шаг 4. Порты привязывайте к нужному адресу

docker run -p 127.0.0.1:5432:5432 postgres:15-alpine     # только с самого хоста
docker run -p 5432:5432 postgres:15-alpine               # доступно всему интернету

Docker сам вносит правила в firewall и обходит ufw, поэтому опубликованный порт может быть открыт, даже если вы его «закрыли». Подробнее о firewall: nftables и firewalld. В Compose используйте тот же формат: "127.0.0.1:5432:5432".

Шаг 5. Образы и обновления

  • Берите образы из проверенных источников и закрепляйте версию тегом, а не latest.
  • Проверяйте образы на известные уязвимости: trivy image myapp:1.4.2.
  • Не кладите секреты в образ и в переменные окружения в Dockerfile: секреты в git.

Как проверить

docker exec app id
docker inspect app --format 'user={{.Config.User}} priv={{.HostConfig.Privileged}} ro={{.HostConfig.ReadonlyRootfs}} caps={{.HostConfig.CapDrop}}'
docker ps --format '{{.Names}}  {{.Ports}}'
docker ps -q | xargs docker inspect --format '{{.Name}} priv={{.HostConfig.Privileged}}'

Команда id должна показывать не uid=0, privileged — false. Список портов покажет контейнеры, открытые на 0.0.0.0. Эти порты проверьте снаружи сканером: Nmap. Для более строгой изоляции изучите rootless-режим Docker по документации.

Оставьте свой ответ