Organizations
The tenancy layer above namespaces: a company, its verified domain, how people join it, and who administers it.
An Org is the tenancy layer above namespaces: one company, with its namespaces as its teams and business units.
apiVersion: core.ringleader.dev/v1
kind: OrgA namespace stays the unit of isolation, since RBAC, secrets and resources all scope to it. But the company-to-team relationship is real, and an Org is what records it. Without one, five business-unit namespaces are just five unrelated namespaces, and nothing says they are one customer.
Making it explicit is what lets Ringleader offer:
- A billing and plan entity, the one thing a namespace cannot be.
- Self-serve team formation. A verified
@acme.comorg auto-joins the next Acme employee, instead of stranding them in a lonely personal namespace. - An administrator with reach. One
org-admingrant covers every namespace of the org, present and future, with no per-namespace bootstrap. - Cloud federation. A CloudAccount belongs to an org, and the assertion Ringleader signs for your cloud carries the org’s id as its subject.
The shape
The grouping is modeled child to parent: every namespace points at its org, and an org never holds a list of its namespaces.
OrgDomain and a CloudAccount each name their org in spec.org.A namespace rolls up to at most one org. A namespace with no org still works for everything local to it, but it cannot broker a federated cloud mint: there is no organization for Ringleader to sign an assertion for, and it refuses rather than guessing one.
Org spec
apiVersion: core.ringleader.dev/v1
kind: Org
metadata:
name: acmecorp
spec:
displayName: Acme Corporation
plan: team
autoJoin: true
defaultNamespace: acmecorp| Field | Type | Description |
|---|---|---|
displayName | string | Human label. |
plan | string | free, team, or enterprise. |
suspended | bool | Hard stop (non-payment, abuse). Consumers gate on it. |
autoJoin | bool | Whether someone with an email at a verified domain of this org joins it automatically. |
defaultNamespace | string | The shared namespace new members land in. Unset: a namespace named after the org. |
defaultMemberRole | string | Which rung new members are bound to in a shared namespace: namespace-member (the default) or namespace-reader, the read-only rung for people who should see a namespace without being able to provision. See Access control. |
billingAccountRef | string | A reference into the external billing system of record, never a ledger. |
maxNamespaces | integer | How many namespaces this org may hold. Minimum 1; defaults to 50. |
settings | object | Free-form bag for opaque data. The control plane never branches on its contents. |
Orgs are cluster-scoped and created by Ringleader operators. An org-admin
reads their own org but does not rewrite its plan or suspension state; what they
do administer is everything under it (see Who administers an
org).
The namespace cap
spec.maxNamespaces bounds how many namespaces roll up to one org. A write that
would add a namespace to an org already at its cap is refused, naming the org and
the limit; editing or deleting the namespaces an org already has is never affected,
even at the cap.
- The default is 50. An org created before the field existed enforces at that same number.
- A personal org, the one minted for a solo self-serve signup, is stamped at 10 instead. Orgs onboarded before that keep whatever they have.
- There is no “unlimited” value: an org that genuinely needs more gets a larger explicit number.
- Only a Ringleader operator can change it. An
org-adminreads the field but cannot raise their own cap.
Org status
| Field | Type | Description |
|---|---|---|
message | string | The headline: the org's OIDC issuer documents are published, or the org's OIDC issuer documents are not published, so a cloud cannot validate an assertion for this org. |
oidc.published | bool | Whether the org’s issuer documents are live. |
oidc.keyIds | []string | The signing keys currently published for the org. |
oidc.lastPublished | string | When the documents last changed. |
oidc.message | string | The specific reason when publishing has not succeeded. |
This is what a CloudAccount in the org derives its readiness from: until the documents are published, your cloud has nothing to validate Ringleader’s assertion against, so no workstation governed by the org can mint a credential.
local is a reserved name: standalone mode seeds a degenerate local org
alongside the reserved local namespace, purely so org-aware code has one path to
run locally. It never leaves the device, and no one may create an org called
local.
Finding your organization id
The org’s id (not its name) is what a cloud trust configuration pins during
cloud onboarding. It is the
object’s uid, and it never changes:
rl org get acmecorp -o jsonpath='{.metadata.uid}'0192f5bf-af83-7178-8d0a-f1c7aea06bdeNames can be deleted and reused; a uid cannot. That is exactly why the cloud trust pins the uid.
Domain verification (OrgDomain)
An OrgDomain claims a DNS domain for an org. Verifying it is what enables self-serve membership.
apiVersion: core.ringleader.dev/v1
kind: OrgDomain
metadata:
name: acme-com
spec:
org: acmecorp
domain: acme.com| Field | Type | Description |
|---|---|---|
org | string | Required. The owning org. |
domain | string | Required. The DNS domain to verify, e.g. acme.com. |
Verifying it
Apply the object, then read the token it asks you to publish:
rl apply -f acme-com.yaml
rl orgdomain get acme-com -o jsonpath='{.status.expectedToken}'Publish it as a TXT record on the domain:
acme.com. IN TXT "ringleader-verification=<token>"A controller re-checks DNS periodically and flips the object’s Verified
condition:
rl orgdomain describe acme-com
rl od get| Status field | Description |
|---|---|
message | The one human headline, e.g. acme.com is verified or waiting for the ringleader-verification= DNS TXT record to appear on acme.com. |
conditions[] | Verified (True/False) with a reason; see below. |
expectedToken | The token to publish. |
lastCheckedAt | When DNS was last resolved. |
verifiedAt | When it first verified. |
consecutiveFailures | How many checks in a row failed to find the record. Drives the de-verification grace window. |
pendingChecks | How many checks have run while still waiting for the record. Drives the pending backoff. |
matchedKeyId | Which verification key the live record matched. Set only while verified. |
The Verified condition’s reason tells you which situation you are in:
| Reason | Meaning |
|---|---|
Pending | The token is minted; nobody has published the record yet. |
Verified | The record is live and matches. |
Rejected | Terminal: the domain is empty, reserved, or a public/free email domain. |
Conflict | Terminal: another organization verified this domain first. |
LookupFailed | De-verified after a grace window of failed DNS lookups. |
Four behaviors worth knowing:
- The token is deterministic, not random. The same domain and key always yield the same record, so you publish it once and a control-plane redeploy never invalidates it.
- All TXT records are scanned, not just the first, so the verification record can sit alongside every other TXT record on the domain.
- A single failed DNS lookup never de-verifies you. Public DNS fails
transiently, and revoking a whole org’s membership on one
SERVFAILwould be unacceptable, so de-verification takes several consecutive failures, visible inconsecutiveFailuresthe whole time.status.messagenarrates the grace window as it runs (acme.com is verified, but its DNS TXT record could not be confirmed on the last 2 checks; it will be de-verified after 3), which is your warning that a record went missing while the condition still readsTrue. - Free email domains are refused.
gmail.com,outlook.com,icloud.comand friends are rejected outright, since otherwise one org could verify a public domain and absorb everyone who signs up with a matching address.
If two orgs claim the same verified domain, one deterministically wins and the
other is marked Conflict. Duplicate claims within one org never conflict.
How people join an org
On a user’s first login the control plane decides where they land, once. Three rules, in precedence order:
- An explicit pre-assignment by an operator wins over everything else. The
user is placed in a shared namespace, on the rung
spec.defaultMemberRoleselects. - A verified, auto-join domain. If the user’s email domain matches an
OrgDomainthat is verified and whose org hasautoJoin: true, they land in that org’s shared namespace (Org.spec.defaultNamespace, else a namespace named after the org), on that same rung. No operator involvement at all. - Otherwise, a personal namespace, derived from their email with a short
random suffix, which the user owns as
namespace-admin, rolling up to a personal org they are theorg-adminof.
Rule 3 makes a solo signup self-sufficient: because you administer your own personal org, you can register a trust anchor and author a service account to federate a CI job in, with nobody having to act for you first. It applies to the personal branch only: someone auto-joined into their company’s org under rule 2 lands as an ordinary member of it, never as an administrator of the company.
The rung for rules 1 and 2 comes from the landing namespace’s own org, so it
applies to a pre-assignment and an auto-join alike. A namespace with no spec.org,
or one naming an org that does not exist, falls back to namespace-member; a row the
control plane cannot read, the org’s or the namespace’s, fails the login instead of
guessing a rung, and the next attempt retries.
A namespace created for a pre-assignment carries no org, and until it names one
every member lands on the default rung whatever the org selected. An org-admin can
create a namespace that already names their org, but stamping one that already exists
without an org is Ringleader’s to do, so ask them. Rule 3 never consults the field: a
personal namespace is always namespace-admin.
Two gates guard rule 2, and both matter:
- The email must be verified by the identity provider. An unverified identity is never auto-joined into someone else’s org; it falls back to a personal namespace. It still onboards; it never lands inside another tenant.
- The domain’s verification is re-checked at the moment of joining. A domain that lapsed between the lookup and the placement cannot still absorb the user.
Placement is decided once and not revisited, and so is the rung. Someone who
onboarded into a personal namespace before their company’s domain was verified
stays where they are, and changing defaultMemberRole later moves nobody already
onboarded, since both only change where and how new users land. Moving an existing
member between rungs means editing their RoleBinding, which a namespace-admin or
org-admin can do. Re-homing an existing member
is an operator action, not something that happens behind their back; ask
Ringleader if you need one moved.
Who administers an org
org-admin is a built-in role bound to a subject for one org by an
OrgRoleBinding. It is the root of its org and nothing more.
apiVersion: core.ringleader.dev/v1
kind: OrgRoleBinding
metadata:
name: acmecorp-admins
spec:
org: acmecorp
subjects: ["user:alice@acme.com"]
roleRef:
kind: GlobalRole
name: org-adminThat single object is the entire “make Alice an admin of Acme” step. It reaches every namespace of the org, including ones created later, so there is no per-namespace bootstrap to maintain.
An org-admin may:
- Create and manage the org’s namespaces (their
spec.orgmust be the admin’s own org, which is what makes the boundary structural rather than a convention). - Administer membership across every namespace of the org: bind and unbind
users to
namespace-reader,namespace-memberornamespace-admin. - Author org-scoped roles (
OrgRole/OrgRoleBinding), bounded by the escalation guard: every rule they grant must be one they already hold. - Write the org’s CloudAccounts: which cloud identity to use for cloud federation.
- Write the org’s policies (
OrgPolicy): what a workstation manifest may name anywhere in the org, and whether its namespaces may refine that or not. - List the org’s members (
rl user get), folded to their own org. A normal member gets a403; another org’s users are invisible, not merely filtered.
An org-admin may not grant themselves more than they hold, write global roles, or reach any other org. They are not a wildcard.
Custom org roles
apiVersion: core.ringleader.dev/v1
kind: OrgRole
metadata:
name: ci-bot
spec:
org: acmecorp
rules:
- apiGroups: ["core.ringleader.dev"]
resources: ["workstations"]
verbs: ["get", "list", "watch"]
---
apiVersion: core.ringleader.dev/v1
kind: OrgRoleBinding
metadata:
name: acmecorp-ci
spec:
org: acmecorp
subjects: ["sa:acmecorp/ci"]
roleRef:
kind: OrgRole
name: ci-botBoth objects carry spec.org, and the role is only accepted because every rule in
it is a subset of what its author already holds.
Onboarding a company, end to end
Company Acme, domain acme.com, first admin alice@acme.com.
1. Ringleader creates the org, its landing namespace, and its domain claim.
apiVersion: core.ringleader.dev/v1
kind: Org
metadata: { name: acmecorp }
spec:
displayName: Acme Corporation
autoJoin: true
defaultNamespace: acmecorp
---
apiVersion: core.ringleader.dev/v1
kind: Namespace
metadata: { name: acmecorp }
spec: { org: acmecorp }
---
apiVersion: core.ringleader.dev/v1
kind: OrgDomain
metadata: { name: acme-com }
spec: { org: acmecorp, domain: acme.com }2. Acme publishes the TXT record. The domain verifies on the next check.
3. Ringleader appoints the first org-admin, the one OrgRoleBinding shown
above. Alice is now the root of Acme, and no further staff action is needed.
4. Alice creates the team namespaces.
apiVersion: core.ringleader.dev/v1
kind: Namespace
metadata: { name: acmecorp-team-a }
spec: { org: acmecorp } # must be Alice's own org, or it is denied5. Acme employees log in and land automatically. bob@acme.com signs in,
matches the verified domain, and is onboarded into acmecorp on the rung
defaultMemberRole selects, here the default, namespace-member. Nobody does
anything.
6. Alice places people into teams.
apiVersion: core.ringleader.dev/v1
kind: RoleBinding
metadata: { name: bob-team-a, namespace: acmecorp-team-a }
spec:
subjects: ["user:bob@acme.com"]
roleRef: { kind: GlobalRole, name: namespace-member }7. Acme connects its cloud. Follow cloud onboarding, which uses the org’s uid from step 1 as the subject its cloud trust pins.
Where you see your org
rl org get
rl auth status -o json # includes the org your default namespace rolls up toSee also
- Access control: namespaces, ownership, the role ladder, and Grants.
- CloudAccount: an org’s link to a cloud account, recording the cloud identity to use.
- how Ringleader signs in to your cloud: how the org’s id becomes the subject your cloud trusts.
- Policy: the org-wide and per-namespace constraints on what a workstation manifest may name.