Статья Workload Identity в Kubernetes: SPIFFE/SPIRE, pod identity и отказ от long-lived credentials

Схема потока идентификации на матовой бумаге с диаграммой аттестации пода и краткосрочным токеном вместо статического ключа. Латунные грузики удерживают лист на светлом столе в мягком дневном свете.


На прошлогоднем incident response в финтех-компании вся цепочка атаки уместилась в три строки CloudTrail: SSRF в микросервисе дёрнул IMDS на 169.251.163.254, атакующий вытащил IAM-ключ из метаданных EC2-инстанса, за 47 минут выгрузил клиентские данные из S3. Ключ был создан 14 месяцев назад и ни разу не ротировался. Когда я разбирал этот postmortem, картина сложилась быстро: проблема не в SSRF и не в отсутствии WAF. Проблема в том, что под в Kubernetes имел доступ к статичному IAM-ключу, которому не было причин существовать - workload identity делает такие ключи ненужными.
incident response - отчёт об инциденте
IAM-ключ - это набор секретных данных, используемый для безопасной аутентификации пользователя, приложения или бота в облачных сервисах или корпоративных системах.
Далее в статье - про то, какие attack paths закрывает переход на short-lived credentials через SPIFFE/SPIRE и нативные механизмы облачных провайдеров, и как мониторить всё это в SIEM.

Статичные credentials в Kubernetes: карта атак через MITRE ATT&CK​

Kubernetes-кластер в облаке по умолчанию подсовывает подам несколько векторов для получения credentials. Каждый маппится на конкретную технику MITRE ATT&CK, и без понимания этой карты нормальные detection-правила не выстроить.

Cloud Instance Metadata API (T1552.005, Credential Access). Под на EC2-инстансе без IMDSv2 обращается к 169.251.163.254/latest/meta-data/iam/security-credentials/ и получает временные ключи роли ноды. При IMDSv2 с hop limit = 1 обычные поды (без hostNetwork) не получают ответ от IMDS - трафик проходит через veth + bridge, это 2 L3-хопа, TTL истекает. А вот контейнер с hostNetwork: true делит сетевой namespace с хостом напрямую - для него IMDS на 1 hop и всегда отдаёт credentials роли ноды. Именно так развивался инцидент из вступления.

Container API (T1552.007, Credential Access). ServiceAccount token монтируется в каждый под по умолчанию в /var/run/secrets/kubernetes.io/serviceaccount/token. Если у ServiceAccount избыточные RBAC-привилегии, атакующий из скомпрометированного пода получает доступ к API-серверу. В моей практике kubectl describe pod с env-переменными вида AWS_ACCESS_KEY_ID=AKIA... встречается удручающе регулярно.

Steal Application Access Token (T1528, Credential Access). В средах без workload identity разработчики прописывают OAuth-токены и API-ключи в ConfigMap или Secret. Напомню: Secret в Kubernetes хранится в etcd в base64 (не зашифрованном по умолчанию), и любой пользователь с правами get secrets видит содержимое в plaintext. Base64 - это не шифрование, это кодировка. Разница примерно как между замком и наклейкой «не открывать».

Cloud Accounts (T1078.004, Initial Access / Persistence / Privilege Escalation). Утёкший статичный IAM-ключ используется для закрепления в облаке: создание новых ролей, расширение policy, развёртывание compute-ресурсов. По классификации MITRE ATT&CK эта техника одновременно применима на этапах initial access, persistence и privilege escalation - один утёкший ключ покрывает сразу три тактики.

Финансовый контекст для российского рынка: если утечка затрагивает PII граждан РФ, в игру вступают оборотные штрафы по 152-ФЗ - до 3% годовой выручки. Один скомпрометированный IAM-ключ с доступом к бакету с клиентскими данными генерирует штраф, несоизмеримый с затратами на внедрение workload identity.

SPIFFE и SPIRE: workload identity без секретов в Kubernetes​

1780542744375.webp

SPIFFE (Secure Production Identity Framework For Everyone) - открытый стандарт CNCF для идентификации workload'ов. Идея простая: вместо секретов, которые нужно ротировать и где-то хранить, каждый workload получает криптографически верифицируемую идентичность, привязанную к его runtime-окружению. Согласно исследованию на arxiv, SPIFFE «decouples identity from infrastructure, enabling strong, portable authentication across job runners and deployed workloads» - идентичность отвязана от конкретного облака и переносима между кластерами.

SPIRE (SPIFFE Runtime Environment) - production-ready реализация SPIFFE, graduated-проект CNCF.

Проблему «turtles all the way down» - цепочку секретов, управляющих другими секретами - хорошо описал инженер из Microsoft-экосистемы: «каждый скрипт для ротации секрета требует другой секрет для аутентификации, и этот секрет тоже нужно ротировать». Знакомо? SPIFFE разрывает этот цикл: идентичность выдаётся на основе аттестации окружения, а не предъявления секрета.

Архитектура SPIRE и выдача SVID​

