Verified15 commandsAI-assisted

Configuration, Auth & IAM

.md

Verified 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/zone

Core concepts: the resource + identity hierarchy#

Diagram

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-token

Important

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 staging

A "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/zone

Projects#

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 once

Service 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 deleting

A 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-bucket

Requires 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.

Diagram
# 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.json

set-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 condition

A 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-id

Custom 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-bucket

Tip

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=None

The 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 login with gcloud 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-binding calls against a busy policy — use get-iam-policy + set-iam-policy with 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.