Поды Kubernetes как облачные удостоверения: EKS IRSA против GKE Workload Identity Federation
Как Kubernetes ServiceAccount становится реальным облачным удостоверением в AWS и Google Cloud - цепочка доверия федерации OIDC, лежащая в основе EKS IRSA и GKE Workload Identity Federation, сопоставленная один к одному, с нюансами, которые нарушают вашу мышечную память.
Обе платформы, AWS и Google Cloud, решают одну и ту же проблему одинаковым способом: под аутентифицируется в облачных API как сам себя, используя кратковременный токен и не храня секрет, превращая свой Kubernetes ServiceAccount в федеративное облачное удостоверение. В AWS эта функция называется IRSA (IAM Roles for Service Accounts); в Google Cloud это Workload Identity Federation for GKE (переименовано в 2024 году из просто "Workload Identity"). По сути, обе реализации используют один и тот же трюк федерации OIDC - но GKE скрывает гораздо больше внутренней механики, и это меняет подход к настройке, отладке и пониманию.
Краткая версия, если вы прочитаете только один абзац: кластер подписывает кратковременный токен для каждого ServiceAccount пода,
и облако обменивает этот токен на реальные учетные данные, привязанные к одному удостоверению. В AWS вы регистрируете
провайдера IAM OIDC для каждого кластера, создаете IAM role и ограничиваете ее политикой доверия; SDK внутри пода вызывает
AssumeRoleWithWebIdentity. В GKE вы не регистрируете ничего - каждый проект имеет один постоянный, управляемый Google пул
удостоверений рабочей нагрузки с именем PROJECT_ID.svc.id.goog, каждый ServiceAccount автоматически является субъектом в нем,
и локальный для узла сервер метаданных выполняет обмен токенов, так что ваше приложение просто использует Application Default Credentials,
как если бы оно работало на обычной VM. Три вещи отличаются достаточно, чтобы вызвать проблемы: GKE требует двух переключателей
включения (кластер и пул узлов), GKE может предоставлять Kubernetes ServiceAccount облачные разрешения без какого-либо объекта облачного удостоверения,
и - как всегда - извлечение образа контейнера является другим удостоверением, отличным от удостоверения вашей рабочей нагрузки.
Проблема, которую решают обе платформы
До федерации предоставление облачных разрешений поду означало плохие варианты: встраивание статического ключа в образ или Secret (утечки, проблемы с ротацией) или предоставление разрешения всему узлу, чтобы каждый под на нем наследовал его (отсутствие изоляции, чрезмерно широкая область действия). В AWS временными решениями были перехватчики IMDS узлов, такие как kube2iam и kiam; в GCP - монтирование ключа service-account узла в поды. Все они были сложными, слишком широкими и подверженными гонкам.
Федерация заменяет это криптографическим доверием. Кластер проецирует подписанный, кратковременный токен OIDC, привязанный к ServiceAccount пода. Облако проверяет этот токен по опубликованным ключам кластера и, если удостоверение токена соответствует настроенному вами правилу, возвращает учетные данные для ровно одного облачного удостоверения. Никакой секрет нигде не хранится; токен автоматически ротируется (срок действия по умолчанию около часа) и бесполезен за пределами кластера.
Общий механизм: федерация OIDC
Каждая настройка удостоверения рабочей нагрузки, в любом облаке, представляет собой один и тот же треугольник доверия:
- Кластер является эмитентом OIDC. Он предоставляет документ обнаружения и конечную точку JWKS с открытыми ключами, которыми он подписывает токены ServiceAccount. Этот эмитент является якорем доверия.
- Облако доверяет этому эмитенту для конкретного удостоверения ServiceAccount. AWS выражает доверие для каждой роли; GCP выражает его один раз для каждого проекта, постоянно, и Google управляет этим для вас.
- Рабочая нагрузка обменивает проецированный токен на облачные учетные данные и получает кратковременные учетные данные для одного облачного удостоверения.
Все остальное - это именование, и то, сколько из шага 2 вам придется строить самостоятельно. Именно здесь два облака расходятся больше всего.
Сопоставление один к одному
- Сторона Kubernetes. В AWS вы аннотируете ServiceAccount с помощью
eks.amazonaws.com/role-arn: <role arn>. В GKE, в современной прямой модели, вы вообще ничего не аннотируете в ServiceAccount - вы просто ссылаетесь на него как на субъект IAM. (Старая модель имперсонации использует аннотацию; подробнее ниже.) - "Провайдер OIDC". В AWS это эмитент OIDC кластера EKS, который вы регистрируете один раз в IAM как провайдера
удостоверений OIDC - для каждого кластера. В GKE это пул удостоверений рабочей нагрузки проекта
PROJECT_ID.svc.id.goog, создаваемый автоматически и управляемый Google; вы никогда его не регистрируете, и каждый ServiceAccount в каждом кластере проекта уже является субъектом в нем. - Удостоверение. AWS: IAM role. GKE прямая модель: нет выделенного объекта удостоверения - сам Kubernetes ServiceAccount является субъектом. GKE модель имперсонации: Google service account, который KSA имперсонирует.
- Правило доверия. AWS: политика доверия IAM role привязывает
sub = system:serviceaccount:<ns>:<sa>иaud = sts.amazonaws.com. GKE прямая: нет отдельного документа доверия - вы предоставляете роль напрямую идентификатору субъекта, который кодирует пространство имен и ServiceAccount. GKE модель имперсонации: вы предоставляете KSA рольroles/iam.workloadIdentityUserдля Google service account. - Разрешения. AWS: IAM policy, прикрепленная к роли. GKE: привязки ролей IAM allow-policy к целевому ресурсу
(или проекту) -
roles/storage.objectViewer,roles/secretmanager.secretAccessorи так далее. Это более глубокое различие: AWS прикрепляет политику к удостоверению; GCP предоставляет роль в области действия ресурса. - Обмен. AWS: SDK внутри пода вызывает
sts:AssumeRoleWithWebIdentityс проецированным токеном. GKE: сервер метаданных узла выполняет обмен прозрачно, и ваше приложение использует Application Default Credentials без какого-либо кода обмена.
Остальная часть этого поста описывает каждый облачный сервис от начала до конца, а затем останавливается на различиях, которые на самом деле сбивают людей с толку.
Пошаговое руководство: AWS EKS IRSA, от начала до конца
Компоненты, в порядке потока доверия:
1. Эмитент OIDC кластера, по URL, например https://oidc.eks.<region>.amazonaws.com/id/<hash>. Вы регистрируете его один раз
в IAM как провайдера удостоверений OIDC, чтобы IAM доверял токенам, которые он подписывает.
2. IAM role и ее политика доверия. Политика доверия делает роль доступной для одного ServiceAccount и ни для кого другого:
{
"Effect": "Allow",
"Principal": { "Federated": "arn:aws:iam::<account>:oidc-provider/oidc.eks.<region>.amazonaws.com/id/<hash>" },
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.<region>.amazonaws.com/id/<hash>:sub": "system:serviceaccount:apps:checkout",
"oidc.eks.<region>.amazonaws.com/id/<hash>:aud": "sts.amazonaws.com"
}
}
}
Привяжите оба :sub и :aud - привязка только эмитента позволила бы любому ServiceAccount в кластере принять роль.
3. IAM policy, прикрепленная к роли, предоставляет то, что нужно приложению (s3:GetObject для одного бакета и так далее).
4. ServiceAccount содержит одну аннотацию:
apiVersion: v1
kind: ServiceAccount
metadata:
name: checkout
namespace: apps
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::<account>:role/checkout
5. Проекция. EKS поставляется с Pod Identity Webhook, встроенным в кластер. Когда под использует аннотированный
ServiceAccount, вебхук внедряет AWS_ROLE_ARN и AWS_WEB_IDENTITY_TOKEN_FILE и монтирует проецированный токен
(аудитория sts.amazonaws.com, автоматически ротируемый).
6. Обмен. AWS SDK обнаруживает AWS_WEB_IDENTITY_TOKEN_FILE, вызывает sts:AssumeRoleWithWebIdentity и
кэширует возвращенные кратковременные учетные данные. Ваш код просто boto3.client("s3").
Новая альтернатива: EKS Pod Identity (2023) выполняет ту же работу через агент в кластере и API ассоциации вместо провайдера IAM OIDC для каждого кластера и политики доверия для каждой роли - что, примечательно, делает его гораздо более похожим на управляемую модель GKE. IRSA и Pod Identity сосуществуют в продакшене; IRSA - это та, которая чисто соответствует фреймворку федерации OIDC, поэтому ее следует держать в уме здесь.
Пошаговое руководство: GKE Workload Identity Federation, от начала до конца
Тот же поток доверия, гораздо меньше нужно строить:
1. Два переключателя включения. Включите пул удостоверений рабочей нагрузки на кластере и сервер метаданных на пуле узлов:
gcloud container clusters update <cluster> --workload-pool=<project-id>.svc.id.goog
gcloud container node-pools update <pool> --cluster=<cluster> --workload-metadata=GKE_METADATA
Оба обязательны. Флаг кластера включает кластер в пул проекта; флаг пула узлов развертывает сервер метаданных GKE
(DaemonSet на каждом узле), который будет перехватывать запросы учетных данных. Под на пуле узлов без
GKE_METADATA получает удостоверение узла, а не свое собственное - это тихое, слишком широкое резервное решение.
2. Пул уже существует. <project-id>.svc.id.goog создается автоматически для проекта; вы никогда не регистрируете
провайдера OIDC, и нет ограничения на количество провайдеров для каждой учетной записи. Каждый ServiceAccount в кластерах
проекта уже является субъектом.
3a. Прямой доступ (современный по умолчанию). Предоставьте роль напрямую ServiceAccount, обращаясь к нему как к субъекту IAM. Без Google service account, без аннотаций:
gcloud storage buckets add-iam-policy-binding gs://<bucket> \
--role=roles/storage.objectViewer \
--member="principal://iam.googleapis.com/projects/<project-number>/locations/global/workloadIdentityPools/<project-id>.svc.id.goog/subject/ns/apps/sa/checkout"
Путь субъекта кодирует ns/apps/sa/checkout - пространство имен и ServiceAccount являются правилом доверия; нет
отдельного документа доверия для написания.
3b. Имперсонация (старая модель, все еще необходима для некоторых сервисов). Создайте Google service account, позвольте KSA имперсонировать его и аннотируйте KSA:
gcloud iam service-accounts add-iam-policy-binding checkout@<project-id>.iam.gserviceaccount.com \
--role=roles/iam.workloadIdentityUser \
--member="serviceAccount:<project-id>.svc.id.goog[apps/checkout]"
apiVersion: v1
kind: ServiceAccount
metadata:
name: checkout
namespace: apps
annotations:
iam.gke.io/gcp-service-account: checkout@<project-id>.iam.gserviceaccount.com
Здесь Google service account содержит разрешения (через обычные привязки ролей), а KSA заимствует их.
4. Обмен. Когда ваше приложение запрашивает учетные данные, сервер метаданных GKE перехватывает запрос к
http://metadata.google.internal, извлекает JWT ServiceAccount из Kubernetes API для этого пода, обменивает его через
Security Token Service на кратковременный федеративный токен доступа (по умолчанию 1 час, активно обновляется до
истечения срока действия) и возвращает его. Ваше приложение использует Application Default Credentials и стандартные
клиентские библиотеки Google Cloud - оно ведет себя точно так же, как если бы работало на VM GCE. Нет вебхука,
внедряющего переменные среды, нет переменных в стиле AZURE_*, и нет вызова обмена в вашем коде: чтение Cloud Storage
просто storage.Client() в Python.
Субъект - это ключевой элемент (в обоих облаках)
Значение, которое должно точно совпадать в обоих облаках, - это удостоверение ServiceAccount:
- AWS привязывает субъект токена
system:serviceaccount:<namespace>:<serviceaccount>в политике доверия роли. - GKE direct кодирует ту же пару в пути субъекта
.../subject/ns/<namespace>/sa/<serviceaccount>. - GKE impersonation кодирует ее в члене
serviceAccount:<project-id>.svc.id.goog[<namespace>/<serviceaccount>].
Наиболее распространенная ошибка "не аутентифицируется как никто" в любом облаке - это несоответствие здесь: чарт
развернут в другом пространстве имен, ServiceAccount получил имя релиза вместо имени приложения, или под вернулся к
default ServiceAccount. Когда федерация молчаливо терпит неудачу, сначала проверьте пространство имен и имя ServiceAccount.
Различия, которые на самом деле могут вас сбить с толку
1. GKE регистрирует "провайдера OIDC" для вас - один раз, для каждого проекта, навсегда. В EKS вы регистрируете
провайдера IAM OIDC для каждого кластера, и при масштабировании флота вы можете столкнуться с мягким ограничением в
100 провайдеров OIDC на учетную запись AWS и вам придется обходить его. В GKE существует ровно один пул на проект
(PROJECT_ID.svc.id.goog), созданный и управляемый Google, общий для каждого кластера в проекте. Ничего не нужно
регистрировать, ничего не нужно ограничивать. Обратная сторона: доверие по умолчанию распространяется на весь проект,
поэтому вы определяете область действия с помощью привязок IAM (какое пространство имен и ServiceAccount вы
предоставляете), а не путем разделения провайдеров.
2. GKE обменивает токен на локальном сервере метаданных узла, а не с помощью вызова SDK внутри пода. IRSA изменяет
под (вебхук внедряет переменные среды и проецированный токен), и SDK вызывает STS. GKE перехватывает
metadata.google.internal на узле и прозрачно возвращает учетные данные, поэтому приложение использует Application
Default Credentials, как если бы оно работало на обычной VM. Два следствия: вашему приложению не нужен код учетных
данных, специфичный для облака, и пул узлов должен иметь включенный сервер метаданных (GKE_METADATA), иначе
под молчаливо получит удостоверение узла вместо своего собственного.
3. GKE может предоставлять ServiceAccount облачные разрешения без объекта облачного удостоверения. В IRSA всегда
есть IAM role. В прямой модели GKE ничего не нужно создавать на стороне облака, кроме самой привязки роли -
Kubernetes ServiceAccount является субъектом (principal://.../subject/ns/NS/sa/SA). Старая модель имперсонации
действительно вводит Google service account и аннотацию iam.gke.io/gcp-service-account, и она все еще нужна для
нескольких сервисов, которые не принимают федеративный субъект напрямую - но сначала используйте прямое связывание.
4. GKE требует двух переключателей, как и Azure. Кластер (--workload-pool) плюс пул узлов
(--workload-metadata=GKE_METADATA). Пропустите половину, относящуюся к пулу узлов, и не будет сбоя, просто
неправильное (узловое) удостоверение. IRSA включает проекцию в сам EKS, поэтому эквивалентного второго переключателя нет.
5. Извлечение образа - это другое удостоверение, отличное от удостоверения рабочей нагрузки - в обоих облаках.
Извлечение образа - это задача удостоверения узла: в GKE Google service account пула узлов нуждается в
roles/artifactregistry.reader для извлечения из Artifact Registry; в EKS роль экземпляра группы узлов нуждается в
AmazonEC2ContainerRegistryReadOnly (или вы используете pull secrets). Удостоверение рабочей нагрузки предназначено
только для случаев, когда код приложения вызывает облачный API. Чистый HTTP-сервис, который никогда не общается с
облачным SDK, вообще не нуждается в удостоверении рабочей нагрузки - и предоставление удостоверению рабочей нагрузки
artifactregistry.reader ничего не дает для извлечения образа, потому что извлечение происходит до того, как
приложение (и его федеративный токен) начнет работать.
6. Разрешения прикрепляются по-разному. AWS прикрепляет IAM policy к роли - разрешение перемещается вместе с удостоверением. GCP предоставляет привязку IAM role в области действия целевого ресурса (или проекта) - предоставление живет на объекте, к которому осуществляется доступ, а не на удостоверении. Тот же конечный результат, разное место для аудита: в AWS читайте прикрепленные политики роли; в GCP перечисляйте IAM policy ресурса (или используйте Policy Analyzer для того, к чему может получить доступ субъект).
Что делает код приложения
Оба облака приходят к "нет секрета, нет кода учетных данных", но достигают этого по-разному:
- AWS: вебхук устанавливает
AWS_ROLE_ARNиAWS_WEB_IDENTITY_TOKEN_FILE; цепочка по умолчанию SDK вызываетAssumeRoleWithWebIdentity. Ваш код:boto3.client("s3"). - GKE: ничего не внедряется в под; цепочка Application Default Credentials SDK обращается к серверу метаданных
узла, который выполняет обмен. Ваш код:
storage.Client().
В обоих случаях учетные данные кратковременны и прозрачно обновляются. Нет ничего, что нужно ротировать, хранить или что может утечь.
Контрольный список отладки, работающий в обоих облаках
Когда под "не может аутентифицироваться", пройдите по цепочке доверия по порядку:
- Федерация кластера/проекта включена? EKS: провайдер IAM OIDC зарегистрирован. GKE: кластер имеет
--workload-poolи пул узлов имеетGKE_METADATA. В GKE отсутствие переключателя пула узлов - это классический тихий сбой - под получает удостоверение узла, а не свое собственное. - Правильный ServiceAccount? Под фактически использует ServiceAccount, который вы думаете (не
default). Выполните exec и проверьте. - Совпадение удостоверений? Сравните правило доверия с реальным пространством имен и ServiceAccount:
system:serviceaccount:<ns>:<sa>в AWS,.../subject/ns/<ns>/sa/<sa>(или[ns/sa]) в GKE. Это наиболее частая причина сбоев в обоих случаях. - Обмен работает? EKS: под имеет
AWS_WEB_IDENTITY_TOKEN_FILE. GKE:curl -H "Metadata-Flavor: Google" metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/emailиз пода возвращает ожидаемое удостоверение. - Разрешения? Только после разрешения удостоверения: IAM policy для роли (AWS) или привязка роли к целевому ресурсу (GCP). Пустой результат здесь - это отказ авторизации, а не аутентификации - что говорит о том, что сама федерация работает.
Какие сертификации охватывают это
Удостоверение рабочей нагрузки находится на стыке Kubernetes, облачного IAM и федерации OIDC, поэтому оно встречается в трех направлениях экзаменов. Если у вас уже есть учетные данные одного облака, та же концепция в другом облаке - это небольшой шаг.
Со стороны AWS:
- AWS Certified Solutions Architect - Associate (SAA-C03) - IAM roles, EKS и как удостоверения получают облачные разрешения.
- AWS Certified Security - Specialty (SCS-C03) - политики доверия IAM, федерация OIDC и определение области действия с наименьшими привилегиями (точно цепочка доверия IRSA).
Со стороны Google Cloud:
- Google Cloud Associate Cloud Engineer - GKE, IAM, service accounts и привязки ролей.
- Google Cloud Professional Cloud Security Engineer - удостоверение рабочей нагрузки, глубина IAM, безопасность service-account и наименьшие привилегии.
Со стороны Kubernetes:
- CNCF Certified Kubernetes Administrator (CKA) - ServiceAccounts, проецируемые токены и спецификации подов.
- CNCF Certified Kubernetes Security Specialist (CKS) - безопасность токенов ServiceAccount, наименьшие привилегии и снижение риска утечки статических секретов.
Итог
IRSA и GKE Workload Identity Federation - это одна и та же идея, реализованная с разным уровнем сложности: кластер
подписывает кратковременный токен OIDC для ServiceAccount, и облако обменивает его на учетные данные, привязанные к
одному удостоверению. AWS заставляет вас самостоятельно собирать цепочку доверия - провайдер OIDC для каждого
кластера, IAM role, политика доверия, проецированный токен, который обменивает SDK. GKE предоставляет вам
управляемый, общепроектный пул, позволяет привязать роль напрямую к Kubernetes ServiceAccount без какого-либо
объекта облачного удостоверения и выполняет обмен на сервере метаданных узла, так что ваше приложение никогда не
видит учетных данных. Изучите треугольник доверия один раз, и перевод будет механическим: роль становится привязкой
субъекта (или service account, который вы имперсонируете), политика доверия становится строкой члена IAM,
прикрепленная политика становится привязкой роли к ресурсу, а AssumeRoleWithWebIdentity становится невидимым
обменом через сервер метаданных. Держите в уме три вещи - два переключателя GKE, его прямую модель без объекта
удостоверения и тот факт, что извлечение образа - это совершенно другое удостоверение - и отказы, которые раньше
казались случайными, начнут восприниматься как связный, продуманный дизайн.