SPIRE Server - trust anchor домена. Хранит реестр Registration Entries (маппинг workload -> identity), управляет CA, выдаёт SVID. В Kubernetes разворачивается как StatefulSet. В nested-топологии для мультикластерных сред root SPIRE Server выдаёт промежуточные CA дочерним серверам - все workload'ы получают SVID из одного trust domain, mTLS между кластерами работает прозрачно.

SPIRE Agent - DaemonSet на каждой ноде. Проводит node attestation через плагин k8s_psat: агент предъявляет свой projected ServiceAccount token (с audience SPIRE Server), сервер валидирует его через TokenReview API Kubernetes, затем - workload attestation (проверяет pod labels, service account, namespace). После успешной аттестации workload получает SVID через Unix Domain Socket.

SVID (SPIFFE Verifiable Identity Document) - короткоживущий X.509-сертификат или JWT-токен с SPIFFE ID вида spiffe://trust-domain/ns/payments/sa/payment-service. TTL по умолчанию - 1 час, автоматическая ротация без участия разработчика.

Пример создания Registration Entry:
Bash:
spire-server entry create \
  -spiffeID spiffe://example.org/ns/default/sa/myapp \
  -parentID spiffe://example.org/ns/spire/sa/spire-agent \
  -selector k8s:ns:default \
  -selector k8s:sa:myapp
Селекторы k8s:ns:default и k8s:sa:myapp - механизм аттестации: SPIRE Agent проверяет, что под запущен в namespace default с ServiceAccount myapp. Подделать это из соседнего пода - задача нетривиальная, требующая escape to host (T1611, Privilege Escalation).

Замечание для команд на Istio: service mesh жёстко требует формата SPIFFE ID spiffe://<trust.domain>/ns/<namespace>/sa/<service-account>. Инженеры Indeed столкнулись с тем, что их существующий формат идентификаторов в SPIRE не совпадал с требованиями Istio - миграция потребовала временного отключения mTLS в mesh и поэтапного обновления конфигурации по кластерам. При планировании интеграции SPIRE с Istio этот constraint нужно закладывать на этапе архитектуры, а не обнаруживать в production.

Pod identity: IRSA, Workload Identity Federation, Entra Workload ID​

1780543141684.webp

Каждый крупный облачный провайдер реализовал нативный механизм привязки Kubernetes ServiceAccount к облачной IAM-роли. Все три подхода решают одну задачу: pod получает short-lived token для доступа к облачным ресурсам без статичных ключей.

AWS: IRSA (IAM Roles for Service Accounts). ServiceAccount аннотируется ARN IAM-роли. Webhook инъецирует в под projected token и env-переменные для SDK. Pod получает temporary credentials через STS AssumeRoleWithWebIdentity. TTL STS-сессии - от 900 секунд до значения MaxSessionDuration IAM-роли (по умолчанию 3600 сек, максимум 43200 сек при явной настройке роли).

GCP: GKE Workload Identity / Workload Identity Federation. Для GKE-кластеров - GKE Workload Identity: Kubernetes ServiceAccount аннотируется через iam.gke.io/gcp-service-account, pod получает токен Google Cloud без статичных ключей. Для не-GKE и мультиоблачных кластеров - Workload Identity Federation с workload identity pools: ServiceAccount token обменивается на Google Cloud token через Security Token Service. Attribute conditions с CEL-выражениями - например, assertion['kubernetes.io']['namespace'] in ['backend', 'monitoring'] - применимы именно к Workload Identity Federation, а не к GKE Workload Identity. Не путать.

Azure: Microsoft Entra Workload ID. AKS-кластер выступает OIDC-издателем токенов. Entra ID валидирует подпись ServiceAccount token через OIDC discovery и обменивает его на Entra token. Обязательный label для подов: azure.workload.identity/use: "true". Есть лимит federated identity credentials на один user-assigned managed identity или app registration (см. актуальную документацию Microsoft Entra) - при масштабировании на сотни сервисов это ограничение всплывает неожиданно.

С точки зрения detection все три механизма генерируют события в CloudTrail, Cloud Audit Logs и Azure Activity Log соответственно - каждый AssumeRoleWithWebIdentity или обмен токена привязан к конкретному ServiceAccount, namespace и кластеру. Принципиальное преимущество перед статичными ключами: атрибуция на уровне workload'а, которой раньше просто не было.

Attack paths: как атакующий использует workload identity​

Переход на short-lived credentials сужает окно атаки, но attack surface никуда не девается. Вот конкретные TTP, которые работают в средах с настроенным pod identity.

Escape to Host -> кража SVID (T1611, Privilege Escalation). Container escape через уязвимость в runc или kubelet даёт атакующему доступ к host filesystem. SPIRE Agent socket (/run/spire/sockets/agent.sock) на хосте доступен - агент выдаёт SVID процессу, прошедшему attestation, но на уровне хоста проверки менее гранулярные. Если агент запущен с hostPID: true - считай, attestation для хостового процесса пройдёт.

