Skip to main content
Version: v5.7.0

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: Governance → Security → the Administration tab holds the role grants and the git identity policy (shown to security administrators, auditors and superusers); 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 authorisation policies and rules themselves (the path model of lesson 7.2 — this role, not any rule path, is the gate on every policy, rule and assignment write), the git identity policy entries, the estate-wide allow-all switch, the review queues, and the role grants themselves. Delegable per project, so a project security administrator manages their project's entries and only the policies whose rules all sit under the project; the system policies (Superuser, Block) stay superuser-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 requires packs: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).

Coming from the upstream Rails server? Its project-level policy administrators held a rule on <path>/authorisation_policies. The upgrade converts every such holder into the matching grant automatically — the global prefix into a global grant, a project prefix into a project delegation, a deeper prefix into the enclosing project (marked widened), a group into a group-targeted grant — active at once and queued for the global security administrator's one-off post-upgrade review:

opschain security roles review list
opschain security roles review confirm 019a...
opschain security roles review revoke 019a...

The securing rules themselves are retired by the conversion and listed in its report (opschain admin security convert-policy-admins --report). 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_complete
  • metadata.protective_write — only when a write touches a protective field such as core.criticality
  • change_governance.policy_write, change_governance.owner_write, and change_governance.execute for creates matched by a require_step_up policy
  • 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 names the provider; the GUI's step-up dialog offers Re-authenticate at <provider>, which calls BeginIDPStepUp with your session's own token and opens the authorize_url it answers — complete the provider's own MFA there and the session returns elevated. A sign-in link cannot start a step-up: only the session's own token can. 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 does opschain events --type api:auth:step_up show it? The Academy's step-up exercise reads that event raised since the attempt began.
  • Verified version: opschain academy runs the step-up exercise against this lesson.

What you learned​

  • security_administrator owns the authorisation policies, the git identity policy and the role grants; auditor reads 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-up on a rule extends them to a subtree.
  • The refusal names the verb and the remedy; enrol once, then opschain elevate opens 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).