Pods de Kubernetes como identidades en la nube: IRSA de EKS vs. Workload Identity de AKS
Cómo un ServiceAccount de Kubernetes se convierte en una identidad real en la nube - la cadena de confianza de federación OIDC detrás de IRSA de AWS y Workload Identity de Azure AKS, mapeada uno a uno, con las particularidades que rompen tu memoria muscular.
Tanto AWS como Azure resuelven el mismo problema de la misma manera: un pod se autentica en las API de la nube como sí mismo, con un token de corta duración y sin secreto almacenado, al convertir su Kubernetes ServiceAccount en una identidad federada en la nube. En AWS, la característica es IRSA (IAM Roles for Service Accounts); en Azure, es Microsoft Entra Workload Identity (el sucesor de aad-pod-identity, que está en desuso). En el fondo, ambos son el mismo truco de federación OIDC, y las piezas móviles se alinean casi uno a uno.
La versión corta, si solo lees un párrafo: el clúster se convierte en un emisor de OpenID Connect que firma un token para el ServiceAccount de cada pod; el proveedor de identidad de la nube confía en ese emisor e intercambia el token por credenciales reales de la nube con alcance a una identidad. AWS llama a la identidad un IAM role y la protege con una trust policy; Azure la llama una user-assigned managed identity y la protege con una federated identity credential. La cadena de sujeto system:serviceaccount:<namespace>:<name> es el eje en ambos. Tres cosas difieren lo suficiente como para causarte problemas: Azure divide el "OIDC provider" en dos conmutadores de clúster separados que ambos están desactivados por defecto, Azure requiere adicionalmente una etiqueta en el pod mismo, y extraer tu imagen de contenedor es una identidad diferente de tu workload identity en ambas nubes.
El problema que ambos resuelven
Antes de la federación, otorgar permisos de nube a un pod significaba malas opciones: integrar una clave de acceso estática en la imagen o un Secret (filtraciones, dolor de rotación), o conceder el permiso a todo el nodo para que cada pod en él lo heredara (sin aislamiento, alcance excesivo). Las soluciones provisionales del lado de AWS eran interceptores de IMDS de nodo como kube2iam y kiam; la del lado de Azure era aad-pod-identity. Todos ellos actuaban como proxy de la identidad del nodo y eran complicados y propensos a condiciones de carrera.
La federación reemplaza eso con confianza criptográfica. El kubelet proyecta un token OIDC firmado y de corta duración en el pod, con alcance al ServiceAccount del pod. El IdP de la nube verifica la firma de ese token contra las claves públicas publicadas del clúster y, si el emisor, el sujeto y la audiencia del token coinciden con una trust rule que configuraste, devuelve credenciales para exactamente una identidad en la nube. No se almacena ningún secret en ningún lugar; el token proyectado rota automáticamente (vida útil predeterminada de aproximadamente una hora) y es inútil fuera del clúster.
El mecanismo compartido: federación OIDC
Cada configuración de workload identity, en cualquiera de las nubes, es el mismo triángulo de confianza:
- El clúster es un emisor OIDC. Expone un discovery document en
<issuer-url>/.well-known/openid-configurationy un JWKS endpoint con las claves públicas con las que firma los tokens de ServiceAccount. Esa issuer URL es el ancla de confianza. - El IdP de la nube confía en ese emisor para un par
(subject, audience)específico. El subject identifica qué ServiceAccount, y la audience identifica para quién es el token. - La workload intercambia el token proyectado por credenciales de la nube. El SDK lee el archivo de token que el kubelet proyectó, lo presenta al token endpoint de la nube y recibe credenciales de corta duración para una única identidad en la nube.
Todo lo demás es nomenclatura. Aquí está el mapa, recorrido fila por fila.
El mapa uno a uno
- El lado de Kubernetes. En AWS, anotas el ServiceAccount con
eks.amazonaws.com/role-arn: <role arn>. En Azure, lo anotas conazure.workload.identity/client-id: <managed identity client id>y etiquetas el pod conazure.workload.identity/use: "true". Ambos apuntan el pod a una identidad en la nube; Azure necesita la etiqueta adicional del pod (más sobre esto a continuación). - El "OIDC provider". En AWS, este es el OIDC issuer del clúster de EKS registrado una vez en IAM como un OIDC identity provider. En Azure, son dos configuraciones de clúster: el OIDC issuer (
oidcIssuerProfile.enabled) que publica la issuer URL y las signing keys, y el workload-identity add-on (securityProfile.workloadIdentity.enabled), un mutating admission webhook. Ambos deben estar activados. - La identidad. AWS: un IAM role. Azure: una user-assigned managed identity (UAMI). Este es el objeto en el que se convierte el pod.
- La trust rule. AWS: la trust policy del IAM role federa el OIDC provider y fija
sub = system:serviceaccount:<ns>:<sa>yaud = sts.amazonaws.com. Azure: una federated identity credential en la UAMI conissuer = <cluster OIDC issuer url>,subject = system:serviceaccount:<ns>:<sa>, yaudience = api://AzureADTokenExchange. Los mismos tres campos, diferentes ubicaciones. - Los permisos. AWS: una IAM policy adjunta al role (leer este bucket de S3, extraer de este repo de ECR). Azure: RBAC role assignments en los recursos de destino (Key Vault Secrets User, Storage Blob Data Reader, AcrPull, ...). Esta es la división filosófica más profunda: AWS adjunta una policy a la identidad; Azure concede un role en el ámbito del recurso.
- El intercambio. AWS: el SDK llama a
sts:AssumeRoleWithWebIdentitycon el token proyectado. Azure: el SDK realiza un Entra token exchange a través deDefaultAzureCredential/WorkloadIdentityCredential, presentando el token proyectado como una client assertion. Ambos producen credenciales de corta duración, ambos necesitan cero material secreto en tu código.
El resto de esta publicación recorre cada nube de principio a fin, y luego se detiene en las diferencias que realmente confunden a la gente.
Recorrido: AWS EKS IRSA, de principio a fin
Las piezas, en el orden en que fluye la confianza:
1. El OIDC issuer del clúster. Cada clúster de EKS tiene uno, en una URL como https://oidc.eks.<region>.amazonaws.com/id/<hash>. Lo registras una vez en IAM como un OIDC identity provider para que IAM confíe en los tokens que firma.
2. El IAM role y su trust policy. La trust policy es lo que hace que el role sea asumible por un ServiceAccount específico y nada más:
{
"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"
}
}
}
Fija tanto :sub como :aud. Fijar solo el issuer permitiría que cualquier ServiceAccount en el clúster asumiera el role - un clásico error de exceso de confianza.
3. La IAM policy. Una IAM policy normal basada en identidad adjunta al role concede lo que la aplicación realmente necesita (s3:GetObject en un bucket, ecr:GetDownloadUrlForLayer, y así sucesivamente).
4. El ServiceAccount. Un ServiceAccount simple con una anotación:
apiVersion: v1
kind: ServiceAccount
metadata:
name: checkout
namespace: apps
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::<account>:role/checkout
5. La proyección. EKS incluye el Pod Identity Webhook integrado en el clúster. Cuando un pod utiliza un ServiceAccount anotado, el webhook lo muta: inyecta las variables de entorno AWS_ROLE_ARN y AWS_WEB_IDENTITY_TOKEN_FILE y monta un volumen de token proyectado (audience sts.amazonaws.com, auto-rotado). No etiquetas el pod; la anotación del SA es suficiente.
6. El intercambio. La cadena de credenciales predeterminada del SDK de AWS detecta AWS_WEB_IDENTITY_TOKEN_FILE, llama a sts:AssumeRoleWithWebIdentity con el token y almacena en caché las credenciales de corta duración devueltas. Tu código es simplemente boto3.client("s3") - sin manejo de credenciales en absoluto.
Alternativa más reciente: EKS Pod Identity (2023) realiza el mismo trabajo a través de un agente en el clúster y una API de asociación en lugar de un IAM OIDC provider por clúster y una trust policy por role. Es más fácil de operar a escala, pero es una implementación específica de AWS; el modelo de federación OIDC anterior es el que se mapea limpiamente a Azure, por lo que es el que debes tener en cuenta para una comparación entre nubes.
Recorrido: Azure AKS Workload Identity, de principio a fin
Mismo flujo de confianza, sustantivos de Azure:
1. Dos conmutadores de clúster. Activa el OIDC issuer (oidcIssuerProfile.enabled = true), que publica una issuer URL como https://<region>.oic.prod-aks.azure.com/<tenant>/<guid>/, y el workload-identity add-on (securityProfile.workloadIdentity.enabled = true), que instala el mutating webhook. Ambos están desactivados por defecto en un clúster nuevo, y la oidcIssuerUrl del clúster es nula hasta que se habilita el primero. Habilitarlos es una actualización in-place, no una recreación.
2. La managed identity. Crea una user-assigned managed identity. (Las federated credentials solo se adjuntan a user-assigned identities, no a system-assigned ones.) Su client id es lo que referenciará el ServiceAccount.
3. La federated identity credential. Esta es la trust rule, un objeto hijo en la UAMI:
issuer: https://<region>.oic.prod-aks.azure.com/<tenant>/<guid>/
subject: system:serviceaccount:apps:checkout
audience: api://AzureADTokenExchange
Observa la misma gramática de subject que AWS y una audience fija diferente.
4. El RBAC. Concede al principal de la UAMI los roles que necesita en los recursos de destino: Key Vault Secrets User en un vault, Storage Blob Data Reader en una storage account, AcrPull en un registry si la aplicación llama directamente al data plane del registry. No hay un policy document en la identidad; el acceso son role assignments en el ámbito de cada recurso.
5. El ServiceAccount y la etiqueta del pod. El ServiceAccount lleva la anotación client-id, y - esta es la parte que la gente de AWS olvida - el pod también debe estar etiquetado:
apiVersion: v1
kind: ServiceAccount
metadata:
name: checkout
namespace: apps
annotations:
azure.workload.identity/client-id: <uami-client-id>
---
# in the Deployment's pod template:
metadata:
labels:
azure.workload.identity/use: "true" # required on the POD, not just the SA
spec:
serviceAccountName: checkout
6. La proyección. El webhook del add-on solo muta los pods que llevan la etiqueta azure.workload.identity/use: "true". Cuando se activa, inyecta AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_FEDERATED_TOKEN_FILE y AZURE_AUTHORITY_HOST, y proyecta un token con audience api://AzureADTokenExchange.
7. El intercambio. DefaultAzureCredential() (o el WorkloadIdentityCredential explícito) lee esas variables de entorno, presenta el token proyectado a Entra ID como una client assertion y recibe un access token para el recurso de destino. Una lectura de Key Vault es azure.identity.DefaultAzureCredential() más SecretClient en Python, o new DefaultAzureCredential() más @azure/keyvault-secrets en TypeScript, y de nuevo, ningún secret en el código.
La cadena de sujeto es el eje (en ambas nubes)
El único valor que debe coincidir exactamente, en ambas nubes, es el token subject:
system:serviceaccount:<namespace>:<serviceaccount-name>
El kubelet estampa esto en el token basándose en el namespace y ServiceAccount reales del pod. La trust rule (IAM trust policy en AWS, federated credential en Azure) fija la cadena que aceptará. Si la aplicación se ejecuta en el namespace apps bajo el ServiceAccount checkout, la trust rule debe decir system:serviceaccount:apps:checkout - carácter por carácter. El fallo más común de "no se autentica como nadie" es una falta de coincidencia aquí: el chart se desplegó en un namespace diferente, el ServiceAccount obtuvo el nombre de la release en lugar del nombre de la aplicación, o alguien asumió el ServiceAccount default. Cuando la federación falla silenciosamente, verifica primero el subject.
La audience es el otro campo fijado, y es una constante fija por nube: sts.amazonaws.com en AWS, api://AzureADTokenExchange en Azure. Rara vez lo cambias, pero la trust rule y el token proyectado deben estar de acuerdo en ello.
Cuatro diferencias que realmente te causarán problemas
1. Azure divide el "OIDC provider" en dos conmutadores, y ambos están desactivados por defecto. En EKS, el pod-identity webhook está integrado en el clúster y el único paso único es registrar el OIDC provider en IAM. En AKS, debes habilitar por separado el OIDC issuer y el workload-identity add-on; si olvidas el add-on, el issuer seguirá publicando tokens, pero nada los proyectará en los pods, por lo que la aplicación no verá ninguna variable de entorno AZURE_* y DefaultAzureCredential pasará silenciosamente a la siguiente fuente de credenciales. Habilita ambos, y recuerda que la federated credential no se puede crear hasta que exista la issuer URL.
2. Azure necesita una etiqueta en el pod, no solo una anotación en el ServiceAccount. El webhook de AKS se basa en la etiqueta azure.workload.identity/use: "true" en el pod (la anotación del ServiceAccount por sí sola no es suficiente). El webhook de IRSA se basa en la anotación del ServiceAccount y no necesita ninguna etiqueta de pod. Este es el error más común del lado de Azure: el ServiceAccount parece perfecto, pero la plantilla del pod olvidó la etiqueta, por lo que no se proyecta ningún token.
3. Extraer la imagen es una identidad diferente de la workload identity - en ambas nubes. La extracción de imágenes es trabajo de la identidad del nodo/kubelet: en AKS, la identidad del kubelet tiene AcrPull; en EKS, el instance role del grupo de nodos tiene AmazonEC2ContainerRegistryReadOnly (o usas pull secrets). La workload identity solo se necesita cuando el código de la aplicación llama a una API de la nube (leer un secret, listar un bucket, llamar al data plane del registry). Un servicio HTTP puro que nunca habla con un SDK de la nube no necesita ninguna workload identity en absoluto, y a la inversa, conceder una workload identity AcrPull no hace nada para la extracción de imágenes, porque la extracción ocurre antes de que la aplicación (y su token federado) se ejecute.
4. Los permisos se adjuntan de manera diferente. AWS adjunta una IAM policy al role; el permiso viaja con la identidad. Azure concede RBAC role assignments en el ámbito del recurso de destino; la concesión reside en el elemento al que se accede, no en la identidad. Mismo estado final (este pod puede leer ese vault), pero buscas en diferentes lugares para auditarlo: en AWS, lee las policies adjuntas al role; en Azure, lista los role assignments en el recurso (o los assignments de la identidad en todos los ámbitos). Esto refleja la diferencia más amplia entre IAM y RBAC entre las dos nubes.
Cómo se ve el entorno inyectado
Ambos webhooks entregan al SDK todo lo que necesita a través de variables de entorno y un archivo proyectado, por lo que el código de la aplicación no referencia ningún secret ni lógica de credenciales específica de la nube:
- AWS:
AWS_ROLE_ARN,AWS_WEB_IDENTITY_TOKEN_FILE,AWS_REGION. La cadena de credenciales predeterminada llama aAssumeRoleWithWebIdentitypor ti. - Azure:
AZURE_CLIENT_ID,AZURE_TENANT_ID,AZURE_FEDERATED_TOKEN_FILE,AZURE_AUTHORITY_HOST.DefaultAzureCredentialrealiza el Entra exchange por ti.
En ambos casos, el archivo de token proyectado es de corta duración y es auto-rotado por el kubelet; el SDK lo vuelve a leer y actualiza las credenciales de la nube de forma transparente. No hay nada que rotar, nada que almacenar y nada que filtrar.
Una lista de verificación de depuración que funciona en ambas nubes
Cuando un pod "no puede autenticarse", recorre la cadena de confianza en orden:
- ¿Issuer del clúster activado? Confirma que la OIDC issuer URL existe (AKS: ambos conmutadores en true; EKS: el IAM OIDC provider está registrado). Sin issuer, no hay trust anchor.
- ¿Proyección en curso? Ejecuta exec en el pod y verifica la existencia del archivo de token y las variables de entorno (
AWS_WEB_IDENTITY_TOKEN_FILE/AZURE_FEDERATED_TOKEN_FILE). La ausencia en Azure suele significar que la etiqueta del pod está ausente o que el add-on está desactivado. - ¿Coincidencia de subject? Compara el subject de la trust rule con
system:serviceaccount:<actual-namespace>:<actual-sa>. Este es el fallo más frecuente. - ¿Coincidencia de audience?
sts.amazonaws.com/api://AzureADTokenExchange, coincidiendo en ambos lados. - ¿Permisos? Solo después de que la identidad se resuelva: la IAM policy en el role, o el RBAC role assignment en el recurso de destino. Un resultado vacío aquí es un fallo de autorización, no de autenticación, una distinción útil, porque te indica que la federación en sí misma está funcionando.
Qué certificaciones cubren esto
Workload identity se encuentra justo donde se unen Kubernetes, cloud IAM y la federación OIDC, por lo que aparece en tres vías de examen. Si ya tienes una credencial de una nube, el mismo concepto en la otra nube es un salto corto.
Por el lado de AWS:
- AWS Certified Solutions Architect - Associate (SAA-C03) - IAM roles, EKS y cómo las identidades obtienen permisos en la nube.
- AWS Certified Security - Specialty (SCS-C03) - IAM trust policies, federación OIDC y alcance de mínimo privilegio (exactamente la cadena de confianza de IRSA).
Por el lado de Azure:
- Microsoft Azure Administrator Associate (AZ-104) - AKS, managed identities y RBAC role assignments.
- Microsoft Azure Security Engineer Associate (AZ-500) - Entra ID, managed identities, federated credentials y profundidad de RBAC.
Por el lado de Kubernetes:
- CNCF Certified Kubernetes Administrator (CKA) - ServiceAccounts, projected tokens y especificaciones de pod.
- CNCF Certified Kubernetes Security Specialist (CKS) - Seguridad de tokens de ServiceAccount, mínimo privilegio y reducción de la exposición de secretos estáticos.
Conclusión
IRSA y AKS Workload Identity son la misma idea con vocabulario diferente: el clúster firma un token OIDC de corta duración para un ServiceAccount, el IdP de la nube confía en ese issuer para un (subject, audience), y el pod intercambia el token por credenciales con alcance a una única identidad en la nube - un IAM role en AWS, una managed identity en Azure. Aprende el triángulo de confianza una vez y la traducción es mecánica: role se convierte en managed identity, trust policy se convierte en federated credential, attached policy se convierte en RBAC assignment, AssumeRoleWithWebIdentity se convierte en un Entra token exchange. Ten en cuenta tres cosas: los dos conmutadores de clúster de Azure, la etiqueta de pod requerida de Azure y el hecho de que la extracción de imágenes es una identidad completamente diferente, y las denegaciones que antes parecían aleatorias comenzarán a leerse como un diseño coherente y deliberado.