Skip to content

Enrolment code: memory-bounded scope for the verb; ntfy auth-file is the observed auth.db - #12614

Merged
gunbai-bot[bot] merged 5 commits into
mainfrom
session/fierce-deer-555-enrolment-scope
Sep 29, 2026
Merged

gunbai-bot[bot] merged 5 commits into
mainfrom
session/fierce-deer-555-enrolment-scope

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

Two defects found on release 02568b3 (broker listening), reported by proud-deer-538.

1. No cgroup memory bound

  • Symptom: fleet-converge run 36556990543 refused HostBudgetUnreadable. The seed derives its typed-module cache cap from memory.high/memory.max and refuses rather than guess, and an SSH session binds neither.
  • Fix: gunbc.auth.approval_device_enrolment_code_issue enrolment_code_issue_remote_argv now runs:
    sudo -n systemd-run --scope --uid=<operator> --gid=<operator> --property=MemoryMax=… --property=MemoryHigh=… -- /usr/bin/env GUNBC_WORKSPACE_ROOT=<release> <release gunbc> run …
  • Why a system scope: it is a system-manager scope that runs the verb as the operator's account. A --user scope needs that account's session bus, which the sudo -u command lacks.
  • Helper: new extdeps.systemd.systemd_run systemd_run_scope_as_account_argv, beside the existing user-scope form, reusing its property rendering.
  • Bounds: from gunbc.live_deploy.slice_bounds approval_broker_slice_memory_max / _high, not literals. The verb resolves the broker's own routes closure, so the broker's measured demand bounds it. It gets its own scope; joining the broker's slice would split one budget between the running broker and this run.
  • Elevation: the bootstrap principal holds (ALL) NOPASSWD: ALL, so root elevation for systemd-run is already covered.

2. ntfy auth file

  • Symptom: the readback stat'd /var/lib/gunbc-ntfy/user.db, but srv1's running server.yml names auth.db (which exists; user.db doesn't).
  • Fix: gunbc.auth.approval_ntfy_deployment approval_ntfy_auth_file_path is corrected to the observed file.
  • Why it stays a declaration, not a value read from the config: the stat runs through sudo -n at an exact argv that an operator-installed sudoers line must name, and a path chosen at runtime could only be granted by a pattern. The readback refuses unless the running config names exactly this path, so the config is checked, not trusted.
  • Operator step: srv1's sudoers line for that stat must be re-issued naming auth.db; the operator instructions rendered from this row update with it. Fixtures follow; the other.db RED is unchanged.

Witness: the_remote_argv_runs_the_release_verb_as_the_operator_user pins the whole prefix: root sudo, the scope as the operator, both bounds built from the slice rows, the env binding, then the release binary.

Not evaluated locally. One unverified bet: that srv1's systemd applies --uid/--gid to a --scope child. My reading of systemd-run(1) and run.c says it does; the wet check is the next enrolment dispatch at the merged sha.

🤖 Generated with Claude Code

Brian Searls and others added 2 commits September 29, 2026 11:01
…ile is the observed auth.db

Found on release 02568b3 by proud-deer-538:

1. fleet-converge run 36556990543 refused HostBudgetUnreadable: the remote argv ran gunbc with no
   cgroup memory bound. It now runs 'sudo -n systemd-run --scope --uid=<operator> --gid=<operator>
   --property=MemoryMax=.. --property=MemoryHigh=.. -- env GUNBC_WORKSPACE_ROOT=.. <bin> run ..',
   bounds from gunbc.live_deploy.slice_bounds approval_broker_slice_memory_{max,high} (the verb
   resolves the broker's own closure), in its own scope rather than the broker's slice.
   New extdeps.systemd.systemd_run systemd_run_scope_as_account_argv beside the user-scope form
   (a user scope needs the account's session bus, which the elevated command lacks).

2. The ntfy readback stat'd /var/lib/gunbc-ntfy/user.db; srv1's server.yml names auth.db.
   approval_ntfy_auth_file_path is corrected to the observed file and stays a declaration: the stat
   runs through sudo at an exact argv an operator-installed sudoers line must name, and the
   readback refuses unless the running config names exactly this path. The srv1 sudoers line
   for that stat must be re-issued for auth.db. Fixtures follow; the other.db RED is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…(no narrow stat grant; operator's broader sudo covers it)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

Corrections to the description, from proud-deer-538's reads on srv1 (2026-09-29):

  • --uid on a scope is verified: sudo -n systemd-run --scope --uid=briansrls --gid=briansrls -p MemoryMax=1G -- id -un prints briansrls, so the one unverified bet in the description holds.
  • No operator action is needed for the auth-file stat. srv1 has no sudoers line naming it (sudoers.d holds only the broker-helpers grant), and the stat of auth.db succeeds under the operator account's own broader sudo. The earlier refusal was the missing user.db, not a grant.
  • A gap to note, not a blocker: the model's human step asks for a narrow exact-argv grant for that stat, and srv1 has broader access than that. e9a415f rewrites the row's note to say so instead of asserting that a grant must be re-issued.

— sent from fierce-deer-555

Brian Searls and others added 3 commits September 29, 2026 11:16
… publisher, briansrls operator)

proud-deer-538 ran #12614's verb by hand on srv1; the readback refused on the ACL principals. The live
'ntfy access' lists publisher gunbc-broker (write-only) and operator briansrls (read-only), anonymous
denied; the declared gunbc-approval-publisher / gunbc-approval-operator never existed on the host.
The live accounts hold the broker's publisher token and the operator's phone subscription, so the
declarations follow them (still compared, still refusing any other shape). Fixtures follow; the
eve/root REDs are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…er-only), closing a hand step

approval_device_store_root (/var/lib/gunbc/approval-devices) was created by nothing in the model;
the first issued enrolment code (srv1, 2026-09-29) needed it made by hand as briansrls 0700.
gunbc.auth.approval_device_redemption approval_device_store_directory declares it as a
ManagedDirectory (owner and sole dependent = fleet_posix_operator_user, so the derived mode is
owner-only; Ensured so a retract keeps the enrolled devices), and approval_broker_dark_install_release_steps
ensures it first. The broker closure does not grow (every new import was already in it).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…of assuming it absent

The claim required approval_device_store_root to be absent on the runner; srv1 now has it (created by
hand for the first issued code, and ensured by the broker install from this PR), so it went red on
srv1-09 (run 36562259065) while the verb refused correctly. It now asserts the refusal and that the
call leaves the store's existence unchanged -- host-independent, and still red for a verb that
proceeds to the store on a host without one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit 178fb76 Sep 29, 2026
5 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/fierce-deer-555-enrolment-scope branch September 29, 2026 13:46
gunbai-bot Bot pushed a commit that referenced this pull request Sep 29, 2026
…with a binary built from the merged tree

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants