forked from heavy-duty/rig
feat: users apply grants the box tier, not just the socket
Role `box` resolved to exactly one action, `usermod -aG incus`. That is the socket — step 1 of the five `box grant` performs. Without the other four (the user-<uid> project, its narrowing to boxnet and only boxnet, the snapshot and backup allowances clone and `box export` ride, and the shipped box-net profile installed into that project) the user's first `box new` refuses for want of a box-net profile, so apply's promise — the users file is the fleet's source of truth — was not kept for this role. Worse, until an admin arrived by hand the user held an `incus` membership with no converged project, and incus-user would lazily hand them a stock unhardened NAT bridge: a state box's own contract forbids. On host=yes apply now calls `box grant <user>` per box-role user. rig calls box's grant rather than reimplementing four fifths of it — the "rig never installs Incus" boundary is about installation, not invocation, and grant is already script-callable: idempotent, root-or-sudo, stdin-pinned, with its own run-as-the-user touch. Three decisions the code carries in comment form: - Ordering. The call sits after `useradd` (grant opens with a getent passwd and refuses an unknown account) and after the other groups, so a user whose grant fails still lands with everything rig owns outright. - Failure granularity, split the way the host= guard beside it already splits. A missing box CLI on host=yes dies, like the missing incus group: a broken VM host, not a per-user accident. A per-user grant failure warns and continues — one box-role user somewhere in the fleet must not stop apply everywhere VMs don't live. host=no and marker-less boxes keep their existing skip-with-warning untouched. - The group ADD is deferred to grant, while `incus` stays in the wanted set so the exact-convergence loop never strips a box-role user's socket. Grant's rollback only reaches a membership that run added, so rig opening the socket first would leave a failed grant unable to close it. And grant is the authority on whether the group belongs at all: for an incus-admin member it deliberately does not add `incus`. An incus-admin member is warned, never fatal: box grant refuses them today, which heavy-duty/box#99 fixes box-side with no rig change needed. Closes #49 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
71fd0513ab
commit
a950569832
4 changed files with 233 additions and 5 deletions
20
CHANGELOG.md
20
CHANGELOG.md
|
|
@ -6,6 +6,26 @@ on the way to cutting its first release, and this file starts there.
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **`users apply` grants the box *tier*, not just its socket** (#49) — role
|
||||||
|
`box` resolved to exactly one action, `usermod -aG incus`. That is the
|
||||||
|
socket; it is step 1 of the five `box grant` performs, so every box-role
|
||||||
|
user still needed an admin to run `box grant <user>` by hand before their
|
||||||
|
first `box new` would do anything but refuse ("your project has no box-net
|
||||||
|
profile"), and until that admin arrived they held an `incus` membership
|
||||||
|
with no converged project — incus-user would lazily hand them a stock
|
||||||
|
unhardened NAT bridge, which is worse than no grant at all. On `host=yes`
|
||||||
|
apply now calls `box grant` per box-role user, after `useradd` (grant
|
||||||
|
refuses an unknown account) and with the group ADD deferred to grant, so a
|
||||||
|
grant that fails partway can take the socket back with it. Failures split
|
||||||
|
the way the `host=` guard beside them already splits: a missing `box` CLI
|
||||||
|
on `host=yes` dies (a broken VM host), a per-user grant failure warns and
|
||||||
|
continues (one box-role user must not stop apply for the fleet). `host=no`
|
||||||
|
and marker-less boxes keep their existing skip-with-warning. An
|
||||||
|
`incus-admin` member is warned, not fatal — `box grant` refuses them today,
|
||||||
|
which heavy-duty/box#99 fixes box-side with no rig change needed.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- **A `host=no` box with an `incus` group no longer hands out the bare
|
- **A `host=no` box with an `incus` group no longer hands out the bare
|
||||||
|
|
|
||||||
18
README.md
18
README.md
|
|
@ -750,7 +750,7 @@ holds nothing secret anyway: usernames, roles, and *public* keys.
|
||||||
|---------|----------------------------------------------|-------------|
|
|---------|----------------------------------------------|-------------|
|
||||||
| `admin` | full NOPASSWD sudo | `rig-admin` |
|
| `admin` | full NOPASSWD sudo | `rig-admin` |
|
||||||
| `rig` | NOPASSWD sudo for `/usr/local/bin/rig` only | `rig` |
|
| `rig` | NOPASSWD sudo for `/usr/local/bin/rig` only | `rig` |
|
||||||
| `box` | Incus **restricted** tier, no sudo | `incus` |
|
| `box` | Incus **restricted** tier, no sudo | `incus` + `box grant` |
|
||||||
|
|
||||||
**The honest limit of the `rig` role:** its sudo grant is binary-scoped, not
|
**The honest limit of the `rig` role:** its sudo grant is binary-scoped, not
|
||||||
argument-scoped — it trusts its holder with every rig verb *except* identity
|
argument-scoped — it trusts its holder with every rig verb *except* identity
|
||||||
|
|
@ -784,6 +784,22 @@ apply dies pointing at `box setup-host` rather than conjure a group nothing
|
||||||
would consult. `incus-admin` is deliberately **not** a role: that group is
|
would consult. `incus-admin` is deliberately **not** a role: that group is
|
||||||
host-root-equivalent, break-glass by hand only.
|
host-root-equivalent, break-glass by hand only.
|
||||||
|
|
||||||
|
**The group is the socket; the tier is `box grant`.** On `host=yes`, apply
|
||||||
|
calls `box grant <user>` for every box-role user — the `incus` group is only
|
||||||
|
the first of the five steps that grant performs (the `user-<uid>` project,
|
||||||
|
its narrowing to `boxnet` and *only* `boxnet`, the snapshot and backup
|
||||||
|
allowances `box clone` and `box export` ride, and the shipped `box-net`
|
||||||
|
profile installed into that project). rig calls box's own grant rather than
|
||||||
|
reimplementing four fifths of it: the "rig never installs Incus" boundary is
|
||||||
|
about installation, not invocation, and deferring here respects it harder
|
||||||
|
than a rig-side copy would. The group ADD is left to grant too, so a grant
|
||||||
|
that fails partway can take the socket back with it — rig opening the socket
|
||||||
|
first would leave a user with live access to an *un-narrowed* project, which
|
||||||
|
is worse than no grant at all. A grant that fails **warns and continues**:
|
||||||
|
one box-role user's project must not stop apply for the fleet. A missing
|
||||||
|
`box` CLI on `host=yes` **dies**, like the missing `incus` group — that is a
|
||||||
|
broken VM host, not a per-user accident.
|
||||||
|
|
||||||
Losing the `box` role goes back through box, too. `rig-admin` and `rig` are
|
Losing the `box` role goes back through box, too. `rig-admin` and `rig` are
|
||||||
rig's groups and a `gpasswd -d` is the whole story for them; `incus` is box's,
|
rig's groups and a `gpasswd -d` is the whole story for them; `incus` is box's,
|
||||||
and `box revoke` does more with it than remove a membership — it says out loud
|
and `box revoke` does more with it than remove a membership — it says out loud
|
||||||
|
|
|
||||||
|
|
@ -46,11 +46,16 @@ follows it to the user. Apply dies if root has no authorized_keys to seed.
|
||||||
roles:
|
roles:
|
||||||
admin group rig-admin — full NOPASSWD sudo
|
admin group rig-admin — full NOPASSWD sudo
|
||||||
rig group rig — NOPASSWD sudo for /usr/local/bin/rig only
|
rig group rig — NOPASSWD sudo for /usr/local/bin/rig only
|
||||||
box group incus — Incus restricted tier, no sudo (box's setup-host
|
box group incus — Incus restricted tier, no sudo. On host=yes apply
|
||||||
owns the Incus install; rig only asserts it).
|
calls 'box grant <user>', which is the tier: the
|
||||||
Dropping this role hands the group back through
|
group is only its socket, the user-<uid> project,
|
||||||
|
the boxnet-only narrowing, snapshots, backups and
|
||||||
|
the box-net profile are the rest of it. Dropping
|
||||||
|
the role hands the group back through
|
||||||
'box revoke' — never --purge: convergence removes
|
'box revoke' — never --purge: convergence removes
|
||||||
access, never someone's running boxes.
|
access, never someone's running boxes. box's
|
||||||
|
setup-host owns the Incus install; rig only
|
||||||
|
asserts it, and defers BOTH directions to box.
|
||||||
|
|
||||||
All passwords stay locked, always — the SSH key at the door is the
|
All passwords stay locked, always — the SSH key at the door is the
|
||||||
authentication, and NOPASSWD sudo does not weaken it. Convergent: membership
|
authentication, and NOPASSWD sudo does not weaken it. Convergent: membership
|
||||||
|
|
@ -223,6 +228,40 @@ if [ "$NEED_INCUS" -eq 1 ] && [ "$BOX_ROLE_OK" -eq 1 ] && [ "$INCUS_OK" -eq 0 ];
|
||||||
die "a user carries role box and this box hosts VMs (host=yes) but group incus is absent — install the box CLI and run 'box setup-host' first; rig never installs Incus"
|
die "a user carries role box and this box hosts VMs (host=yes) but group incus is absent — install the box CLI and run 'box setup-host' first; rig never installs Incus"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# --- the box TIER, not just its socket (#49) ---------------------------------
|
||||||
|
# Group incus is step 1 of the five 'box grant' performs: the group is the
|
||||||
|
# SOCKET, while the tier is the per-user convergence behind it — the
|
||||||
|
# user-<uid> project, its narrowing to boxnet and only boxnet, the snapshot
|
||||||
|
# and backup allowances clone and 'box export' ride, and the shipped box-net
|
||||||
|
# profile installed into that project. rig doing step 1 alone left every
|
||||||
|
# box-role user's first 'box new' refusing ("your project has no box-net
|
||||||
|
# profile"), so apply's promise — the users file is the fleet's source of
|
||||||
|
# truth — was not kept for this role. It is kept by CALLING box's own grant
|
||||||
|
# rather than reimplementing four fifths of it: 'box grant' is idempotent,
|
||||||
|
# root-or-sudo (which apply already is), stdin-pinned so no incus client can
|
||||||
|
# wedge waiting on an answer, and does its own run-as-the-user touch, so the
|
||||||
|
# granted user never has to log in first for the lazy project to exist.
|
||||||
|
#
|
||||||
|
# Only on host=yes. The box role binds where VMs live, and the two other
|
||||||
|
# arms of the host= decision above are deliberately untouched: host=no keeps
|
||||||
|
# its skip-with-warning, and a marker with no host= trait keeps its own.
|
||||||
|
# Granting a tier on a box that does not host VMs would be converging policy
|
||||||
|
# into a daemon that is not there to enforce it.
|
||||||
|
#
|
||||||
|
# The box CLI's ABSENCE is a host-level fact, so it dies here, in the same
|
||||||
|
# register and for the same reason as the missing-incus-group die above: a
|
||||||
|
# host=yes box carrying box-role users but no box CLI is a broken VM host,
|
||||||
|
# not a per-user accident, and no amount of continuing converges it.
|
||||||
|
BOX_GRANT=0
|
||||||
|
if [ "$NEED_INCUS" -eq 1 ] && [ "$INCUS_OK" -eq 1 ]; then
|
||||||
|
case "$(read_role_marker "${RIG_ROLE_MARKER:-/etc/rig/role}")" in
|
||||||
|
*host=yes*)
|
||||||
|
command -v box >/dev/null 2>&1 \
|
||||||
|
|| die "a user carries role box and this box hosts VMs (host=yes) but the box CLI is not on PATH — the incus group is only the socket; the restricted tier is 'box grant', which rig calls rather than reimplements. Install the box CLI (rig bootstrap does it on host=yes) and re-run"
|
||||||
|
BOX_GRANT=1 ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
in_group() { id -nG "$1" 2>/dev/null | tr ' ' '\n' | grep -qx "$2"; }
|
in_group() { id -nG "$1" 2>/dev/null | tr ' ' '\n' | grep -qx "$2"; }
|
||||||
|
|
||||||
# --- dropping role box: give the group back to the tool that owns it (#50) ---
|
# --- dropping role box: give the group back to the tool that owns it (#50) ---
|
||||||
|
|
@ -313,6 +352,30 @@ for u in "${USERS[@]}"; do
|
||||||
for g in rig-admin rig incus; do
|
for g in rig-admin rig incus; do
|
||||||
case " $want " in
|
case " $want " in
|
||||||
*" $g "*)
|
*" $g "*)
|
||||||
|
# incus stays in the WANTED set — that is what keeps the exact
|
||||||
|
# convergence below from stripping a box-role user's socket — but on
|
||||||
|
# a host where we are about to run 'box grant', the ADD is deferred to
|
||||||
|
# grant itself. Two reasons, both load-bearing:
|
||||||
|
#
|
||||||
|
# Grant's rollback only reaches a membership THAT RUN added: a user it
|
||||||
|
# finds already in incus keeps it when a later step trips
|
||||||
|
# (box/host/grant-user.sh's was_member branch), by design — stripping
|
||||||
|
# a membership it did not add could break a working user over a failed
|
||||||
|
# re-run. So if rig opens the socket first, grant can no longer close
|
||||||
|
# it, and a grant that fails midway leaves the user holding live
|
||||||
|
# access to an UN-narrowed project: the stock unhardened incusbr-<uid>
|
||||||
|
# one --network flag away. That state is strictly worse than no grant
|
||||||
|
# at all, which is the sharp end of #49. Deferring makes the socket and
|
||||||
|
# the tier land together or fail together.
|
||||||
|
#
|
||||||
|
# And grant is the authority on whether the group belongs at all: for
|
||||||
|
# an incus-admin member it deliberately does NOT add incus (admin
|
||||||
|
# membership already wins at the socket, so the row would only mislead
|
||||||
|
# whoever reads the group list later). rig adding it here would put a
|
||||||
|
# membership on that user which box has decided against — not a loop
|
||||||
|
# that oscillates, but rig overruling box on box's own tier, which is
|
||||||
|
# exactly the boundary this whole change is respecting.
|
||||||
|
if [ "$g" = incus ] && [ "$BOX_GRANT" -eq 1 ]; then continue; fi
|
||||||
if ! in_group "$u" "$g"; then
|
if ! in_group "$u" "$g"; then
|
||||||
usermod -aG "$g" "$u"
|
usermod -aG "$g" "$u"
|
||||||
log "added $u to $g"
|
log "added $u to $g"
|
||||||
|
|
@ -331,6 +394,55 @@ for u in "${USERS[@]}"; do
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# The tier itself. AFTER the account exists — 'box grant' opens with a
|
||||||
|
# getent passwd and refuses an unknown user — and after the other groups,
|
||||||
|
# so a user whose grant fails still lands with everything rig owns outright.
|
||||||
|
#
|
||||||
|
# Per-user failures WARN and continue, matching the governing call the
|
||||||
|
# host= split above already makes: one box-role user somewhere in the fleet
|
||||||
|
# must not stop apply everywhere VMs don't live, and a project that would
|
||||||
|
# not converge is exactly that shape of local fact. Host-level facts still
|
||||||
|
# die, but they die before this loop — the missing group and the missing
|
||||||
|
# CLI both. What is left here (project creation refused, an instance still
|
||||||
|
# parked on the private bridge blocking the narrowing, an incus-user socket
|
||||||
|
# that will not answer) is per-user by construction.
|
||||||
|
if [ "$BOX_GRANT" -eq 1 ]; then
|
||||||
|
case ",$roles," in
|
||||||
|
*,box,*)
|
||||||
|
had_socket=0
|
||||||
|
if in_group "$u" incus; then had_socket=1; fi
|
||||||
|
log "granting $u the box restricted tier (box grant $u)"
|
||||||
|
grant_rc=0
|
||||||
|
box grant "$u" || grant_rc=$?
|
||||||
|
if [ "$grant_rc" -eq 0 ]; then
|
||||||
|
# CHANGED is a claim about what apply DID, and the only part of the
|
||||||
|
# grant rig can observe from out here is the group transition —
|
||||||
|
# every other step converges inside incus's own state, with no cheap
|
||||||
|
# "already converged?" probe short of redoing the work. So report
|
||||||
|
# the transition when it happens and stay silent otherwise: a
|
||||||
|
# convergent call that changed nothing must not turn "already
|
||||||
|
# converged; no changes" into a permanent lie on every host=yes box.
|
||||||
|
if [ "$had_socket" -eq 0 ] && in_group "$u" incus; then
|
||||||
|
log "granted $u the restricted tier (incus group, user-$(id -u "$u") project, boxnet-only, box-net profile)"
|
||||||
|
CHANGED=1
|
||||||
|
fi
|
||||||
|
elif in_group "$u" incus-admin; then
|
||||||
|
# Today 'box grant' refuses incus-admin members outright — it reads
|
||||||
|
# their admin membership as "there is nothing tighter to grant",
|
||||||
|
# which conflates permission with provisioning: they hold the full
|
||||||
|
# socket but have no project of their own. heavy-duty/box#99 makes
|
||||||
|
# grant provision them instead of refusing, at which point this
|
||||||
|
# branch simply stops being reached — no rig change needed, because
|
||||||
|
# rig asks for the tier and lets box decide what that means. Until
|
||||||
|
# then the honest report is that nothing was lost: they still hold
|
||||||
|
# strictly more access than the tier would give them.
|
||||||
|
warn "box grant $u exited $grant_rc and $u is in incus-admin, which box refuses to grant today — they keep the full socket (incus-admin is strictly stronger than the tier), but they get no user-<uid> project of their own and land in the shared default project alongside every other admin. Blocked on heavy-duty/box#99; everything else converged, and a later apply picks the tier up once box stops refusing"
|
||||||
|
else
|
||||||
|
warn "box grant $u exited $grant_rc: $u has their account, keys and rig groups, but NOT the box restricted tier — no user-<uid> project, no boxnet narrowing, no box-net profile, so their 'box new' will refuse. box's own output above names the cause; fix it and re-run apply (or 'box grant $u' by hand). Every other user still converged — one box-role user must not stop apply for the fleet"
|
||||||
|
fi ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
# authorized_keys becomes exactly the file's keys — only the content WRITE
|
# authorized_keys becomes exactly the file's keys — only the content WRITE
|
||||||
# is cmp-guarded, so an unchanged file is a clean no-op. Ownership and mode
|
# is cmp-guarded, so an unchanged file is a clean no-op. Ownership and mode
|
||||||
# converge UNCONDITIONALLY: sshd's StrictModes treats them as load-bearing
|
# converge UNCONDITIONALLY: sshd's StrictModes treats them as load-bearing
|
||||||
|
|
|
||||||
80
test/cli.sh
80
test/cli.sh
|
|
@ -964,6 +964,86 @@ check "drop_incus: an absent pgrep warns rather than guessing" 0 "" \
|
||||||
in_out "$DROP_NOPGREP" "loginctl terminate-user dan"
|
in_out "$DROP_NOPGREP" "loginctl terminate-user dan"
|
||||||
rm -rf "$NOPGREP_D"
|
rm -rf "$NOPGREP_D"
|
||||||
|
|
||||||
|
# --- the box role grants the TIER, not just the socket (#49) -----------------
|
||||||
|
# Group incus is step 1 of the five 'box grant' performs; without the rest the
|
||||||
|
# user's first 'box new' refuses for want of a box-net profile. Running the
|
||||||
|
# real thing needs root, an Incus daemon and real accounts, so these assert the
|
||||||
|
# CALL and its guard rails in the source — the same way every other root-only
|
||||||
|
# refusal in this harness is pinned.
|
||||||
|
# The $-refs below are literals we grep FOR in the script — single quotes
|
||||||
|
# are the point, as in the bootstrap ordering checks above.
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
check "users apply: box role calls 'box grant', not just usermod" 0 "" \
|
||||||
|
grep -qE '^[[:space:]]*box grant "\$u"' "$ROOT/commands/users-apply.sh"
|
||||||
|
# Ordering is the safety property (repo precedent: bootstrap's marker-then-box
|
||||||
|
# assert): 'box grant' opens with a getent passwd and refuses an unknown user,
|
||||||
|
# so the call must come AFTER useradd, never before. Defaults fail closed.
|
||||||
|
useradd_at="$(grep -nE '^[[:space:]]*useradd -m' "$ROOT/commands/users-apply.sh" | head -n1 | cut -d: -f1)"
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
grant_at="$(grep -nE '^[[:space:]]*box grant "\$u"' "$ROOT/commands/users-apply.sh" | head -n1 | cut -d: -f1)"
|
||||||
|
check "users apply: 'box grant' runs after useradd (grant refuses unknown users)" \
|
||||||
|
0 "" test "${useradd_at:-999999}" -lt "${grant_at:-0}"
|
||||||
|
# Failure granularity, both halves. A HOST-level fact — box-role users on a
|
||||||
|
# host=yes box with no box CLI — dies, like the missing-incus-group die beside
|
||||||
|
# it. A PER-USER grant failure warns and continues, because one box-role user
|
||||||
|
# somewhere in the fleet must not stop apply everywhere VMs don't live.
|
||||||
|
check "users apply: a missing box CLI on host=yes is a die, not a warning" 0 "" \
|
||||||
|
grep -qF 'die "a user carries role box and this box hosts VMs (host=yes) but the box CLI is not on PATH' \
|
||||||
|
"$ROOT/commands/users-apply.sh"
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
check "users apply: a per-user grant failure warns and continues" 0 "" \
|
||||||
|
grep -qF 'warn "box grant $u exited $grant_rc:' "$ROOT/commands/users-apply.sh"
|
||||||
|
# The grant is host=yes only: a tier converged into a daemon that is not there
|
||||||
|
# to enforce it is not policy. Read the guard's own block — BOX_GRANT=0 up to
|
||||||
|
# the line that sets it to 1 — rather than the file at large, so a host=yes
|
||||||
|
# match borrowed from the die above cannot pass this for free.
|
||||||
|
# shellcheck disable=SC2016 # $1 resolves inside the inner bash -c
|
||||||
|
check "users apply: the grant is gated on host=yes" 0 "" \
|
||||||
|
bash -c 'awk "/^BOX_GRANT=0\$/,/BOX_GRANT=1 ;;/" "$1" | grep -q "[*]host=yes[*])"' \
|
||||||
|
_ "$ROOT/commands/users-apply.sh"
|
||||||
|
# The incus-admin case is blocked on heavy-duty/box#99: grant refuses those
|
||||||
|
# members today, and rig must NOT turn that refusal into a failed apply. The
|
||||||
|
# branch names the blocker so whoever reads the warning can find the fix.
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
check "users apply: an incus-admin grant refusal is warned, not fatal" 0 "" \
|
||||||
|
grep -q 'elif in_group "$u" incus-admin; then' "$ROOT/commands/users-apply.sh"
|
||||||
|
check "users apply: the incus-admin warning cites the box-side blocker" 0 "" \
|
||||||
|
grep -q "heavy-duty/box#99" "$ROOT/commands/users-apply.sh"
|
||||||
|
# The group ADD is deferred to grant so a failed grant can take the socket back
|
||||||
|
# with it (grant only rolls back a membership THAT RUN added). But incus must
|
||||||
|
# stay in the WANTED set, or the exact-convergence loop's other arm would strip
|
||||||
|
# a box-role user's socket on the very run that granted it — assert both, since
|
||||||
|
# either alone is a bug.
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
check "users apply: the incus group add is deferred to 'box grant'" 0 "" \
|
||||||
|
grep -qF 'if [ "$g" = incus ] && [ "$BOX_GRANT" -eq 1 ]; then continue; fi' \
|
||||||
|
"$ROOT/commands/users-apply.sh"
|
||||||
|
# The wanted-set arm gained #58's BOX_ROLE_OK gate on rebase, and the two
|
||||||
|
# conditions answer different questions: BOX_ROLE_OK is "does the box role
|
||||||
|
# apply on this box at all" (the marker's call), INCUS_OK is "is the group
|
||||||
|
# there to converge". Both must hold, and `incus` must still ENTER the wanted
|
||||||
|
# set when they do — otherwise the exact-convergence else-arm below would
|
||||||
|
# strip a box-role user's socket on the very run that granted it, which is
|
||||||
|
# the hazard this PR exists to remove. Pinned as the composed line so a
|
||||||
|
# regression in either operand fails here.
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
check "users apply: role box still puts incus in the wanted set" 0 "" \
|
||||||
|
grep -qF 'case ",$roles," in *,box,*) if [ "$BOX_ROLE_OK" -eq 1 ] && [ "$INCUS_OK" -eq 1 ]; then want="$want incus"; fi ;; esac' \
|
||||||
|
"$ROOT/commands/users-apply.sh"
|
||||||
|
# The deferral must be an ADD-side skip only. Landing it on the removal arm
|
||||||
|
# would leave a de-roled user's socket open forever, so prove the guard sits
|
||||||
|
# above the usermod -aG and below the wanted-set case, not in the else branch.
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
defer_at="$(grep -nF 'if [ "$g" = incus ] && [ "$BOX_GRANT" -eq 1 ]; then continue; fi' "$ROOT/commands/users-apply.sh" | head -n1 | cut -d: -f1)"
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
strip_at="$(grep -nE '^[[:space:]]*gpasswd -d "\$u" "\$g"' "$ROOT/commands/users-apply.sh" | head -n1 | cut -d: -f1)"
|
||||||
|
check "users apply: the deferral sits on the add arm, not the removal arm" \
|
||||||
|
0 "" test "${defer_at:-999999}" -lt "${strip_at:-0}"
|
||||||
|
# rig still never installs Incus (the #12/#25 design law, asserted for
|
||||||
|
# bootstrap above): calling box's grant is invocation, not installation.
|
||||||
|
check "users apply: never apt-installs incus (box owns the daemon)" 1 "" \
|
||||||
|
grep -nE 'apt-get install.* incus' "$ROOT/commands/users-apply.sh"
|
||||||
|
|
||||||
# --- users close-root: the human-class root-door shutter ---------------------
|
# --- users close-root: the human-class root-door shutter ---------------------
|
||||||
check "users close-root: --help exits 0" 0 "usage:" "$ROOT/commands/users-close-root.sh" --help
|
check "users close-root: --help exits 0" 0 "usage:" "$ROOT/commands/users-close-root.sh" --help
|
||||||
check "users close-root: unknown flag exits 2" 2 "unknown flag" "$ROOT/commands/users-close-root.sh" --nope
|
check "users close-root: unknown flag exits 2" 2 "unknown flag" "$ROOT/commands/users-close-root.sh" --nope
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue