Почему плохо запускать Docker-контейнер от пользователя root?

Ответ

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

Конкретные риски:

  • Уязвимости ядра: Эксплойт может использовать уязвимость в ядре Linux для выхода из контейнера на хост.
  • Монтирование чувствительных директорий хоста: Если том смонтирован без должных ограничений (например, / или /etc), root в контейнере может их модифицировать.
  • Использование --privileged: Даёт контейнеру почти все capabilities ядра, делая его почти эквивалентным процессу на хосте.

Best Practices для смягчения рисков:

  1. Используйте непривилегированного пользователя в Dockerfile.

    FROM alpine:latest
    RUN addgroup -g 1000 appgroup && 
        adduser -u 1000 -G appgroup -s /bin/sh -D appuser
    USER appuser
    COPY --chown=appuser:appgroup app /app
    CMD ["/app/start.sh"]
  2. Запускайте контейнер с явным указанием пользователя.

    docker run --user 1000:1000 my-image
  3. В Kubernetes используйте securityContext.

    securityContext:
      runAsNonRoot: true
      runAsUser: 1000
      runAsGroup: 1000
      allowPrivilegeEscalation: false
      capabilities:
        drop:
          - ALL
  4. Ограничивайте Linux Capabilities. Сбрасывайте все и добавляйте только необходимые.

    docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE my-image
  5. Избегайте --privileged. Вместо этого монтируйте только конкретные устройства (--device) или используйте нужные capabilities.

  6. Используйте пользовательские пространства имён (user namespaces). Это мапит root в контейнере в непривилегированного пользователя на хосте (dockerd --userns-remap=default).

Ответ 18+ 🔞

Э, слушай, тут один чувак решил в контейнере от рута побегать, типа «я тут главный». Ну и пишет в Dockerfile USER root, а то и вообще по умолчанию так. Мужик, это же пиздец, а не идея! Это как оставить ключи от сейфа в замочной скважине и написать записку «не воровать». Доверия ебать ноль к такой схеме.

Представь: зловредный тип пролез в твое приложение внутри контейнера. А там — охуенно — он уже рут! Не на всём хосте, пока что, а только внутри этой песочницы. Но это же стартовая площадка для настоящего пиздеца! Это называется «эскалация привилегий», и звучит это страшнее, чем «гомосеки налетели» на твой незащищённый сервер.

Конкретно, что может пойти по пизде:

  • Дыры в ядре: Найдётся какая-нибудь годная уязвимость — и хуй с горы, злоумышленник уже не в контейнере, а на твоём основном хосте, делает что хочет. Удивление пиздец, ага.
  • Монтирование всякого говна: Если ты смонтировал в контейнер чувствительные папки хоста (типа / или /etc) без ограничений, то рут из контейнера может туда нагадить. Это чих-пых тебя в сраку от такой небрежности.
  • Флаг --privileged: А это вообще манда с ушами. Ты даёшь контейнеру почти все возможности ядра. Он уже почти не отличим от процесса на самом хосте. Зачем? Какого хуя?

Ладно, терпения ноль ебать читать про проблемы. Давай лучше, как делать надо, чтобы не было мучительно больно.

Как не наебаться с безопасностью:

  1. Заведи нормального пользователя в Dockerfile. Создай отдельную группу и юзера, и переключись на него. Это основа основ.

    FROM alpine:latest
    RUN addgroup -g 1000 appgroup && 
        adduser -u 1000 -G appgroup -s /bin/sh -D appuser # Создали юзера
    USER appuser # И переключились на него! ВАЖНО!
    COPY --chown=appuser:appgroup app /app
    CMD ["/app/start.sh"]

    Видишь? Никакого рута в рантайме.

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

    docker run --user 1000:1000 my-image

    Просто и эффективно.

  3. В Kubernetes используй securityContext. Тут всё серьёзно, нужно всё закрутить.

    securityContext:
      runAsNonRoot: true # Вообще запрещает запуск от рута
      runAsUser: 1000    # Запускай от конкретного UID
      runAsGroup: 1000   # И GID
      allowPrivilegeEscalation: false # Чтобы нельзя было повысить права
      capabilities:
        drop:
          - ALL # Сначала сбрасываем ВСЕ возможности

    Вот это — хитрая жопа, которая реально защищает.

  4. Режь Linux Capabilities. Контейнеру по умолчанию дают кучу ненужных прав. Отбери их все и дай только то, без чего он реально не работает.

    docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE my-image

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

  5. Забей хуй на --privileged. Серьёзно, да похуй, что тебе так удобно. Если нужно устройство — монтируй его конкретно (--device). Нужна какая-то способность — дай только её (--cap-add). Не включай овердохуища прав просто потому что лень.

  6. User namespaces — твой друг. Это такая крутая штука, которая мапит рута внутри контейнера в какого-нибудь непривилегированного юзера на хосте. Включил dockerd --userns-remap=default — и спи спокойно. Даже если в контейнере взломают рута, на хосте это будет просто цифра в файле /etc/subuid.

Короче, э бошка думай, когда деплоишь. Запуск от рута — это распиздяйство уровня «смерд пердящий». Делай по-человечески, с отдельным юзером и урезанными правами, и твой хуй с винтом будет в безопасности. Иначе будет вам хиросима и нигерсраки на продакшене, а тебе — ночной разбор полётов.