Configuration, Auth & IAM
.mdVerified against Google Cloud SDK 553.0.0, flags verified via `gcloud auth login --help`, `gcloud iam · official docs
What it is and where it fits 🎯#
gcloud is Google Cloud's official command-line tool — the scriptable equivalent of everything the Google
Cloud Console can do, and the same role aws and az play for their respective clouds. This page covers
authentication, project/configuration switching, and IAM: service accounts, role bindings, and — the
current recommended pattern for CI/CD — Workload Identity Federation. The companion pages cover Compute
Engine/GKE (02) and Cloud Storage/networking (03).
GCP's identity model has a distinctive shape worth internalizing early: almost everything is scoped to a
project (GCP's rough equivalent of an AWS account or an Azure resource group, though with its own
independent billing and IAM), projects nest under folders, and folders nest under an organization.
A service account is itself a first-class IAM identity with its own email-shaped identifier
(name@project-id.iam.gserviceaccount.com) — closer to an IAM role in AWS than to an Azure managed
identity, and it's the identity workloads and automation authenticate as.
Installation#
# Debian/Ubuntu — official apt repo
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" \
| sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add -
sudo apt-get update && sudo apt-get install google-cloud-cli
# macOS
brew install --cask google-cloud-sdk
# Any platform — interactive installer
curl https://sdk.cloud.google.com | bash
gcloud version # confirm install + list installed components
gcloud components update # upgrade gcloud and every installed component together
gcloud init # interactive first-time setup: login, project, default region/zoneCore concepts: the resource + identity hierarchy#
IAM policy bindings can be set at any level of this hierarchy — a binding on a folder or organization is inherited by every project underneath it, which is powerful for org-wide baselines and a common source of "why does this identity have access I never granted at the project level" when someone forgets to check higher up the tree.
Authenticating#
gcloud auth login # interactive browser login for a user account
gcloud auth login --no-launch-browser # for headless/remote shells
gcloud auth activate-service-account --key-file=key.json # authenticate as a service account (key file)
gcloud auth list # show all authenticated accounts + which is active
gcloud auth revoke my-account@example.com
gcloud auth application-default login # separate credential set for client libraries/SDKs
gcloud auth application-default print-access-tokenImportant
gcloud auth login and gcloud auth application-default login set up two different, independent
credential stores. The first is what gcloud CLI commands themselves use; the second — Application
Default Credentials (ADC) — is what client libraries (the Python/Go/Node SDKs, Terraform's google
provider) look for when running locally. Running only one of the two is the most common "gcloud works but
my script/Terraform can't authenticate" (or vice versa) confusion for anyone new to GCP.
Configurations — named sets of gcloud settings#
gcloud config configurations create staging # a separate named config (project, account, region, etc.)
gcloud config configurations activate staging # switch to it
gcloud config configurations list
gcloud config configurations describe stagingA "configuration" in gcloud bundles project + account + default region/zone together — the equivalent of an
AWS CLI named profile. Switching configurations is the standard way to work across multiple GCP
projects/accounts from one terminal without re-typing --project on every command.
Setting individual config values#
gcloud config set project my-project-id
gcloud config set compute/region us-central1
gcloud config set compute/zone us-central1-a
gcloud config list # show current effective config
gcloud config unset compute/zoneProjects#
gcloud projects list
gcloud projects describe my-project-id
gcloud projects create my-new-project --folder=<folder-id> # or --organization=<org-id>
gcloud projects add-iam-policy-binding my-project-id \
--member="user:jane@example.com" --role="roles/viewer"
gcloud projects get-iam-policy my-project-id # see the full policy, all bindings at onceService accounts#
gcloud iam service-accounts create my-service --display-name="My Service"
gcloud iam service-accounts list
gcloud iam service-accounts describe my-service@my-project-id.iam.gserviceaccount.com
gcloud iam service-accounts keys create key.json --iam-account=my-service@my-project-id.iam.gserviceaccount.com
gcloud iam service-accounts keys list --iam-account=my-service@my-project-id.iam.gserviceaccount.com
gcloud iam service-accounts disable my-service@my-project-id.iam.gserviceaccount.com # freeze without deletingA downloaded service-account key file is a long-lived credential — treat it like a password. For workloads running on GCP compute (GCE/GKE/Cloud Run), prefer attaching the service account directly to the resource instead of distributing key files, so there's no static secret to leak or rotate. For anything running outside GCP (GitHub Actions, another cloud), prefer Workload Identity Federation over a downloaded key — see below.
Impersonating a service account (instead of downloading its key)#
gcloud auth print-access-token --impersonate-service-account=my-service@my-project-id.iam.gserviceaccount.com
gcloud storage ls --impersonate-service-account=my-service@my-project-id.iam.gserviceaccount.com gs://my-bucketRequires the roles/iam.serviceAccountTokenCreator role on the target service account, granted to your
identity — impersonation lets you act as a service account temporarily using your own already-authenticated
session, without ever generating or holding a key file for it. This is the standard way to test "does this
service account actually have the access it needs" without handing out its credentials.
Workload Identity Federation: keyless auth for external systems#
Workload Identity Federation (WIF) lets an external identity — a GitHub Actions workflow, a workload running
on AWS/Azure, a Kubernetes service account — exchange its own short-lived token for a GCP access token,
without ever storing a service-account key. This is the GCP equivalent of the federated-credential pattern
covered on this site's Azure CLI page 01.
# 1. Create a workload identity pool — a container for external identity providers
gcloud iam workload-identity-pools create github-pool \
--location=global --display-name="GitHub Actions pool"
# 2. Create an OIDC provider inside it, trusting GitHub's OIDC issuer
gcloud iam workload-identity-pools providers create-oidc github-provider \
--location=global --workload-identity-pool=github-pool \
--issuer-uri="https://token.actions.githubusercontent.com" \
--attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
--attribute-condition="assertion.repository=='my-org/my-repo'"
# 3. Allow the pool (scoped by the condition above) to impersonate a service account
gcloud iam service-accounts add-iam-policy-binding my-service@my-project-id.iam.gserviceaccount.com \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/<project-number>/locations/global/workloadIdentityPools/github-pool/attribute.repository/my-org/my-repo"Important
--attribute-condition is not optional in practice — a pool with no condition accepts a valid token
from any GitHub repository in existence, not just yours, since GitHub is a shared OIDC issuer across
every public and private repo. Scoping the condition to your specific repository (and, for anything
sensitive, the ref/branch too) is what keeps the trust relationship actually narrow. This is the GCP
analogue of the Azure federated-credential subject match described on the Azure CLI page — same
underlying risk if skipped.
IAM role bindings#
gcloud iam service-accounts add-iam-policy-binding my-service@my-project-id.iam.gserviceaccount.com \
--member="user:jane@example.com" --role="roles/iam.serviceAccountUser"
gcloud projects get-iam-policy my-project-id # see the full policy, all bindings at once
gcloud projects remove-iam-policy-binding my-project-id \
--member="user:jane@example.com" --role="roles/viewer"add-iam-policy-binding is a read-modify-write under the hood — on a resource with many bindings, two
concurrent add-iam-policy-binding calls can race and one can silently lose. For scripted/CI changes to a
policy with many existing bindings, get-iam-policy + edit + set-iam-policy with an explicit etag is the
safer pattern:
gcloud projects get-iam-policy my-project-id --format=json > policy.json
# edit policy.json — add/remove bindings, keep the "etag" field untouched
gcloud projects set-iam-policy my-project-id policy.jsonset-iam-policy fails outright if the etag in your submitted policy doesn't match the current server-side
policy's etag — this is the concurrency-safety mechanism add-iam-policy-binding skips.
IAM conditional bindings#
gcloud projects add-iam-policy-binding my-project-id \
--member="user:jane@example.com" --role="roles/storage.objectViewer" \
--condition='expression=request.time < timestamp("2027-01-01T00:00:00Z"),title=expires-2026,description=Temporary access, expires end of 2026'
gcloud projects add-iam-policy-binding my-project-id \
--member="user:jane@example.com" --role="roles/viewer" --condition=None # explicitly add a binding with no conditionA conditional binding only grants the role while the CEL expression evaluates true — commonly used for
time-boxed access grants like the one above. --role cannot be a basic role (roles/owner, roles/editor,
roles/viewer) when a real condition is attached; title and expression are required, description is
optional. --condition-from-file takes the same fields from a JSON/YAML file instead of an inline
key-value string, which is easier to keep readable for a long CEL expression.
Custom roles#
gcloud iam roles create myCustomViewer --project=my-project-id \
--title="My Custom Viewer" --permissions=compute.instances.get,compute.instances.list \
--stage=GA
gcloud iam roles list --project=my-project-idCustom roles are worth reaching for when a built-in predefined role is either too broad (grants dozens of
permissions a job doesn't need) or, less commonly, doesn't quite cover a narrow combination — but start from
gcloud iam roles describe roles/storage.objectViewer --format="value(includedPermissions)" on the closest
predefined role and trim, rather than hand-listing permissions from scratch.
Config file locations#
ls ~/.config/gcloud/ # active_config, configurations/, legacy_credentials/
cat ~/.config/gcloud/application_default_credentials.json # ADC file, when set via `auth application-default login`gcloud config configurations are stored as plain INI files under ~/.config/gcloud/configurations/ —
useful to know when scripting a container image that needs a config baked in (mount or COPY the file
directly) rather than running gcloud config set interactively at build time.
Real-world scenario: keyless GitHub Actions deploy pipeline#
Building on the Workload Identity Federation setup above, the actual CI job needs no secret beyond identifiers:
# .github/workflows/deploy.yml
name: Deploy to GCP
on:
push:
branches: [main]
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/<project-number>/locations/global/workloadIdentityPools/github-pool/providers/github-provider
service_account: my-service@my-project-id.iam.gserviceaccount.com
- uses: google-github-actions/setup-gcloud@v2
- run: gcloud storage ls gs://my-bucketTip
actions/checkout should run before google-github-actions/auth in the job — running it after (or
omitting it) has caused later steps to lose authentication in real setups, since checkout can reset
environment state the auth action relies on. It's a small ordering detail that's easy to get backwards
when composing a workflow from separate examples.
Real-world scenario: cross-project service account impersonation for a shared CI identity#
A platform team runs one CI service account in a central tooling project but needs it to deploy into
several per-team application projects, without creating a duplicate service account (and duplicate WIF
trust setup) in every project:
gcloud projects add-iam-policy-binding app-team-a-project \
--member="serviceAccount:ci@tooling-project.iam.gserviceaccount.com" \
--role="roles/iam.serviceAccountTokenCreator" \
--condition=NoneThe CI identity authenticates once (via WIF, into the tooling project's service account) and then
impersonates a per-project deployer service account as needed — one federated trust relationship instead of
one per application project, with each application project independently controlling exactly what its own
deployer role can do.
Common pitfalls#
- Confusing
gcloud auth loginwithgcloud auth application-default login— see the IMPORTANT note above; they're separate credential stores serving different consumers. - Creating a workload identity pool provider with no
--attribute-condition— trusts every repository on the shared OIDC issuer, not just yours. - Racing concurrent
add-iam-policy-bindingcalls against a busy policy — useget-iam-policy+set-iam-policywith the etag for anything scripted against a policy with many existing bindings. - Downloading a service-account key when impersonation or WIF would do — a key file is a permanent secret to manage; both alternatives avoid ever creating one.
Exit codes#
0 success · non-zero on any API/auth/permission error — a PERMISSION_DENIED from a missing IAM binding
and an INVALID_ARGUMENT from a malformed flag both exit non-zero with no further distinction in the exit
code itself; parse the printed error text or add --verbosity=debug for the underlying request/response.
When to reach for something else#
For declarative, reviewable IAM and project provisioning, prefer Terraform's google/google-beta
providers over scripting gcloud iam/gcloud projects calls directly, the same declarative-IaC preference
as this site's AWS/Azure CLI pages. gcloud remains the right tool for interactive debugging, one-off
operational tasks, and CI glue that doesn't warrant a full Terraform apply cycle.