Лучшие практики по усилению безопасности Docker-образов и снижению поверхности атаки
Усиление безопасности Docker-образов: использование непривилегированных пользователей, минимальных базовых образов, многоэтапной сборки, безопасное управление секретами и сканирование уязвимостей.
Лучшие практики по усилению безопасности Docker-образов и снижению поверхности атаки
Усиление безопасности Docker-образов начинается с простого вопроса: что внутри этого образа не нужно вашему приложению? Лишние пользователи, оболочки, менеджеры пакетов, инструменты сборки и раскрытые секреты — всё это увеличивает ущерб, который может нанести скомпрометированный контейнер.
Применяйте эти практики при написании или проверке Dockerfile, особенно перед тем, как образ попадёт в общий реестр или production-кластер.
Запуск контейнеров от имени непривилегированного пользователя
Один из самых фундаментальных принципов безопасности — принцип минимальных привилегий. По умолчанию процессы внутри Docker-контейнера выполняются от имени root. Это даёт им обширные привилегии, которые могут быть использованы злоумышленниками при компрометации контейнера. Запуск приложения от имени непривилегированного пользователя значительно снижает потенциальный ущерб, который злоумышленник может нанести внутри контейнера.
Создание непривилегированного пользователя
Вы можете создать нового пользователя и группу в вашем Dockerfile, а затем переключиться на этого пользователя перед запуском приложения.
# Используем официальный образ Python в качестве родительского
FROM python:3.9-slim
# Устанавливаем рабочую директорию
WORKDIR /app
# Копируем содержимое текущей директории в контейнер в /app
COPY . /app
# Устанавливаем пакеты, указанные в requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
# Создаём непривилегированного пользователя и группу
RUN addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 --ingroup appgroup appuser
# Переключаемся на непривилегированного пользователя
USER appuser
# Делаем порт 80 доступным для внешнего мира
EXPOSE 80
# Определяем переменную окружения
ENV NAME World
# Запускаем app.py при запуске контейнера
CMD ["python", "app.py"]
Особенности использования непривилегированных пользователей
- Разрешения: Убедитесь, что у непривилегированного пользователя есть необходимые права на чтение и запись для директорий и файлов, требуемых вашим приложением. Возможно, потребуется использовать
chownдля установки соответствующих прав владельца. - Привязка портов: Непривилегированные пользователи обычно могут привязываться только к портам выше 1024. Если вашему приложению нужно привязаться к привилегированному порту (например, 80 или 443), рассмотрите возможность использования обратного прокси-сервера (например, Nginx или Traefik), работающего на хосте или в другом контейнере с соответствующими разрешениями, или настройте возможности Linux.
Минимизация установленных пакетов и зависимостей
Каждый пакет, установленный в вашем Docker-образе, увеличивает его размер и, что более важно, его поверхность атаки. Каждый пакет может иметь свои собственные уязвимости, которые могут быть использованы злоумышленниками. Поэтому крайне важно включать только то, что абсолютно необходимо.
Лучшие практики управления пакетами:
- Используйте минимальные базовые образы: Рассмотрите образы
slim, distroless или на основе Alpine, если они подходят для вашего runtime. Меньшие образы обычно содержат меньше пакетов, но всегда проверяйте совместимость, так как Alpine использует musl libc и может вести себя иначе, чем образы на основе Debian или Ubuntu. - Очищайте после установки: После установки пакетов очищайте кеш менеджера пакетов и временные файлы. Это не только уменьшает размер образа, но и удаляет потенциальные области для атак.
# Пример для образов на основе Debian/Ubuntu RUN apt-get update && apt-get install -y --no-install-recommends some-package && \ rm -rf /var/lib/apt/lists/* # Пример для образов на основе Alpine RUN apk add --no-cache some-package - Многоэтапная сборка: Это мощный метод для сохранения финального образа лёгким. Вы используете один этап для сборки вашего приложения (установка инструментов сборки, компиляторов и т.д.) и второй, чистый этап для копирования только необходимых артефактов из этапа сборки. Это предотвращает попадание зависимостей сборки в ваш production-образ.
# --- Этап сборки --- FROM golang:1.18-alpine AS builder WORKDIR /app COPY . . RUN go build -o myapp # --- Production-этап --- FROM alpine:latest WORKDIR /app COPY --from=builder /app/myapp . CMD ["./myapp"] - Регулярно обновляйте зависимости: Поддерживайте зависимости вашего приложения и базовые образы в актуальном состоянии для включения исправлений безопасности.
Реализация надёжных проверок работоспособности (Health Checks)
Проверки работоспособности (health checks) критически важны для мониторинга состояния ваших контейнеров. Docker может использовать эти проверки для определения, работает ли контейнер корректно, и для автоматического перезапуска или удаления нездоровых контейнеров. Чётко определённая проверка работоспособности помогает убедиться, что ваше приложение не только запущено, но и отвечает на запросы и функционирует должным образом.
Определение проверок работоспособности
Инструкция HEALTHCHECK в вашем Dockerfile задаёт команду, которую Docker будет периодически выполнять внутри контейнера для проверки его состояния. Если команда завершается с ненулевым статусом, контейнер считается нездоровым.
# Пример для веб-приложения
FROM nginx:latest
# ... другие инструкции ...
# Проверяем, запущен ли процесс Nginx и слушает ли он порт 80
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:80/ || exit 1
# ... другие инструкции ...
Лучшие практики для проверок работоспособности:
- Делайте их простыми: Команда проверки работоспособности должна быть лёгкой и быстрой в выполнении. Избегайте сложной логики, которая может замедлить проверку или внести собственные точки отказа.
- Тестируйте ключевую функциональность: Проверка должна в идеале тестировать основную функциональность вашего приложения, а не только то, запущен ли процесс. Для веб-сервера это может означать проверку возможности ответа на базовый HTTP-запрос.
- Настраивайте
start-period: Для приложений, которым требуется время на инициализацию, используйте опциюstart-period, чтобы дать им время на запуск до того, как проверки работоспособности начнут сигнализировать о сбоях.
Безопасное управление секретами и конфиденциальными данными
Никогда не встраивайте секреты, такие как API-ключи, пароли или сертификаты, непосредственно в ваш Dockerfile или образ. Эти секреты станут частью слоя образа и будут легко обнаружены. Вместо этого используйте Docker Secrets или переменные окружения, управляемые вашей платформой оркестрации (например, Kubernetes или Docker Swarm), для конфиденциальной информации.
Docker Secrets в режиме Swarm
Docker Swarm предоставляет встроенный механизм для управления секретами. Вы можете создавать секреты и монтировать их как файлы в контейнеры.
# Создание секрета
docker secret create my_api_key api_key.txt
# Развёртывание сервиса с использованием секрета
docker service create --secret my_api_key my_web_app
Переменные окружения с осторожностью
Хотя переменные окружения удобны, они также видны при инспектировании запущенного контейнера (docker inspect). Используйте их для неконфиденциальных данных конфигурации. Для конфиденциальных данных предпочтительнее использовать Docker Secrets или внешние системы управления секретами.
Использование конкретных тегов образов
При ссылке на базовые образы или другие образы в вашем Dockerfile (например, FROM ubuntu:latest) всегда используйте конкретные теги версий вместо latest. Использование latest может привести к непредсказуемым сборкам, так как тег latest может меняться со временем, потенциально внося критические изменения или даже уязвимости безопасности без вашего ведома.
# Избегайте этого:
# FROM ubuntu:latest
# Предпочитайте это:
FROM ubuntu:22.04
Сканирование образов на наличие уязвимостей
Регулярно сканируйте ваши Docker-образы на наличие известных уязвимостей. Несколько инструментов могут помочь вам в этом, как в вашем CI/CD пайплайне, так и в вашем реестре.
Популярные инструменты сканирования
- Trivy: Простой и всеобъемлющий сканер уязвимостей для контейнеров. Он сканирует пакеты ОС и зависимости приложений.
trivy image your-image-name:tag - Clair: Инструмент статического анализа с открытым исходным кодом для обнаружения уязвимостей в образах контейнеров.
- Docker Scout: Сервис от Docker, который анализирует образы контейнеров на предмет уязвимостей и предоставляет рекомендации.
Интеграция этих сканеров в ваш процесс сборки гарантирует, что вы будете осведомлены о потенциальных проблемах безопасности и сможете устранить их до развёртывания образов.
Понимание слоёв образа
Docker-образы строятся послойно. Когда вы вносите изменения в Dockerfile, создаётся новый слой. Понимание того, как работают слои, может помочь вам оптимизировать ваш Dockerfile как с точки зрения размера, так и безопасности. Размещайте инструкции, которые меняются реже (например, установка базовых пакетов), раньше в Dockerfile, а инструкции, которые меняются чаще (например, копирование кода приложения), позже. Это эффективно использует кеш сборки Docker и может ускорить сборку.
Что более важно для безопасности, конфиденциальная информация или случайные раскрытия в более ранних слоях могут сохраняться. Убедитесь, что любые конфиденциальные файлы или команды обрабатываются таким образом, чтобы они не оставались в финальных слоях образа, если они больше не нужны.
Сделайте усиление безопасности рутиной
Начните с Dockerfile, которые отправляются в production. Удалите инструменты сборки из финальных образов, запускайте от имени непривилегированного пользователя, фиксируйте базовые образы и сканируйте каждую сборку. Затем относитесь к усилению безопасности образа как к части обычного ревью кода, а не как к разовой чистке.