Azure 上的服务需要访问 Kong,又不想在容器里保存 RSA 私钥,可以采用这样的链路:Azure Managed Identity 获取 Entra 访问令牌,Google Workload Identity Federation(WIF,工作负载身份联合)验证外部身份,再由 Google 为指定 Service Account 签发 ID Token。
这个方案成立。但它适合“需要把调用方身份统一到 Google”的场景,不能笼统称为所有跨云架构的最优解。如果 Kong 本来就支持验证 Entra 访问令牌,直接信任 Entra 通常更简单。
真正容易出错的地方,是把三种 audience 当成一个值,把不同 Azure 运行环境当成同一种元数据接口,以及把“验签成功”当成“有权调用”。
先分清三张票
下图沿蓝色箭头向下阅读。每次交换都改变令牌的用途,最后交给 Kong 的才是 Google ID Token。图中表示凭据转换顺序,实际请求均由业务服务发起。
Entra 访问令牌先经过 STS,再用于请求 Google ID Token,Kong 只接收最后一张票。
第一张是 Entra access token。服务通过托管身份取得它;其 aud 指向你在 Entra 注册的目标资源应用。它不是随意写一个 Kong 地址就能得到的令牌。
第二张是 Google STS federated access token。服务把第一张票提交给 STS。这里请求参数中的 audience 是 Google WIF Provider 的完整资源名,通常以 //iam.googleapis.com/projects/ 开头。它告诉 STS 使用哪一组信任配置。
第三张是 Google OIDC ID Token。服务使用 STS 令牌调用 IAM Credentials API 的 generateIdToken,将 audience 设置为 Kong 约定的标识,例如 https://kong.example.com。这个字段是接收方标识,不意味着 Google 会访问该 URL。
Google 管理签名私钥,业务服务只接触短期令牌。Keyless 消除的是应用保存长期私钥的需求;短期 Bearer Token 仍是凭据,泄露后可能在有效期内被使用。
Azure 侧:不要混淆两个应用身份
需要区分调用者的托管身份,以及作为令牌接收方的 Entra 资源应用。后者需要注册应用、创建对应的服务主体,并设置 Application ID URI,例如 api://<RESOURCE_APP_CLIENT_ID>。如果资源应用要求分配权限,还需给调用者完成相应分配。
api://AzureADTokenExchange 常见于 AKS Workload Identity 的联合凭据配置:它是 Kubernetes Service Account 令牌交给 Entra 换票时的 audience。它不是所有托管身份访问令牌的默认 audience,不能直接照搬到 Google Provider。
还要区分三种身份字段:
Object ID / Principal ID:托管身份在租户中的服务主体对象标识。
Client ID:应用标识,不可随意替代 Object ID;它常用于选择某个 User-Assigned Managed Identity。
sub:当前令牌声明的主体。Google 的 Azure VM 指南说明,托管身份访问令牌的sub包含该托管身份的 Object ID。因此原方案使用google.subject=assertion.sub并不天然错误,错误在于认为 Object ID 和 Client ID 可以互换。
配置前,在本地检查实际令牌中的 iss、aud、sub、oid 和 ver。解码只用于检查字段,不等于验证签名,也不要把完整令牌粘贴到公共调试网站。
下面选用 google.subject=assertion.oid,明确按 Object ID 绑定。前提是目标令牌确实包含预期的 oid。如果沿用 assertion.sub,IAM principal 的最后一段必须使用实际 sub。
一个范围明确的配置例子
以下以 Azure VM + 单个托管身份 + Entra v1 访问令牌为例。命令是配置模板,需替换占位值;它们经过文档核对,未在真实租户执行。
先为 VM 启用或挂载托管身份,完成 Entra 资源应用配置,再准备变量:
PROJECT_ID="your-gcp-project"
PROJECT_NUMBER="123456789012"
TENANT_ID="your-entra-tenant-id"
MI_OBJECT_ID="your-managed-identity-object-id"
APP_ID_URI="api://your-resource-app-client-id"
AZURE_TOKEN_AUD="$APP_ID_URI"
SA_EMAIL="kong-client-sa@${PROJECT_ID}.iam.gserviceaccount.com"
PROVIDER="projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/azure-pool/providers/azure-provider"
KONG_AUDIENCE="https://kong.example.com"
AZURE_TOKEN_AUD 必须与实际令牌的 aud 一致。v1 令牌可能使用资源 URI 或客户端 ID;v2 令牌的 aud 通常为资源 API 的 Client ID。升级版本不能只替换 issuer,还要重新检查 audience。
在 Google 项目启用 API,创建专用身份池和 Provider:
gcloud services enable iam.googleapis.com sts.googleapis.com \
iamcredentials.googleapis.com --project="$PROJECT_ID"
gcloud iam workload-identity-pools create azure-pool \
--project="$PROJECT_ID" --location=global \
--display-name="Azure Kong clients"
gcloud iam workload-identity-pools providers create-oidc azure-provider \
--project="$PROJECT_ID" --location=global \
--workload-identity-pool=azure-pool \
--issuer-uri="https://sts.windows.net/${TENANT_ID}/" \
--allowed-audiences="$AZURE_TOKEN_AUD" \
--attribute-mapping="google.subject=assertion.oid" \
--attribute-condition="assertion.tid == 'TENANT_ID' && assertion.oid == '{MI_OBJECT_ID}'"
issuer 必须与实际 iss 匹配,包括路径和尾部斜杠。v2 issuer 通常为 https://login.microsoftonline.com/<TENANT_ID>/v2.0。不要使用不限定租户的地址扩大信任范围。
然后创建专用 Service Account,并在该账户上绑定权限:
gcloud iam service-accounts create kong-client-sa \
--project="$PROJECT_ID"
gcloud iam service-accounts add-iam-policy-binding "$SA_EMAIL" \
--project="$PROJECT_ID" \
--role="roles/iam.workloadIdentityUser" \
--member="principal://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/azure-pool/subject/{MI_OBJECT_ID}"
roles/iam.workloadIdentityUser 已包含 iam.serviceAccounts.getOpenIdToken,因此不需要再叠加 roles/iam.serviceAccountOpenIdTokenCreator。如果整条实现只直接生成 ID Token,也可以评估仅授予后者;但一旦 SDK 中间调用 generateAccessToken,就还需要相应权限。上面的例子使用常见 WIF 角色,不把它描述为只含一个权限的最小角色。
不要为了修复 403 直接授予项目级 Owner 或宽泛 Token Creator。先确认是谁调用哪个 API,以及权限绑定是否落在正确的 Service Account 上。
同一个 Pool 内的主体命名也需要统一管理。若以后加入其他 Provider,避免把不同来源映射成相同的 google.subject,导致它们共享同一 IAM principal。
显式走完换票流程
VM 可以通过 IMDS 获取 Entra 令牌。下面的 Python 示例把三次调用写出来,方便理解和排错;依赖 requests,变量从环境读取。前面的 shell 变量需要先 export。示例不打印令牌,也不实现生产缓存。
import os
import requests
provider = os.environ["PROVIDER"]
sa_email = os.environ["SA_EMAIL"]
audience = os.environ["KONG_AUDIENCE"]
# Azure VM IMDS: select the intended user-assigned identity if needed.
params = {
"api-version": "2018-02-01",
"resource": os.environ["APP_ID_URI"],
}
if os.environ.get("MI_CLIENT_ID"):
params["client_id"] = os.environ["MI_CLIENT_ID"]
metadata = requests.Session()
metadata.trust_env = False
response = metadata.get(
"http://169.254.169.254/metadata/identity/oauth2/token",
headers={"Metadata": "true"}, params=params, timeout=10,
)
response.raise_for_status()
azure_token = response.json()["access_token"]
response = requests.post(
"https://sts.googleapis.com/v1/token",
data={
"audience": f"//iam.googleapis.com/{provider}",
"grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
"requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
"scope": "https://www.googleapis.com/auth/cloud-platform",
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
"subject_token": azure_token,
}, timeout=20,
)
response.raise_for_status()
sts_token = response.json()["access_token"]
response = requests.post(
"https://iamcredentials.googleapis.com/v1/projects/-/"
f"serviceAccounts/{sa_email}:generateIdToken",
headers={"Authorization": f"Bearer {sts_token}"},
json={"audience": audience, "includeEmail": True},
timeout=20,
)
response.raise_for_status()
id_token = response.json()["token"]
response = requests.get(
os.environ["KONG_REQUEST_URL"],
headers={"Authorization": f"Bearer {id_token}"},
timeout=20,
)
response.raise_for_status()
KONG_REQUEST_URL 是实际业务接口地址,可能包含路径;它与约定的 KONG_AUDIENCE 不必逐字相同。请求地址必须由可信配置提供。
生产实现需要根据到期时间缓存令牌,提前刷新并加入随机抖动;并发请求应合并刷新操作。Azure 取票、STS 和 IAM Credentials 分别处理超时、限流与重试。不要在每个业务请求之前重新执行整条链路,也不要在异常日志中记录 Authorization 头或令牌响应。
配置文件没有私钥,但不代表 SDK 自动包办
在 Azure VM 上,可以生成 external account 配置:
gcloud iam workload-identity-pools create-cred-config "$PROVIDER" \
--service-account="$SA_EMAIL" \
--azure --app-id-uri="$APP_ID_URI" \
--output-file=gcp-wif-config.json
这个文件描述凭据来源和换票地址,不包含私钥。可以作为普通部署配置管理,但仍需防止被篡改,因为客户端会依据其中的地址获取凭据。
设置 GOOGLE_APPLICATION_CREDENTIALS 后,支持 external account 的客户端可以加载它。然而,“能获取 Google access token”和“任意语言的 ID Token helper 都能自动生成目标 audience 的 ID Token”是两件事。后者取决于库、版本和调用方式,不能只凭一个环境变量承诺全自动完成。
先用上面的显式流程验证,再选择支持相应身份类型的 SDK,分别确认外部凭据刷新、服务账户冒充和 ID Token 刷新行为。
--azure 生成的是面向 Azure IMDS 的配置,也不能直接推广到所有 Azure 产品:
Azure VM:使用 IMDS;多个 User-Assigned Identity 时,要明确选择身份。
App Service:使用该服务提供的身份端点和请求头,常见环境变量为
IDENTITY_ENDPOINT、IDENTITY_HEADER,或交给 Azure Identity SDK 处理。AKS:可以先用投射的 Kubernetes Service Account Token 通过 Entra Workload Identity 换取 Entra 访问令牌,再接 Google;也可以让 Google WIF 直接信任 AKS 的 OIDC issuer,省去 Entra 这一跳。后一种方案需要重新配置 issuer、subject 和 audience,不能复用 VM 配置冒充完成。
Kong 的任务:验签之后,还要授权
本流程通过 Google 签发 RS256 签名的 ID Token,签名私钥由 Google 管理。Kong 需要从可信的 OIDC discovery / JWKS 获取公钥,按 kid 选钥,并处理缓存与密钥轮转。
下图从上到下表示必须同时通过的检查。注意最下方橙色关卡:即使前面都通过,陌生 Service Account 仍应被拒绝。
有效签名、正确接收方和允许的服务账户共同决定请求能否进入业务接口。
至少验证以下内容:
允许的签名算法与有效签名,不根据请求任意接受算法。
精确的
iss、约定的aud,以及exp等时间约束。允许的 Service Account
sub,以及它有权访问的路由。
generateIdToken 的 Google ID Token 以服务账户的唯一数字 ID 作为 sub,不是 Azure Object ID。多个 Azure 工作负载共用一个 Google Service Account 时,Kong 看到的是同一个 Google 主体。需要不同权限或审计边界的工作负载,应使用不同 Service Account 或保留独立、可信的身份关联记录。
尤其不能只检查 iss=https://accounts.google.com 和正确的 aud。其他 Google 主体也可能获得面向相同 audience 的令牌,授权必须进一步收窄。
Kong 的插件选择也有区别。OpenID Connect 插件在官方文档中标为 Enterprise only,支持 discovery、JWKS 等能力,使用前需确认版本、许可及部署模式;普通 JWT 插件主要围绕 Consumer 的 JWT credential 和配置的公钥验证令牌,不能把它当成开箱即用的自动 JWKS 轮转方案。普通 JWT 插件的 claims_to_verify 只提供 exp、nbf 检查,不能用它代替 audience 校验。具体声明校验和主体白名单要在所用插件或可信授权层落实。
上线前至少测试:正确身份成功、错误 audience 失败、未授权 Service Account 失败、过期令牌失败,以及密钥更新后的重新取钥行为。还要检查请求头无法伪造上游可信身份。
什么时候值得增加 Google 这一跳
如果 Kong 已统一使用 Google 服务账户作为机器身份,这条链路可以保留现有授权模型,让 Azure 服务无须保存长期密钥。代价是刷新期间依赖 Azure、Google STS 和 IAM Credentials 的可用性,并增加配置与排障工作。
如果调用方主要在 Azure,网关也能验证 Entra 访问令牌,可以直接为业务 API 注册资源应用,让 Kong 验证面向该 API 的 token,并按调用者身份、应用角色或明确的授权规则放行。不能拿一张面向 Microsoft Graph 的令牌直接访问自建 API。
因此,我会先问:网关究竟需要统一的 Google 服务账户身份,还是只需要可信的机器调用者?前者适合 WIF 桥接,后者通常可以从直接信任 Entra 开始。无论选哪条路径,都应把长期私钥移出应用,把身份映射、接收方校验和路由授权配置清楚。

