CloudIdentity
The credential broker that lets the control plane create cloud VMs for a namespace.
A CloudIdentity brokers the credentials the control plane uses to create cloud machines for workloads in its namespace. It answers two separate questions: how Ringleader authenticates to your cloud, and what shape the workstations it governs take.
apiVersion: core.ringleader.dev/v1
kind: CloudIdentityIt is admin-managed: only a namespace admin can author or change one.
How it is selected
When a Workstation routes to a cloud provider, the control plane picks
the CloudIdentity in the same namespace whose spec.provider matches and whose
spec.selector.matchLabels match the workstation’s labels. If several match, the
highest priority wins (ties broken by name). An empty selector matches all
workloads, so a high-priority catch-all can silently outrank scoped identities;
keep catch-alls at priority: 0.
Exactly one identity is selected. They are not merged, so a high-priority, narrowly-scoped identity must carry everything the workstations it wins need — including its credential path — not just the field that makes it special.
Credential paths
A CloudIdentity takes exactly one credential path, chosen by which field it carries. Knowing which one you are on is the most important thing on this page.
| Path | Selected by | What Ringleader presents to your cloud |
|---|---|---|
| Per-organization federation (recommended) | spec.cloudAccountRef | A short-lived assertion Ringleader signs, whose subject is your organization and which only your trust configuration accepts. |
| Impersonation (legacy) | spec.impersonation | A token from a Ringleader-owned intermediary identity that your account has granted impersonation rights to. The intermediary is shared across customers; prefer cloudAccountRef. |
| Ambient | none of the above | The worker’s own identity plus the deployment defaults. Valid, and the meaning of a provider-only CloudIdentity. |
cloudAccountRef and impersonation are mutually exclusive. Federation
presents a signed assertion and holds no cloud credential at all, so combining the
two would be an object whose credential path depends on which field you read first.
The control plane refuses it rather than resolving it by a precedence rule nobody
can see:
apply cloudidentity/gcp: validation failed: spec: spec.cloudAccountRef and
spec.impersonation are mutually exclusive (cloudAccountRef selects the per-org
OIDC federation; impersonation selects the shared-intermediary chain — pick one)There is no fallback between the paths. If a federated identity cannot mint — the account is missing, the namespace belongs to no organization, the control plane has no federation configured — the workstation fails with a clear error. It never quietly reverts to another path.
`spec.credentialRef` is retired
A CloudIdentity once took a fourth, keyed path: spec.credentialRef named a
Secret holding a long-lived cloud credential — a service-account JSON key on
Google Cloud, an application client secret on Azure — which seeded the mint.
Both are gone. Ringleader now stores no cloud credential at rest on any path. Per-organization federation replaced them: a short-lived assertion, signed per mint and exchanged at your own cloud’s trust.
The field is refused at apply, not ignored — an accepted-and-dropped
credentialRef would leave you believing a workstation authenticates as the principal you
pinned while it quietly minted through the deployment default instead. If you are
carrying an old manifest, the apply names its replacement:
apply cloudidentity/azure: validation failed: spec.credentialRef: is no longer a
CloudIdentity field: ringleader stores no cloud credential at rest (the GCP
service-account key and the Azure keyed client secret are both retired). Use
spec.cloudAccountRef to name a CloudAccount — the per-org OIDC federation mints a
short-lived assertion exchanged at your own cloud's trust, with no stored secret.Replace it with a CloudAccount and a
cloudAccountRef — see per-organization federation.
Per-organization federation
This is the path to prefer for new cloud accounts. Ringleader signs a short-lived OpenID Connect assertion whose subject is the organization that owns the workstation’s namespace, derived server-side, and exchanges it directly at your cloud’s trust configuration. No Ringleader-owned intermediary identity is in the chain, and no credential of yours is stored anywhere.
Which cloud identity to use — which principal to become, whose trust to present to — lives on a separate, cluster-scoped CloudAccount that only an organization administrator can write. The CloudIdentity just points at it by name:
apiVersion: core.ringleader.dev/v1
kind: CloudIdentity
metadata:
name: gcp
namespace: dev
spec:
provider: gcp
selector:
matchLabels:
cloud: gcp
# The credential EDGE (which principal, whose trust) lives on the CloudAccount.
# The machine SHAPE (project / zone / machineType) stays here.
cloudAccountRef: acme-gcp
defaultProviderConfig:
gcp:
project: acme-dev
zone: us-east4-c
machineType: c4-standard-4Azure is the same shape — the account carries the Entra application and the tenant, the identity carries the placement:
apiVersion: core.ringleader.dev/v1
kind: CloudIdentity
metadata:
name: azure
namespace: dev
spec:
provider: azure
selector:
matchLabels:
cloud: azure
cloudAccountRef: acme-azure
defaultProviderConfig:
azure:
subscriptionId: 8d6e5f6e-5090-4221-9eae-cc956b238590
resourceGroup: acme-workstations
location: eastus
subnetId: /subscriptions/…/virtualNetworks/acme-vnet/subnets/workstations
publicIp: trueAWS is the same shape — the account carries the IAM role to assume, the identity carries the placement:
apiVersion: core.ringleader.dev/v1
kind: CloudIdentity
metadata:
name: aws
namespace: dev
spec:
provider: aws
selector:
matchLabels:
cloud: aws
cloudAccountRef: acme-aws
defaultProviderConfig:
aws:
region: us-east-1
instanceType: m6i.xlarge
subnetId: subnet-0abc123
securityGroupIds: [sg-0abc123]
assignPublicIp: trueThe referenced account must belong to the same organization as this CloudIdentity’s namespace; a cross-organization reference is refused when you apply it. See CloudAccount for the account object, and per-organization federation for the trust configuration to create in your cloud.
Impersonation (legacy)
Do not onboard a new account this way
This is the older path, kept only so existing accounts keep working while they
migrate. Use cloudAccountRef instead.
It works by having Ringleader’s own identity impersonate an intermediary it owns, which in turn impersonates the target principal in your account. No secret is stored, but the intermediary is shared across every customer, so your account must trust an identity that other tenants can also name. Per-organization federation exists precisely to remove that: the trust is pinned to a signed claim carrying your organization’s id, and nothing else can present it.
Ringleader’s own identity impersonates an intermediary it owns, which in turn impersonates the target principal in your account. No secret is stored, but the intermediary is shared, so your account must trust it.
apiVersion: core.ringleader.dev/v1
kind: CloudIdentity
metadata:
name: gcp
namespace: dev
spec:
provider: gcp
selector:
matchLabels:
cloud: gcp
impersonation:
intermediaryPrincipal: ringleader-iam@example-iam.iam.gserviceaccount.com
targetPrincipal: ringleader-workstations@acme-dev.iam.gserviceaccount.comAzure, keyless, over the same chain — targetPrincipal is the Entra application’s
client id, and that application carries a federated identity credential trusting
Ringleader’s intermediary:
spec:
provider: azure
selector:
matchLabels:
cloud: azure
impersonation:
intermediaryPrincipal: ringleader-iam@example-iam.iam.gserviceaccount.com
targetPrincipal: 00000000-0000-0000-0000-000000000000 # the Entra app client id
overrideProviderConfig:
azure:
subscriptionId: 22222222-2222-2222-2222-222222222222
resourceGroup: ringleader-workstations
location: eastus
subnetId: /subscriptions/…/virtualNetworks/ringleader-vnet/subnets/workstationsOn this path the Azure tenant is a control-plane-wide setting, not something the CloudIdentity carries — which is precisely one of the things federation fixes: a CloudAccount holds the tenant per-customer, so two customers in two tenants become expressible.
Migrating an identity from impersonation to federation is a one-line edit —
replace the impersonation block with a cloudAccountRef — and it reverses the
same way, per identity, with no coordinated all-at-once switch.
Spec fields
| Field | Type | Description |
|---|---|---|
provider | string | Required. The backend the credentials target (gcp, azure, aws). |
priority | int | Precedence when several match (default 0); higher wins. |
selector.matchLabels | map | Which workloads this identity brokers for. Empty matches all. |
cloudAccountRef | string | The cluster-scoped CloudAccount that specifies which cloud identity this uses. Selects per-organization federation. Mutually exclusive with impersonation. |
impersonation.intermediaryPrincipal | string | Intermediary the control plane’s own default identity impersonates (hop 1). |
impersonation.targetPrincipal | string | Target principal in the tenant (hop 2). GCP: a service-account email; Azure: the Entra app client id. |
impersonation.targetScope | string | Additional provider-specific scope. |
serviceAccount.accountId | string | Declares a runtime identity the workstation VMs run as (see below). GCP: the service-account id; Azure: the user-assigned managed-identity name. |
serviceAccount.displayName | string | Human label for the runtime identity (GCP displayName; advisory on Azure). |
serviceAccount.roles | array | Exact-set role grants for the runtime identity: {role, resource} pairs. GCP: roles/… or a custom role id, bound at project scope (see below); Azure: a built-in role name, GUID, or role-definition id, with resource an ARM scope id (empty: the identity’s resource group). |
serviceAccount.autoCreate | bool | Does Ringleader create the runtime identity? Default true. Set false when your own infrastructure-as-code pre-creates it: Ringleader then only adopts it, and an absent one fails the workstation loudly rather than being conjured without the grants it exists to carry. |
serviceAccount.autoDelete | bool | Does Ringleader delete the runtime identity when the last workstation using it goes away? Default true. Set false so tearing down a workstation cannot destroy an identity your own tooling declares — and with it any grants attached elsewhere. Independent of autoCreate on purpose: auto-create plus never-delete is a legitimate combination. |
defaultProviderConfig | object | Per-provider providerConfig defaults (workstation overrides these). |
overrideProviderConfig | object | Per-provider providerConfig forced by policy (wins over the workstation). |
Everything a CloudIdentity declares is validated when you apply it, not when a workstation later tries to boot. This object is a template for other people’s workstations, so a typo in it would otherwise surface as an opaque cloud error on someone else’s machine.
Shaping the workstations it brokers
defaultProviderConfig and overrideProviderConfig mirror a Workstation’s own
spec.providerConfig (a per-provider map) and fold around it:
defaultProviderConfig ◁ the workstation's own providerConfig ◁ overrideProviderConfigA default supplies a value a workstation may override (a fleet-wide machine type). An override forces one regardless of what the workstation authored (a project and zone pinned by policy).
Both blocks may also carry the VM’s cloud labels/tags, and those values may be
templated per workstation — owner: ${shortHash:creatorUserId}, team: ${metadata:namespace} —
plus an addRingleaderLabels: true shorthand for the standard Ringleader-owned
set. They are expanded once per governed workstation and sanitized to one portable
character set, so the same manifest produces the same label on every cloud.
Put an identity label in overrideProviderConfig, never in
defaultProviderConfig: a default is by definition overridable by the
workstation’s own providerConfig, so a member could relabel their workstation as someone
else. An override cannot be.
Ringleader only ever removes labels it applied itself. Keys put on a VM by anything else — your own cost-centre tagging, a cloud policy that appends tags — are left alone.
Runtime identity (spec.serviceAccount)
Everything above is about the identity Ringleader authenticates to the cloud
with. spec.serviceAccount is a different axis: the identity each matching
workstation VM runs as. Without it, VMs are booted with no attached
service account or managed identity, so nothing inside the workstation can act as any
cloud principal — with one exception on Google Cloud, described just below.
With it, the control plane provisions a dedicated identity (a GCP service account,
or an Azure user-assigned managed identity), grants it exactly the declared roles,
and attaches it to the VM, so an agent working in the workstation holds its own scoped,
revocable identity, never the operator’s.
A GCP cloud VM always gets an attached SA — make it a permission-less one
A GCP cloud workstation must present a Google-signed instance-identity assertion
to come up, and Google mints that assertion only for a VM that has an attached
service account. So a GCP workstation with no service account of its own falls back to the
project’s default compute service account — which on most projects is broadly
privileged. The result: a GCP workstation that declares no serviceAccount runs as a broad
identity purely so it can start.
Whether the workstation can start only ever depends on Google’s signature and the instance’s identity claims (project, zone, instance name); it never looks at the service account’s IAM roles. So a zero-role service account works exactly the same and lets the workstation act as nothing. To get least privilege on GCP, name a permission-less identity:
spec:
provider: gcp
cloudAccountRef: acme-gcp
defaultProviderConfig:
gcp:
project: acme-dev
zone: us-east4-c
serviceAccount:
accountId: rl-ws-default # a service account with NO role bindings
roles: [] # least privilege — Ringleader binds nothing
autoCreate: false # your IaC pre-creates the zero-role SA; Ringleader ADOPTS it
autoDelete: false # Ringleader never deletes your SASet autoCreate: true instead if you would rather Ringleader mint the zero-role
SA for you — that needs the same IAM-admin opt-in as any other provisioned identity
(see the note further down). Either way the SA holds no roles, so the workstation starts yet
can reach nothing.
apiVersion: core.ringleader.dev/v1
kind: CloudIdentity
metadata:
name: gcp-bucket-read-only
namespace: dev
spec:
provider: gcp
priority: 100
selector:
matchLabels:
ws-bucket: read-only
cloudAccountRef: acme-gcp
defaultProviderConfig:
gcp:
project: acme-dev
zone: us-east4-c
machineType: c4-standard-4
serviceAccount:
accountId: ws-ro-${shortHash:creatorUserId} # one identity per user
displayName: "Workstation bucket read-only (per user)"
roles:
- role: projects/acme-dev/roles/wsBucketReadOnlyA workstation opts in by carrying the label:
metadata:
labels:
ws-bucket: read-only
spec:
requirements: [provider:gcp]Because identities are selected and not merged, the higher-priority object above
carries the full credential path (cloudAccountRef) and placement as well as
the serviceAccount block — otherwise a workstation that matched it would win an identity
with no way to create a VM at all.
Supported for gcp and azure. The declared roles are kept as an
exact set: a role removed from the spec is revoked in the cloud on the next
update cycle. Templating accountId with ${shortHash:creatorUserId} gives each
user their own runtime identity, shared across that user’s workstations.
On AWS the analogue is an EC2 instance profile you name with
providerConfig.aws.iamInstanceProfile (Ringleader attaches, but does not create,
the profile). Attaching one needs iam:PassRole, an opt-in on the
onboarding assets; without it a workstation runs
with no AWS identity at all.
On Google Cloud, roles are bound at project scope today. A role entry with a
non-empty resource is not yet bound, and is reported on the resulting
CloudServiceAccount’s status rather than silently applied. Scope the role itself
(a custom project role over just the resources you mean) until per-resource
bindings land. Azure honours resource as an ARM scope id.
Your cloud account must be onboarded for this
Two operational notes:
- The provisioned identity is tracked as a
CloudServiceAccount(aliascsa), a control-plane bookkeeping record you can inspect withrl csa getbut never author yourself. It is owner-referenced by the workstations that use it and garbage-collected (including the real cloud identity and its role grants) once they are all gone. - Changing the runtime identity of a running GCP workstation requires a stop. GCE only allows attaching or swapping a VM’s service account while the instance is stopped, so the controller applies such a change through an automatic stop, apply, and start cycle rather than in place. Expect a brief restart of the workstation.
Status fields
| Field | Type | Description |
|---|---|---|
valid | bool | Whether the identity is well-formed and usable. |
message | string | On success, the provider; on failure, the validation/broker error (plus advisory warnings). |
lastVerified | string | RFC3339 timestamp of the last verification (re-checked on a periodic cadence, and immediately after you edit the spec). |
See also
- CloudAccount: the org-owned choice of which cloud identity to use, referenced by a federated identity.
- Per-organization federation: the onboarding runbook for the recommended path.
- Providers: how a workstation is
placed, and what each provider’s
providerConfigaccepts.