
Привет, Кореша! Вы когда-нибудь задумывались о безопасности контейнеров, работающих в продакшене? 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.
Практический чеклист для внедрения
-
Сборка: Используйте multi-stage сборку и
distroless/scratchобразы. -
Запуск: Всегда запускайте контейнеры от непривилегированного пользователя (
USERв Dockerfile +runAsNonRoot: trueв K8s). -
Политики: Настройте
Pod Security Admissionна уровне неймспейсов как минимум сbaselineуровнем. -
Сканирование: Встройте сканирование образов на уязвимости (Trivy) в CI/CD.
-
Сетевые политики: Не забывайте про
NetworkPolicyдля изоляции трафика между подами.
Заключение
Безопасность контейнеров — это не продукт, а процесс и правильная конфигурация. Начиная с минимального образа и заканчивая строгими политиками в кластере, вы выстраивайте многоуровневую защиту, которая серьезно усложнит жизнь злоумышленнику.
Если нужен шаблон правильных контейнеров, конфиг PSS, загляните на bfd cards, где эти вопросы разбираются регулярно.
А какие практики безопасности используете вы? Делитесь в комментариях!
ссылка на оригинал статьи https://habr.com/ru/articles/944608/
Добавить комментарий