Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Role-Based Access Control

A cluster reached over its API requires authentication, and every request is checked against the privileges of the authenticated user.

Privileges live in the grant keyword of a usr object, as a whitespace-separated list of grant expressions. A grant is a role, optionally scoped to namespaces:

[DEFAULT]
grant = guest:prod* admin:test*

That user is a guest in every namespace whose name starts with prod, and an admin in those starting with test.

A role given without a scope applies cluster-wide:

[DEFAULT]
grant = operator

Namespace roles

These roles are the ones you scope to namespaces. Each inherits the previous.

guest

Read-only. List objects, read their configuration, status, schedule, resource info and logs.

operator

Inherits guest, and may change the state of what already exists: start, stop, restart, freeze, unfreeze, shutdown, switch, giveback, abort, run, clear, refresh status and resource info.

An operator runs services. An operator does not create, delete or provision them.

admin

Inherits operator, and may change what exists: create and delete objects, edit their configuration, provision and unprovision their resources, purge them, and manage the keys of cfg and sec objects.

Cluster roles

These roles are not namespace-scoped.

root

All privileges. A user granted root can read and change the system’s files and run commands as the root system user, so it is the grant to give last and audit first.

squatter

May create namespaces, and may write a namespace configuration. The creator is automatically granted admin on the namespace it creates.

A namespace configuration says what the objects of that namespace may take of what the cluster shares: how much of a pool, how many addresses of a network, how much of the node the slice they all run under may use. That is given from outside the namespace, which is why it is this role and not admin. Administering the objects of a namespace and rationing the cluster between namespaces are two jobs, and holding the first does not give the second: no admin grant, scoped or not, writes a namespace configuration.

$ om ns1/ config update --set claim#1.limit=500m   # as admin:ns1
403 need one of [squatter root] grant

prioritizer

May set object priority. Priority arbitrates between objects across namespace boundaries, which is why it is a cluster role rather than something an admin holds in its own namespace.

blacklistadmin

Reserved for clearing the client blacklist. The role is accepted in a grant, but no v3 endpoint requires it yet, so granting it currently confers nothing.

heartbeat

May use the relay handlers. A relay heartbeat authenticates on a foreign OpenSVC daemon with a user holding this role.

om daemon relay status

join, leave

May add a node to the cluster, or remove one from it, plus the reads a node performs while joining. These back the token minted on a node to enroll, which om cluster enroll --token uses to reach it:

om daemon auth --role join --duration 10m

om cluster enroll and om cluster evict themselves require the root grant.

Keyword rules

A role says which objects a user may write. Some keywords ask for more than the role, whatever the object, because of what setting them does to the node rather than to the object.

om <path> config doc says so, keyword by keyword:

$ om <path> config doc --kw container#1.run_args
	rbac:        Requires the root grant.

$ om <path> config doc --kw fs#1.type
	rbac:        Requires the root grant, except for the values flag.

The shape of the rules:

  • A trigger requires root, on every driver. pre_start and its siblings run a command of the user’s choosing on the node.
  • A driver group whose resources take something from the node requires root, opening only the keywords that say what the object does with what it was given. An ip resource is the example: which address and which link are the node administrator’s, while network, netns and expose are the object’s.
  • A driver group the policy says nothing about requires root. A driver added to om later is refused until a rule allows it, rather than allowed until someone remembers to refuse it.
  • Some rules read the value. A container’s type is open for the types that run an image under the container engine, and root for the ones that run on the node itself. A volume’s install is root only when its source is a file of the server.
  • A keyword can mean different things on different kinds. A pg_ limit on a namespace caps every object of that namespace and needs squatter; the same keyword on a service caps that service alone and is the object administrator’s.

A volume belongs to root, except its size

A volume object is the storage itself, where a service is what consumes it. Its resources were written when the volume was served — which pool served it, which device it exposes, what filesystem is on it — so none of it is the namespace administrator’s to edit, not even the flag filesystems that are open on a service.

DEFAULT.size is the exception. A namespace grows its own volume within the claim it holds on the pool, and rationing that growth is what the claim is for, so the size is written by the namespace and bounded by the claim.

Asking for different storage is a keyword of the service that consumes the volume, not of the volume.

A write answers for what it changes

A configuration you may not have written is not a configuration you may not change. The keywords already in it were written by whoever was allowed to write them, and only what a write changes is checked against the policy.

An object administrator can therefore fix a comment on a service that mounts a filesystem, without holding the root grant that mounting one asks for.

Creating an object has nothing to compare against, so every keyword of it is a change and answers for itself. Unsetting a keyword is a change like setting one: a user who may not set a keyword may not unset it either.

What counts as a change is what the value evaluates to, not what is written. A keyword can hold a reference, and moving what it refers to rewrites it without touching it:

[env]
cmd = /bin/evil          # written by the namespace administrator

[app#1]
pre_start = {env.cmd}    # written by root, and now running something else

That write is refused. References are followed however deep they go, and on every node the object runs on, because a keyword is written once per node and a reference that resolves the same here can resolve to something else on a peer.

➡️ See Also