
Привет, Кореша! Вы когда-нибудь задумывались о безопасности контейнеров, работающих в продакшене? 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, где эти вопросы разбираются регулярно.
А какие практики безопасности используете вы? Делитесь в комментариях!