Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4fd4ae09c | ||
|
|
ecb2ae0995 |
1 changed files with 285 additions and 0 deletions
285
docs/research/steward-orders-prior-art.md
Normal file
285
docs/research/steward-orders-prior-art.md
Normal file
|
|
@ -0,0 +1,285 @@
|
||||||
|
# Steward orders prior art: privileged agents, break-glass, two-person rule
|
||||||
|
|
||||||
|
Serves Eureka campaign `eureka-body`, follow-up `eureka-root-idunn-orders`, under
|
||||||
|
ruling `eureka-root-idunn-orders-break-glass` (operator 2026-10-31: Eureka issues
|
||||||
|
typed orders to Idunn, run as root and posted to #operator, plus an announced,
|
||||||
|
brakeable break-glass order). Imagination pass, session eureka-self-2026-10-31.
|
||||||
|
|
||||||
|
Written from recall of the systems' documentation, not re-fetched on
|
||||||
|
2026-10-31; each claim names where to check it. Facts first, then what each one
|
||||||
|
says about this design.
|
||||||
|
|
||||||
|
## 1. Privileged agents that take requests from less privileged callers
|
||||||
|
|
||||||
|
### nix-daemon (closest shape)
|
||||||
|
|
||||||
|
- Unprivileged users never run as root: the Nix client talks to `nix-daemon`
|
||||||
|
over a Unix socket (`/nix/var/nix/daemon-socket/socket`), and the daemon reads
|
||||||
|
the caller's uid from the socket's peer credentials. `nix.conf`
|
||||||
|
`allowed-users` says who may connect at all; `trusted-users` says who may
|
||||||
|
pass privileged settings. (nix.conf manual, "allowed-users",
|
||||||
|
"trusted-users".)
|
||||||
|
- The daemon accepts a closed set of typed operations (the worker protocol),
|
||||||
|
not shell strings.
|
||||||
|
- Lesson: a local Unix socket plus `SO_PEERCRED` is the established way for a
|
||||||
|
root agent to know which local principal is asking. The principal is derived
|
||||||
|
from the kernel, never declared by the caller.
|
||||||
|
|
||||||
|
### Postfix maildrop and pickup
|
||||||
|
|
||||||
|
- Unprivileged `sendmail` writes into the `maildrop` queue through the setgid
|
||||||
|
`postdrop` helper; the privileged `pickup` daemon reads each file, checks it,
|
||||||
|
and copies it into its own queue before anything acts on it. (Postfix
|
||||||
|
architecture overview, `pickup(8)`, `postdrop(1)`.)
|
||||||
|
- Lesson: the submitter's inbox is not the record. The privileged side copies
|
||||||
|
the request into a store only it can write, and only that copy is acted on.
|
||||||
|
|
||||||
|
### SaltStack
|
||||||
|
|
||||||
|
- The master publishes jobs to minions that run as root; each job gets a job
|
||||||
|
id (jid) from the master, not from the caller, and returns are kept in the
|
||||||
|
job cache (`salt-run jobs.lookup_jid`). (Salt docs, "Job management".)
|
||||||
|
- `publisher_acl` and external auth (`external_auth`) grant a user named
|
||||||
|
functions only, for example `pkg.install` and `service.restart`, optionally
|
||||||
|
with argument patterns. (Salt docs, "Publisher ACL system", "External
|
||||||
|
Authentication System".)
|
||||||
|
- CVE-2020-11651 and CVE-2020-11652: the master's request server exposed
|
||||||
|
methods that skipped authentication, which gave unauthenticated root on every
|
||||||
|
minion. (Salt advisory, April 2020.)
|
||||||
|
- Lesson: grant typed functions with argument grammar, not a shell; ids belong
|
||||||
|
to the authority; and a root agent's intake is its attack surface, so keep it
|
||||||
|
local and small.
|
||||||
|
|
||||||
|
### Ansible and AWX (Tower)
|
||||||
|
|
||||||
|
- Modules (`ansible.builtin.apt`, `ansible.builtin.systemd_service`) take typed
|
||||||
|
parameters and report changed, ok or failed; `become` is per task.
|
||||||
|
- AWX job templates with RBAC let a user launch only pre-approved templates,
|
||||||
|
with survey fields as typed, validated parameters; the activity stream and
|
||||||
|
job output are the audit. (AWX docs, "Job Templates", "Surveys", "Activity
|
||||||
|
Stream".)
|
||||||
|
- Lesson: "a named operation with validated parameters, launched by a principal
|
||||||
|
who may launch only that" is the mature shape of a typed order.
|
||||||
|
|
||||||
|
### NixOS deploy tools (deploy-rs, colmena, nixos-rebuild)
|
||||||
|
|
||||||
|
- Activation switches a whole system closure; deploy-rs "magic rollback" rolls
|
||||||
|
the activation back unless the deployer confirms it within a timeout over a
|
||||||
|
fresh connection. (deploy-rs README, "Magic Rollback".)
|
||||||
|
- Lesson: a change that can cut off its own operator wants a confirm-or-revert
|
||||||
|
window. For Idunn this already exists for releases (continuity, fencing); it
|
||||||
|
matters for any later host-level order (reboot, network) and for break-glass.
|
||||||
|
|
||||||
|
### polkit and sudoers allowlists
|
||||||
|
|
||||||
|
- polkit rules can grant a user `org.freedesktop.systemd1.manage-units` for
|
||||||
|
named units and verbs only (rules see `action.lookup("unit")` and `"verb"`).
|
||||||
|
(polkit(8), systemd's polkit actions.) The mind units already use a polkit
|
||||||
|
start grant.
|
||||||
|
- sudoers `Cmnd_Alias` allowlists and `log_output` I/O logs are what the interim
|
||||||
|
root (`gamecult-ops deploy/eureka-root`) uses today.
|
||||||
|
- Lesson: a unit allowlist is ordinary practice. polkit alone would give
|
||||||
|
restarts without a single order record, brake gate or #operator post, so it
|
||||||
|
is the smaller tool but not the ruled one.
|
||||||
|
|
||||||
|
## 2. Break-glass access
|
||||||
|
|
||||||
|
- The common pattern (healthcare "break the glass", cloud emergency accounts,
|
||||||
|
Teleport access requests, AWS's guidance on emergency access): the actor
|
||||||
|
states a justification; use raises a loud alert at once; access is
|
||||||
|
time-boxed; the session is recorded; every use is reviewed afterwards.
|
||||||
|
(Teleport docs, "Access Requests" and "Session Recording"; AWS Well-Architected
|
||||||
|
security pillar, emergency access.)
|
||||||
|
- What these systems assume and Yggdrasil lacks: a human on call who sees the
|
||||||
|
alert in minutes. With the operator away, the review is done by Minds and
|
||||||
|
members in #operator, and the alert reaches them only if chat is up.
|
||||||
|
- Lesson: break-glass is defended by visibility and review, not by its
|
||||||
|
intake. It must announce before it runs and keep a full record of what ran.
|
||||||
|
|
||||||
|
## 3. Two-person rule and veto windows
|
||||||
|
|
||||||
|
- Two-person integrity (military nuclear surety; banking dual control): no
|
||||||
|
single person can complete a critical action; a second must positively
|
||||||
|
approve. Software forms: required reviewers on protected branches; Google's
|
||||||
|
multi-party authorization for production changes; Teleport requests needing
|
||||||
|
N approvers.
|
||||||
|
- A veto window is weaker and different: lazy consensus (Apache "lazy
|
||||||
|
consensus": silence means assent after a stated period). It protects only if
|
||||||
|
someone is watching during the window, and an attacker who controls the
|
||||||
|
timing picks the quiet hour.
|
||||||
|
- The ruling in force (`brake-anyone-pulls-motion-releases`) is a veto shape:
|
||||||
|
anyone pulls at once, only a motion releases. Positive approval would need a
|
||||||
|
second principal who is awake; with the operator gone that is another Mind,
|
||||||
|
and Minds read the same chat an injection arrives through.
|
||||||
|
- Lesson: for typed orders the type is the defence (closed operations,
|
||||||
|
validated arguments, an allowlist); for break-glass the window plus the
|
||||||
|
record is the defence, and its strength is the attention of whoever reads
|
||||||
|
#operator. Say so where the window is configured.
|
||||||
|
|
||||||
|
## 4. What this gives the first cut
|
||||||
|
|
||||||
|
- Authenticate by kernel peer credentials on a root-owned local socket; derive
|
||||||
|
the principal and the issuing unit, never accept a declared requester.
|
||||||
|
- Idunn assigns the order id and keeps the only record in its own store;
|
||||||
|
the caller's request is not the record.
|
||||||
|
- Orders are a closed set of typed operations with argument grammar and an
|
||||||
|
allowlist in root-owned configuration.
|
||||||
|
- Every order and its result are posted; the store is the record and the post
|
||||||
|
is its projection.
|
||||||
|
- Break-glass, when it lands, waits on an announcement and a window, and is
|
||||||
|
recorded in full.
|
||||||
|
|
||||||
|
## 5. Windows hosts: Starfire and Raven
|
||||||
|
|
||||||
|
Serves follow-up `steward-orders-windows-hosts` under ruling
|
||||||
|
`remote-admin-on-desktops` (operator 2026-10-31: "We'll need a way to do this
|
||||||
|
kind of thing remotely"). The trigger: on 2026-10-31 she approved through UAC,
|
||||||
|
sitting at Starfire, an elevated script that added an inbound firewall rule for
|
||||||
|
Ollama on the WireGuard interface and registered a SYSTEM startup task. She is
|
||||||
|
away from today, possibly for months; a hard reboot already needs a call.
|
||||||
|
|
||||||
|
### Body, as read on 2026-10-31 (read-only)
|
||||||
|
|
||||||
|
- Both hosts answer TCP 22 and 3389 from Yggdrasil over WireGuard: Starfire
|
||||||
|
10.77.0.2, Raven 10.77.0.4. WinRM is stopped (start type Manual) on both.
|
||||||
|
winget v1.29 is present; choco is not on Raven.
|
||||||
|
- Yggdrasil holds no SSH client config and no private key for either host:
|
||||||
|
`gamecultadmin` has only `authorized_keys` and `known_hosts`, root has no
|
||||||
|
client key, and the mind users have no home. Nothing on Yggdrasil can reach a
|
||||||
|
Windows shell today.
|
||||||
|
- Starfire: sshd runs, the stock `Match Group administrators` stanza is in
|
||||||
|
force, `C:\ProgramData\ssh\administrators_authorized_keys` does not exist and
|
||||||
|
the operator's user has no `authorized_keys`. Its admins are the operator's
|
||||||
|
user (`Meta`) and the built-in Administrator. No key reaches Starfire; RDP,
|
||||||
|
Sunshine/Moonlight and her own UAC click are the only privileged paths in.
|
||||||
|
- Raven: `administrators_authorized_keys` holds one key, commented
|
||||||
|
`192.168.1.84 access key`, whose private half is on Starfire (the `raven`
|
||||||
|
host entry in the operator's `~/.ssh/config`). A session with it is
|
||||||
|
`BUILTIN\Administrators` at High Mandatory Level: Starfire already holds raw,
|
||||||
|
unaudited admin on Raven.
|
||||||
|
- Raven already runs Idunn's host actuator (`idunn-host serve`, task
|
||||||
|
`GameCult-Idunn-Host`, at logon, interactive, Limited): it dials
|
||||||
|
`10.77.0.1:17890/udp` over CultNet RUDP, accepts only requests Idunn's
|
||||||
|
service identity signed, and signs its reports with its own host identity
|
||||||
|
(`docs/host-actuator.md`; runbook `gamecult-ops/runbooks/idunn-host-raven.md`).
|
||||||
|
It is unelevated by design: Muninn must live in the desktop session. Starfire
|
||||||
|
has no actuator.
|
||||||
|
- Memory, not re-proven here: Starfire has frozen under load (PSU or GPU
|
||||||
|
suspected), and nothing that must survive may depend on Starfire or Raven
|
||||||
|
staying up (`workstation-load-budget.md`, 2026-10-29).
|
||||||
|
|
||||||
|
### PowerShell JEA (Just Enough Administration)
|
||||||
|
|
||||||
|
- A session configuration (`.pssc`, registered with
|
||||||
|
`Register-PSSessionConfiguration`) maps caller groups to role capabilities
|
||||||
|
(`.psrc`); a role lists `VisibleCmdlets` with per-parameter `ValidateSet` or
|
||||||
|
`ValidatePattern`, the session runs in `NoLanguage` mode and executes as a
|
||||||
|
temporary virtual account (`RunAsVirtualAccount`) or a gMSA, so the caller
|
||||||
|
holds no admin right. `TranscriptDirectory` records every session.
|
||||||
|
(Microsoft Learn: "JEA overview", "JEA role capabilities", "JEA session
|
||||||
|
configurations", "Auditing and reporting on JEA".)
|
||||||
|
- JEA rides WinRM. SSH-based PowerShell remoting has not supported endpoint
|
||||||
|
configurations or JEA (Microsoft Learn, "PowerShell remoting over SSH",
|
||||||
|
limitations; the current page may have changed, check before relying on it).
|
||||||
|
- Microsoft's own warning: a role that exposes a command able to run arbitrary
|
||||||
|
code (`Start-Process`, `Invoke-Expression`, `Register-ScheduledTask` with a
|
||||||
|
free action, `New-Service` with a free path) is an escape to the run-as
|
||||||
|
account ("JEA security considerations").
|
||||||
|
- On a workgroup desktop, WinRM needs a listener (HTTPS for anything but
|
||||||
|
Kerberos), a local Windows account for the caller, and that account's
|
||||||
|
password or certificate mapping held by the caller.
|
||||||
|
- Lesson: JEA is the platform's answer to "typed, parameter-validated admin
|
||||||
|
with a transcript", and its role file is the same shape as a typed order. It
|
||||||
|
brings WinRM (off today), a Windows credential that would live on Yggdrasil,
|
||||||
|
and no order id, brake or post: those would still be Idunn's, so the grammar
|
||||||
|
would live twice, once in PowerShell attributes on each host and once in the
|
||||||
|
order record.
|
||||||
|
|
||||||
|
### Ansible `win_*` modules over WinRM or SSH
|
||||||
|
|
||||||
|
- `ansible.windows.win_service` (state started, stopped, restarted; start
|
||||||
|
mode), `community.windows.win_firewall_rule` (name, direction, action,
|
||||||
|
protocol, localport, remoteip, profiles, interfacetypes),
|
||||||
|
`community.windows.win_scheduled_task` (actions, triggers, principal, run
|
||||||
|
level), `ansible.windows.win_package` and the community `win_chocolatey` and
|
||||||
|
winget modules. Each takes typed parameters, declares desired state, is
|
||||||
|
idempotent and reports changed, ok or failed. Transport is WinRM or OpenSSH
|
||||||
|
with `ansible_shell_type: powershell`; over OpenSSH an admin key already has
|
||||||
|
the elevated token, so no `become`. (Ansible docs: "Windows Remote
|
||||||
|
Management", "Windows SSH", module pages.)
|
||||||
|
- Lesson: these modules are the order vocabulary to copy, with desired-state
|
||||||
|
semantics ("rule X exists with these fields") rather than imperative verbs.
|
||||||
|
Agentless push means the controller holds admin credentials for every host,
|
||||||
|
which is the shape the brief rules out.
|
||||||
|
|
||||||
|
### Intune Remediations and Endpoint Privilege Management
|
||||||
|
|
||||||
|
- The Intune Management Extension runs on the device as SYSTEM, polls the
|
||||||
|
service outbound over HTTPS, runs a detection script and, when it reports
|
||||||
|
non-compliant, the remediation script, and returns both outputs to the
|
||||||
|
console. No inbound port; the device authenticates by its enrolment
|
||||||
|
certificate. (Microsoft Learn: "Remediations", "Intune Management
|
||||||
|
Extension".)
|
||||||
|
- Endpoint Privilege Management adds support-approved elevation: the user asks
|
||||||
|
to elevate one file, the request reaches the console, an admin approves
|
||||||
|
there, and the device elevates that file hash only, logged. (Microsoft
|
||||||
|
Learn: "Endpoint Privilege Management", "support approved elevation".)
|
||||||
|
- Lesson: the mature Windows pattern for an absent administrator is a SYSTEM
|
||||||
|
agent that dials out, executes only what its authority signed and reports
|
||||||
|
back, and EPM is the nearest thing to "remote approval in place of a UAC
|
||||||
|
click". It is the shape `idunn-host` already has on Raven, minus elevation.
|
||||||
|
|
||||||
|
### Windows OpenSSH and administrators
|
||||||
|
|
||||||
|
- For a member of Administrators, sshd reads only
|
||||||
|
`%ProgramData%\ssh\administrators_authorized_keys` (ACL Administrators and
|
||||||
|
SYSTEM), and a key-authenticated session gets the full token: UAC does not
|
||||||
|
apply. Observed on Raven at High Mandatory Level. (Win32-OpenSSH wiki;
|
||||||
|
issue #1942 on the file's location.)
|
||||||
|
- Forced commands: `ForceCommand` in a `Match` block is the documented route;
|
||||||
|
whether `command=` in an authorized_keys line is honoured on Windows has an
|
||||||
|
open report of it being ignored (Win32-OpenSSH issue #473). Unproven on
|
||||||
|
these hosts.
|
||||||
|
- Lesson: an admin key is a remote UAC bypass with nothing but sshd's event log
|
||||||
|
for a record. As an order transport it needs a forced command that parses
|
||||||
|
the order on the host, and that parser is the whole boundary.
|
||||||
|
|
||||||
|
### How remote approval replaces a local UAC click
|
||||||
|
|
||||||
|
- Entra PIM: a user eligible for a role activates it for a bounded time with a
|
||||||
|
justification, optional MFA, and optional approval by a named approver who
|
||||||
|
is notified and approves in the portal or app; every activation is logged.
|
||||||
|
(Microsoft Learn, "Privileged Identity Management".)
|
||||||
|
- CyberArk dual control: use of a privileged account needs an approver's
|
||||||
|
confirmation in the vault's web or mobile client; sessions run through a
|
||||||
|
recording proxy (PSM). (CyberArk docs: "Dual control", "Privileged Session
|
||||||
|
Manager".)
|
||||||
|
- Teleport access requests: a role is requested with a reason; reviewers
|
||||||
|
approve or deny, thresholds can require N approvals, and plugins put the
|
||||||
|
request in Slack, Mattermost or PagerDuty with approve buttons; the grant is
|
||||||
|
time-boxed and sessions are recorded. (Teleport docs: "Access Requests",
|
||||||
|
"Access Request plugins".)
|
||||||
|
- Shared shape: the approval arrives over an out-of-band channel authenticated
|
||||||
|
as the approver, is scoped to one request, expires, and is recorded beside
|
||||||
|
the request. A request nobody answers is denied, never run. Shared
|
||||||
|
assumption: an approver who answers in minutes.
|
||||||
|
- Lesson for an operator away for months: positive approval is only as live as
|
||||||
|
her reachability. Where her approval is kept, the channel is her Matrix
|
||||||
|
account replying in #operator to one order id (Synapse authenticates the
|
||||||
|
sender; a Mind cannot forge her mxid without root on Yggdrasil), with an
|
||||||
|
expiry after which the order is refused. Where it is not, the order's type
|
||||||
|
is the defence, as for Linux (section 3).
|
||||||
|
|
||||||
|
### What this gives the Windows design
|
||||||
|
|
||||||
|
- A SYSTEM agent on each host dials out to an authority that signs every
|
||||||
|
request; no inbound port, no Windows credential and no admin key on
|
||||||
|
Yggdrasil (Intune, Salt minion, `idunn-host`).
|
||||||
|
- The four Ansible modules become typed, desired-state orders, and JEA's
|
||||||
|
warning becomes the grammar rule: no order field names a free program,
|
||||||
|
script, or path to run outside a root the host binding declares.
|
||||||
|
- A Windows host is often absent (a frozen Starfire, a reboot waiting on a
|
||||||
|
call): an order to an unattached host is refused at once, never queued to
|
||||||
|
fire days later.
|
||||||
|
- Approval, where kept, is a Matrix reply from her account to one order id,
|
||||||
|
scoped and expiring; anything outside the grammar is break-glass.
|
||||||
Loading…
Reference in a new issue