Additional Container Cluster Roles (T1098.006, Persistence / Privilege Escalation). Атакующий с доступом к Kubernetes API создаёт ClusterRoleBinding, дающий произвольному ServiceAccount расширенные привилегии. Этот ServiceAccount получает SVID с широкими полномочиями или аннотацию с IAM-ролью администратора - и всё через легитимный Kubernetes API. Чисто, аккуратно, в логах выглядит как штатная операция.

Application Access Token replay (T1550.001, Lateral Movement). Перехваченный JWT-SVID или облачный token валиден до истечения TTL. При default_svid_ttl = "1h" - это час для lateral movement к другим сервисам внутри trust domain. Для IRSA с durationSeconds=3600 - тот же час. Час - это много.

Insider threat: скомпрометированный легитимный pod. Разработчик с доступом к namespace создаёт под с ServiceAccount, аннотированным на IAM-роль с избыточными привилегиями. Под проходит attestation легитимно, получает SVID и short-lived credentials. Без baseline поведения workload'а этот вектор невидим для стандартных detection-правил - всё выглядит как нормальная операция. Именно поэтому «у нас SPIRE, мы защищены» - опасная мантра.

Detection short-lived credentials и workload identity в SIEM​

Для SOC-команды задача конкретная - построить baseline нормального поведения workload identity и детектировать отклонения. Источники и правила корреляции ниже.

Источники событий для сбора:
📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме

Результат - ваш опорный документ. Любое изменение без соответствующего change request - повод для расследования. Согласно NIST CSF DE.AE-01, baseline сетевых операций и ожидаемых потоков данных должен быть установлен и актуализироваться - для workload identity это означает регулярную сверку маппингов.

Чеклист hardening: сервисные аккаунты и zero trust аутентификация в Kubernetes​

Чеклист пригоден для передачи инфраструктурной команде или включения в отчёт по аудиту. Основан на DISA Kubernetes STIG и NIST CSF PR.AA-01 (управление идентичностями и credentials).
  1. Отключить автомонтирование SA-токена для подов без доступа к Kubernetes API: automountServiceAccountToken: false в PodSpec.
  2. Включить IMDSv2 с hop limit = 1 на всех EC2-нодах EKS. Проверка: aws ec2 describe-instances --query "Reservations[].Instances[].MetadataOptions".
  3. Мигрировать со статичных IAM-ключей на IRSA / Workload Identity Federation / Entra Workload ID. Каждый AKIA* в env-переменных пода - инцидент в ожидании.
  4. Ограничить TTL credentials: для IRSA - durationSeconds не более 3600 сек (минимум платформы - 900 сек); для SPIRE - default_svid_ttl = "15m" для критичных workload'ов.
  5. Настроить attribute conditions для Workload Identity Federation: явный whitelist namespace'ов через CEL-выражения.
  6. Включить Kubernetes Audit Log с уровнем RequestResponse для: serviceaccounts, clusterrolebindings, rolebindings, secrets.
  7. Развернуть Falco с правилами на обращение к IMDS, чтение /var/run/secrets/, доступ к SPIRE Agent socket из неожиданных процессов.
  8. Запретить создание SA с аннотацией IAM-роли вне CI/CD pipeline через OPA/Gatekeeper или Kyverno policy.
  9. Провести инвентаризацию всех маппингов SA -> Cloud IAM Role (NIST CSF ID.AM-01). Формат: таблица «namespace/SA -> Role ARN -> минимально необходимые permissions».
  10. Мониторить ротацию SPIRE CA через Prometheus-метрику SPIRE Server, настроить alert на приближение expiration.
На одном из проектов миграция с хардкоженных AWS Access Keys на IRSA заняла четыре месяца для 80 микросервисов. Самой болезненной частью оказался не технический переход, а обнаружение всех мест, где ключи были прошиты: env-переменные подов, ConfigMap'ы, CI/CD-пайплайны, скрипты инициализации. Аудит начинается с grep -r "AKIA" . по кластеру - и результат обычно неприятно удивляет.

Главная иллюзия, которую я вижу у команд после внедрения pod identity: «у нас short-lived credentials, мы защищены». Опасное заблуждение. Short-lived credentials сужают окно атаки с месяцев до минут, но не закрывают его. JWT-SVID с TTL в час - это час, в течение которого украденный токен полностью валиден для lateral movement. IRSA-токен с durationSeconds=3600 - тот же час. Без behavioral detection на уровне SIEM - baseline API-вызовов для каждого ServiceAccount, аномальные паттерны, нетипичные source IP - вы просто заменили один класс неконтролируемых credentials на другой, более короткий, но по-прежнему слепой для мониторинга.

Реальный zero trust в cloud-native среде начинается не с SPIFFE/SPIRE и не с IRSA, а с момента, когда SOC может ответить на вопрос: «Какой набор API-вызовов нормален для ServiceAccount X в namespace Y и что является отклонением?» Пока этого baseline нет - identity layer работает вхолостую, создавая ложное чувство защищённости. Если калибруете правила для мониторинга workload identity аномалий под конкретный SIEM-стек - тематический тред с playbook'ом по cloud-native TTP ведётся на codeby.net.
 
Последнее редактирование модератором:
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

Похожие темы

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab