Service accounts

Identities for automation and devices, and the keys they authenticate with.

Not every caller is a person. Scripts, CI pipelines, monitoring integrations, and the devices themselves all need to authenticate, and none of them should do so as a human being. A service account is the identity they use instead.

Using one is not merely tidier. A human account carries whatever access that person has accumulated, disappears when they leave the company, and gives you no way to tell which automation did what. A service account has exactly the access you granted it, outlives any individual, and shows up in the audit trail under its own name.

What a service account is

A service account belongs to a project and lives in a region:

projects/{project}/regions/{region}/serviceAccounts/{serviceAccount}

It also has an email-style identifier, which is what you use when granting it access:

automation@acme-retail.us-west2.serviceaccounts.iam.edgelq.com

It is a principal like any other. It appears in role bindings, in audit logs, and in the dashboard’s access lists alongside users and groups.

Create one per job

Create a separate service account for each distinct job rather than one shared account for “automation”. They cost nothing, and the benefits compound:

  • Each gets only the access its job needs, so a compromised credential has a bounded blast radius.
  • You can revoke one without breaking the others.
  • The audit trail tells you which job did something, not just that something did.

Name them after the job — ci-deploy, metrics-export, fleet-inventory — not after the person or team that created them.

cuttle iam create service-account fleet-inventory \
  --project $PROJECT --region $REGION

Keys

A service account is an identity; a key is how a caller proves it holds that identity. You choose the type when you create the key.

Type What you get Suits
API key A single bearer token. Shell scripts, CI jobs, REST calls.
RSA keypair A private key returned to you once. gRPC clients and long-running services.
cuttle iam create service-account-key ci \
  --parent projects/$PROJECT/regions/$REGION/serviceAccounts/fleet-inventory \
  --algorithm API_KEY \
  -o json

Because of that, prefer writing the credentials straight to a file over copying them out of terminal output, where they linger in your scrollback and shell history:

cuttle iam create service-account-key ci \
  --parent projects/$PROJECT/regions/$REGION/serviceAccounts/fleet-inventory \
  --algorithm RSA_2048 \
  -c ./ci-credentials.json

A key can also be given a validity window. Setting an expiry is the simplest way to ensure a credential issued for a short-lived job cannot quietly remain valid for years.

Rotating a key

An account can hold more than one key at a time, so rotation needs no downtime:

  1. Create a second key.
  2. Deploy the new value wherever the old one is used.
  3. Confirm traffic is succeeding on the new key.
  4. Delete the old key.

Deleting takes effect immediately and cannot be undone, so do not skip step 3.

Where to keep keys

The failure to design against is a credential sitting somewhere you did not intend, still valid months later.

  • Never commit key material to a repository — application or infrastructure.
  • Inject it at runtime from your CI system’s secret store or your platform’s secret manager.
  • Prefer a mounted file over an environment variable, which leaks into logs, crash dumps, and child processes.
  • Give keys an expiry so that forgotten ones lapse on their own.

Granting access

A new service account can authenticate and do nothing at all. It needs a role binding, exactly like a user, and the member string must carry the serviceAccount: prefix:

cuttle iam create role-binding inventory-viewer \
  --parent projects/$PROJECT \
  --member "serviceAccount:fleet-inventory@$PROJECT.$REGION.serviceaccounts.iam.edgelq.com" \
  --role "services/iam.edgelq.com/roles/viewer"

Grant the narrowest role that works. A job that reads a device inventory needs read access and nothing more; if it later needs to write, widen it then.

As with users, role bindings take a minute or two to become effective, so a PERMISSION_DENIED immediately after granting access usually means “not yet” — see Accounts & access.

Restricting where a key works

A role binding can carry a condition that constrains it further. The most useful for automation is an IP condition — a list of CIDR ranges the caller must come from. Applied to a key held by a CI runner with stable egress addresses, it means a leaked credential is not usable from anywhere else.

Device service accounts

Devices authenticate the same way. When a device is provisioned it receives its own service account, and the credentials on its disk are a service account key — which is why a provisioning policy specifies the role each new device’s account should be granted.

You do not normally manage these by hand, but knowing they exist explains two things you will see: devices appearing as principals in your audit logs, and a device’s access being governed by exactly the same role bindings as everything else.

When a device is decommissioned, its service account should go with it. Removing the device through the dashboard or the API handles this; manually deleting a device record without cleaning up its identity leaves a credential that can still authenticate.

Next steps