Pods Kubernetes como identidades de nuvem: EKS IRSA vs. GKE Workload Identity Federation
Como um ServiceAccount do Kubernetes se torna uma identidade de nuvem real na AWS e no Google Cloud - a cadeia de confiança de federação OIDC por trás do EKS IRSA e do GKE Workload Identity Federation, mapeada um-para-um, com as nuances que quebram sua memória muscular.
Tanto a AWS quanto o Google Cloud resolvem o mesmo problema da mesma forma: um pod se autentica nas APIs da nuvem como ele mesmo, com um token de curta duração e sem segredo armazenado, transformando seu Kubernetes ServiceAccount em uma identidade de nuvem federada. Na AWS, o recurso é IRSA (IAM Roles for Service Accounts); no Google Cloud, é Workload Identity Federation for GKE (renomeado em 2024 de "Workload Identity" simples). Por baixo dos panos, ambos são o mesmo truque de federação OIDC - mas o GKE esconde muito mais da infraestrutura, e isso muda a forma como você o configura, depura e raciocina sobre ele.
A versão curta, se você ler apenas um parágrafo: o cluster assina um token de curta duração para o ServiceAccount de cada pod, e a nuvem troca esse token por credenciais reais com escopo para uma identidade. Na AWS, você registra um provedor IAM OIDC por cluster, cria um IAM role e o protege com uma trust policy; o SDK dentro do pod chama AssumeRoleWithWebIdentity. No GKE, você não registra nada - cada projeto tem um workload identity pool permanente, gerenciado pelo Google, chamado PROJECT_ID.svc.id.goog, cada ServiceAccount é automaticamente um principal nele, e um metadata server local do nó faz a troca de token para que seu aplicativo use Application Default Credentials como se estivesse em uma VM simples. Três coisas diferem o suficiente para te pegar: o GKE precisa de dois toggles de habilitação (cluster e node pool), o GKE pode conceder permissões de nuvem a um Kubernetes ServiceAccount com nenhum objeto de identidade de nuvem, e - como sempre - puxar sua imagem de contêiner é uma identidade diferente da sua workload identity.
O problema que ambos resolvem
Antes da federação, conceder permissões de nuvem a um pod significava opções ruins: incorporar uma chave estática na imagem ou em um Secret (vazamentos, dor na rotação), ou conceder a permissão a todo o nó para que cada pod nele a herdasse (sem isolamento, escopo excessivamente amplo). As soluções provisórias do lado da AWS eram interceptores node-IMDS como kube2iam e kiam; a do lado do GCP era montar uma chave de service account do nó em pods. Todas eram complicadas, excessivamente amplas e propensas a condições de corrida.
A federação substitui isso por confiança criptográfica. O cluster projeta um token OIDC assinado e de curta duração com escopo para o ServiceAccount do pod. A nuvem verifica esse token em relação às chaves publicadas do cluster e, se a identidade do token corresponder a uma regra que você configurou, retorna credenciais para exatamente uma identidade de nuvem. Nenhum segredo é armazenado em qualquer lugar; o token auto-rotaciona (vida útil padrão de cerca de uma hora) e é inútil fora do cluster.
O mecanismo compartilhado: federação OIDC
Toda configuração de workload identity, em qualquer nuvem, é o mesmo triângulo de confiança:
- O cluster é um OIDC issuer. Ele expõe um discovery document e um JWKS endpoint com as chaves públicas com as quais assina os tokens do ServiceAccount. Esse issuer é a âncora de confiança.
- A nuvem confia nesse issuer para uma identidade de ServiceAccount específica. A AWS expressa a confiança por role; o GCP a expressa uma vez por projeto, permanentemente, e o Google a gerencia para você.
- A workload troca o token projetado por credenciais de nuvem e recebe credenciais de curta duração para uma única identidade de nuvem.
Todo o resto é nomenclatura - e o quanto do passo 2 você precisa construir por conta própria. É aí que as duas nuvens mais divergem.
O mapeamento um-para-um
- O lado do Kubernetes. Na AWS, você anota o ServiceAccount com
eks.amazonaws.com/role-arn: <role arn>. No GKE, no modelo direto moderno, você não anota nada no ServiceAccount - você apenas o referencia como um IAM principal. (O modelo de impersonation mais antigo usa uma anotação; mais abaixo.) - O "OIDC provider". Na AWS, este é o OIDC issuer do cluster EKS, que você registra uma vez no IAM como um OIDC identity provider - por cluster. No GKE, é o workload identity pool do projeto
PROJECT_ID.svc.id.goog, criado automaticamente e gerenciado pelo Google; você nunca o registra, e cada ServiceAccount em cada cluster do projeto já é um principal dentro dele. - A identidade. AWS: um IAM role. Modelo direto do GKE: nenhum objeto de identidade dedicado - o próprio Kubernetes ServiceAccount é o principal. Modelo de impersonation do GKE: um Google service account que o KSA impersona.
- A trust rule. AWS: a trust policy do IAM role fixa
sub = system:serviceaccount:<ns>:<sa>eaud = sts.amazonaws.com. GKE direto: não há um documento de confiança separado - você concede um role diretamente a um principal identifier que codifica o namespace e o ServiceAccount. GKE impersonation: você concede ao KSA o roleroles/iam.workloadIdentityUserno Google service account. - As permissões. AWS: uma IAM policy anexada ao role. GKE: IAM allow-policy role bindings no recurso de destino (ou projeto) -
roles/storage.objectViewer,roles/secretmanager.secretAccessor, e assim por diante. Esta é a divisão mais profunda: a AWS anexa uma policy à identidade; o GCP concede um role no escopo do recurso. - A troca. AWS: o SDK dentro do pod chama
sts:AssumeRoleWithWebIdentitycom o token projetado. GKE: o metadata server do nó faz a troca de forma transparente e seu aplicativo usa Application Default Credentials sem nenhum código de troca.
O restante desta postagem percorre cada nuvem de ponta a ponta, e então se aprofunda nas diferenças que realmente confundem as pessoas.
Passo a passo: AWS EKS IRSA, de ponta a ponta
As peças, na ordem em que a confiança flui:
1. O OIDC issuer do cluster, em uma URL como https://oidc.eks.<region>.amazonaws.com/id/<hash>. Você o registra uma vez no IAM como um OIDC identity provider para que o IAM confie nos tokens que ele assina.
2. O IAM role e sua trust policy. A trust policy torna o role assumível por um ServiceAccount e nada mais:
{
"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"
}
}
}
Fixe ambos :sub e :aud - fixar apenas o issuer permitiria que qualquer ServiceAccount no cluster assumisse o role.
3. A IAM policy anexada ao role concede o que o aplicativo precisa (s3:GetObject em um bucket, e assim por diante).
4. O ServiceAccount possui uma anotação:
apiVersion: v1
kind: ServiceAccount
metadata:
name: checkout
namespace: apps
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::<account>:role/checkout
5. A projeção. O EKS inclui o Pod Identity Webhook integrado ao cluster. Quando um pod usa o ServiceAccount anotado, o webhook injeta AWS_ROLE_ARN e AWS_WEB_IDENTITY_TOKEN_FILE e monta um token projetado (audience sts.amazonaws.com, auto-rotacionado).
6. A troca. O AWS SDK detecta AWS_WEB_IDENTITY_TOKEN_FILE, chama sts:AssumeRoleWithWebIdentity e armazena em cache as credenciais de curta duração retornadas. Seu código é apenas boto3.client("s3").
Alternativa mais recente: EKS Pod Identity (2023) faz o mesmo trabalho através de um agente no cluster e uma API de associação em vez de um provedor IAM OIDC por cluster e uma trust policy por role - o que, notavelmente, o faz parecer muito mais com o modelo gerenciado do GKE. Tanto o IRSA quanto o Pod Identity coexistem em produção; o IRSA é o que se mapeia de forma limpa para o enquadramento da federação OIDC, então é o que deve ser mantido em mente aqui.
Passo a passo: GKE Workload Identity Federation, de ponta a ponta
Mesmo fluxo de confiança, muito menos para construir:
1. Dois toggles de habilitação. Habilite o workload identity pool no cluster e o metadata server no node pool:
gcloud container clusters update <cluster> --workload-pool=<project-id>.svc.id.goog
gcloud container node-pools update <pool> --cluster=<cluster> --workload-metadata=GKE_METADATA
Ambos são necessários. A flag do cluster opta o cluster para o pool do projeto; a flag do node pool implanta o GKE metadata server (um DaemonSet em cada nó) que interceptará as solicitações de credenciais. Um pod em um node pool sem GKE_METADATA obtém a identidade do nó, não a sua própria - um fallback silencioso e excessivamente amplo.
2. O pool já existe. <project-id>.svc.id.goog é criado automaticamente para o projeto; você nunca registra um OIDC provider, e não há limite de provedor por conta a ser atingido. Cada ServiceAccount nos clusters do projeto já é um principal.
3a. Acesso direto (o padrão moderno). Conceda um role diretamente ao ServiceAccount, endereçado como um IAM principal. Sem Google service account, sem anotação:
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"
O principal path codifica ns/apps/sa/checkout - o namespace e o ServiceAccount são a trust rule; não há um documento de confiança separado para escrever.
3b. Impersonation (o modelo mais antigo, ainda necessário para alguns serviços). Crie um Google service account, permita que o KSA o impersonate, e anote o 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
Aqui o Google service account detém as permissões (via role bindings normais), e o KSA as empresta.
4. A troca. Quando seu aplicativo solicita credenciais, o GKE metadata server intercepta a solicitação para http://metadata.google.internal, busca um ServiceAccount JWT da Kubernetes API para aquele pod, o troca via Security Token Service por um access token federado de curta duração (padrão de 1 hora, atualizado proativamente antes do vencimento), e o retorna. Seu aplicativo usa Application Default Credentials e as bibliotecas cliente padrão do Google Cloud - ele se comporta exatamente como se estivesse rodando em uma GCE VM. Não há webhook injetando variáveis de ambiente, nem variáveis no estilo AZURE_*, e nenhuma chamada de troca em seu código: uma leitura do Cloud Storage é apenas storage.Client() em Python.
O subject é o ponto crucial (em ambas as nuvens)
O valor que deve corresponder exatamente, em ambas as nuvens, é a identidade do ServiceAccount:
- AWS fixa o token subject
system:serviceaccount:<namespace>:<serviceaccount>na trust policy do role. - GKE direct codifica o mesmo par no principal path
.../subject/ns/<namespace>/sa/<serviceaccount>. - GKE impersonation o codifica no member
serviceAccount:<project-id>.svc.id.goog[<namespace>/<serviceaccount>].
A falha mais comum de "não autentica como ninguém" em qualquer nuvem é uma incompatibilidade aqui: o chart implantado em um namespace diferente, o ServiceAccount recebeu o nome da release em vez do nome do aplicativo, ou o pod reverteu para o ServiceAccount default. Quando a federação falha silenciosamente, verifique primeiro o namespace e o nome do ServiceAccount.
Diferenças que realmente te confundirão
1. O GKE registra o "OIDC provider" para você - uma vez, por projeto, para sempre. No EKS, você registra um IAM OIDC provider por cluster, e em escala de frota você pode atingir o limite flexível de 100 OIDC providers por conta AWS e ter que contorná-lo. No GKE, há exatamente um pool por projeto (PROJECT_ID.svc.id.goog), criado e gerenciado pelo Google, compartilhado por cada cluster no projeto. Nada para registrar, nada para limitar. O outro lado: a confiança é padrão para todo o projeto, então você define o escopo com IAM bindings (qual namespace e ServiceAccount você concede), não dividindo provedores.
2. O GKE troca o token em um metadata server local do nó, não com uma chamada SDK dentro do pod. O IRSA muta o pod (um webhook injeta variáveis de ambiente e um token projetado) e o SDK chama o STS. O GKE intercepta metadata.google.internal no nó e retorna as credenciais de forma transparente, então o aplicativo usa Application Default Credentials como se estivesse em uma VM simples. Duas consequências: seu aplicativo não precisa de nenhum código de credencial específico da nuvem, e o node pool deve ter o metadata server habilitado (GKE_METADATA) ou o pod silenciosamente obtém a identidade do nó em vez da sua própria.
3. O GKE pode conceder permissões de nuvem a um ServiceAccount sem nenhum objeto de identidade de nuvem. No IRSA, sempre há um IAM role. No modelo direto do GKE, não há nada para criar no lado da nuvem, exceto o próprio role binding - o Kubernetes ServiceAccount é o principal (principal://.../subject/ns/NS/sa/SA). O modelo de impersonation mais antigo introduz um Google service account e a anotação iam.gke.io/gcp-service-account, e você ainda precisa dele para o punhado de serviços que não aceitam um principal federado diretamente - mas priorize a vinculação direta.
4. O GKE precisa de dois toggles, como o Azure. Cluster (--workload-pool) mais node pool (--workload-metadata=GKE_METADATA). Perder a metade do node pool não causa falha, apenas a identidade errada (do nó). O IRSA incorpora a projeção no próprio EKS, então não há um segundo switch equivalente.
5. Puxar a imagem é uma identidade diferente da workload identity - em ambas as nuvens. O pull da imagem é trabalho da identidade do nó: no GKE, o Google service account do node pool precisa de roles/artifactregistry.reader para puxar do Artifact Registry; no EKS, o instance role do node group precisa de AmazonEC2ContainerRegistryReadOnly (ou você usa pull secrets). A workload identity é apenas para quando o código do aplicativo chama uma API de nuvem. Um serviço HTTP puro que nunca se comunica com um SDK de nuvem não precisa de workload identity alguma - e conceder artifactregistry.reader a uma workload identity não faz nada para o pull da imagem, porque o pull acontece antes que o aplicativo (e seu token federado) seja executado.
6. As permissões se anexam de forma diferente. A AWS anexa uma IAM policy ao role - a permissão viaja com a identidade. O GCP concede um IAM role binding no escopo do recurso de destino (ou do projeto) - a concessão reside no item que está sendo acessado, não na identidade. Mesmo estado final, lugar diferente para auditá-lo: na AWS, leia as policies anexadas ao role; no GCP, liste a IAM policy do recurso (ou use o Policy Analyzer para o que um principal pode alcançar).
O que o código do aplicativo faz
Ambas as nuvens terminam em "sem segredo, sem código de credencial", mas chegam lá de forma diferente:
- AWS: o webhook define
AWS_ROLE_ARNeAWS_WEB_IDENTITY_TOKEN_FILE; a cadeia padrão do SDK chamaAssumeRoleWithWebIdentity. Seu código:boto3.client("s3"). - GKE: nada é injetado no pod; a cadeia de Application Default Credentials do SDK atinge o metadata server do nó, que faz a troca. Seu código:
storage.Client().
Em ambos os casos, as credenciais são de curta duração e atualizadas de forma transparente. Não há nada para rotacionar, nada para armazenar e nada para vazar.
Um checklist de depuração que funciona em ambas as nuvens
Quando um pod "não consegue autenticar", percorra a cadeia de confiança em ordem:
- Federação de cluster/projeto ativada? EKS: o IAM OIDC provider está registrado. GKE: o cluster tem
--workload-poole o node pool temGKE_METADATA. No GKE, um toggle ausente no node pool é a falha silenciosa clássica - o pod obtém a identidade do nó, não a sua própria. - ServiceAccount correto? O pod realmente usa o ServiceAccount que você pensa (não o
default). Execute um exec e verifique. - Correspondência de identidade? Compare a trust rule com o namespace e ServiceAccount reais:
system:serviceaccount:<ns>:<sa>na AWS,.../subject/ns/<ns>/sa/<sa>(ou[ns/sa]) no GKE. Esta é a falha mais frequente em ambos. - Troca funcionando? EKS: o pod tem
AWS_WEB_IDENTITY_TOKEN_FILE. GKE: curl -H "Metadata-Flavor: Google" metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email do pod retorna a identidade esperada. - Permissões? Somente depois que a identidade for resolvida: a IAM policy no role (AWS) ou o role binding no recurso de destino (GCP). Um resultado vazio aqui é uma falha de autorização, não de autenticação - o que indica que a federação em si está funcionando.
Quais certificações abordam isso
A workload identity se encontra exatamente onde o Kubernetes, o cloud IAM e a federação OIDC se encontram, então ela aparece em três trilhas de exames. Se você já possui uma credencial de uma nuvem, o mesmo conceito na outra nuvem é um pequeno salto.
No lado da AWS:
- AWS Certified Solutions Architect - Associate (SAA-C03) - IAM roles, EKS e como as identidades obtêm permissões de nuvem.
- AWS Certified Security - Specialty (SCS-C03) - IAM trust policies, federação OIDC e escopo de privilégio mínimo (exatamente a cadeia de confiança do IRSA).
No lado do Google Cloud:
- Google Cloud Associate Cloud Engineer - GKE, IAM, service accounts e role bindings.
- Google Cloud Professional Cloud Security Engineer - workload identity, profundidade do IAM, segurança de service account e privilégio mínimo.
No lado do Kubernetes:
- CNCF Certified Kubernetes Administrator (CKA) - ServiceAccounts, projected tokens e especificações de pod.
- CNCF Certified Kubernetes Security Specialist (CKS) - Segurança de token de ServiceAccount, privilégio mínimo e redução da exposição de segredos estáticos.
Conclusão
IRSA e GKE Workload Identity Federation são a mesma ideia com diferentes níveis de infraestrutura: o cluster assina um token OIDC de curta duração para um ServiceAccount, e a nuvem o troca por credenciais com escopo para uma única identidade. A AWS faz você montar a confiança por conta própria - um OIDC provider por cluster, um IAM role, uma trust policy, um token projetado que o SDK troca. O GKE oferece um pool gerenciado e de todo o projeto, permite que você vincule um role diretamente ao Kubernetes ServiceAccount sem nenhuma identidade de nuvem, e faz a troca em um metadata server do nó para que seu aplicativo nunca veja uma credencial. Aprenda o triângulo de confiança uma vez e a tradução é mecânica: role se torna principal binding (ou um service account que você impersona), trust policy se torna uma string de IAM member, attached policy se torna um role binding no recurso, e AssumeRoleWithWebIdentity se torna uma troca invisível do metadata server. Mantenha três coisas em mente - os dois toggles do GKE, seu modelo direto sem objeto de identidade, e o fato de que o pull de imagem é uma identidade completamente diferente - e as negações que antes pareciam aleatórias começam a ser lidas como um design coerente e deliberado.