Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Безопасность контейнеров
security

Безопасность контейнеров

Изоляция, сканирование уязвимостей, security best practices

Безопасность контейнеров

Безопасность — критический аспект production-развёртывания. Изучите лучшие практики защиты контейнеров от уязвимостей и атак.

#Угрозы безопасности контейнеров

Основные риски:

  1. Уязвимости в образах — старые пакеты, известные CVE
  2. Запуск от root — компрометация хоста при взломе
  3. Небезопасные секреты — пароли в образах, переменных окружения
  4. Недостаточная изоляция — побег из контейнера
  5. Незащищённые сети — доступ к внутренним сервисам
  6. Неограниченные ресурсы — DoS через потребление ресурсов

#Запуск от непривилегированного пользователя

Проблема: По умолчанию контейнеры запускаются от root.

# ❌ Плохо — запуск от root FROM python:3.11-slim COPY app.py . CMD ["python", "app.py"] # ✅ Хорошо — создание пользователя FROM python:3.11-slim RUN useradd --create-home --shell /bin/bash appuser WORKDIR /app COPY --chown=appuser:appuser . . USER appuser CMD ["python", "app.py"]

Почему важно:

  • При уязвимости в приложении злоумышленник не получит root на хосте
  • Соответствие требованиям безопасности (CIS Benchmark, PCI DSS)

#Docker Compose

services: app: image: myapp:latest user: "1000:1000" # UID:GID # или user: appuser

#Сканирование образов на уязвимости

#Docker Scout

# Сканирование образа docker scout cve myapp:latest # Рекомендации docker scout recommendations myapp:latest # Сравнение версий docker scout compare myapp:v1.0 myapp:v2.0

#Trivy

# Установка brew install trivy # Сканирование образа trivy image myapp:latest # Сканирование с отчётом trivy image --format table --output report.html myapp:latest # Только критические уязвимости trivy image --severity CRITICAL myapp:latest # Сканирование Dockerfile trivy config Dockerfile

#Snyk

# Установка npm install -g snyk # Аутентификация snyk auth # Сканирование образа snyk container test myapp:latest # Сканирование с исправлениями snyk container test myapp:latest --file=Dockerfile

#В CI/CD

# GitHub Actions - name: Scan for vulnerabilities run: | trivy image --exit-code 1 --severity CRITICAL myapp:latest

#Безопасное управление секретами

#❌ Неправильно

# Секреты в Dockerfile ENV DATABASE_PASSWORD=supersecret123 COPY .env /app/.env # Хардкод в коде PASSWORD = "admin123"
# Секреты в docker-compose.yml services: app: environment: - DATABASE_PASSWORD=supersecret123 - API_KEY=sk-1234567890

#✅ Правильно

#1. Docker Secrets (Swarm)

# Создание секрета echo "supersecret123" | docker secret create db_password - # Использование в сервисе docker service create \ --secret db_password \ --name myapp \ myapp:latest
# docker-compose.yml (Swarm mode) version: '3.8' services: app: image: myapp:latest secrets: - db_password - api_key secrets: db_password: external: true api_key: external: true
# Чтение секрета в приложении with open('/run/secrets/db_password', 'r') as f: password = f.read().strip()

#2. Переменные окружения из .env

# .env (добавить в .gitignore) DATABASE_PASSWORD=supersecret123 API_KEY=sk-1234567890
# docker-compose.yml services: app: image: myapp:latest env_file: - .env

#3. Внешние менеджеры секретов

# HashiCorp Vault services: app: image: myapp:latest environment: - VAULT_ADDR=http://vault:8200 # Секреты загружаются при старте через init-контейнер

#4. Docker BuildKit secrets

# Dockerfile FROM python:3.11-slim # Секрет доступен только во время сборки RUN --mount=type=secret,id=pip_conf \ cp /run/secrets/pip_conf /etc/pip.conf && \ pip install -r requirements.txt CMD ["python", "app.py"]
# Сборка с секретом docker build --secret id=pip_conf,src=~/.pip/pip.conf -t myapp .

#Ограничение ресурсов

Проблема: Без ограничений контейнер может потребить все ресурсы хоста.

# Ограничение памяти docker run -d \ --memory=512m \ --memory-swap=512m \ myapp:latest # Ограничение CPU docker run -d \ --cpus=1.5 \ --cpu-shares=512 \ myapp:latest # Ограничение процессов docker run -d \ --pids-limit=100 \ myapp:latest # Комбинированное ограничение docker run -d \ --memory=1g \ --cpus=2 \ --pids-limit=50 \ --read-only \ --tmpfs /tmp:size=100m \ myapp:latest

#В Docker Compose

services: app: image: myapp:latest deploy: resources: limits: cpus: '1' memory: 512M pids: 100 reservations: cpus: '0.5' memory: 256M

#Read-only файловая система

docker run -d \ --read-only \ --tmpfs /tmp:size=100m \ --tmpfs /var/run:size=10m \ myapp:latest

Преимущества:

  • Злоумышленник не может записать вредоносные файлы
  • Предотвращает модификацию бинарников

#В Docker Compose

services: app: image: myapp:latest read_only: true tmpfs: - /tmp:size=100m - /var/run:size=10m

#Сетевая безопасность

#Изоляция сетей

version: '3.8' services: frontend: image: nginx networks: - frontend-network api: image: myapi networks: - frontend-network - backend-network db: image: postgres networks: - backend-network networks: frontend-network: driver: bridge backend-network: driver: bridge internal: true # Нет доступа наружу

Принцип наименьших привилегий:

  • Frontend не имеет доступа к backend-network напрямую
  • DB изолирована в internal сети (нет интернета)
  • API — единственный мост между сетями

#Ограничение публикации портов

# ✅ Хорошо — только localhost docker run -d -p 127.0.0.1:5432:5432 postgres # ❌ Плохо — доступно всем интерфейсам docker run -d -p 5432:5432 postgres
# docker-compose.yml services: db: image: postgres ports: - "127.0.0.1:5432:5432" # Только localhost

#Network Policies (Kubernetes)

apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: db-access spec: podSelector: matchLabels: app: postgres policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app: api ports: - protocol: TCP port: 5432

#Security Context (Capabilities)

#Удаление capabilities

# Удалить все capabilities, добавить только необходимые docker run -d \ --cap-drop=ALL \ --cap-add=NET_BIND_SERVICE \ myapp:latest

Common capabilities:

  • NET_BIND_SERVICE — привязка к портам < 1024
  • CHOWN — изменение владельца файлов
  • SETUID, SETGID — изменение UID/GID
  • SYS_CHROOT — использование chroot

#В Docker Compose

services: app: image: myapp:latest cap_drop: - ALL cap_add: - NET_BIND_SERVICE

#AppArmor и SELinux

#AppArmor (Ubuntu/Debian)

# Проверка профиля docker inspect --format='{{.AppArmorProfile}}' container_id # Использование кастомного профиля docker run -d \ --security-opt apparmor=/etc/apparmor.d/myapp \ myapp:latest

#SELinux (RHEL/CentOS)

# Запуск с SELinux docker run -d \ --security-opt label=level:s0:c100,c200 \ myapp:latest # Отключение SELinux для контейнера (не рекомендуется) docker run -d \ --security-opt label=disable \ myapp:latest

#Seccomp профили

Seccomp ограничивает системные вызовы.

# Запуск с кастомным профилем docker run -d \ --security-opt seccomp=/path/to/seccomp.json \ myapp:latest # Отключение (не рекомендуется) docker run -d \ --security-opt seccomp=unconfined \ myapp:latest

#Минимальный seccomp профиль

{ "defaultAction": "SCMP_ACT_ERRNO", "syscalls": [ { "names": ["accept", "bind", "connect"], "action": "SCMP_ACT_ALLOW" } ] }

#Health Checks

Проблема: Контейнер может работать, но приложение не отвечать.

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/health || exit 1

Проверка статуса:

docker ps # Покажет (healthy) или (unhealthy) docker inspect --format='{{.State.Health.Status}}' container_id

#В Docker Compose

services: api: image: myapi:latest healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 3s retries: 3 start_period: 10s

#Логирование и аудит

#Настройка логирования

docker run -d \ --log-driver=json-file \ --log-opt max-size=10m \ --log-opt max-file=3 \ myapp:latest

#Централизованный сбор логов

# docker-compose.yml services: app: image: myapp:latest logging: driver: syslog options: syslog-address: "udp://logserver:514" tag: "myapp"

#Аудит Docker событий

# Просмотр событий docker events # Фильтрация docker events --filter 'type=container' --filter 'action=start'

#Best Practices Checklist

#Образы

  • Использовать минимальные базовые образы (alpine, distroless)
  • Использовать конкретные теги (не latest)
  • Сканировать образы на уязвимости (Trivy, Snyk)
  • Регулярно обновлять базовые образы
  • Не запускать процессы от root

#Контейнеры

  • Ограничивать ресурсы (CPU, память, pids)
  • Использовать read-only файловую систему
  • Удалять capabilities (--cap-drop=ALL)
  • Использовать seccomp и AppArmor/SELinux профили
  • Настраивать health checks

#Секреты

  • Не хранить секреты в образах
  • Не передавать секреты через переменные окружения в production
  • Использовать Docker Secrets или внешние менеджеры (Vault)
  • Шифровать секреты при хранении

#Сеть

  • Изолировать сети по уровням приложения
  • Использовать internal: true для backend-сетей
  • Публиковать порты только на localhost при необходимости
  • Использовать network policies в Kubernetes

#CI/CD

  • Сканировать образы в пайплайне
  • Подписывать образы (Docker Content Trust)
  • Использовать private registry для production
  • Автоматизировать обновление образов

#Docker Content Trust

Подпись образов:

# Включить DCT export DOCKER_CONTENT_TRUST=1 # Подписать образ при push docker push myrepo/myapp:v1.0 # При pull проверяется подпись docker pull myrepo/myapp:v1.0

#Практика: безопасный Dockerfile

# Многоэтапная сборка FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # Production этап FROM node:18-alpine # Метаданные LABEL maintainer="security@example.com" # Установка зависимостей для health check RUN apk --no-cache add curl # Создание пользователя RUN addgroup -S appgroup && adduser -S appuser -G appgroup WORKDIR /app # Копирование артефактов COPY --from=builder --chown=appuser:appgroup /app/dist ./dist COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules COPY --from=builder --chown=appuser:appgroup /app/package.json ./ # Переключение на непривилегированного пользователя USER appuser # Health check HEALTHCHECK --interval=30s --timeout=3s --retries=3 \ CMD curl -f http://localhost:3000/health || exit 1 # Порт EXPOSE 3000 CMD ["node", "dist/index.js"]

#Troubleshooting

#Контейнер не запускается с read-only

# Проверить, куда пытается писать приложение docker logs container_id # Добавить tmpfs для записи docker run -d --read-only --tmpfs /tmp:size=100m myapp

#Приложение требует root capabilities

# Добавить только необходимую capability docker run -d --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp # Или использовать setuid бинарник (не рекомендуется)

#Сканирование показывает уязвимости

# Обновить базовый образ docker pull python:3.11-slim # Пересобрать образ docker build --no-cache -t myapp:latest . # Проверить снова trivy image myapp:latest

#📊 Case Studies

#Case Study 1: Уязвимость в production из-за устаревшего образа

Контекст: FinTech стартап с API на Python (FastAPI).

Инцидент:

  • Критическая уязвимость CVE-2023-XXXX в библиотеке urllib3
  • Образ основан на python:3.9 (не обновлялся 18 месяцев)
  • Злоумышленник эксплуатировал уязвимость для SSRF-атаки
  • Утечка данных 10 000 пользователей

Причина:

# ❌ Было — образ 2021 года FROM python:3.9.0 # urllib3==1.26.3 с уязвимостью

Решение:

# ✅ Стало — автоматическое обновление FROM python:3.11-slim # Сканирование в CI/CD # RUN trivy image --exit-code 1 --severity CRITICAL . # Обновление зависимостей RUN apt update && apt upgrade -y && rm -rf /var/lib/apt/lists/* # Установка зависимостей с проверкой версий COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Проверка уязвимостей # RUN pip-audit || true

Процесс безопасности:

# .github/workflows/security.yml name: Security Scan on: push: branches: [main] schedule: - cron: '0 6 * * *' # Ежедневно в 6:00 jobs: trivy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Build image run: docker build -t myapp:${{ github.sha }} . - name: Run Trivy run: | docker run --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ aquasec/trivy image \ --exit-code 1 \ --severity CRITICAL,HIGH \ myapp:${{ github.sha }} - name: Upload report if: failure() uses: actions/upload-artifact@v3 with: name: trivy-report path: trivy-report.sarif

Результат:

МетрикаДоПослеУлучшение
Критические уязвимости230100%
Время обнаруженияНедели<24 часа7x
Возраст базового образа18 мес<1 мес18x

Выводы:

  • Автоматическое сканирование в CI/CD обязательно
  • Регулярное обновление базовых образов
  • Pin версий зависимостей с проверкой на уязвимости

#Case Study 2: Утечка секретов через GitHub

Контекст: E-commerce платформа с Docker Compose.

Инцидент:

  • Разработчик закоммитил .env с паролями в публичный репозиторий
  • Пароли от БД и API ключи украдены
  • Злоумышленники получили доступ к production БД

Причина:

# ❌ Было — секреты в репозитории cat docker-compose.yml # environment: # - DATABASE_PASSWORD=SuperSecret123 # Виден всем! cat .env # Закоммичен в git! # DATABASE_PASSWORD=SuperSecret123 # API_KEY=sk-1234567890

Решение:

# ✅ Стало — Docker Secrets + external manager # 1. Создание секрета echo "SuperSecret123" | docker secret create db_password - # 2. Использование в Compose cat docker-compose.yml
version: '3.8' services: db: image: postgres:15 environment: POSTGRES_PASSWORD_FILE: /run/secrets/db_password secrets: - db_password app: image: myapp:latest secrets: - db_password - api_key secrets: db_password: external: true api_key: external: true
# Чтение секрета в приложении def get_secret(name): with open(f'/run/secrets/{name}', 'r') as f: return f.read().strip() DB_PASSWORD = get_secret('db_password') API_KEY = get_secret('api_key')

.gitignore для защиты:

# Никогда не коммитить! .env .env.*.local *.pem *.key secrets/ credentials/

Результат:

МетрикаДоПослеУлучшение
Секреты в git150100%
Доступ к secretsВсе разработчикиТолько runtime✅
Audit logНетЕсть (Docker)✅

#Case Study 3: Побег из контейнера через privileged режим

Контекст: Multi-tenant платформа с общим Docker-хостом.

Инцидент:

  • Злоумышленник запустил контейнер с --privileged
  • Получил доступ ко всем устройствам хоста
  • Прочитал /var/run/docker.sock
  • Запустил контейнер с доступом к ФС хоста
  • Украл данные всех tenants

Причина:

# ❌ Было — privileged режим docker run -d --privileged myapp # Внутри контейнера: mount -t proc none /mnt # Доступ к хосту! cat /mnt/1/etc/shadow # Чтение паролей хоста

Решение:

# ✅ Стало — минимальные capabilities docker run -d \ --cap-drop=ALL \ --cap-add=NET_BIND_SERVICE \ --read-only \ --tmpfs /tmp:size=100m \ --security-opt seccomp=default \ --security-opt apparmor=docker-default \ myapp # Блокировка доступа к docker.sock # Никогда не монтировать /var/run/docker.sock в приложение!
# docker-compose.yml с безопасностью services: app: image: myapp:latest read_only: true tmpfs: - /tmp:size=100m - /var/run:size=10m cap_drop: - ALL cap_add: - NET_BIND_SERVICE security_opt: - no-new-privileges:true # Никогда не использовать: # privileged: true # ❌ # devices: # ❌ # - /dev/sda:/dev/sda

Hardening хоста:

# Запрет privileged контейнеров # /etc/docker/daemon.json { "no-new-privileges": true, "userns-remap": "default" } # Аудит контейнеров docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" | grep -v "alpine\|slim" # Проверка на privileged for container in $(docker ps -q); do docker inspect $container --format='{{.HostConfig.Privileged}}' | grep -q true && \ echo "WARNING: $container is privileged!" done

Результат:

МетрикаДоПослеУлучшение
Privileged контейнеры120100%
Доступ к docker.sock5 сервисов0100%
Surface для атакиВысокийМинимальный✅

Далее: Работа с реестрами