Service accounts
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
The platform does not retain the secret half of a key. The create response is the only copy you will ever receive. If it is lost, delete the key and create a replacement.
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:
- Create a second key.
- Deploy the new value wherever the old one is used.
- Confirm traffic is succeeding on the new key.
- 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
- Manage resource limits — the quotas that apply to what your automation creates.
- Integrate — using a service account key from your own code.