Loading...
Loading...
Diagnoses Workload Identity Federation for GKE authentication failures for Pods (403 "iam.serviceAccounts.getAccessToken" / permission denied, "could not find default credentials", or GKE metadata server unreachable) by verifying cluster and node-pool Workload Identity configuration, the Kubernetes ServiceAccount (KSA) to IAM binding (direct principal binding and legacy Google ServiceAccount impersonation), target-resource IAM roles, and gke-metadata-server health. Use when a Pod cannot authenticate to Google Cloud APIs even though Workload Identity is expected to be in effect. Don't use for in-cluster Kubernetes RBAC errors (API-server authorization), general workload crashes (use gke-workload-troubleshooting), or Workload Identity setup and hardening (use gke-workload-security).
npx skill4agent add google/skills gke-workload-identityHTTP/403 ... Permission 'iam.serviceAccounts.getAccessToken' denied on resourcegoogle.auth.exceptions ... could not find default credentialsComputeEngineCredentials cannot find the metadata servergcloudPROJECT_IDPROJECT_NUMBERCLUSTERLOCATIONNAMESPACE# Resolve the project number (used in the direct-binding principal identifier).
gcloud projects describe "{PROJECT_ID}" --format="value(projectNumber)"
# Confirm which KSA the workload runs as.
kubectl get pod "{pod_name}" -n "{namespace}" \
-o jsonpath='{.spec.serviceAccountName}'
# Identify the node the Pod runs on, then the node pool that node belongs to
# (Step 2 checks the node pool's Workload Identity mode).
NODE=$(kubectl get pod "{pod_name}" -n "{namespace}" -o jsonpath='{.spec.nodeName}')
kubectl get node "$NODE" \
-o jsonpath='{.metadata.labels.cloud\.google\.com/gke-nodepool}'gke-metadata-serverkubectl logs "{pod_name}" -n "{namespace}" --all-containers --prefix
kubectl describe pod "{pod_name}" -n "{namespace}"https://console.cloud.google.com/logs/query;query={URL_ENCODED_QUERY};timeRange={start}%2F{end}?project={project_id}/%2F;duration=PT1Hresource.type="k8s_container"
resource.labels.namespace_name="{namespace}"
resource.labels.pod_name="{pod_name}"
severity>=WARNINGiam.serviceAccounts.getAccessTokenHTTP/403iam.gke.io/gcp-service-account403 PERMISSION_DENIEDgetAccessTokencould not find default credentialscannot find the metadata serverGCE_METADATAGKE_METADATA# Cluster must have a workload identity pool (PROJECT_ID.svc.id.goog).
gcloud container clusters describe "{cluster}" --location "{location}" \
--format="value(workloadIdentityConfig.workloadPool)"
# Node pool must have workloadMetadataConfig.mode = GKE_METADATA.
gcloud container node-pools describe "{node_pool}" --cluster "{cluster}" \
--location "{location}" \
--format="value(config.workloadMetadataConfig.mode)"GCE_METADATA--workload-metadata=GKE_METADATAworkloadMetadataConfig.modegcloud container node-pools update "{node_pool}" --cluster "{cluster}" \
--location "{location}" --workload-metadata=GKE_METADATAHow the two models fail differently: with direct binding the KSA principal accesses resources directly, so failures show up as a plainon the target API (fix in Step 4). A403 PERMISSION_DENIEDinstead means an impersonation attempt — intended under the legacy path, or unintended if a leftover403 iam.serviceAccounts.getAccessTokenannotation remains on a KSA that was meant to use direct binding.iam.gke.io/gcp-service-account
principal://iam.googleapis.com/projects/{PROJECT_NUMBER}/locations/global/workloadIdentityPools/{PROJECT_ID}.svc.id.goog/subject/ns/{NAMESPACE}/sa/{KSA_NAME}roles/iam.workloadIdentityUser# The KSA annotation must reference the intended GSA.
kubectl get serviceaccount "{ksa_name}" -n "{namespace}" \
-o jsonpath='{.metadata.annotations.iam\.gke\.io/gcp-service-account}'
# The GSA's IAM policy must bind the KSA member to workloadIdentityUser.
gcloud iam service-accounts get-iam-policy \
"{gsa_name}@{project_id}.iam.gserviceaccount.com" \
--format=json
# Expect a binding: role roles/iam.workloadIdentityUser,
# member serviceAccount:{PROJECT_ID}.svc.id.goog[{NAMESPACE}/{KSA_NAME}]If the annotation is present but the binding is missing, the binding was likely removed — check theaudit logs around the failure time to find the responsible principal.setIamPolicy
roles/storage.objectViewer# Direct binding: search for the KSA principal's bindings across the project.
gcloud asset search-all-iam-policies \
--scope="projects/{PROJECT_ID}" \
--query='policy:"{PROJECT_ID}.svc.id.goog"'
# Legacy: search for the GSA's bindings on the target resource's project.
gcloud asset search-all-iam-policies \
--scope="projects/{TARGET_PROJECT_ID}" \
--query='policy:"{gsa_name}@{project_id}.iam.gserviceaccount.com"'add-iam-policy-bindinggcloud projects add-iam-policy-binding "{TARGET_PROJECT_ID}" \
--member="principal://iam.googleapis.com/projects/{PROJECT_NUMBER}/locations/global/workloadIdentityPools/{PROJECT_ID}.svc.id.goog/subject/ns/{NAMESPACE}/sa/{KSA_NAME}" \
--role="{REQUIRED_ROLE}" # e.g. roles/storage.objectViewercould not find the metadata servercannot find the metadata servergke-metadata-serverkube-system169.254.169.254# The DaemonSet Pods must be healthy on the workload's node.
kubectl get pods -n kube-system -l k8s-app=gke-metadata-server -o wide
# A gke-metadata-server Pod can be OOM-evicted when the cluster has many
# (>3,000) Kubernetes service accounts. Look for CrashLoopBackOff, then confirm
# the eviction was OOMKilled.
kubectl get pods -n kube-system | grep CrashLoopBackOff
kubectl describe pod {gke_metadata_server_pod} --namespace=kube-system | grep OOMKilledgke-metadata-serverresource.type="k8s_container"
resource.labels.namespace_name="kube-system"
labels."k8s-pod/k8s-app"="gke-metadata-server"
severity>=WARNING# Token endpoint via the hardcoded metadata IP (a healthy path returns a token).
kubectl exec {pod_name} -n {namespace} -- \
curl -sS -H 'Metadata-Flavor: Google' \
'http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token'initContainermetadata.google.internalcannot find the metadata serverGCE_METADATA_HOST=169.254.169.254169.254.169.254/3280169.254.169.252/32988sts.googleapis.com:443504 Gateway Timeoutcontext deadline exceededgke-metadata-serverApplies only to the legacy GSA impersonation setup (a KSA annotated with), not to direct principal binding.iam.gke.io/gcp-service-account
ERROR: Invalid form of account ID SERVICEACCOUNT_NAME.svc.id.goog.
Should be [Gaia ID | Email | Unique ID | ] of the accountSERVICEACCOUNT_NAME.svc.id.googiam.gke.io/return-principal-id-as-email="true"kubectl annotate serviceaccount "{ksa_name}" --namespace "{namespace}" \
iam.gke.io/return-principal-id-as-email="true"setIamPolicyroles/iam.workloadIdentityUseriam.serviceAccounts.getAccessTokeniam.gke.io/return-principal-id-as-emailgke-metadata-server--workload-metadata=GKE_METADATAprincipal://...serviceAccount:PROJECT_ID.svc.id.goog[NAMESPACE/KSA_NAME]gcloud asset search-all-iam-policies