7.4 Security roles and step-up
Included from Basic tierpacks:security-governance
After this you can: grant the two security roles beside the path model, meet the step-up refusal, enrol a factor, elevate, act, and let the elevation expire.
Before you start — tick each one:
- Lesson 7.2 — Users, groups, rules, policies (
opschain learn 7.2)
In the GUI: Security administration under Governance holds the role grants and the git identity policy; any governance action that hits the step-up floor opens the elevation prompt in place and retries the action once you have elevated.
Lesson 7.2's path model gates node path + action name. Two things sit beside it: a pair of security roles for the decisions the path model cannot express, and step-up — a second ceremony that proves you are still you, right now before the most sensitive verbs. This lesson grants the roles, then walks the ceremony end to end.
1. Two roles beside the path model
A git-remote change has two dimensions the path model does not see — which remote, which rev — and those are gated by the git identity policy, owned by a dedicated role (separation of duties: your ops admin is not automatically your security admin):
security_administrator— reads and writes the security-domain objects: the git identity policy entries, the estate-wide allow-all switch, the seeded review queue, and the role grants themselves. Delegable per project, so a project security administrator manages their project's entries only.auditor— metadata-only read over everything: nodes, changes, policies, grants and the event trail, with zero write verbs, masked forms everywhere and no unmask privilege. The role verifies controls, not material. Granting it requirespacks:security-governance.
Two grant lanes, both audited: map a directory group to the role name through identity-provider group mappings (the enterprise lane — leavers lose the role with their account; lesson 7.5), or a local grant row — the break-glass lane, flagged loudly in the audit trail:
opschain security roles grant security_administrator alice
opschain security roles grant security_administrator bob --project acme
Granted security_administrator to alice (scope global) as grant 019a...
Granted security_administrator to bob (scope acme) as grant 019a...
opschain security roles list shows the local grants and
opschain security roles revoke <grant-id> ends one. Superusers retain
access to everything; granting and revoking these roles is itself a
security-domain act (api:security_role_grants:create). The instrument
the security administrator wields is opschain security git-policies
(opschain docs cli/security-git-policies; the use-time behaviour is
lesson 3.3).
2. What sits on the step-up floor
The platform pins floors per verb — customer policy can add step-up
(an approval policy's require_step_up), never remove it. The floors
you will meet in this track:
governance.attest,governance.campaign_decision,governance.campaign_close,governance.legal_hold,governance.waiver_grant,governance.work_item_completemetadata.protective_write— only when a write touches a protective field such ascore.criticalitychange_governance.policy_write,change_governance.owner_write, andchange_governance.executefor creates matched by arequire_step_uppolicy- the platform's own sensitive verbs — platform halt and resume, licence apply, authorisation-policy writes, user disable and enable (single and bulk), service-account creation and automation reassignment
Break-glass accounts and API keys are exempt — the floor binds
interactive sessions. Break-glass is deliberately not on the floor:
it is the exemption, carrying its own loud ceremony
(opschain learn governance-approvals).
Beyond per-object flags, you can attach step-up to any subtree you
govern by setting the bit on an authorisation rule
(packs:security-governance):
opschain admin authz rules create --path /projects/payments \
--readable --updatable --executable --require-step-up --name "payments guard"
created authorisation rule /projects/payments (0199d1…)
Any write-shaped action the rule matches (update, delete, execute) then
demands a live elevation — reads too, if you add --step-up-on-read —
and the refusal names your rule. The composition only ever adds: no rule
can take a verb off the platform floor.
3. Meet the refusal
Without a live elevation, a floored verb answers the typed
failed_precondition with reason ERROR_REASON_STEP_UP_REQUIRED
naming the verb — and the CLI prints the hint telling you exactly what
to do next:
opschain governance campaigns decide <campaign-id> <item-id> --decision retain
step-up required (verb governance.campaign_decision)
hint: elevate this session - opschain elevate --totp-code <code>
A session that has never enrolled a factor answers
ERROR_REASON_MFA_ENROLMENT_REQUIRED instead, with the enrolment hint.
4. Enrol a factor (once)
Enrolment is verify-to-activate: enrol, scan the provisioning URI into your authenticator, activate with a current code. The recovery codes are shown exactly once:
opschain api v2 call AuthService/EnrolTOTPFactor -d '{"label":"cli"}'
{"factor_id":"019a0e11-…","provisioning_uri":"otpauth://totp/OpsChain:alice?secret=…&issuer=OpsChain"}
opschain api v2 call AuthService/ActivateTOTPFactor -d '{"factor_id":"019a0e11-…","code":"123456"}'
{"recovery_codes":["…","…","…","…","…","…","…","…"]}
Store the recovery codes now — they are hashed at rest and never shown again.
5. Elevate, act, expire
Elevation takes one proof — a six-digit TOTP code from an active
factor, or an unspent recovery code — and opens the elevation window
(mfa.elevation_window, default 15 minutes, non-sliding: using it
does not extend it):
opschain elevate --totp-code 123456
session elevated (totp) until 2026-10-15T15:02:11Z
opschain governance campaigns decide <campaign-id> <item-id> --decision retain # now passes
When the window lapses mid-task, the next floored verb refuses with
reason elevation_expired — elevate again and re-run. Elevations are
audited (api:auth:step_up; failures draw warn:auth:step_up_failed),
and opschain tokens list shows the session's strength while it lasts.
6. Federated sessions
Sessions from an idp-mode identity provider elevate through the
provider instead of a platform factor: the refusal carries mechanism
idp and a re-authentication URL —
/auth/login/<provider>?step_up=<session-id> — open it, complete the
provider's own MFA, and the session returns elevated. Which mechanism a
provider uses is the connection's step-up mode (lesson 7.5).
Check yourself
- Which role may edit the git identity policy, and which may read everything and write nothing?
- What does a floored verb print before you elevate, and how long does an elevation last?
- Does
opschain elevate --totp-code …print the window's expiry, and doesopschain events --type api:auth:step_upshow it? The Academy'sstep-upexercise reads that event raised since the attempt began. - Verified version:
opschain academyruns thestep-upexercise against this lesson.
What you learned
security_administratorowns the git identity policy and the role grants;auditorreads controls, never material.- Roles arrive through directory group mappings or a loudly audited local grant.
- Floors are per verb and only ever added to;
--require-step-upon a rule extends them to a subtree. - The refusal names the verb and the remedy; enrol once, then
opschain elevateopens a fixed window. - Federated sessions elevate at their provider.
Next: lesson 7.5 — Federated sign-in (opschain learn 7.5), or back to your track (opschain learn).