Всем привет, меня зовут Сергей Прощаев, и в этой статье я расскажу про базовые принципы безопасности в Kubernetes, которые уберегут вас от кошмаров вроде взлома кластера для майнинга криптовалюты.

Если вы только начинаете работать с k8s, то ваша главная задача — не просто задеплоить приложение, а сделать это так, чтобы завтра не проснуться героем техновостей. Помните инцидент с Tesla в 2018 году? Тогда хакеры нашли незащищенный Kubernetes Dashboard, выставленный в интернет без пароля. Через него они получили доступ к AWS-ключам и другим секретам, а потом тихо использовали вычислительные мощности облака Tesla для майнинга Monero. Компания не только понесла прямые финансовые потери, но и оказалась в крайне неприятной ситуации с точки зрения доверия клиентов и регуляторов.

Это не какая-то сложная атака нулевого дня. Это цепочка элементарных упущений: незакрытый доступ, избыточные права сервисного аккаунта, секреты в открытом доступе. И именно такие ошибки — daily bread для начинающих инженеров, которые торопятся «завести» кластер и забывают про security by design.

Я руковожу направлением Java-разработки в FinTech, и вместе с командой мы активно изучаем и внедряем лучшие практики деплоя бэкенд-приложений. Поэтому сегодня я разберу три кита безопасности Kubernetes: аутентификацию и доступ, управление секретами и изоляцию workload'ов. Мы посмотрим, где чаще всего ошибаются, и как эти ошибки исправить простыми, но эффективными практиками. А в конце расскажу, где можно системно прокачать эти навыки, чтобы не учиться на своих дорогих ошибках.

Кто в доме хозяин? Минимизация доступа (Principle of Least Privilege)

Самая частая и опасная ошибка — раздача прав «на всё». По умолчанию сервисный аккаунт pod'а в namespace default имеет совсем не нулевые права в этом же неймспейсе. А если ему дали cluster-admin для «удобства деплоя», то один скомпрометированный под становится королём всего кластера.

Что чаще всего идёт не так?

  • Сервисные аккаунты с правами cluster-admin или edit. «Ну чтобы всё работало». С одной стороны понятно, что хочется чтобы все быстрее взлетело, но с другой стороны это может стать результатом эскалация привилегий при любой уязвимости в приложении.

  • Использование default сервисного аккаунта. У него часто есть права на чтение секретов в своём неймспейсе. И это обязательно я рекомендовал бы проверить!

  • Широкие роли (Role/ClusterRole) с правилами  на ресурсы . «Так проще потом не искать, почему нет доступа». Да, но тут опасность как раз кроется в том, что доступ будет у того, у кого его быть не должно.

Как делать правильно?

  1. Во-первых, всегда создавайте отдельный сервисный аккаунт для каждого приложения или компонента. Это базис для изоляции!

  2. Во-вторых назначайте права по принципу минимальной необходимости. Используйте встроенные роли (viewedit) только как отправную точку, а лучше создавайте кастомные.

  3. И в третьих, регулярно аудитируйте права. Лучше аудита может быть только два аудита! Инструменты вроде kubectl-who-can или rbac-lookupпокажут, у кого какие есть права.

Практический пример: роли вместо всевластия

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

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: myapp-production
  name: myapp-reader-logger
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["get", "list", "watch"]
- apiGroups: [""]
  resources: ["events"]
  verbs: ["create"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: myapp-production
  name: myapp-backend-sa
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: myapp-production
  name: bind-myapp-sa-to-role
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: myapp-reader-logger
subjects:
- kind: ServiceAccount
  name: myapp-backend-sa
  namespace: myapp-production

Этот код — антипод подходу «дадим edit и забудем». Мы явно прописали, что можно делать, и ничего лишнего.

При проведении аудита одного из проектов я обнаружил, что более 80% сервисных аккаунтов имели права на чтение секретов во всём кластере. Угроза была не в злом умысле, а в случайной утечке логов или ошибке в библиотеке, которая могла прочитать все секреты и отправить «куда не надо». Устраняли это два месяца, компонент за компонентом.

Секреты — не для всех глаз. Управление чувствительными данными

Вторая по популярности ошибка — отношение к секретам Kubernetes (объектам Secret) как к безопасному хранилищу. Они не шифруются на диске etcd по умолчанию (только base64). Если злоумышленник получит доступ к API или бэкапу etcd, все пароли, токены и ключи — его.

Что чаще всего идёт не так?

  • Хранение секретов в виде plain-text или base64 в git-репозиториях. Вспомните кейс 2023 года от Aqua Security, когда утечки из публичных репозиториев затронули гигантов вроде SAP.

  • Монтирование всего объекта Secret в под, когда нужно только одно конкретное значение.

  • Отсутствие ротации секретов. Ключи живут годами.

Как делать правильно?

  1. Никогда не коммитьте секреты в git. Даже в приватный репозиторий. Используйте инструменты типа HashiCorp Vault или облачные менеджеры секретов (AWS Secrets Manager, Azure Key Vault). Для GitOps-подхода отлично подходит External Secrets Operator (ESO), который автоматически подтягивает секреты из внешних систем в кластер.

  2. Включайте шифрование данных на rest (at-rest encryption) для etcd. Это must-have для любого продакшн-кластера. Настраивается через EncryptionConfiguration.

  3. Монтируйте секреты как отдельные файлы или переменные окружения с минимальными правами. И используйте securityContext.readOnlyRootFilesystem: true там, где возможно.

Пример безопасного использования секрета через переменную окружения:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-backend
spec:
  template:
    spec:
      serviceAccountName: myapp-backend-sa # Используем созданный ранее SA
      containers:
      - name: app
        image: myapp:latest
        env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-credentials
              key: password
        securityContext:
          readOnlyRootFilesystem: true
          allowPrivilegeEscalation: false

Оптимальный способ дополнительной защиты — это настройка в CI/CD-пайплайне статического анализа манифестов с помощью Checkov или kubeaudit. Эти инструменты подсветят потенциальные проблемы: от незашифрованных секретов до подов, запущенных от root.

Крепость для каждого подка: изоляция workload'ов с помощью Security Context и Policies

Запуск контейнера с правами root (USER 0 в Dockerfile) внутри пода — это как оставить ключи от квартиры под ковриком. Если злоумышленник скомпрометирует такое приложение, он получит root-права внутри ноды. В истории с Tesla это был один из ключевых факторов эскалации.

Что чаще всего идёт не так?

  • Запуск контейнеров от root (или без явного securityContext).

  • Использование privileged: true для решения проблем с доступом к устройству или сети.Это почти всегда можно решить безопаснее.

  • Монтирование чувствительных путей ноды (//etc/var/run/docker.sock) в поды.

Как делать правильно?

  1. Всегда задавайте securityContext для пода и контейнера. Указывайте не привилегированного пользователя (UID/GID), запрещайте эскалацию привилегий.

  2. Внедряйте стандарты безопасности подов. Начните с Pod Security Standards (PSS). Примените хотя бы уровень baseline как advisory, а потом как enforcement. Для более гибкого и мощного контроля используйте Kyverno или OPA Gatekeeper. Kyverno, на мой взгляд, идеален для начинающих: политики пишутся на YAML, и есть богатая библиотека готовых правил.

  3. Запрещайте privileged. Используйте SecurityContextDeny адмиссион-контроллер или политики в Kyverno.

Пример безопасного securityContext для пода:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: security-demo-app
spec:
  template:
    spec:
      securityContext: # Контекст на уровне пода
        runAsNonRoot: true
        runAsUser: 1000
        runAsGroup: 1000
        fsGroup: 2000
      containers:
      - name: app
        image: nginx:alpine
        securityContext: # Контекст на уровне контейнера
          allowPrivilegeEscalation: false
          capabilities:
            drop:
            - ALL
          readOnlyRootFilesystem: true

Эта конфигурация делает под максимально замкнутым: не-root пользователь, сброшены все Linux capabilities, корневая ФС только для чтения.

Моя ошибка, ставшая правилом: Раньше я думал, что runAsNonRoot: true — это излишняя строгость. Пока в одном тестовом окружении приложение с уязвимостью в библиотеке не начало пытаться писать в /etc/passwd. Блокировка на уровне securityContext остановило эту попытку моментально. С тех пор это обязательный атрибут в наших шаблонах.

Чекап-лист для начинающего: с чего начать завтра

Чтобы всё это не казалось горой несделанного, вот план действий на первые недели внедрения:

  1. Неделя 1: Аудит.

    Запустите kubectl get serviceaccounts --all-namespaces и проверьте, кто и где использует default.

    С помощью kubectl-who-can или rbac-lookup проверьте, у кого есть права cluster-admin или на secrets.

    Просканируйте манифесты в git с помощью checkov или kubeaudit.

  2. Неделя 2: Базовые защиты.

    Для нового неймспейса включите Pod Security Admission (PSA) на уровне baseline: warn. Посмотрите, какие поды не проходят.

    Начните создавать отдельные ServiceAccount для новых приложений с кастомными Role/RoleBinding.

    Включите (или запланируйте включение) шифрование rest для etcd.

  3. Неделя 3: Инструментарий и культура.

    Установите Kyverno и внедрите 2-3 ключевые политики (например, запрет privileged и требование runAsNonRoot).

    Интегрируйте проверку манифестов в CI/CD-пайплайн.

    Проведите ликбез для команды: покажите на примере Tesla, к чему ведут пробелы в безопасности.

Заключение: Безопасность — это не фича, а процесс

Как вы видите, основы безопасности в Kubernetes — это не про сложные инструменты и сертификаты. Это про дисциплину, внимание к деталям и отказ от шаблона «работает — и ладно».

Тот самый инцидент с Tesla начался не с хакерской атаки нулевого дня, а с банально открытого дашборда. Цепочка из небольших упущений привела к крупному скандалу. Ваша цель — разорвать эту цепь в самом начале.

Если вам хочется не просто прочитать статью, а разобрать эти практики на реальных задачах, посмотреть на полные конфигурации и пообщаться с практиками, приглашаю вас на бесплатный урок «Основы безопасности Kubernetes» 17 февраля в 20:00.

Занятие пройдёт в рамках курса «Инфраструктурная платформа на основе Kubernetes» в OTUS. Мы разберём не только теоретические основы, но и конкретные инструменты, рассмотрим архитектурные паттерны и типичные ошибки, которые не описаны в официальной документации. А чтобы узнать, подойдет ли вам программа курса, пройдите вступительный тест.

Полный список бесплатных уроков от преподавателей курсов можно посмотреть в календаре мероприятий.