将 Kubernetes Pod 用作云身份:EKS IRSA 与 AKS Workload Identity
Kubernetes ServiceAccount 如何成为真正的云身份--AWS IRSA 和 Azure AKS Workload Identity 背后的 OIDC 联邦信任链,进行一对一映射,并指出那些会打破您肌肉记忆的细节。
AWS 和 Azure 都以相同的方式解决了相同的问题:通过将其 Kubernetes ServiceAccount 转换为联邦云身份,Pod 可以 作为自身 向云 API 进行身份验证,使用短期令牌且无需存储秘密。在 AWS 上,此功能是 IRSA(IAM Roles for Service Accounts);在 Azure 上,它是 Microsoft Entra Workload Identity(已弃用的 aad-pod-identity 的继任者)。实际上,两者都是相同的 OIDC 联邦技巧,其组成部分几乎一一对应。
如果您只阅读一段话,简而言之:集群成为一个 OpenID Connect 颁发者,为每个 Pod 的 ServiceAccount 签署令牌;云的身份提供者信任该颁发者,并将令牌交换为限定于一个身份的真实云凭据。 AWS 将此身份称为 IAM 角色,并通过信任策略对其进行限制;Azure 将其称为用户分配的托管身份,并通过联邦身份凭据对其进行限制。system:serviceaccount:<namespace>:<name> 这个主题字符串是两者的关键。有三点差异足以让您头疼:Azure 将“OIDC 提供者”拆分为两个独立的集群开关,两者都默认关闭;Azure 额外要求在 Pod 本身添加一个标签;以及在两个云上,拉取容器镜像的身份与您的工作负载身份是 不同 的。
两者解决的问题
在联邦之前,授予 Pod 云权限意味着糟糕的选择:将静态访问密钥烘焙到镜像或 Secret 中(泄露、轮换痛苦),或者将权限授予整个节点,以便其上 所有 Pod 都继承它(无隔离,权限范围过大)。AWS 方面的权宜之计是 kube2iam 和 kiam 等节点 IMDS 拦截器;Azure 方面是 aad-pod-identity。它们都代理了节点的身份,并且操作繁琐且容易出现竞态条件。
联邦用加密信任取代了这一点。kubelet 将一个签名的、短期的 OIDC 令牌投射到 Pod 中,该令牌的作用域限定为 Pod 的 ServiceAccount。云 IdP 根据集群发布的公钥验证该令牌的签名,如果令牌的颁发者、主题和受众与您配置的信任规则匹配,则返回恰好一个云身份的凭据。任何地方都不存储秘密;投射的令牌会自动轮换(默认生命周期约为一小时),并且在集群外部无用。
共享机制:OIDC 联邦
无论在哪种云上,每个工作负载身份设置都是相同的信任三角:
- 集群是一个 OIDC 颁发者。 它在
<issuer-url>/.well-known/openid-configuration暴露一个发现文档,以及一个包含其签署 ServiceAccount 令牌所用公钥的 JWKS 端点。该颁发者 URL 是信任锚点。 - 云 IdP 信任该颁发者 用于特定的
(subject, audience)对。主题标识 哪个 ServiceAccount,受众标识 令牌是为谁。 - 工作负载将投射的令牌交换为云凭据。 SDK 读取 kubelet 投射的令牌文件,将其呈现给云的令牌端点,并接收单个云身份的短期凭据。
其他一切都只是命名。下面是逐行解析的映射。
一对一映射
- Kubernetes 侧。 在 AWS 上,您使用
eks.amazonaws.com/role-arn: <role arn>注释 ServiceAccount。在 Azure 上,您使用azure.workload.identity/client-id: <managed identity client id>注释它,并 使用azure.workload.identity/use: "true"标记 Pod。两者都将 Pod 指向一个云身份;Azure 需要额外的 Pod 标签(下文详述)。 - “OIDC 提供者”。 在 AWS 上,这是 EKS 集群的 OIDC 颁发者,在 IAM 中一次性注册为 OIDC 身份提供者。在 Azure 上,它是 两个 集群设置:发布颁发者 URL 和签名密钥的 OIDC 颁发者 (
oidcIssuerProfile.enabled),以及 工作负载身份附加组件 (securityProfile.workloadIdentity.enabled),一个变异准入 webhook。两者都必须开启。 - 身份。 AWS:一个 IAM 角色。Azure:一个 用户分配的托管身份 (UAMI)。这是 Pod 成为的对象。
- 信任规则。 AWS:IAM 角色的 信任策略 联邦 OIDC 提供者并固定
sub = system:serviceaccount:<ns>:<sa>和aud = sts.amazonaws.com。Azure:UAMI 上的 联邦身份凭据,其中issuer = <cluster OIDC issuer url>、subject = system:serviceaccount:<ns>:<sa>和audience = api://AzureADTokenExchange。相同的三个字段,不同的归属。 - 权限。 AWS:附加到角色的 IAM 策略(读取此 S3 存储桶,从此 ECR 仓库拉取)。Azure:目标资源上的 RBAC 角色分配(Key Vault Secrets User、Storage Blob Data Reader、AcrPull 等)。这是更深层次的哲学分歧--AWS 将策略附加到身份;Azure 在资源范围内授予角色。
- 交换。 AWS:SDK 使用投射的令牌调用
sts:AssumeRoleWithWebIdentity。Azure:SDK 通过DefaultAzureCredential/WorkloadIdentityCredential进行 Entra 令牌交换,将投射的令牌作为客户端断言呈现。两者都产生短期凭据,两者在您的代码中都不需要任何秘密材料。
本文的其余部分将端到端地介绍每个云,然后深入探讨那些实际会让人困惑的差异。
演练:AWS EKS IRSA,端到端
信任流动的顺序如下:
1. 集群 OIDC 颁发者。 每个 EKS 集群都有一个,其 URL 类似于 https://oidc.eks.<region>.amazonaws.com/id/<hash>。您需要在 IAM 中将其注册为 OIDC 身份提供者一次,以便 IAM 信任其签名的令牌。
2. IAM 角色及其信任策略。 信任策略使得该角色只能由特定的 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 策略。 附加到角色的普通基于身份的策略授予应用程序实际所需的权限(对一个存储桶的 s3:GetObject、ecr:GetDownloadUrlForLayer 等)。
4. ServiceAccount。 一个带有单个注释的普通 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。当 Pod 使用带有注释的 ServiceAccount 时,webhook 会对其进行变异:它注入 AWS_ROLE_ARN 和 AWS_WEB_IDENTITY_TOKEN_FILE 环境变量,并挂载一个投射令牌卷(受众 sts.amazonaws.com,自动轮换)。您无需标记 Pod;SA 注释就足够了。
6. 交换。 AWS SDK 的默认凭据链会注意到 AWS_WEB_IDENTITY_TOKEN_FILE,使用令牌调用 sts:AssumeRoleWithWebIdentity,并缓存返回的短期凭据。您的代码只需 boto3.client("s3")--完全无需凭据处理。
新的替代方案:EKS Pod Identity (2023) 通过集群内代理和关联 API 完成相同的工作,而不是通过每个集群的 IAM OIDC 提供者和每个角色的信任策略。它更容易大规模操作,但它是 AWS 特有的底层实现;上述 OIDC 联邦模型是与 Azure 清晰映射的模型,因此在进行跨云比较时应牢记这一点。
演练:Azure AKS Workload Identity,端到端
相同的信任流,Azure 术语:
1. 两个集群开关。 开启 OIDC 颁发者 (oidcIssuerProfile.enabled = true),它发布一个类似于 https://<region>.oic.prod-aks.azure.com/<tenant>/<guid>/ 的颁发者 URL,以及 工作负载身份附加组件 (securityProfile.workloadIdentity.enabled = true),它安装变异 webhook。两者在新集群上都默认 关闭,并且集群的 oidcIssuerUrl 在第一个启用之前为 null。启用它们是就地更新,而不是重新创建。
2. 托管身份。 创建一个 用户分配的托管身份。(联邦凭据仅附加到用户分配的身份,而不是系统分配的身份。) 它的 客户端 ID 是 ServiceAccount 将引用的内容。
3. 联邦身份凭据。 这是信任规则,是 UAMI 上的一个子对象:
issuer: https://<region>.oic.prod-aks.azure.com/<tenant>/<guid>/
subject: system:serviceaccount:apps:checkout
audience: api://AzureADTokenExchange
请注意与 AWS 相同的主题语法和 不同 的固定受众。
4. RBAC。 授予 UAMI 的主体在 目标资源 上所需的角色:在密钥保管库上授予 Key Vault Secrets User,在存储账户上授予 Storage Blob Data Reader,如果应用程序本身调用注册表数据平面,则在注册表上授予 AcrPull。身份上没有策略文档;访问权限是每个资源范围内的角色分配。
5. ServiceAccount 和 Pod 标签。 ServiceAccount 带有客户端 ID 注释,而且--这是 AWS 用户容易忘记的部分--Pod 也必须被标记:
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. 投射。 附加组件的 webhook 只会变异带有 azure.workload.identity/use: "true" 标签的 Pod。当它触发时,它会注入 AZURE_CLIENT_ID、AZURE_TENANT_ID、AZURE_FEDERATED_TOKEN_FILE 和 AZURE_AUTHORITY_HOST 环境变量,并投射一个受众为 api://AzureADTokenExchange 的令牌。
7. 交换。 DefaultAzureCredential()(或显式的 WorkloadIdentityCredential)读取这些环境变量,将投射的令牌作为客户端断言呈现给 Entra ID,并获取目标资源的访问令牌。在 Python 中读取 Key Vault 是 azure.identity.DefaultAzureCredential() 加上 SecretClient,或者在 TypeScript 中是 new DefaultAzureCredential() 加上 @azure/keyvault-secrets--同样,代码中没有任何秘密。
主题字符串是关键(在两个云上)
在两个云上都必须精确匹配的单个值是令牌主题:
system:serviceaccount:<namespace>:<serviceaccount-name>
kubelet 根据 Pod 的实际命名空间和 ServiceAccount 将此信息写入令牌。信任规则(AWS 上的 IAM 信任策略,Azure 上的联邦凭据)固定了它将接受的字符串。如果应用程序在 apps 命名空间下以 ServiceAccount checkout 运行,则信任规则必须精确地写明 system:serviceaccount:apps:checkout。最常见的“无法认证”失败是这里的 Mismatch:chart 部署到了不同的命名空间,ServiceAccount 获得了发布名称而不是应用程序名称,或者有人假设使用了 default ServiceAccount。当联邦静默失败时,首先检查主题。
受众是另一个固定的字段,它是每个云的固定常量:AWS 上是 sts.amazonaws.com,Azure 上是 api://AzureADTokenExchange。您很少更改它,但信任规则和投射的令牌必须就此达成一致。
四个实际会让你困惑的差异
1. Azure 将“OIDC 提供者”拆分为两个开关,并且两者都默认关闭。 在 EKS 上,Pod 身份 webhook 内置于集群中,唯一的一次性步骤是在 IAM 中注册 OIDC 提供者。在 AKS 上,您必须单独启用 OIDC 颁发者 和 工作负载身份附加组件;如果忘记启用附加组件,颁发者仍然会发布令牌,但没有任何东西将它们投射到 Pod 中,因此应用程序看不到 AZURE_* 环境变量,DefaultAzureCredential 会悄悄地回退到下一个凭据源。两者都必须启用,并且请记住,在颁发者 URL 存在之前无法创建联邦凭据。
2. Azure 需要在 Pod 上添加标签,而不仅仅是在 ServiceAccount 上添加注释。 AKS webhook 依赖于 Pod 上的 azure.workload.identity/use: "true" 标签(仅 ServiceAccount 注释不足)。IRSA 的 webhook 依赖于 ServiceAccount 注释,不需要 Pod 标签。这是 Azure 方面最常见的陷阱:ServiceAccount 看起来完美无缺,但 Pod 模板忘记了标签,因此没有令牌被投射。
3. 拉取镜像的身份与工作负载身份不同--在两个云上都是如此。 镜像拉取是 节点/kubelet 身份的工作:在 AKS 上,kubelet 身份持有 AcrPull;在 EKS 上,节点组的实例角色持有 AmazonEC2ContainerRegistryReadOnly(或者您使用拉取秘密)。工作负载身份仅在 应用程序代码 调用云 API 时才需要(读取秘密、列出存储桶、调用注册表的数据平面)。一个从不与云 SDK 通信的纯 HTTP 服务根本不需要工作负载身份--反之,授予工作负载身份 AcrPull 对镜像拉取没有任何作用,因为拉取发生在应用程序(及其联邦令牌)运行之前。
4. 权限附加方式不同。 AWS 将 IAM 策略附加到角色--权限随身份而行。Azure 在 目标资源 的范围内授予 RBAC 角色分配--授权存在于被访问的对象上,而不是身份上。最终状态相同(此 Pod 可以读取该保管库),但您在不同的地方进行审计:在 AWS 上,读取角色的附加策略;在 Azure 上,列出资源上的角色分配(或身份在不同范围内的分配)。这反映了两个云之间更广泛的 IAM 与 RBAC 差异。
注入的环境变量是什么样的
两个 webhook 都通过环境变量和投射文件向 SDK 提供所需的一切,因此应用程序代码不引用任何秘密,也没有云特定的凭据逻辑:
- AWS:
AWS_ROLE_ARN、AWS_WEB_IDENTITY_TOKEN_FILE、AWS_REGION。默认凭据链会为您调用AssumeRoleWithWebIdentity。 - Azure:
AZURE_CLIENT_ID、AZURE_TENANT_ID、AZURE_FEDERATED_TOKEN_FILE、AZURE_AUTHORITY_HOST。DefaultAzureCredential会为您执行 Entra 交换。
在这两种情况下,投射的令牌文件都是短期的,并由 kubelet 自动轮换;SDK 会重新读取它并透明地刷新云凭据。无需轮换,无需存储,也无需泄露。
适用于两个云的调试清单
当 Pod“无法认证”时,请按顺序检查信任链:
- 集群颁发者是否开启? 确认 OIDC 颁发者 URL 存在(AKS:两个开关都为 true;EKS:IAM OIDC 提供者已注册)。没有颁发者,就没有信任锚点。
- 投射是否发生? Exec 进入 Pod 并检查令牌文件和环境变量(
AWS_WEB_IDENTITY_TOKEN_FILE/AZURE_FEDERATED_TOKEN_FILE)。在 Azure 上缺失通常意味着 Pod 标签缺失或附加组件已关闭。 - 主题是否匹配? 将信任规则的主题与
system:serviceaccount:<actual-namespace>:<actual-sa>进行比较。这是最常见的失败。 - 受众是否匹配?
sts.amazonaws.com/api://AzureADTokenExchange,双方一致。 - 权限? 仅在身份解析后:角色上的 IAM 策略,或目标资源上的 RBAC 角色分配。此处为空结果是授权失败,而不是身份验证失败--这是一个有用的区别,因为它告诉您联邦本身正在工作。
哪些认证会深入探讨此内容
工作负载身份恰好位于 Kubernetes、云 IAM 和 OIDC 联邦的交汇处,因此它出现在三个考试轨道中。如果您已经持有一个云的凭据,那么在另一个云上理解相同的概念将是轻而易举的。
在 AWS 方面:
- AWS Certified Solutions Architect - Associate (SAA-C03) - IAM 角色、EKS 以及身份如何获取云权限。
- AWS Certified Security - Specialty (SCS-C03) - IAM 信任策略、OIDC 联邦和最小权限范围(正是 IRSA 信任链)。
在 Azure 方面:
- Microsoft Azure Administrator Associate (AZ-104) - AKS、托管身份和 RBAC 角色分配。
- Microsoft Azure Security Engineer Associate (AZ-500) - Entra ID、托管身份、联邦凭据和 RBAC 深度。
在 Kubernetes 方面:
- CNCF Certified Kubernetes Administrator (CKA) - ServiceAccounts、投射令牌和 Pod 规范。
- CNCF Certified Kubernetes Security Specialist (CKS) - ServiceAccount 令牌安全、最小权限和减少静态秘密暴露。
总结
IRSA 和 AKS Workload Identity 是同一个概念,只是使用了不同的术语:集群为 ServiceAccount 签署一个短期 OIDC 令牌,云 IdP 信任该颁发者用于一个 (subject, audience),然后 Pod 将令牌交换为限定于单个云身份的凭据--在 AWS 上是 IAM 角色,在 Azure 上是托管身份。一旦理解了信任三角,翻译就变得机械化:角色变为托管身份,信任策略变为联邦凭据,附加策略变为 RBAC 分配,AssumeRoleWithWebIdentity 变为 Entra 令牌交换。牢记三件事--Azure 的两个集群开关、Azure 所需的 Pod 标签,以及镜像拉取完全是另一个身份的事实--那么过去看起来随机的拒绝就会开始被理解为一种连贯、深思熟虑的设计。