Les pods Kubernetes comme identités cloud : EKS IRSA vs. AKS Workload Identity
Comment un ServiceAccount Kubernetes devient une véritable identité cloud - la chaîne de confiance de fédération OIDC derrière AWS IRSA et Azure AKS Workload Identity, mappée un-à-un, avec les particularités qui bousculent vos habitudes.
AWS et Azure résolvent tous deux le même problème de la même manière : un pod s'authentifie auprès des API cloud en tant que lui-même, avec un jeton de courte durée et sans secret stocké, en transformant son ServiceAccount Kubernetes en une identité cloud fédérée. Sur AWS, la fonctionnalité est IRSA (IAM Roles for Service Accounts) ; sur Azure, il s'agit de Microsoft Entra Workload Identity (le successeur de aad-pod-identity, qui est déprécié). En coulisses, les deux sont le même mécanisme de fédération OIDC, et les composants s'alignent presque un-à-un.
En bref, si vous ne lisez qu'un seul paragraphe : le cluster devient un émetteur OpenID Connect qui signe un jeton pour le ServiceAccount de chaque pod ; le fournisseur d'identité du cloud fait confiance à cet émetteur et échange le jeton contre de véritables identifiants cloud associés à une seule identité. AWS appelle l'identité un rôle IAM et la protège avec une politique de confiance ; Azure l'appelle une identité gérée attribuée par l'utilisateur et la protège avec un identifiant d'identité fédérée. La chaîne de sujet system:serviceaccount:<namespace>:<name> est le pivot sur les deux. Trois choses diffèrent suffisamment pour vous poser problème : Azure divise le "fournisseur OIDC" en deux commutateurs de cluster distincts qui sont tous deux désactivés par défaut, Azure exige en outre une étiquette sur le pod lui-même, et l'extraction de votre image de conteneur est une identité différente de votre identité de charge de travail sur les deux clouds.
Le problème que les deux résolvent
Avant la fédération, accorder des permissions cloud à un pod impliquait de mauvaises options : intégrer une clé d'accès statique dans l'image ou un Secret (fuites, difficulté de rotation), ou accorder la permission au nœud entier afin que chaque pod sur celui-ci l'hérite (pas d'isolation, portée excessive). Les palliatifs côté AWS étaient des intercepteurs node-IMDS comme kube2iam et kiam ; côté Azure, c'était aad-pod-identity. Tous ces mécanismes proxyaient l'identité du nœud et étaient délicats et sujets aux conditions de concurrence.
La fédération remplace cela par une confiance cryptographique. Le kubelet projette un jeton OIDC signé et de courte durée dans le pod, dont la portée est limitée au ServiceAccount du pod. L'IdP cloud vérifie la signature de ce jeton par rapport aux clés publiques publiées du cluster et, si l'émetteur, le sujet et l'audience du jeton correspondent à une règle de confiance que vous avez configurée, renvoie les identifiants pour une seule identité cloud. Aucun secret n'est stocké nulle part ; le jeton projeté s'auto-rotatif (durée de vie par défaut d'environ une heure) et est inutile en dehors du cluster.
Le mécanisme partagé : la fédération OIDC
Chaque configuration d'identité de charge de travail, sur l'un ou l'autre cloud, est le même triangle de confiance :
- Le cluster est un émetteur OIDC. Il expose un document de découverte à
<issuer-url>/.well-known/openid-configurationet un point de terminaison JWKS avec les clés publiques avec lesquelles il signe les jetons ServiceAccount. Cette URL d'émetteur est l'ancre de confiance. - L'IdP cloud fait confiance à cet émetteur pour une paire
(subject, audience)spécifique. Le sujet identifie quel ServiceAccount, et l'audience identifie à qui le jeton est destiné. - La charge de travail échange le jeton projeté contre des identifiants cloud. Le SDK lit le fichier de jeton projeté par le kubelet, le présente au point de terminaison de jeton du cloud et reçoit des identifiants de courte durée pour une seule identité cloud.
Tout le reste est une question de terminologie. Voici la correspondance, ligne par ligne.
La correspondance un-à-un
- Côté Kubernetes. Sur AWS, vous annotez le ServiceAccount avec
eks.amazonaws.com/role-arn: <role arn>. Sur Azure, vous l'annotez avecazure.workload.identity/client-id: <managed identity client id>et étiquetez le pod avecazure.workload.identity/use: "true". Les deux dirigent le pod vers une identité cloud ; Azure nécessite l'étiquette de pod supplémentaire (plus de détails ci-dessous). - Le "fournisseur OIDC". Sur AWS, il s'agit de l'émetteur OIDC du cluster EKS enregistré une seule fois dans IAM en tant que fournisseur d'identité OIDC. Sur Azure, il s'agit de deux paramètres de cluster : l'émetteur OIDC (
oidcIssuerProfile.enabled) qui publie l'URL de l'émetteur et les clés de signature, et l'add-on Workload Identity (securityProfile.workloadIdentity.enabled), un webhook d'admission mutateur. Les deux doivent être activés. - L'identité. AWS : un rôle IAM. Azure : une identité gérée attribuée par l'utilisateur (UAMI). C'est l'objet que le pod devient.
- La règle de confiance. AWS : la politique de confiance du rôle IAM fédère le fournisseur OIDC et épingle
sub = system:serviceaccount:<ns>:<sa>etaud = sts.amazonaws.com. Azure : un identifiant d'identité fédérée sur l'UAMI avecissuer = <cluster OIDC issuer url>,subject = system:serviceaccount:<ns>:<sa>etaudience = api://AzureADTokenExchange. Les trois mêmes champs, des emplacements différents. - Les permissions. AWS : une politique IAM attachée au rôle (lire ce bucket S3, extraire de ce dépôt ECR). Azure : des attributions de rôles RBAC sur les ressources cibles (Key Vault Secrets User, Storage Blob Data Reader, AcrPull, ...). C'est la division philosophique plus profonde - AWS attache une politique à l'identité ; Azure accorde un rôle au niveau de la portée de la ressource.
- L'échange. AWS : le SDK appelle
sts:AssumeRoleWithWebIdentityavec le jeton projeté. Azure : le SDK effectue un échange de jeton Entra viaDefaultAzureCredential/WorkloadIdentityCredential, en présentant le jeton projeté comme une assertion client. Les deux produisent des identifiants de courte durée, les deux ne nécessitent aucun secret dans votre code.
Le reste de cet article décrit chaque cloud de bout en bout, puis s'attarde sur les différences qui posent réellement problème.
Présentation : AWS EKS IRSA, de bout en bout
Les éléments, dans l'ordre du flux de confiance :
1. L'émetteur OIDC du cluster. Chaque cluster EKS en possède un, à une URL comme https://oidc.eks.<region>.amazonaws.com/id/<hash>. Vous l'enregistrez une seule fois dans IAM en tant que fournisseur d'identité OIDC afin qu'IAM fasse confiance aux jetons qu'il signe.
2. Le rôle IAM et sa politique de confiance. La politique de confiance est ce qui rend le rôle assumable par un ServiceAccount spécifique et rien d'autre :
{
"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"
}
}
}
Épinglez à la fois :sub et :aud. N'épingler que l'émetteur permettrait à n'importe quel ServiceAccount du cluster d'assumer le rôle - un bug classique de confiance excessive.
3. La politique IAM. Une politique normale basée sur l'identité attachée au rôle accorde ce dont l'application a réellement besoin (s3:GetObject sur un bucket, ecr:GetDownloadUrlForLayer, etc.).
4. Le ServiceAccount. Un ServiceAccount simple avec une annotation :
apiVersion: v1
kind: ServiceAccount
metadata:
name: checkout
namespace: apps
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::<account>:role/checkout
5. La projection. EKS intègre le Pod Identity Webhook directement dans le cluster. Lorsqu'un pod utilise un ServiceAccount annoté, le webhook le modifie : il injecte les variables d'environnement AWS_ROLE_ARN et AWS_WEB_IDENTITY_TOKEN_FILE et monte un volume de jeton projeté (audience sts.amazonaws.com, auto-rotatif). Vous n'étiquetez pas le pod ; l'annotation du SA est suffisante.
6. L'échange. La chaîne d'identifiants par défaut du SDK AWS détecte AWS_WEB_IDENTITY_TOKEN_FILE, appelle sts:AssumeRoleWithWebIdentity avec le jeton, et met en cache les identifiants de courte durée renvoyés. Votre code est simplement boto3.client("s3") - aucune gestion des identifiants n'est nécessaire.
Alternative plus récente : EKS Pod Identity (2023) fait le même travail via un agent sur le cluster et une API d'association au lieu d'un fournisseur IAM OIDC par cluster et d'une politique de confiance par rôle. C'est plus facile à opérer à grande échelle mais c'est une plomberie spécifique à AWS ; le modèle de fédération OIDC ci-dessus est celui qui se mappe proprement à Azure, c'est donc celui à garder à l'esprit pour une comparaison inter-cloud.
Présentation : Azure AKS Workload Identity, de bout en bout
Même flux de confiance, termes Azure :
1. Deux commutateurs de cluster. Activez l'émetteur OIDC (oidcIssuerProfile.enabled = true), qui publie une URL d'émetteur comme https://<region>.oic.prod-aks.azure.com/<tenant>/<guid>/, et l'add-on Workload Identity (securityProfile.workloadIdentity.enabled = true), qui installe le webhook mutateur. Les deux sont désactivés par défaut sur un nouveau cluster, et l'oidcIssuerUrl du cluster est nulle tant que le premier n'est pas activé. L'activation de ces options est une mise à jour sur place, pas une recréation.
2. L'identité gérée. Créez une identité gérée attribuée par l'utilisateur. (Les identifiants fédérés ne s'attachent qu'aux identités attribuées par l'utilisateur, pas aux identités attribuées par le système.) Son ID client est ce que le ServiceAccount référencera.
3. L'identifiant d'identité fédérée. C'est la règle de confiance, un objet enfant sur l'UAMI :
issuer: https://<region>.oic.prod-aks.azure.com/<tenant>/<guid>/
subject: system:serviceaccount:apps:checkout
audience: api://AzureADTokenExchange
Notez la même grammaire de sujet que AWS et une audience fixe différente.
4. Le RBAC. Accordez au principal de l'UAMI les rôles dont il a besoin sur les ressources cibles : Key Vault Secrets User sur un coffre-fort, Storage Blob Data Reader sur un compte de stockage, AcrPull sur un registre si l'application appelle elle-même le plan de données du registre. Il n'y a pas de document de politique sur l'identité ; l'accès est géré par des attributions de rôles au niveau de la portée de chaque ressource.
5. Le ServiceAccount et l'étiquette du pod. Le ServiceAccount porte l'annotation client-id, et - c'est la partie que les utilisateurs AWS oublient - le pod doit également être étiqueté :
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 projection. Le webhook de l'add-on ne modifie que les pods qui portent l'étiquette azure.workload.identity/use: "true". Lorsqu'il se déclenche, il injecte AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_FEDERATED_TOKEN_FILE et AZURE_AUTHORITY_HOST, et projette un jeton avec l'audience api://AzureADTokenExchange.
7. L'échange. DefaultAzureCredential() (ou l'explicite WorkloadIdentityCredential) lit ces variables d'environnement, présente le jeton projeté à Entra ID comme une assertion client, et reçoit en retour un jeton d'accès pour la ressource cible. Une lecture de Key Vault est azure.identity.DefaultAzureCredential() plus SecretClient en Python, ou new DefaultAzureCredential() plus @azure/keyvault-secrets en TypeScript - et encore une fois, aucun secret nulle part dans le code.
La chaîne de sujet est le pivot (sur les deux clouds)
La seule valeur qui doit correspondre exactement, sur les deux clouds, est le sujet du jeton :
system:serviceaccount:<namespace>:<serviceaccount-name>
Le kubelet appose cette valeur dans le jeton en fonction du namespace et du ServiceAccount réels du pod. La règle de confiance (politique de confiance IAM sur AWS, identifiant fédéré sur Azure) épingle la chaîne qu'elle acceptera. Si l'application s'exécute dans le namespace apps sous le ServiceAccount checkout, la règle de confiance doit indiquer system:serviceaccount:apps:checkout - caractère par caractère. L'échec le plus courant "il s'authentifie en tant que personne" est une non-concordance ici : le chart déployé dans un namespace différent, le ServiceAccount a reçu le nom de la release au lieu du nom de l'application, ou quelqu'un a supposé le ServiceAccount default. Lorsque la fédération échoue silencieusement, vérifiez d'abord le sujet.
L'audience est l'autre champ épinglé, et c'est une constante fixe par cloud : sts.amazonaws.com sur AWS, api://AzureADTokenExchange sur Azure. Vous le changez rarement, mais la règle de confiance et le jeton projeté doivent s'accorder sur ce point.
Quatre différences qui vous poseront réellement problème
1. Azure divise le "fournisseur OIDC" en deux commutateurs, et les deux sont désactivés par défaut. Sur EKS, le webhook d'identité de pod est intégré au cluster et la seule étape unique est l'enregistrement du fournisseur OIDC dans IAM. Sur AKS, vous devez activer séparément l'émetteur OIDC et l'add-on Workload Identity ; si vous oubliez l'add-on, l'émetteur publie toujours des jetons mais rien ne les projette dans les pods, de sorte que l'application ne voit aucune variable d'environnement AZURE_* et DefaultAzureCredential passe silencieusement à la source d'identifiants suivante. Activez les deux, et rappelez-vous que l'identifiant fédéré ne peut pas être créé tant que l'URL de l'émetteur n'existe pas.
2. Azure nécessite une étiquette sur le pod, pas seulement une annotation sur le ServiceAccount. Le webhook AKS se base sur l'étiquette azure.workload.identity/use: "true" sur le pod (l'annotation du ServiceAccount seule ne suffit pas). Le webhook d'IRSA se base sur l'annotation du ServiceAccount et n'a pas besoin d'étiquette de pod. C'est le piège le plus courant côté Azure : le ServiceAccount semble parfait, mais le modèle de pod a oublié l'étiquette, donc aucun jeton n'est projeté.
3. L'extraction de l'image est une identité différente de l'identité de charge de travail - sur les deux clouds. L'extraction d'image est le travail de l'identité du nœud/kubelet : sur AKS, l'identité du kubelet détient AcrPull ; sur EKS, le rôle d'instance du groupe de nœuds détient AmazonEC2ContainerRegistryReadOnly (ou vous utilisez des secrets d'extraction). L'identité de charge de travail n'est nécessaire que lorsque le code de l'application appelle une API cloud (lire un secret, lister un bucket, appeler le plan de données du registre). Un service HTTP pur qui ne communique jamais avec un SDK cloud n'a pas besoin d'identité de charge de travail du tout - et inversement, accorder un AcrPull à une identité de charge de travail ne fait rien pour l'extraction d'image, car l'extraction se produit avant que l'application (et son jeton fédéré) ne s'exécute.
4. Les permissions s'attachent différemment. AWS attache une politique IAM au rôle - la permission voyage avec l'identité. Azure accorde des attributions de rôles RBAC au niveau de la portée de la ressource cible - l'octroi réside sur l'élément accédé, et non sur l'identité. Même état final (ce pod peut lire ce coffre-fort), mais vous cherchez à différents endroits pour l'auditer : sur AWS, lisez les politiques attachées au rôle ; sur Azure, listez les attributions de rôles sur la ressource (ou les attributions de l'identité à travers les portées). Cela reflète la différence plus large entre IAM et RBAC entre les deux clouds.
À quoi ressemble l'environnement injecté
Les deux webhooks fournissent au SDK tout ce dont il a besoin via des variables d'environnement et un fichier projeté, de sorte que le code de l'application ne référence aucun secret et aucune logique d'identifiants spécifique au cloud :
- AWS :
AWS_ROLE_ARN,AWS_WEB_IDENTITY_TOKEN_FILE,AWS_REGION. La chaîne d'identifiants par défaut appelleAssumeRoleWithWebIdentitypour vous. - Azure :
AZURE_CLIENT_ID,AZURE_TENANT_ID,AZURE_FEDERATED_TOKEN_FILE,AZURE_AUTHORITY_HOST.DefaultAzureCredentialeffectue l'échange Entra pour vous.
Dans les deux cas, le fichier de jeton projeté est de courte durée et auto-rotatif par le kubelet ; le SDK le relit et rafraîchit les identifiants cloud de manière transparente. Il n'y a rien à faire pivoter, rien à stocker et rien à divulguer.
Une liste de contrôle de débogage qui fonctionne sur les deux clouds
Lorsqu'un pod "ne peut pas s'authentifier", parcourez la chaîne de confiance dans l'ordre :
- Émetteur du cluster activé ? Confirmez que l'URL de l'émetteur OIDC existe (AKS : les deux commutateurs sont à true ; EKS : le fournisseur IAM OIDC est enregistré). Pas d'émetteur, pas d'ancre de confiance.
- Projection en cours ? Exécutez exec dans le pod et vérifiez la présence du fichier de jeton et des variables d'environnement (
AWS_WEB_IDENTITY_TOKEN_FILE/AZURE_FEDERATED_TOKEN_FILE). L'absence sur Azure signifie généralement que l'étiquette du pod est absente ou que l'add-on est désactivé. - Sujet correspondant ? Comparez le sujet de la règle de confiance à
system:serviceaccount:<actual-namespace>:<actual-sa>. C'est l'échec le plus fréquent. - Audience correspondante ?
sts.amazonaws.com/api://AzureADTokenExchange, concordant des deux côtés. - Permissions ? Seulement après la résolution de l'identité : la politique IAM sur le rôle, ou l'attribution de rôle RBAC sur la ressource cible. Un résultat vide ici est un échec d'autorisation, pas d'authentification - une distinction utile, car elle vous indique que la fédération elle-même fonctionne.
Quelles certifications abordent ce sujet
L'identité de charge de travail se situe à l'intersection de Kubernetes, de l'IAM cloud et de la fédération OIDC, elle apparaît donc dans trois parcours d'examen. Si vous détenez déjà un identifiant cloud, le même concept sur l'autre cloud est un petit pas.
Côté AWS :
- AWS Certified Solutions Architect - Associate (SAA-C03) - Les rôles IAM, EKS et comment les identités obtiennent les permissions cloud.
- AWS Certified Security - Specialty (SCS-C03) - Les politiques de confiance IAM, la fédération OIDC et la portée du moindre privilège (exactement la chaîne de confiance IRSA).
Côté Azure :
- Microsoft Azure Administrator Associate (AZ-104) - AKS, les identités gérées et les attributions de rôles RBAC.
- Microsoft Azure Security Engineer Associate (AZ-500) - Entra ID, les identités gérées, les identifiants fédérés et la profondeur du RBAC.
Côté Kubernetes :
- CNCF Certified Kubernetes Administrator (CKA) - Les ServiceAccounts, les jetons projetés et les spécifications de pod.
- CNCF Certified Kubernetes Security Specialist (CKS) - La sécurité des jetons ServiceAccount, le moindre privilège et la réduction de l'exposition aux secrets statiques.
En résumé
IRSA et AKS Workload Identity sont la même idée exprimée avec un vocabulaire différent : le cluster signe un jeton OIDC de courte durée pour un ServiceAccount, l'IdP cloud fait confiance à cet émetteur pour une paire (subject, audience), et le pod échange le jeton contre des identifiants associés à une seule identité cloud - un rôle IAM sur AWS, une identité gérée sur Azure. Apprenez le triangle de confiance une fois et la traduction est mécanique : le rôle devient une identité gérée, la politique de confiance devient un identifiant fédéré, la politique attachée devient une attribution RBAC, AssumeRoleWithWebIdentity devient un échange de jeton Entra. Gardez trois choses à l'esprit - les deux commutateurs de cluster d'Azure, l'étiquette de pod requise par Azure, et le fait que l'extraction d'image est une identité entièrement différente - et les refus qui semblaient aléatoires commencent à être perçus comme une conception cohérente et délibérée.