GKE Pod 身份:访问 Bucket 与 Secret Manager
验证:静态检查 · 核验于 2026-10-14
- 适用版本
- GKE Autopilot / VPC-native Standard;目标云环境未实测
- 验证范围
- 官方行为核对;本地构建、链接、Bash、YAML 和 Python 语法检查。未创建云资源或部署 Pod。
先注意:同 Project 的跨集群身份共享#
同一 GCP Project 的 GKE 集群共享 Workload Identity Pool。使用本页按名称授权的 Principal 时,相同 namespace + Kubernetes ServiceAccount(KSA)名称会被识别为同一身份,不能默认只授权了某一个集群。
例如,集群 A 和集群 B 都有 orders/reader。给 …/subject/ns/orders/sa/reader 授予 Bucket / Secret 权限时,两个集群中使用该 KSA 的 Pod 都可能获得权限,因为这个标识不包含 cluster 名称。
- 互不信任的团队或需要独立身份池的环境:使用不同 Project 隔离。
- 必须共用 Project:按官方支持的 IAM Conditions 限制具体集群,并检查其他 binding 是否仍授予宽泛权限。
- 不同 namespace/KSA 命名可减少误用,但不能隔离有权创建同名 KSA 的其他集群管理员。官方 identity sameness 说明
WIF(Workload Identity Federation) 让工作负载用已有身份换取短期云凭据,无须保存长期 Service Account key。本文使用 Workload Identity Federation for GKE:Pod 以 KSA 身份访问 Google Cloud API;GKE 管理 identity pool/provider,无须自行搭建集群的外部 OIDC 信任。
推荐做法#
- 业务使用独立 Kubernetes ServiceAccount(KSA);在目标 Bucket / Secret 上授权,不把业务权限授给 Node SA。
- 优先直接授权 KSA 对应的 IAM Principal。遇到 Cloud Run 调用、Storage signed URL 等限制时,再使用 Google IAM Service Account 身份。
- 应用使用 ADC(Application Default Credentials) 自动取凭据,不挂载 SA JSON key,不在 Pod 中执行用户登录。
- Secret 读取固定数字版本,轮换时明确刷新方式;不把 Secret 内容或 token 打到日志。
| 方式 | 权限授予谁 | 需要 Google Service Account 吗 |
|---|---|---|
| 本页主方案:直接授权 | 目标资源上的 KSA Principal | 不需要 |
| 备选:Service Account impersonation | 资源授权给 Google SA;Google SA 再允许 KSA impersonate | 需要,且 IAM binding 与 KSA annotation 都要配置 |
Pod 使用 KSA → GKE metadata server → STS 交换短期凭据 → Google Cloud APISTS(Security Token Service)负责交换短期凭据。KSA 是 Kubernetes 对象,Google SA 是云身份,两者不同;启用 WIF 不自动授予业务权限。身份机制
检查启用状态 · 资源级授权 · Pod 读取示例 · SDK · 排障
检查集群与执行身份#
已有集群、gcloud、kubectl 和可达的控制面;私有 endpoint 的访问方式见 集群入口。以下在获授权的操作终端执行,不是在 Pod 内登录管理员账号。
操作者需要启用 API、创建测试资源及修改其 IAM policy 的权限、Kubernetes namespace/KSA/Pod 管理和 pods/exec 权限;示例 KSA 只获得资源读取权限。
GKE_PROJECT_ID='REPLACE_GKE_PROJECT_ID'
RESOURCE_PROJECT_ID="$GKE_PROJECT_ID" # Bucket / Secret 可属于另一项目
CLUSTER='REPLACE_CLUSTER'
LOCATION='REPLACE_REGION_OR_ZONE'
KUBE_CONTEXT='REPLACE_VERIFIED_KUBE_CONTEXT'
BUCKET_LOCATION='asia-east1' # 按实际数据位置要求调整
gcloud auth list --filter=status:ACTIVE
gcloud container clusters describe "$CLUSTER" \
--project="$GKE_PROJECT_ID" --location="$LOCATION" \
--format='yaml(autopilot.enabled,workloadIdentityConfig,nodePools.name,nodePools.config.workloadMetadataConfig,nodePools.config.oauthScopes)'
kubectl config get-contexts "$KUBE_CONTEXT"- Autopilot:始终启用 WIF。
- Standard:集群的 pool 应为
GKE_PROJECT_ID.svc.id.goog;承载 Pod 的 node pool 应使用GKE_METADATA。
只启用集群 WIF,不会自动更新已有 node pool。 将已有 pool 切到
GKE_METADATA前,先为依赖 Node SA 权限的应用配置自己的身份和授权,否则应用可能失去访问权限。
- Secret Manager 访问还须核对节点的
cloud-platformOAuth scope;授权与 API 网络路径仍分别检查。
Standard 未启用时的配置命令
仅对计划迁移的 Standard 集群/node pool 执行。已有工作负载若依赖 Node SA,切到 GKE metadata server 后可能失去原权限;先完成应用授权,必要时用新 pool 迁移。
gcloud container clusters update "$CLUSTER" \
--project="$GKE_PROJECT_ID" --location="$LOCATION" \
--workload-pool="${GKE_PROJECT_ID}.svc.id.goog"
NODE_POOL='REPLACE_NODE_POOL'
gcloud container node-pools update "$NODE_POOL" \
--project="$GKE_PROJECT_ID" --location="$LOCATION" \
--cluster="$CLUSTER" --workload-metadata=GKE_METADATA这两条分别设置集群身份池和 node pool 凭据来源,不会代替 Bucket / Secret IAM 授权。启用与迁移
示例资源与 KSA#
本例新建专用 namespace、Bucket 和 Secret,只写入合成内容,不依赖 lab.env。下方折叠区是准备步骤;已有资源的操作只需按自己的资源值使用后续授权方式,不执行本例清理。
准备专用 Bucket、Secret 和 KSA
保留本终端变量供后续测试与清理使用。创建报错时先处理原因,不继续使用同名现有资源。
GKE_PROJECT_NUMBER=$(gcloud projects describe "$GKE_PROJECT_ID" --format='value(projectNumber)')
RUN_ID=$(date -u +%Y%m%d%H%M%S)
NAMESPACE="wi-demo-${RUN_ID}"
KSA='reader'
BUCKET="gke-wi-${GKE_PROJECT_NUMBER}-${RUN_ID}"
SECRET="gke-wi-${RUN_ID}"
gcloud services enable iamcredentials.googleapis.com sts.googleapis.com --project="$GKE_PROJECT_ID"
gcloud services enable storage.googleapis.com secretmanager.googleapis.com --project="$RESOURCE_PROJECT_ID"
kubectl --context="$KUBE_CONTEXT" create namespace "$NAMESPACE"
kubectl --context="$KUBE_CONTEXT" -n "$NAMESPACE" create serviceaccount "$KSA"
gcloud storage buckets create "gs://$BUCKET" \
--project="$RESOURCE_PROJECT_ID" --location="$BUCKET_LOCATION" \
--uniform-bucket-level-access --public-access-prevention
printf 'hello from GKE workload identity\n' | gcloud storage cp - "gs://$BUCKET/hello.txt" --project="$RESOURCE_PROJECT_ID"
printf 'synthetic-demo-value' | \
gcloud secrets create "$SECRET" --project="$RESOURCE_PROJECT_ID" \
--replication-policy=automatic --data-file=-新 Secret 的首次版本为 1。本例使用 global Secret resource;regional Secret 的资源名和调用方式需按对应 API 调整。
在 Bucket 和 Secret 上授权#
以下变量使用上一节的示例值;复用现有资源时明确填写 NAMESPACE、KSA、BUCKET、SECRET。
Principal 中的 project number 与 pool ID 使用 GKE 所属 Project;IAM binding 配置在目标资源上。 跨 Project 访问时,不要把 Principal 中的项目改成 Bucket / Secret 所属项目。
Principal 为什么这样写#
Principal 是 IAM 用来识别“给谁授权”的身份标识。 本例按 KSA 名称选身份,Google 规定的格式是:
principal://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/PROJECT_ID.svc.id.goog/subject/ns/NAMESPACE/sa/KSA_NAME| 部分 | 规则与来源 |
|---|---|
principal://iam.googleapis.com |
固定前缀,表示 IAM Principal 标识,不是浏览器访问地址 |
projects/PROJECT_NUMBER |
GKE 所属项目的数字编号,由下方 gcloud projects describe 查询;不是 Project ID |
locations/global |
身份池的固定位置,不填集群所在 Region |
workloadIdentityPools/PROJECT_ID.svc.id.goog |
GKE 托管的项目身份池;.svc.id.goog 是固定后缀,goog 没有拼错 |
subject/ns/NAMESPACE/sa/KSA_NAME |
固定路径关键字加实际 namespace、KSA 名称;ns 和 sa 不替换 |
例如 GKE 项目 ID 为 example-gke、编号为 123456789012,namespace 为 orders,KSA 为 reader,标识就是:
principal://iam.googleapis.com/projects/123456789012/locations/global/workloadIdentityPools/example-gke.svc.id.goog/subject/ns/orders/sa/reader在已启用 WIF 的 GKE 中创建 orders/reader 后,就可按此格式在 IAM 中引用它,不需要再创建一个 Principal 资源。仅创建 namespace 不会创建自定义的 reader KSA。
下方 KSA_PRINCIPAL="..." 只是给 shell 变量赋值;${变量名} 会替换成前面设置的值。真正授予权限的是后面的 add-iam-policy-binding --member="$KSA_PRINCIPAL"。Pod 再通过 serviceAccountName: reader 使用这个身份。官方 Principal 格式
GKE_PROJECT_NUMBER=$(gcloud projects describe "$GKE_PROJECT_ID" --format='value(projectNumber)')
KSA_PRINCIPAL="principal://iam.googleapis.com/projects/${GKE_PROJECT_NUMBER}/locations/global/workloadIdentityPools/${GKE_PROJECT_ID}.svc.id.goog/subject/ns/${NAMESPACE}/sa/${KSA}"
gcloud storage buckets add-iam-policy-binding "gs://$BUCKET" \
--project="$RESOURCE_PROJECT_ID" \
--member="$KSA_PRINCIPAL" --role=roles/storage.objectViewer
gcloud secrets add-iam-policy-binding "$SECRET" \
--project="$RESOURCE_PROJECT_ID" \
--member="$KSA_PRINCIPAL" --role=roles/secretmanager.secretAccessor| Role | 授权范围 | 允许的操作 |
|---|---|---|
roles/storage.objectViewer |
这个 Bucket | 读取、列出对象,不写入或删除 |
roles/secretmanager.secretAccessor |
这个 Secret | 读取其可访问版本的 payload,不管理 Secret |
Bucket 直接授权的前提:启用 uniform bucket-level access。 本例创建命令已启用;复用已有 Bucket 时先检查,不能只添加 IAM binding 就认为可用。
不在 Project 上授这两个 role,可避免把权限扩大到项目全部 Bucket / Secret。首次 IAM 变更需等待传播。Storage roles、Secret Manager IAM
Pod 读取 Bucket 和 Secret#
使用 Cloud CLI 镜像只为验证身份,无须在业务镜像安装 gcloud。把 Pod 的 serviceAccountName 指定为获授权 KSA;省略时会使用 namespace 的 default KSA。
创建测试 Pod:显式 KSA,不挂 key
生成本地清单;环境变量都是资源标识,没有 Secret 值。stable 仅作测试镜像,正式交付固定审核过的 digest。
cat > wi-check.yaml <<EOF_POD
apiVersion: v1
kind: Pod
metadata:
name: wi-check
namespace: ${NAMESPACE}
spec:
serviceAccountName: ${KSA}
restartPolicy: Never
containers:
- name: cli
image: gcr.io/google.com/cloudsdktool/google-cloud-cli:stable
command: ["sleep", "3600"]
env:
- name: RESOURCE_PROJECT_ID
value: "${RESOURCE_PROJECT_ID}"
- name: BUCKET
value: "${BUCKET}"
- name: SECRET
value: "${SECRET}"
resources:
requests:
cpu: "250m"
memory: "512Mi"
ephemeral-storage: "256Mi"
limits:
cpu: "250m"
memory: "512Mi"
ephemeral-storage: "256Mi"
EOF_PODStandard 在上述 Pod spec 下补充以下字段,保证调度到启用 WIF 的节点;Autopilot 不添加该 selector:
spec:
nodeSelector:
iam.gke.io/gke-metadata-server-enabled: "true"确认清单后创建 Pod:
kubectl --context="$KUBE_CONTEXT" apply -f wi-check.yaml
kubectl --context="$KUBE_CONTEXT" -n "$NAMESPACE" wait --for=condition=Ready pod/wi-check --timeout=600sReady 只证明容器启动,下面两个实际 API 调用才验证 IAM。首次节点供应或镜像拉取失败时先看 Pod Events,不把它当授权拒绝。
读取 Bucket 对象,预期输出合成文本 hello from GKE workload identity:
kubectl --context="$KUBE_CONTEXT" -n "$NAMESPACE" exec wi-check -- \
gcloud storage cat "gs://$BUCKET/hello.txt" --project="$RESOURCE_PROJECT_ID"读取 Secret version 1,只输出成功标记,不打印 payload;临时文件由退出时的 trap 清理:
kubectl --context="$KUBE_CONTEXT" -n "$NAMESPACE" exec wi-check -- sh -c '
set -eu
umask 077
trap "rm -f /tmp/wi-secret" EXIT
gcloud secrets versions access 1 --project="$RESOURCE_PROJECT_ID" --secret="$SECRET" --out-file=/tmp/wi-secret
test -s /tmp/wi-secret
echo "PASS: Secret version 1 readable"
'Pod 内不执行 gcloud auth login,也不复制本机 ADC。读取来自 GKE metadata server 提供的短期身份;Secret 的 --out-file 将原始字节写入文件。Secret version 读取
应用代码使用 ADC#
应用代码如何读取:Python ADC 示例
在业务镜像构建阶段安装 google-cloud-storage、google-cloud-secret-manager 并锁定依赖;Pod 使用同一 KSA 和上述三个环境变量。下面代码通过 ADC 获取凭据,不传 credentials= 或 key 文件。
import os
from google.cloud import storage, secretmanager
project = os.environ["RESOURCE_PROJECT_ID"]
blob = storage.Client(project=project).bucket(os.environ["BUCKET"]).blob("hello.txt")
object_bytes = blob.download_as_bytes()
client = secretmanager.SecretManagerServiceClient()
version = f"projects/{project}/secrets/{os.environ['SECRET']}/versions/1"
secret_bytes = client.access_secret_version(
request={"name": version}
).payload.data
# 将内容交给业务逻辑;不打印 Secret、token 或对象的敏感内容。
if not object_bytes or not secret_bytes:
raise RuntimeError("Unexpected empty demo payload")
print("PASS: Bucket object and Secret version readable")代码放在你的应用容器中运行;前面的 CLI 测试镜像不保证已安装这些 Python libraries。Storage client、Secret Manager client
备选:KSA impersonate Google Service Account#
本页读取 Bucket 对象和 Secret version,优先直接授权 KSA,不需要额外创建 Google SA。 需要 Google SA 身份的具体场景如下;限制针对操作,不代表整个产品都不支持 WIF。
| 具体操作 | 限制 / 选择 | 需要补充的配置 |
|---|---|---|
| 调用需要 IAM authentication 的 Cloud Run 服务 URL | 不支持直接以 federated identity 调用;使用 Google SA 身份 | 场景:Pod 请求你部署在 Cloud Run 上的业务 HTTP API,例如订单 Pod 调用报表服务的 POST /reports;这里不是创建或部署 Cloud Run 的管理 API。配置:服务启用 Invoker IAM check 且未允许匿名访问时,给调用方 Google SA 授予目标服务的 roles/run.invoker。Pod 以该 SA 身份获取 ID token,通过 Authorization: Bearer ID_TOKEN 发送;audience 为服务基础 URL(不加 /reports)或已配置的 custom audience,不能拿普通 access token 代替。网络路径仍须符合 ingress。服务间调用 |
| 生成 Cloud Storage signed URL | KSA 的直接对象读取权限不提供签名能力;使用 Google SA 作为 signer | signer 必须有 URL 对应的对象权限;调用者还须能对 signer 执行 iam.serviceAccounts.signBlob。下方的 roles/iam.workloadIdentityUser 不包含该签名权限 |
| 访问仍使用 fine-grained ACL 的 Bucket | 直接 federated access 要求 Bucket 启用 uniform bucket-level access | 能迁移时先评估现有 ACL 再启用;必须保留 ACL 时使用 Google SA 身份并配置其对象访问权限 |
| 现有应用明确依赖某个 Google SA 身份 | 这是兼容现有身份设计的选择,不是 WIF API 限制 | 将 KSA 关联到该 Google SA,核对其已有权限,避免多个应用共用身份后权限扩大 |
以上限制核对于 2026-10-15:官方支持与限制、Cloud Run 调用认证、Storage signed URL 签名权限。查支持表时区分 Workload 与 Workforce Identity Federation;后者面向人员登录,不能把它的 Console 限制套到 Pod。
下方命令只演示切换身份后读取 Bucket / Secret;Cloud Run 的 ID token 和 signed URL 的签名配置不包含在这组命令中。
它是资源级直接授权的替代方式。 验证此方案时,不要同时保留 KSA 的直接授权,否则无法确认访问成功来自哪一种授权。
Google SA 授权与 KSA annotation
以下复用示例资源,要求没有执行上面的直接授权;若已经执行,先从两个资源撤销 KSA Principal 的直接 binding,再切换。本方式仍不创建 SA key。
GSA_NAME="wi-demo-${RUN_ID}"
GSA_EMAIL="${GSA_NAME}@${GKE_PROJECT_ID}.iam.gserviceaccount.com"
gcloud iam service-accounts create "$GSA_NAME" --project="$GKE_PROJECT_ID"
gcloud storage buckets add-iam-policy-binding "gs://$BUCKET" \
--project="$RESOURCE_PROJECT_ID" \
--member="serviceAccount:$GSA_EMAIL" --role=roles/storage.objectViewer
gcloud secrets add-iam-policy-binding "$SECRET" \
--project="$RESOURCE_PROJECT_ID" \
--member="serviceAccount:$GSA_EMAIL" --role=roles/secretmanager.secretAccessor
gcloud iam service-accounts add-iam-policy-binding "$GSA_EMAIL" \
--project="$GKE_PROJECT_ID" --role=roles/iam.workloadIdentityUser \
--member="serviceAccount:${GKE_PROJECT_ID}.svc.id.goog[${NAMESPACE}/${KSA}]"
# 在指定集群/namespace 的 KSA 上添加 annotation,指定使用哪个 Google SA 获取凭据;--overwrite 允许替换已有值,此命令本身不授予 IAM 权限。
kubectl --context="$KUBE_CONTEXT" -n "$NAMESPACE" annotate serviceaccount "$KSA" \
"iam.gke.io/gcp-service-account=${GSA_EMAIL}" --overwrite本例的 KSA → Google SA 关联方式,IAM binding 与 annotation 缺一不可;Google SA 还必须获得目标资源权限。 binding 允许 KSA impersonate,annotation 指定使用哪个 Google SA。
重建测试 Pod,再重复两个 API 读取,避免沿用旧凭据状态。官方替代方案
验证权限边界与清理#
主方案读取成功后,单独撤销 Secret binding,再测试同一 Pod。Bucket 应仍可读取;Secret 应因 secretmanager.versions.access 权限不足被拒绝。这样能区分两个资源的授权。
直接授权方案:撤权验证与专用资源清理
以下 Secret 读取预期失败,单独执行并核对错误。网络超时、无法获取 token、Secret/version 不存在都不算撤权通过。
gcloud secrets remove-iam-policy-binding "$SECRET" \
--project="$RESOURCE_PROJECT_ID" \
--member="$KSA_PRINCIPAL" --role=roles/secretmanager.secretAccessor
kubectl --context="$KUBE_CONTEXT" -n "$NAMESPACE" exec wi-check -- \
gcloud storage cat "gs://$BUCKET/hello.txt" --project="$RESOURCE_PROJECT_ID"
kubectl --context="$KUBE_CONTEXT" -n "$NAMESPACE" exec wi-check -- \
gcloud secrets versions access 1 \
--project="$RESOURCE_PROJECT_ID" --secret="$SECRET" >/dev/null等待 IAM 传播后再判断;仍成功时查其他继承 binding、KSA annotation 或显式凭据。备用 Google SA 方案需撤销 Google SA 的 Secret binding,而非直接 KSA binding。后续仍需读取 Secret 时,重新执行对应方案的资源授权。
下面会删除整个测试 namespace、Bucket 和 Secret,只适用于本页新建的专用资源。 复用现有资源时,仅移除本次测试添加的 binding,不执行下面的资源删除命令。
kubectl --context="$KUBE_CONTEXT" delete namespace "$NAMESPACE"
gcloud storage rm --recursive "gs://$BUCKET" --project="$RESOURCE_PROJECT_ID"
gcloud secrets delete "$SECRET" --project="$RESOURCE_PROJECT_ID"
# 仅执行过备选方案且该 SA 是本例专用时:
# gcloud iam service-accounts delete "$GSA_EMAIL" --project="$GKE_PROJECT_ID"Bucket 删除仍受 soft delete / retention 设置影响,见 Storage 清理。不删除集群、Node SA 或共享 API。
常见坑与排障#
| 表现 | 优先检查 |
|---|---|
| Pod 使用了错误身份 | serviceAccountName、namespace、KSA annotation;不要只看 Node SA |
| 403 / PERMISSION_DENIED | 资源路径、role binding、Principal 内的 GKE project number / pool、IAM 传播及组织限制 |
| 能拿到 token,但 Secret 失败 | Secret version 是否 enabled、资源级 Accessor、Standard 节点 cloud-platform scope |
| metadata / token 请求失败 | 集群与 node pool 的 WIF、GKE metadata server、NetworkPolicy;Pod 启动初期可有短暂未就绪 |
| API 超时 | DNS、Google API 出口路径、firewall/NetworkPolicy/VPC Service Controls;IAM 不建立网络路径 |
| ImagePullBackOff | 查 Node SA 的 Artifact Registry Reader 与仓库路径,业务 KSA 授权不解决镜像拉取 |
| 另一集群同名 KSA 也能访问 | 先检查页首的 跨集群身份共享及其他继承授权 |
普通
hostNetwork: truePod 的 metadata 请求走 Compute Engine metadata server,不使用本页的 KSA WIF 路径。 不要把它的访问成功当作 KSA 授权生效;先核对实际 Node SA 身份和权限。
特例:Standard GKE 1.33.3-gke.1226000 起,Cloud Storage FUSE CSI driver 支持为 hostNetwork Pod 配置使用 KSA;这不是所有 hostNetwork 应用自动获得 WIF。不要挂载用户 ADC 或通过 GOOGLE_APPLICATION_CREDENTIALS 指向长期 key,否则也会改变实际凭据来源。WIF 限制与特例