Защищаем контейнеры с нуля: практическое руководство по Docker security и Kubernetes security contexts

от автора

Привет, Кореша! Вы когда-нибудь задумывались о безопасности контейнеров, работающих в продакшене? Docker и Kubernetes предоставляют широкий набор инструментов, которые могут быть использованы плохими людьми. Безопасность контейнеров — это не просто волшебная защита, а многослойная система, охватывающая весь процесс от сборки до запуска в кластере.

В этой статье мы разберем практические шаги по защите ваших контейнеров, от написания безопасного Dockerfile до настройки политик безопасности в Kubernetes.

Этап 1: Безопасность на этапе сборки (Dockerfile)

Безопасность начинается с образа. Ваша цель — создать минимальный, неизменяемый и безопасный артефакт.

1. Используйте минимальные базовые образы
Чем меньше пакетов внутри, тем меньше шанс для атаки.

Нельзя:

dockerfile

FROM ubuntu:latest  # Полноценная ОС со всеми утилитами и уязвимостями RUN apt update && apt install -y python3 COPY app.py . CMD ["python3", "app.py"]

Можно:

dockerfile

FROM python:3.11-slim-bookworm  # Урезанный официальный образ на основе Debian slim COPY app.py . CMD ["python3", "app.py"]

Идеально (для Go, Java, Node.js):

dockerfile

# Multi-stage build - идеальный вариант FROM golang:1.21 AS builder WORKDIR /app COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -o myapp .  # Финальный образ на основе "scratch" (абсолютно пустой) или distroless FROM gcr.io/distroless/static-debian12 COPY --from=builder /app/myapp / CMD ["/myapp"]

Образ distroless от Google содержит только ваше приложение и его минимальные зависимости, без shell, package manager и других стандартных утилит. Взломщику просто нечем будет воспользоваться.

2. Не запускайте процессы от root
Это золотое правило контейнерной безопасности.

Плохо:

dockerfile

FROM node:20 COPY . . RUN npm install CMD ["node", "index.js"] # Запускается от root!

Хорошо:

dockerfile

FROM node:20-slim  # Создаем непривилегированного пользователя и группу RUN groupadd -r appgroup && useradd -r -g appgroup appuser  # Меняем владельца файлов приложения COPY --chown=appuser:appgroup . /app WORKDIR /app RUN npm install  # Переключаемся на непривилегированного пользователя USER appuser  CMD ["node", "index.js"]

3. Сканируйте образы на уязвимости
Интегрируйте сканирование в CI/CD пайплайн.

bash

# Пример с Trivy (бесплатный и открытый сканер) trivy image my-app:latest  # Или с Docker Scout docker scout quickview my-app:latest

Этап 2: Безопасность на этапе выполнения (Kubernetes Security Contexts)

Kubernetes предоставляет мощный инструмент — securityContext, который позволяет fine-grained настройку прав контейнера.

1. Запрещаем запуск от root
Даже если в образе забыли указать USER.

yaml

apiVersion: v1 kind: Pod metadata:   name: security-demo spec:   containers:   - name: sec-ctx-demo     image: node:20-slim     securityContext:       runAsNonRoot: true # Kubernetes не даст запустить pod, если он работает от root       runAsUser: 1000 # Явно указываем UID       runAsGroup: 3000 # Явно указываем GID

2. Ограничиваем возможности (Capabilities)
По умолчанию контейнер получает множество ненужных привилегий. Отзовите всё и дайте только необходимое.

yaml

securityContext:   capabilities:     drop: ["ALL"] # Отзываем ВСЕ возможности     add: ["NET_BIND_SERVICE"] # Добавляем только одну: возможность занять порт ниже 1024

3. Запрещаем эскалацию привилегий
Важная настройка, которая не позволяет процессу внутри контейнера получить больше прав, чем у него есть.

yaml

securityContext:   allowPrivilegeEscalation: false

4. Режим только для чтения (ReadOnlyRootFilesystem)
Идеальная практика для неизменяемых контейнеров. Если вашему приложению не нужно ничего писать на диск — включайте.

yaml

securityContext:   readOnlyRootFilesystem: true # Но если нужно писать логи во временную папку volumeMounts: - name: temp-vol   mountPath: /tmp   readOnly: false  volumes: - name: temp-vol   emptyDir: {}

Этап 3: Системные политики (Pod Security Standards)

SecurityContext — это хорошо для одного пода. Но что если нужно применить политики ко всему кластеру? Здесь на помощь приходят Pod Security Admission (встроено в K8s с v1.25+) или более старый PodSecurityPolicy (устарел).

Уровни политик PSA:

  • Privileged: Без ограничений, для системных workloads.

  • Baseline: Минимальные ограничения, блокирующие известные эскалации привилегий.

  • Restricted: Жесткие ограничения, следующие best practices.

Пример Namespace с политикой Restricted:

yaml

apiVersion: v1 kind: Namespace metadata:   name: my-app   labels:     pod-security.kubernetes.io/enforce: restricted     pod-security.kubernetes.io/enforce-version: v1.29

Теперь при попытке создать под без securityContext в этом неймспейсе он будет отвергнут.

Этап 4: Следующий уровень — AppArmor и seccomp

Для параноиков и тех, кому нужна максимальная защита

Seccomp — ограничивает системные вызовы, которые может делать процесс.

yaml

securityContext:   seccompProfile:     type: RuntimeDefault # Используем профиль по умолчанию, предоставленный container runtime

Можно создавать и свои кастомные профили, чтобы запрещать опасные вызовы вроде execve.

Практический чеклист для внедрения

  1. Сборка: Используйте multi-stage сборку и distroless/scratch образы.

  2. Запуск: Всегда запускайте контейнеры от непривилегированного пользователя (USER в Dockerfile + runAsNonRoot: true в K8s).

  3. Политики: Настройте Pod Security Admission на уровне неймспейсов как минимум с baseline уровнем.

  4. Сканирование: Встройте сканирование образов на уязвимости (Trivy) в CI/CD.

  5. Сетевые политики: Не забывайте про NetworkPolicy для изоляции трафика между подами.

Заключение

Безопасность контейнеров — это не продукт, а процесс и правильная конфигурация. Начиная с минимального образа и заканчивая строгими политиками в кластере, вы выстраивайте многоуровневую защиту, которая серьезно усложнит жизнь злоумышленнику.

Если нужен шаблон правильных контейнеров, конфиг PSS, загляните на bfd cards, где эти вопросы разбираются регулярно.

А какие практики безопасности используете вы? Делитесь в комментариях!


ссылка на оригинал статьи https://habr.com/ru/articles/944608/