Skip to content

Credentials & auth

The GCP provider authenticates to Compute Engine via Application Default Credentials (ADC) — no token flag. On GKE that means Workload Identity (the GKE analogue of AWS IRSA: bind the Kubernetes ServiceAccount to a Google service account, no key files); off-GKE it means a key-file Secret via GOOGLE_APPLICATION_CREDENTIALS.

There are two identities, and keeping them separate is the whole point:

IdentityWhoWhat it does
Provider service accountthe provider processcalls instances.insert/delete, reads instances + machine types, sets metadata/labels
Instance service accountthe VMs the provider launcheswhatever your nodes need at runtime (--instance-service-account); not the provider’s identity

The provider’s identity must never be the node identity. A node should not be able to create or delete other nodes.

1. The provider service account & role

The provider needs exactly one predefined role on the target project:

RoleWhyLifecycle call
roles/compute.instanceAdmin.v1create / delete instances, set metadata + labels, read instances and machine typesCreate, Delete, Configure, Drain, Describe
roles/iam.serviceAccountUser on the instance SAlets the provider launch instances that run as --instance-service-accountCreate (only when --instance-service-account is set)

That’s the least-privilege set. instanceAdmin.v1 already covers compute.instances.{insert,delete,setMetadata,get,list} and compute.machineTypes.get. Map each grant to the call that needs it, and grant nothing more.

Live pricing reads the Cloud Billing Catalog API (cloudbilling.googleapis.com, the public SKU catalogue for service 6F81-5844-456A) on the --price-refresh cadence. The catalogue is public, so it needs no extra project role — only that the Cloud Billing API is enabled and the provider presents some credential: the provider’s own ADC identity, or an API key via --pricing-api-key. If pricing cannot be reached the provider falls back to the pinned seed table (prices go stale but never zero), so a missing key/API only degrades cost accuracy, never the lifecycle calls.

The Terraform under deploy/sa/ creates the provider service account, binds the role on the project, and wires the Workload-Identity binding:

Terminal window
cd providers/gcp/deploy/sa
terraform init
terraform apply \
-var 'project_id=my-gcp-project' \
-var 'name=bigfleet-gcp-us-central1' \
-var 'k8s_namespace=bigfleet' \
-var 'k8s_service_account=bigfleet-gcp' \
-var 'instance_service_account=bigfleet-node@my-gcp-project.iam.gserviceaccount.com'
# -> outputs provider_service_account_email

2. Workload Identity (GKE — preferred, no keys)

Workload Identity lets the Kubernetes ServiceAccount the pod runs as impersonate the Google service account — so there is no key file anywhere. Two bindings, both created by the Terraform above:

  1. an IAM policy binding granting the Kubernetes SA the roles/iam.workloadIdentityUser role on the Google SA, for the member serviceAccount:PROJECT.svc.id.goog[NAMESPACE/KSA_NAME];
  2. the Kubernetes ServiceAccount annotated iam.gke.io/gcp-service-account: <provider-sa-email>.

The Helm chart writes that annotation when you set serviceAccount.gcpServiceAccount:

serviceAccount:
create: true
name: bigfleet-gcp
gcpServiceAccount: bigfleet-gcp-us-central1@my-gcp-project.iam.gserviceaccount.com

Ensure Workload Identity is enabled on the cluster and node pool. Once bound, the provider picks up credentials from the metadata server automatically — ADC needs no env var on GKE.

3. Key-file fallback (off-GKE)

Off-GKE (or on a cluster without Workload Identity), create a key for the provider service account, store it as a Secret, and mount it as GOOGLE_APPLICATION_CREDENTIALS:

Terminal window
gcloud iam service-accounts keys create key.json \
--iam-account bigfleet-gcp-us-central1@my-gcp-project.iam.gserviceaccount.com
kubectl -n bigfleet create secret generic bigfleet-gcp-key --from-file=key.json
credentials:
secretName: bigfleet-gcp-key # mounted at /var/secrets/google/key.json
# GOOGLE_APPLICATION_CREDENTIALS is set for you

A ready-to-edit Secret manifest is in deploy/secret/gcp-key.example.yaml. Prefer Workload Identity — key files are long-lived credentials you must rotate and protect.

4. The instance service account

--instance-service-account is the identity your nodes run as — unrelated to the provider’s. Give it only what the workloads on the node need (often a minimal SA, or the project default). The provider needs roles/iam.serviceAccountUser on this SA to launch instances that run as it (the Terraform grants it). If you omit --instance-service-account, instances use the project default compute service account.

5. The SSH key for in-band bootstrap

Configure and Drain reach the running host over SSH (the secure, transient delivery channel — the join blob is never persisted in instance metadata). The provider needs an SSH private key (--ssh-key); its public key is authorised on each instance via ssh-keys metadata at Create (with enable-oslogin=false so metadata keys are honoured), and the instance’s SSH host key is pinned at Create and verified on every connection. Store the private key as its own Secret:

Terminal window
ssh-keygen -t ed25519 -N '' -f ./id_ed25519
kubectl -n bigfleet create secret generic bigfleet-gcp-ssh --from-file=id_ed25519=./id_ed25519
ssh:
secretName: bigfleet-gcp-ssh # mounted at /etc/bigfleet/ssh/id_ed25519
user: bigfleet

Use a dedicated key for the provider, not an operator’s personal key. For pre-pinned host keys (no trust-on-first-use window) use a cloud-init-enabled image (e.g. Ubuntu); otherwise the provider trust-on-first-uses and pins the observed host key. The provider reaches the host over its internal IP by default (same VPC); set --use-external-ip only if it must use an external IP.

6. Rotate

  • Workload Identity has no key to rotate — that’s its main benefit. Rotating the role binding is a Terraform change; the running process picks up new permissions immediately (no restart needed for an additive grant).
  • Key files are long-lived: create a new key, update the Secret, roll the Deployment (kubectl -n bigfleet rollout restart deploy/…) so the process re-reads it, then delete the old key. Because the persisted --state file is the restart path and transitions run on minute-scale timeouts, a rolling restart is safe.

What the credentials are used for

Every GCE call the provider makes, and the role permission it needs:

CallPermissionWhen
Instances.Insertcompute.instances.create (+ iam.serviceAccounts.actAs for the node SA)Create
Instances.Deletecompute.instances.deleteDelete
Instances.AggregatedListcompute.instances.listDescribe / reconcile
Instances.SetMetadatacompute.instances.setMetadataConfigure / Drain (records/clears the cluster-binding metadata + TOFU host-key pin; the bootstrap itself goes over SSH, never into metadata)
MachineTypes.Getcompute.machineTypes.getallocatable resolution

No credential is ever logged. See Security for the full trust model.