Собесов

Security Engineering Яндекса — безопасный деплой в Docker-контейнерах

Кейсы и метрикиContainer securityСредняяMiddle

Условие

Друг хочет разместить веб-сервис в Docker-контейнерах. Какие рекомендации по безопасному деплою вы дадите?

Решение

1. Минимальный базовый образ

  • Distroless (gcr.io/distroless/...) или scratch для Go-бинарников.
  • Если нужен runtime — alpine или debian-slim. Больше пакетов = больше CVE.
  • Конкретный тег, не :latest. Ещё лучше — digest-pin: image@sha256:....

2. Multi-stage build

# Этап 1: сборка
FROM golang:1.22 AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/app ./cmd/app
 
# Этап 2: runtime
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /out/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]

В итоге — крошечный образ без bash, wget, curl, build-tools.

3. Не запускать от root

RUN addgroup -S app && adduser -S app -G app
USER app

или образы *-nonroot. Если приложение слушает <1024 порт — сменить на 8080+ или использовать setcap cap_net_bind_service=+ep.

4. Read-only filesystem

# docker-compose
services:
  app:
    read_only: true
    tmpfs: [/tmp]

Если приложение пишет на диск — выносим в volumes отдельно.

5. Capabilities и seccomp

  • Сбросить ВСЕ capabilities, добавить только нужные:
    cap_drop: [ALL]
    cap_add: [NET_BIND_SERVICE]   # если нужен 80/443
  • Default seccomp profile Docker уже хорош; кастомный — только при необходимости.
  • --security-opt=no-new-privileges — обязательно.

6. Секреты

  • Никогда не в Dockerfile (ENV PASSWORD=...) — попадёт в layer.
  • Не в build-args для prod — видно в docker history.
  • Использовать Docker Secrets, Kubernetes Secrets, HashiCorp Vault, AWS Secrets Manager.
  • В коде — читать из ENV или mounted file, не commit.
  • .dockerignore обязателен (исключаем .env, .git, node_modules).

7. Образы и реестры

  • Подпись и проверка образов: cosign sign/cosign verify, Docker Content Trust.
  • Сканирование на CVE в CI: trivy, grype, snyk container test.
  • Свой приватный registry (Harbor, ECR, GCR) с auth, не публичный Docker Hub.

8. Сеть

  • Внутри docker-compose — отдельные сети для backend/frontend/db.
  • БД не публиковать наружу: expose: (только внутри сети), а не ports:.
  • TLS терминируется в reverse-proxy (nginx/Traefik), а не в каждом контейнере.

9. Лимиты ресурсов

deploy:
  resources:
    limits:
      cpus: "1.0"
      memory: 512M

Защита от DoS: один контейнер не сожрёт всю машину.

10. Healthcheck

HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget -q --spider http://localhost:8080/health || exit 1

Орекстратор перезапустит «зависшие» контейнеры.

11. Логи и аудит

  • docker logs подключить к централизованной системе (Loki/Elastic).
  • Аудит Docker daemon на хосте (auditd на /var/lib/docker, /etc/docker).

12. Хост (host hardening)

  • Свежие ядро и Docker (CVE-2019-5736 — известный escape).
  • AppArmor/SELinux включён.
  • Никаких --privileged контейнеров. Если очень нужно (docker-in-docker, GPU) — полностью изолированный VM.
  • Не монтировать /var/run/docker.sock без необходимости — это полный root на хосте.

13. Обновления

  • CI пересобирает образы на каждый PR + ночью на свежей базе.
  • Регулярно docker pull базовых тегов и пересборка → пропатченные CVE.

Подводные камни

  1. USER в Dockerfile после EXPOSE может не сработать, если в ENTRYPOINT есть скрипт, требующий root. Решение — gosu/su-exec для понижения уровня в entrypoint.
  2. docker history выдаёт build-args. Не передавайте секреты через ARG. Используйте BuildKit secret-mounts: RUN --mount=type=secret,id=mytoken ….
  3. COPY . . копирует .env/.git. Без .dockerignore всё это попадает в образ.
  4. latest ломает воспроизводимость. Сегодня python:3-alpine = 3.12, завтра 3.13.
  5. --privileged = полный root. Никогда не используйте в продакшне.
  6. Docker socket = эскалация. -v /var/run/docker.sock:/var/run/docker.sock внутри контейнера = root на хосте. Используйте rootless docker или socket-proxy.
  7. alpine не всегда меньше уязвим. musl-libc даёт другие CVE, и некоторые тулы там нестабильны.
  8. HEALTHCHECK ничего не делает в Kubernetes. Там — livenessProbe/readinessProbe.
  9. Read-only без tmpfs для /tmp ломает приложения, пишущие во временные файлы.

Эталонный ответ

Distroless / minimal base + multi-stage build + non-root user + cap_drop: ALL + read_only + no-new-privileges + сканер CVE в CI (trivy) + секреты через Vault/Secrets Manager (не в env/Dockerfile) + лимиты ресурсов + сегментация сетей + регулярные пересборки.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти