Access control

Namespaces, ownership, the seeded RBAC roles, and Grants: who can see, change, SSH into, and read your resources.

Ringleader’s access model has three layers: namespaces (the tenancy boundary), ownership (you control what you create), and Grants (explicit, per-object sharing). RBAC roles decide what verbs you hold in a namespace; ownership narrows the write verbs to your own objects; Grants selectively widen access to specific peers.

Above the namespace sits the organization, the company a namespace belongs to. It is what makes “admin of every namespace in Acme” expressible as a single grant.

Namespaces & onboarding

A namespace is the unit of tenancy: a team or business unit. Every resource lives in one, and cross-references stay within it. Every namespace rolls up to exactly one organization.

The control plane does not create a namespace eagerly. On your first login it decides where you land, once, by this precedence:

  1. A pre-assignment by an operator: a shared namespace, into which you are bound as namespace-member or namespace-reader, per that organization’s choice.
  2. A verified, auto-join company domain: if your email domain is verified for an organization that allows auto-join, you land in that organization’s shared namespace, on the same rung it chose, with no operator involvement. See Organizations.
  3. Otherwise, a personal namespace derived from your email with a short random suffix (e.g. alice-acme-com-k3f9x2), into which you are bound as namespace-admin: full control of everything in it. You are also made the org-admin of the personal organization it rolls up to, so a solo signup can administer their own tenancy, registering a trust anchor or authoring a service account, with no operator acting first. This applies to the personal branch only: someone auto-joined into a company’s org under case 2 is an ordinary participant in it.

Which rung the two shared landings use is set on the organization: Org.spec.defaultMemberRole selects namespace-member, the default, or namespace-reader, the read-only rung for people who should see a namespace without being able to provision. Like every Org field it is written by a Ringleader operator; an org-admin reads it but cannot change it.

It is read from the namespace you land in, so it applies to a pre-assignment and an auto-join alike, but only if that namespace names the organization in its spec.org. A namespace created for a pre-assignment carries no org, and until it names one everyone lands on the default rung whatever the organization selected. An org-admin can create a namespace that already names their org; stamping one that already exists without an org is Ringleader’s to do.

Your rung is decided at your first login and never revised afterwards. Changing either setting later moves nobody who has already onboarded; moving someone between rungs means editing their RoleBinding, which a namespace-admin or org-admin can do.

Either way you can read your teammates’ resources but cannot modify or delete them. As a namespace-member you fully control your own; as a namespace-reader you create nothing in the namespace except your own device-local SSH keys and port-forward bindings, never a workstation, secret or config.

When you omit -n, the CLI targets the namespace you pinned with rl namespace use, else your origin’s spec.defaultNamespace, else the reserved local.

Ownership: personal resources

Workstation, Secret, WorkstationConfig, ConfigMap, Grant, ServiceAccount, and Integration are owner-scoped personal kinds. Like a Unix file, you own what you create. On every write, the control plane stamps the owner from your verified identity, and a client cannot forge it. The consequence for a namespace-member:

  • You may create, update, and delete your own objects of those kinds.
  • Peers may get/list them (reads are open within the namespace) but cannot update or delete them. A Secret is the exception, in the stricter direction: every read returns its redacted projection, yours included, and seeing the values takes --reveal, which needs ownership, the access verb, or a Grant.
  • Owning something does not stop a peer referencing what they can already read. A workstation that names a WorkstationConfig in spec.configs, or a scripts/files entry that names a ConfigMap, gets those bytes whoever authored them.
  • Where a config applies by selector rather than by name, ownership does decide reach: one an admin authored applies to every matching workstation in the namespace, while one a member authored applies only to workstations that member owns, and stops applying to a workstation they lose. An admin who loses their admin rights does not stop: their config narrows to the workstations they own, like any member’s. A member’s ConfigMap likewise supplies content to a selector-applied config only where that member authored the config.
  • A ServiceAccount is an identity rather than something a workstation consumes, and it has a rule of its own: it keeps working only while its owner could still create one in this namespace. Demote that person to namespace-reader, or remove them from the namespace, and the account is suspended, whether they were an admin or an ordinary member. Restore their access, or recreate the account under someone who is staying.

A namespace-admin (personal namespaces, and admins of shared ones) is not owner-restricted: they manage every object in the namespace, whoever created it.

The built-in roles

The built-in roles define what a subject may do. They are predefined by Ringleader (not something you author), so their meaning is consistent everywhere.

The first four form a ladder: each rung contains every rung listed above it, so each row states only what it adds. node-owner and public-info are not part of that ladder, since every authenticated user holds both, and global-admin sits outside it entirely.

RoleWho gets itWhat it adds
namespace-readerShared-namespace members, where Org.spec.defaultMemberRole selects itget/list/watch on everything in the namespace, and create/update/delete on your device-local localbindings and sshkeys. It creates nothing else, in particular no workstation, which on a cloud-backed organization is real spend.
namespace-memberYou, in a shared namespace (the default rung)Owner-scoped create/update/delete on workstations, secrets, workstationconfigs, configmaps, grants, serviceaccounts and integrations, your own only.
namespace-adminYou, in your personal namespace, and the admins of a shared oneThe same verbs for any owner, not just your own. They cover these kinds of the namespace: workstations, workstationconfigs, configmaps, secrets, grants, serviceaccounts, integrations, cloudidentities, edges, policies, sshkeys, localbindings, and the namespace’s own roles and rolebindings. It is a curated set, not a blanket wildcard.
org-adminAn organization’s administratorsEverything a namespace-admin has, in every namespace of their organization, plus that organization’s own structure: its namespaces, orgroles, orgrolebindings, CloudAccounts, identityproviders and orgpolicies, plus read-only access to its user roster. Confined to their own organization. (Reading the organization and its domains is not one of these: every authenticated user can read their own, through public-info.)
node-ownerEvery authenticated userOwner-scoped management of your own device records (nodeinfos).
public-infoEvery authenticated userget/list/watch on namespaces, organizations and their domains, and role definitions and org role bindings, so you can see who administers your org. The user roster is not included.
global-adminRingleader operators (break-glass)Full access to everything, everywhere. The only role that holds the wildcard.

Two limits hold for every rung below global-admin:

  • No role grants access on a secret it does not own. An administrator owns a Secret’s object lifecycle and can delete it, but reading a plaintext they do not own still requires ownership or an explicit Grant.
  • No role can escalate itself. You cannot bind a subject (including yourself) to permissions you do not already hold.

How a role reaches you

A role is just a rule set. What decides where it applies is the binding that carries it:

BindingReaches
RoleBinding (namespaced)One namespace.
OrgRoleBinding (cluster-scoped, carries spec.org)Every namespace of one organization, including ones created later, plus that org’s own structure (its namespaces, org roles, org role bindings, org policies, cloud accounts, identity providers, and user roster).
GlobalRoleBinding (cluster-scoped)Everywhere. Operators only.

A binding may reference a built-in GlobalRole, a namespaced Role, or an org-scoped OrgRole. An OrgRoleBinding’s reach over cluster-scoped objects is confined to the org’s own, so it can never grant a verb on another org’s objects, or on global roles.

Inspect what you actually hold at each origin with:

rl auth status --show-access

The access verb & secrets

Reading a Secret’s values (not just its redacted projection) is gated by a distinct access verb: the same verb that admits objects which consume the secret (a ${secret:NAME} reference, a git source’s credentialRef, a Tailscale authKeyRef). You hold access on your own secrets automatically; a Grant with rights: [access] extends it to a peer.

rl secret get db --reveal    # requires the access right

Grants: explicit per-object sharing

A Grant is a namespaced kind that opens a single owned object to specific subjects. It is the only way a peer gains:

  • SSH access to a workstation you own (rights: [ssh]), or
  • value access to a secret you own (rights: [access]).

There is no namespace-wide SSH fan-out: SSH to a workstation is ownership (automatic) + Grants (explicit), nothing else.

Create and revoke Grants with the share/unshare commands rather than hand-writing them:

rl workstation share my-box --with user:alice@acme.com
rl workstation unshare my-box --with user:alice@acme.com

Grant spec

apiVersion: core.ringleader.dev/v1
kind: Grant
metadata:
  name: my-box-to-alice
  namespace: dev
spec:
  target:
    kind: Workstation       # Workstation | Secret
    matchName: my-box       # exact name, or "*" for every object of the kind
  subjects:
    - user:alice@acme.com   # beneficiaries
  rights:
    - ssh                   # ssh (Workstation) | access (Secret)
FieldTypeDescription
target.kindstringRequired. Workstation or Secret.
target.matchNamestringExact object name, or * for every object of the kind. (Exactly one of matchName/matchLabels.)
target.matchLabelsmapSelect potentially many targets by label.
subjects[]stringBeneficiaries (user:…, sa:…, workload:…, node:…; system:authenticated is admin-only).
rights[]stringssh for a Workstation, access for a Secret.

Owner scope vs. admin scope

Every Grant carries a scope, set by the control plane from the rights you actually held when you created it, never trusted from the client:

  • owner: created by share/unshare. It lights up a target only while you still own it, so a Grant can never hand out more than you could yourself.
  • admin: created with --admin, and requires namespace-admin rights. It widens SSH only: an admin-scope Grant reaches any workstation in the namespace. It does not widen secret access; an admin-scope Grant on a secret still works only for secrets you own, because an admin has no authority over a teammate’s plaintext.

That second half is worth knowing before you rely on it. Naming a teammate’s existing secret is refused outright when you create the Grant, and says why. Every other shape is accepted and then quietly covers only your own secrets: a --all share, and a Grant you write by hand that selects by labels or names a secret nobody has created yet. rl secret access <name> shows the owner and every Grant currently in force.

This “a Grant never grants more than its creator could grant directly” rule is re-checked continuously, so a Grant stops applying if you lose ownership of its target, except an admin-scope SSH Grant, which was never bounded by ownership in the first place, and which keeps applying even if you later lose your admin rights.

See also