7.2 Users, groups, rules, policies
Included from Basic tier
After this you can: create a user, watch them hit the wall, grant read-only access with a rule, and widen it deliberately through policies, assignments and groups — one action at a time.
Before you start — tick each one:
- Lesson 7.1 — Denied means not found; the denial event (
opschain learn 7.1)
In the GUI: Users & Groups in the Operations area creates the user and the group; a local user changes their own password under the profile menu's Preferences → Password; Manage security under Governance holds the rules, policies and assignments.
OpsChain decides access with a small path-based model: a rule is a
node path plus four grantable actions (readable, updatable,
deletable, executable), a policy is a named bundle of rules, and
an assignment binds a policy to a username, a group name, or
everyone. A principal's authority is the union of the rules on every
policy assigned to it — directly or through its groups. This lesson
walks the loop in the order the Academy's least-privilege exercise
checks it: the user, then the refusal, then the rule. You need a
superuser login (lesson 0.1); the identity and authorisation verbs ride
that login (updatable on /admin is the determination — a superuser
holds it by construction).
1. Create a user
DANA_PASSWORD=… opschain admin users create dana --password-env DANA_PASSWORD
Created dana (provider local)
The password never travels on the command line: --password-env names a
variable, --password-prompt reads without echo, and with neither the
server generates one and returns it exactly once. (A server running the
hardened security posture refuses local users that are not break-glass
— workforce identities belong to the directory there; this lesson
assumes the evaluation posture of the server you installed in
lesson 0.1.)
Directory users need no create step at all: they appear at their first
sign-in through their connection's login leg (opschain login --provider <code>), or ahead of it via the connection's sweep
(opschain admin identity providers sweep <code>) — and either way can
be grouped and assigned before they ever sign in (lesson 7.5).
2. Hit the wall
Log in as the new user and look for the project:
opschain login --server https://opschain.example.com --username dana
opschain whoami
Server: https://opschain.example.com
Identity: dana
Provider: local
Superuser: false
opschain nodes show demoenterprise
Error: the server answered HTTP 404: The requested resource could not be found
No assignment, no authority: every node answers not found, exactly as
lesson 7.1 promised, and opschain nodes list is an empty estate.
Nothing is misconfigured — dana simply has not been granted anything
yet.
The refusal is already on the record. Back as the superuser:
opschain login --username admin
opschain events --type-search authz --since 10m
WHEN ID TYPE NODE USER CORRELATION DETAIL
2026-10-15T04:14:02Z 019a0d7e… warn:authz:denied - - - NodeService/GetNode refused: readable on /projects/demoenterprise (principal 019a0d7e-…, session)
The row names the refused procedure and the /projects/demoenterprise
path. Remember the throttle: one event per refused procedure per five
minutes, estate-wide — if you repeat the refused lookup inside the
window, no second row appears, and if someone else was refused the same
lookup moments before you, yours is the one that was swallowed. Wait
out the window and try again.
3. Grant read-only access with a rule
Still as the superuser, create the narrowest useful rule: the project, readable, nothing else:
opschain admin authz rules create --path /projects/demoenterprise \
--readable --name "demoenterprise read-only"
created authorisation rule /projects/demoenterprise (0199c2…)
A rule grants only what it names — every action defaults to denied, so a rule created with no action flags grants nothing. Now carry it to dana through a policy and an assignment:
# demo-readers.yaml
name: demo-readers
description: Read the demo estate
opschain admin authz policies create -f demo-readers.yaml
opschain admin authz policies attach-rule demo-readers --rule 0199c2…
opschain admin authz policies assign demo-readers --user dana
replaced the assignments of "demo-readers": now 1 assignment(s)
assignments:
KIND ASSIGNEE ID
user dana 0199c4…
assign replaces the whole assignment set — read it first with
policies assignments when editing an existing policy. A policy's rules
can also be declared inline in the create/update YAML; attach-rule is
the incremental lane.
Now as dana again: opschain nodes list shows the demoenterprise
subtree and nodes show demoenterprise answers — but creating a change
still answers not found:
opschain runs create change demoenterprise/environments/dev/assets/web --action deploy
Error: the server answered HTTP 404: The requested resource could not be found
That is the exercise's end state: a user who can see the project and not change it, with the refusal on the record.
4. Widen it one action at a time
The four actions gate different verbs. Watch each wall fall by adding one flag at a time on a path:
--readable— visibility: lists include the subtree, shows answer. Without it everything else is moot — the lookup answers not found before any other check runs.--executable— running work: creating changes and workflow runs against the subtree. Readable without executable sees the estate butruns createanswers not found.--updatable— writing: creating and editing nodes, properties, settings, triggers, schedules. Read-and-run operators without it cannot alter what they operate.--deletable— the delete lifecycle (admin nodes deleteand friends authorise read plus deletable).
opschain admin authz rules create --path /projects/demoenterprise/environments/dev \
--readable --executable --name "dev read+run"
created authorisation rule /projects/demoenterprise/environments/dev (0199c7…)
Attach it to the policy and dana may run in dev while still only looking at prod. A policy built from narrow rules like this is how "may run in dev, may only look at prod" is actually spelled.
5. Groups: the second lane
Assigning to people one by one does not scale; assign to a group and
manage membership instead. Local groups are created empty; members are
added by username, which the CLI resolves to the exact user id (--provider
picks one of a same-named pair):
opschain admin groups create app-operators
opschain admin groups add-member app-operators dana
opschain admin authz policies assign demo-readers --user dana --group app-operators
Created local group app-operators (no members yet — 'admin groups add-member app-operators <username>')
replaced the assignments of "demo-readers": now 2 assignment(s)
assignments:
KIND ASSIGNEE ID
user dana 0199c4…
group app-operators 0199c5…
Either assignment alone would have sufficed; the union rules. Read the identity back:
opschain admin users show dana
Username: dana
Id: 019a0d7e-10c2-7f7a-8b53-3b1c9a4f2e07
Provider: local
Break-glass: -
Disabled: -
Created: 2026-10-15T04:12:44Z
Local groups: app-operators
Groups: app-operators
Groups is the effective set — directory-mirror groups plus local
memberships — and is what assignments match against. Local group names
share one namespace with directory group names, so a policy assigned to
a group covers both populations, and adding a local membership in a
directory-named group grants that group's policies without touching the
directory. Deleting a group is refused by name while policy assignments
still reference it.
Two facts for mixed estates: a user assignment is per-identity, so a
local account sharing a directory account's name inherits nothing
automatically (when a username is held by more than one provider,
--user is refused as ambiguous and --user-id picks the exact one);
and group-conferred authority does not cross providers either — a
mirror that should inherit through a group needs its own local
membership. The recommended break-glass fire door is a dedicated
local-only account with its own name and its own policy; the admin
surfaces specifically need updatable on /admin.
Policy administration is not delegated through rules at all: it is the
security_administrator role (lesson 7.4). A global grant manages
every policy; opschain security roles grant security_administrator <user> --project demo makes a project security administrator who manages
only the policies whose rules all sit under /projects/demo. The rule
paths they author must also be readable to them, so they need an ordinary
read rule on /projects/demo too (an auditor role's read access is not
enough). Other policies answer not found, and a path outside their reach,
a top-level |, or a wildcard segment followed by literal ones is refused
with You do not have permission to manage rules for path: …. A rule on
…/authorisation_policies — the upstream way — grants nothing here; the
upgrade converts anyone who held one (lesson 7.4).
Check yourself
- After the read-only rule is assigned, what does the user see for a listing and what for a create — and which event records the refusal?
- Verified version:
opschain academyruns theleast-privilegeexercise against this lesson — the user, the refusal, then the rule, each later than the one before.
What you learned
- Rules name a path and the actions they grant; policies bundle rules; assignments bind policies to users, groups or everyone.
- A new user is refused everywhere until assigned, and the refusal is already on the record as
warn:authz:denied. - A read-only rule makes the estate visible and nothing else; every further action is a deliberate flag.
- Groups are the scalable lane; effective groups are what assignments match, across local and directory populations.
assignreplaces the set — read it before editing.
Next: lesson 7.3 — Tokens, automation principals and the review export (opschain learn 7.3), or back to your track (opschain learn).