host-install v1.7.0: 3b-fix — Datastore.Audit box-wide (restore drive visibility)

v1.6.0 put Datastore.Audit in the per-storage Store role, hiding the enrolled
drives (felhom-usb/felhom-flash) from the agent's storage enumeration → false
detach alerts. Move Datastore.Audit (read-only) to box-wide Base; keep write
privs (Allocate/AllocateSpace) per-storage → visibility restored, write
containment unchanged. apply_scoped_acl reordered Base-before-Store (gap-free
re-apply). Live-repaired felhom-pve (role modify, no agent stop): drives back,
alerts cleared, write-containment + guest-403 re-confirmed. bash -n + shellcheck
clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 13:26:52 +02:00
parent 1be1e0d6a3
commit 457a5cdf2d
2 changed files with 43 additions and 9 deletions
+28
View File
@@ -1,5 +1,33 @@
# Felhom scripts — Changelog # Felhom scripts — Changelog
## felhom-host-install.sh v1.7.0 — 3b-fix: `Datastore.Audit` box-wide (restore drive visibility) (2026-07-01)
Fixes a regression the v1.6.0 pool-scoped ACL introduced: `Datastore.Audit` was placed in the
per-storage `Store` role (granted only on `local`/`local-lvm`/`felhom-pbs`), which **excluded the
enrolled removable drives** `felhom-usb`/`felhom-flash`. The agent enumerates storage via
`ListStorage`/`NodeStorage` (both gated by `Datastore.Audit``internal/storage/observe.go`), so it
could no longer SEE the drives → false "Meghajtó leválasztva" (drive detached) alerts + drives absent
from the agent-view. (The v1.6.0 swap's "felhom-usb → 403" was mis-read as blast-radius success;
felhom-usb is Felhom's OWN customer drive, not an out-of-scope object.)
- **`Datastore.Audit` moved from Store → Base** (`PVE_PRIVS_BASE` now `"Sys.Audit SDN.Use
Datastore.Audit"`; `PVE_PRIVS_STORE` now `"Datastore.Allocate Datastore.AllocateSpace"`). Audit is
read-only metadata, so box-wide Audit restores visibility of ALL storages (incl. dynamically-enrolled
drives — no per-drive grant ever needed) while the **write** privs (`Allocate`/`AllocateSpace`) stay
per-storage → write/allocate blast-radius containment is UNCHANGED. Confirmed at source: the agent
creates no PVE storage (no `POST /storage`/`pvesm add`); drives are dir-storages it observes + mounts
via host ops, so they need only Audit, never Allocate.
- **`apply_scoped_acl` reordered** Base-before-Store (role + grant) so a RE-APPLY on a live box adds
`Audit@/` before Store drops its per-storage Audit → gap-free (the agent never loses enumeration).
- `remove_scoped_acl` / `--uninstall` / `--rescope-acl` operate by role NAME and inherit the corrected
privs automatically (no other change).
- **Live-repaired felhom-pve** (two `pveum role modify`, Base first — no agent stop/restart): drives
reappeared (agent-view 3→5 storages), detach alerts cleared. Re-tested under the scoped token: drives
readable (was 403), write-containment intact (vzdump→felhom-usb still 403; out-of-pool guest 403),
PBS Store grant unchanged. `bash -n` + `shellcheck` clean (0 new warnings).
- **NOT physically run** (source-confirmed, no `Datastore.Allocate` in the path): a brand-new-drive UI
enrollment (needs a spare USB) — the host-ops/Audit path is unchanged from pre-3b.
## felhom-host-install.sh v1.6.0 — pool-scoped token ACL (3-role) + `--rescope-acl` retrofit (2026-07-01) ## felhom-host-install.sh v1.6.0 — pool-scoped token ACL (3-role) + `--rescope-acl` retrofit (2026-07-01)
Colleague-safety batch #4 phase b (script half; agent half = v0.53.0). Moves the agent token's dangerous Colleague-safety batch #4 phase b (script half; agent half = v0.53.0). Moves the agent token's dangerous
+15 -9
View File
@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
#=============================================================================== #===============================================================================
# felhom-host-install.sh v1.6.0 # felhom-host-install.sh v1.7.0
# Day-0 host-bootstrap for a Felhom Proxmox host (operator-deploy model). # Day-0 host-bootstrap for a Felhom Proxmox host (operator-deploy model).
# #
# Run by the operator on a FRESHLY-PVE-INSTALLED box (after a manual PVE install # Run by the operator on a FRESHLY-PVE-INSTALLED box (after a manual PVE install
@@ -104,7 +104,7 @@
set -euo pipefail set -euo pipefail
SCRIPT_VERSION="1.6.0" SCRIPT_VERSION="1.7.0"
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
# Logging (mirrors felhom-controller/scripts/docker-setup.sh) # Logging (mirrors felhom-controller/scripts/docker-setup.sh)
@@ -172,14 +172,17 @@ PVE_ROLE="FelhomAgent" # the PRE-3b single broad role (removed on rescope/unin
# across THREE roles applied at scoped paths so the token can only touch Felhom's own guests + storages # across THREE roles applied at scoped paths so the token can only touch Felhom's own guests + storages
# (blast-radius containment). `pveum acl` grants a whole role per path, hence 3 roles not 1. Each role is # (blast-radius containment). `pveum acl` grants a whole role per path, hence 3 roles not 1. Each role is
# granted to BOTH the user AND the token (privsep intersection). Guest privs (incl. Pool.Allocate so the # granted to BOTH the user AND the token (privsep intersection). Guest privs (incl. Pool.Allocate so the
# agent restores INTO the pool) live at /pool/felhom; Datastore privs at each agent-touched storage; only # agent restores INTO the pool) live at /pool/felhom; Datastore WRITE privs at each agent-touched storage.
# Sys.Audit+SDN.Use box-wide at /. # `Datastore.Audit` is box-wide in Base (3b-fix v1.7.0): the agent must ENUMERATE every storage incl. the
# dynamically-enrolled removable drives (felhom-usb/felhom-flash) it observes but never registers — a
# per-storage Audit grant hid them → false "drive detached" alerts. Audit is read-only, so box-wide Audit
# keeps WRITE containment (Allocate/AllocateSpace stay per-storage). Only Sys.Audit/SDN.Use/Datastore.Audit box-wide.
PVE_ROLE_GUEST="FelhomAgentGuest" PVE_ROLE_GUEST="FelhomAgentGuest"
PVE_ROLE_STORE="FelhomAgentStore" PVE_ROLE_STORE="FelhomAgentStore"
PVE_ROLE_BASE="FelhomAgentBase" PVE_ROLE_BASE="FelhomAgentBase"
PVE_PRIVS_GUEST="VM.Allocate VM.Audit VM.Config.Disk VM.Config.CPU VM.Config.Memory VM.Config.Network VM.Config.Options VM.PowerMgmt VM.Snapshot VM.Snapshot.Rollback VM.Backup Pool.Allocate" PVE_PRIVS_GUEST="VM.Allocate VM.Audit VM.Config.Disk VM.Config.CPU VM.Config.Memory VM.Config.Network VM.Config.Options VM.PowerMgmt VM.Snapshot VM.Snapshot.Rollback VM.Backup Pool.Allocate"
PVE_PRIVS_STORE="Datastore.Allocate Datastore.AllocateSpace Datastore.Audit" PVE_PRIVS_STORE="Datastore.Allocate Datastore.AllocateSpace"
PVE_PRIVS_BASE="Sys.Audit SDN.Use" PVE_PRIVS_BASE="Sys.Audit SDN.Use Datastore.Audit"
# Storages the agent reads/writes (archive+dump=local, restore=local-lvm, offsite DR=felhom-pbs). The # Storages the agent reads/writes (archive+dump=local, restore=local-lvm, offsite DR=felhom-pbs). The
# offsite felhom-pbs MUST be included or the agent's DR backup 403s (SPIKE residual #1). --acl-storages overrides. # offsite felhom-pbs MUST be included or the agent's DR backup 403s (SPIKE residual #1). --acl-storages overrides.
PVE_STORAGES=(local local-lvm felhom-pbs) PVE_STORAGES=(local local-lvm felhom-pbs)
@@ -406,17 +409,20 @@ _grant() {
} }
# apply_scoped_acl — create the 3 scoped roles and grant each at its path(s). Requires the pool to exist. # apply_scoped_acl — create the 3 scoped roles and grant each at its path(s). Requires the pool to exist.
# ORDER (3b-fix): Base (which holds box-wide Datastore.Audit) is ensured + granted BEFORE Store, so a
# RE-APPLY on a live box adds Audit@/ before Store drops its per-storage Audit → the agent never loses
# storage-enumeration visibility mid-apply (gap-free).
apply_scoped_acl() { apply_scoped_acl() {
_ensure_role "$PVE_ROLE_BASE" "$PVE_PRIVS_BASE"
_ensure_role "$PVE_ROLE_GUEST" "$PVE_PRIVS_GUEST" _ensure_role "$PVE_ROLE_GUEST" "$PVE_PRIVS_GUEST"
_ensure_role "$PVE_ROLE_STORE" "$PVE_PRIVS_STORE" _ensure_role "$PVE_ROLE_STORE" "$PVE_PRIVS_STORE"
_ensure_role "$PVE_ROLE_BASE" "$PVE_PRIVS_BASE" _grant / "$PVE_ROLE_BASE"
_grant "/pool/$PVE_POOL" "$PVE_ROLE_GUEST" _grant "/pool/$PVE_POOL" "$PVE_ROLE_GUEST"
local s local s
for s in "${PVE_STORAGES[@]}"; do for s in "${PVE_STORAGES[@]}"; do
_grant "/storage/$s" "$PVE_ROLE_STORE" _grant "/storage/$s" "$PVE_ROLE_STORE"
done done
_grant / "$PVE_ROLE_BASE" log_success " scoped ACL applied (Base@/, Guest@/pool/$PVE_POOL, Store@[${PVE_STORAGES[*]}])"
log_success " scoped ACL applied (Guest@/pool/$PVE_POOL, Store@[${PVE_STORAGES[*]}], Base@/)"
} }
# _acl_grant_present PATH TYPE UGID ROLE — true if that exact ACL grant exists. # _acl_grant_present PATH TYPE UGID ROLE — true if that exact ACL grant exists.