host-install v1.6.0: pool-scoped token ACL (3-role) + --rescope-acl retrofit
step_token now grants 3 scoped roles (Guest@/pool/felhom incl. Pool.Allocate, Store@each PVE_STORAGES incl. felhom-pbs, Base=Sys.Audit+SDN.Use@/) to both user and token, pool-before-token, and removes any pre-3b broad / grant. New --rescope-acl migrates existing installs (add-before-remove; supervised). --uninstall removes both scoped + old shapes. Post-provision pool_add_guest dropped (agent restore --pool owns membership, v0.53.0). Validated dry-run on felhom-pve; bash -n + shellcheck clean. LIVE rescope+swap is the supervised STOP. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,36 @@
|
||||
# Felhom scripts — Changelog
|
||||
|
||||
## 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
|
||||
privileges off `/` (which spanned every guest + storage) to `/pool/felhom` + `/storage/<targets>`, so on
|
||||
a shared box the token can only touch Felhom's own guests + storages. Validated by
|
||||
`documentation/audits/SPIKE-pool-scoped-acl-2026-07-01.md` (PASS) — implemented here.
|
||||
|
||||
- **3-role scoped ACL (`step_token` rewrite).** Replaces the single `FelhomAgent` role granted at `/`
|
||||
with three roles, each granted to BOTH the user AND the token (privsep intersection): `FelhomAgentGuest`
|
||||
(`VM.*` + `Pool.Allocate`) @ `/pool/felhom`; `FelhomAgentStore` (`Datastore.*`) @ each of
|
||||
`PVE_STORAGES` (default `local local-lvm felhom-pbs` — the offsite PBS MUST be included, SPIKE
|
||||
residual #1; `--acl-storages` overrides); `FelhomAgentBase` (`Sys.Audit SDN.Use`) @ `/`. Helpers
|
||||
`apply_scoped_acl`/`remove_scoped_acl`/`_grant`/`_ensure_role`.
|
||||
- **Pool before token.** `ensure_felhom_pool` runs at the top of `step_token` (always, incl.
|
||||
`--skip-provision`) so `/pool/felhom` exists before it's granted on.
|
||||
- **Re-install safety.** `step_token` also removes the pre-3b broad `/` grant + `FelhomAgent` role if
|
||||
present (`remove_old_broad_acl`, tolerate-absent), so a re-install can't leave the old grant unioned
|
||||
with the scoped one. The post-provision `pool_add_guest` is gone (the agent's `restore --pool` makes
|
||||
the guest a member atomically — v0.53.0).
|
||||
- **`--rescope-acl` retrofit** (new mode, mirrors `--adopt-pool`): migrate an existing install — ensure
|
||||
the pool + guest membership, apply the scoped grants, THEN remove the old broad grant (add-before-
|
||||
remove: the token is never grant-less mid-migration). Prints the "now deploy agent ≥ v0.53.0"
|
||||
ordering reminder. Idempotent + dry-run-aware. **SUPERVISED** (run with the agent stopped — the scoped
|
||||
ACL and the pool-param agent are mutually dependent; §13 of the task).
|
||||
- **`--uninstall`** now removes the scoped grants + 3 roles AND the pre-3b broad grant/role (both
|
||||
tolerate-absent → works on either shape), keeping the pool delete-if-empty (v1.5.0).
|
||||
- **Validated on felhom-pve** (dry-run): T-A fresh install (pool-before-token, 3 roles once, scoped
|
||||
grants incl. `/storage/felhom-pbs`), `--rescope-acl` (add scoped → remove old `FelhomAgent`), T-F
|
||||
uninstall (old-shape cleanup + pool not-empty skip). `bash -n` + `shellcheck` clean (0 new warnings).
|
||||
**The live rescope + agent swap is the supervised STOP** — not run here.
|
||||
|
||||
## felhom-host-install.sh v1.5.0 — `felhom` pool by default + `--adopt-pool` retrofit + uninstall teardown (2026-07-01)
|
||||
|
||||
Colleague-safety batch #4 phase a. Every Felhom-managed guest now joins a dedicated **`felhom` pool**
|
||||
|
||||
+186
-43
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
#===============================================================================
|
||||
# felhom-host-install.sh v1.5.0
|
||||
# felhom-host-install.sh v1.6.0
|
||||
# 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
|
||||
@@ -85,6 +85,13 @@
|
||||
# refuses a non-Felhom guest unless --force. Touches ONLY pool membership —
|
||||
# never reconfigures/restarts the guest. (A fresh provision joins the pool
|
||||
# automatically; this retrofits already-installed boxes.)
|
||||
# --rescope-acl migrate an existing install from the pre-3b broad-`/` token grant to the
|
||||
# pool-scoped ACL (Guest@/pool/felhom + Store@each storage + Sys.Audit/SDN.Use@/).
|
||||
# ACL-only (no data touched). SUPERVISED: run with felhom-agent STOPPED, then
|
||||
# deploy agent >= v0.53.0, then start (the scoped ACL + pool-param agent are
|
||||
# mutually dependent).
|
||||
# --acl-storages "a b c" override the storages the scoped ACL grants Datastore.* on
|
||||
# (default: "local local-lvm felhom-pbs"). Used by fresh install + --rescope-acl.
|
||||
#
|
||||
# State (idempotent/resumable): /var/lib/felhom-install/state.json
|
||||
# Agent config written 0600 to the systemd unit's -config path
|
||||
@@ -97,7 +104,7 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_VERSION="1.5.0"
|
||||
SCRIPT_VERSION="1.6.0"
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Logging (mirrors felhom-controller/scripts/docker-setup.sh)
|
||||
@@ -144,6 +151,7 @@ RESUME=false
|
||||
UNINSTALL=false # --uninstall: local host teardown (destroy guest + remove agent/pveum/state)
|
||||
REMOVE_GOLDEN=false # --remove-golden: also delete the golden vzdump during --uninstall
|
||||
ADOPT_POOL=false # --adopt-pool: retrofit an EXISTING Felhom guest into the felhom pool (non-destructive)
|
||||
RESCOPE_ACL=false # --rescope-acl: migrate an existing install from the broad-/ token to the scoped ACL
|
||||
|
||||
# --- Gitea (artifact source) + agent install model (BUNDLE slice) ---
|
||||
GITEA_BASE="https://gitea.dooplex.hu"
|
||||
@@ -157,10 +165,24 @@ AGENT_STATE_DIR="/var/lib/felhom-agent"
|
||||
|
||||
PVE_USER="felhom-agent@pve"
|
||||
PVE_TOKENID="agent"
|
||||
PVE_ROLE="FelhomAgent"
|
||||
PVE_POOL="felhom" # dedicated pool every Felhom-managed guest joins (fleet uniformity; 3b will scope the ACL to it)
|
||||
# The authoritative 16 privileges (agent README; VM.Config.CPUMemory is NOT real, SDN.Use IS required).
|
||||
PVE_PRIVS="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 Datastore.Allocate Datastore.AllocateSpace Datastore.Audit Sys.Audit SDN.Use"
|
||||
PVE_POOL="felhom" # dedicated pool every Felhom-managed guest joins (fleet uniformity + ACL scope)
|
||||
PVE_ROLE="FelhomAgent" # the PRE-3b single broad role (removed on rescope/uninstall if present — legacy)
|
||||
|
||||
# Pool-scoped ACL (3b, validated by SPIKE-pool-scoped-acl-2026-07-01): the agent's privileges are split
|
||||
# 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
|
||||
# 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
|
||||
# Sys.Audit+SDN.Use box-wide at /.
|
||||
PVE_ROLE_GUEST="FelhomAgentGuest"
|
||||
PVE_ROLE_STORE="FelhomAgentStore"
|
||||
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_STORE="Datastore.Allocate Datastore.AllocateSpace Datastore.Audit"
|
||||
PVE_PRIVS_BASE="Sys.Audit SDN.Use"
|
||||
# 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.
|
||||
PVE_STORAGES=(local local-lvm felhom-pbs)
|
||||
|
||||
STATE_DIR="/var/lib/felhom-install"
|
||||
STATE_FILE="${STATE_DIR}/state.json"
|
||||
@@ -182,7 +204,7 @@ ART_GOLDEN_SHA=""
|
||||
#-------------------------------------------------------------------------------
|
||||
# Helpers
|
||||
#-------------------------------------------------------------------------------
|
||||
usage() { sed -n '2,87p' "$0" | sed 's/^# \{0,1\}//'; exit 0; }
|
||||
usage() { sed -n '2,94p' "$0" | sed 's/^# \{0,1\}//'; exit 0; }
|
||||
|
||||
run() { # simple (no pipes/redirects) mutating command
|
||||
if $DRY_RUN; then log_dry "$*"; else "$@"; fi
|
||||
@@ -355,6 +377,82 @@ pool_add_guest() {
|
||||
fi
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Pool-scoped ACL helpers (3b). All ops run as root@pam (installer) — no privilege change to the agent.
|
||||
#-------------------------------------------------------------------------------
|
||||
# _role_exists NAME — true if a pveum role NAME exists.
|
||||
_role_exists() {
|
||||
pveum role list --output-format json 2>/dev/null \
|
||||
| python3 -c "import json,sys;sys.exit(0 if any(r['roleid']==sys.argv[1] for r in json.load(sys.stdin)) else 1)" "$1" 2>/dev/null
|
||||
}
|
||||
|
||||
# _ensure_role NAME "PRIVS" — create the role, or modify it to the exact priv set (idempotent).
|
||||
_ensure_role() {
|
||||
local name="$1" privs="$2"
|
||||
if _role_exists "$name"; then
|
||||
log_info " role $name exists — ensuring exact privileges"
|
||||
run pveum role modify "$name" -privs "$privs"
|
||||
else
|
||||
run pveum role add "$name" -privs "$privs"
|
||||
fi
|
||||
}
|
||||
|
||||
# _grant PATH ROLE — grant ROLE at PATH to BOTH the user AND the token (privsep intersection). `acl
|
||||
# modify` is idempotent so this is safe to repeat / re-apply after a token rotation.
|
||||
_grant() {
|
||||
local path="$1" role="$2"
|
||||
run pveum acl modify "$path" -user "$PVE_USER" -role "$role"
|
||||
run pveum acl modify "$path" -token "${PVE_USER}!${PVE_TOKENID}" -role "$role"
|
||||
}
|
||||
|
||||
# apply_scoped_acl — create the 3 scoped roles and grant each at its path(s). Requires the pool to exist.
|
||||
apply_scoped_acl() {
|
||||
_ensure_role "$PVE_ROLE_GUEST" "$PVE_PRIVS_GUEST"
|
||||
_ensure_role "$PVE_ROLE_STORE" "$PVE_PRIVS_STORE"
|
||||
_ensure_role "$PVE_ROLE_BASE" "$PVE_PRIVS_BASE"
|
||||
_grant "/pool/$PVE_POOL" "$PVE_ROLE_GUEST"
|
||||
local s
|
||||
for s in "${PVE_STORAGES[@]}"; do
|
||||
_grant "/storage/$s" "$PVE_ROLE_STORE"
|
||||
done
|
||||
_grant / "$PVE_ROLE_BASE"
|
||||
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() {
|
||||
pveum acl list --output-format json 2>/dev/null | python3 -c "import json,sys
|
||||
p,t,u,r=sys.argv[1:5]
|
||||
sys.exit(0 if any(e.get('path')==p and e.get('type')==t and e.get('ugid')==u and e.get('roleid')==r for e in json.load(sys.stdin)) else 1)" "$1" "$2" "$3" "$4" 2>/dev/null
|
||||
}
|
||||
|
||||
# remove_scoped_acl — delete the 3-role scoped grants (user+token at each path), then the 3 roles
|
||||
# (roles last — PVE refuses to delete a referenced role). Presence-checked, tolerate-absent.
|
||||
remove_scoped_acl() {
|
||||
local s
|
||||
if _acl_grant_present "/pool/$PVE_POOL" user "$PVE_USER" "$PVE_ROLE_GUEST"; then run pveum acl delete "/pool/$PVE_POOL" --users "$PVE_USER" --roles "$PVE_ROLE_GUEST"; fi
|
||||
if _acl_grant_present "/pool/$PVE_POOL" token "${PVE_USER}!${PVE_TOKENID}" "$PVE_ROLE_GUEST"; then run pveum acl delete "/pool/$PVE_POOL" --tokens "${PVE_USER}!${PVE_TOKENID}" --roles "$PVE_ROLE_GUEST"; fi
|
||||
for s in "${PVE_STORAGES[@]}"; do
|
||||
if _acl_grant_present "/storage/$s" user "$PVE_USER" "$PVE_ROLE_STORE"; then run pveum acl delete "/storage/$s" --users "$PVE_USER" --roles "$PVE_ROLE_STORE"; fi
|
||||
if _acl_grant_present "/storage/$s" token "${PVE_USER}!${PVE_TOKENID}" "$PVE_ROLE_STORE"; then run pveum acl delete "/storage/$s" --tokens "${PVE_USER}!${PVE_TOKENID}" --roles "$PVE_ROLE_STORE"; fi
|
||||
done
|
||||
if _acl_grant_present / user "$PVE_USER" "$PVE_ROLE_BASE"; then run pveum acl delete / --users "$PVE_USER" --roles "$PVE_ROLE_BASE"; fi
|
||||
if _acl_grant_present / token "${PVE_USER}!${PVE_TOKENID}" "$PVE_ROLE_BASE"; then run pveum acl delete / --tokens "${PVE_USER}!${PVE_TOKENID}" --roles "$PVE_ROLE_BASE"; fi
|
||||
local name
|
||||
for name in "$PVE_ROLE_GUEST" "$PVE_ROLE_STORE" "$PVE_ROLE_BASE"; do
|
||||
if _role_exists "$name"; then run pveum role delete "$name"; else log_skip " role $name already absent"; fi
|
||||
done
|
||||
}
|
||||
|
||||
# remove_old_broad_acl — remove the PRE-3b single FelhomAgent role granted at / (user+token) + the role.
|
||||
# Tolerate-absent (fresh 3b installs have none). Used by --rescope-acl (migration) and step_token (so a
|
||||
# re-install can't leave the old broad grant unioned with the new scoped one).
|
||||
remove_old_broad_acl() {
|
||||
if _acl_grant_present / user "$PVE_USER" "$PVE_ROLE"; then run pveum acl delete / --users "$PVE_USER" --roles "$PVE_ROLE"; fi
|
||||
if _acl_grant_present / token "${PVE_USER}!${PVE_TOKENID}" "$PVE_ROLE"; then run pveum acl delete / --tokens "${PVE_USER}!${PVE_TOKENID}" --roles "$PVE_ROLE"; fi
|
||||
if _role_exists "$PVE_ROLE"; then run pveum role delete "$PVE_ROLE"; else log_skip " old broad role $PVE_ROLE already absent"; fi
|
||||
}
|
||||
|
||||
# run_uninstall — the full guarded teardown. Every mutation goes through run() so --dry-run prints it
|
||||
# and executes nothing. Ordering is the reverse of install: guest -> agent -> pveum(ACL,token,user,
|
||||
# role) -> golden(opt-in) -> state file. See the TASK spec §7/§8.
|
||||
@@ -461,19 +559,12 @@ run_uninstall() {
|
||||
if [[ -d "$AGENT_STATE_DIR" ]]; then run rm -rf "$AGENT_STATE_DIR"; else log_skip " $AGENT_STATE_DIR already absent"; fi
|
||||
if id "$AGENT_USER" >/dev/null 2>&1; then run userdel "$AGENT_USER"; else log_skip " service user $AGENT_USER already absent"; fi
|
||||
|
||||
# 5. pveum removal (reverse order; presence-checked; tolerate-absent). The role delete must come
|
||||
# AFTER its ACL grants are gone — PVE refuses to delete a referenced role.
|
||||
local acls; acls=$(pveum acl list --output-format json 2>/dev/null || echo '[]')
|
||||
if echo "$acls" | python3 -c "import json,sys;sys.exit(0 if any(e.get('path')=='/' and e.get('type')=='user' and e.get('ugid')=='$PVE_USER' and e.get('roleid')=='$PVE_ROLE' for e in json.load(sys.stdin)) else 1)" 2>/dev/null; then
|
||||
run pveum acl delete / --users "$PVE_USER" --roles "$PVE_ROLE"
|
||||
else
|
||||
log_skip " ACL user grant ($PVE_USER @ /) already absent"
|
||||
fi
|
||||
if echo "$acls" | python3 -c "import json,sys;sys.exit(0 if any(e.get('path')=='/' and e.get('type')=='token' and e.get('ugid')=='${PVE_USER}!${PVE_TOKENID}' and e.get('roleid')=='$PVE_ROLE' for e in json.load(sys.stdin)) else 1)" 2>/dev/null; then
|
||||
run pveum acl delete / --tokens "${PVE_USER}!${PVE_TOKENID}" --roles "$PVE_ROLE"
|
||||
else
|
||||
log_skip " ACL token grant (${PVE_USER}!${PVE_TOKENID} @ /) already absent"
|
||||
fi
|
||||
# 5. pveum removal (presence-checked; tolerate-absent; roles deleted only after their grants).
|
||||
# Remove the 3-role scoped grants+roles (3b) AND the pre-3b single-role broad grant if present —
|
||||
# both tolerate-absent so --uninstall works on a box of either shape.
|
||||
remove_scoped_acl
|
||||
remove_old_broad_acl
|
||||
# token then user (token-remove purges its ACL; user-delete purges anything else).
|
||||
if pveum user token list "$PVE_USER" --output-format json 2>/dev/null | python3 -c "import json,sys;sys.exit(0 if any(t['tokenid']=='$PVE_TOKENID' for t in json.load(sys.stdin)) else 1)" 2>/dev/null; then
|
||||
run pveum user token remove "$PVE_USER" "$PVE_TOKENID"
|
||||
else
|
||||
@@ -484,11 +575,6 @@ run_uninstall() {
|
||||
else
|
||||
log_skip " user $PVE_USER already absent"
|
||||
fi
|
||||
if pveum role list --output-format json 2>/dev/null | python3 -c "import json,sys;sys.exit(0 if any(r['roleid']=='$PVE_ROLE' for r in json.load(sys.stdin)) else 1)" 2>/dev/null; then
|
||||
run pveum role delete "$PVE_ROLE"
|
||||
else
|
||||
log_skip " role $PVE_ROLE already absent"
|
||||
fi
|
||||
|
||||
# 5b. felhom pool — delete ONLY if empty (a destroyed guest is auto-removed from its pool). Never
|
||||
# delete a pool that still holds members (someone else's guests, or another Felhom guest kept
|
||||
@@ -573,6 +659,50 @@ run_adopt_pool() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# run_rescope_acl — migrate an EXISTING install from the pre-3b broad-/ token to the pool-scoped ACL.
|
||||
# Non-destructive to data (ACL-only): ensure the pool + the guest is a member, apply the 3-role scoped
|
||||
# grants, THEN remove the old broad grant. Idempotent + dry-run-aware. Does NOT touch the guest or hub.
|
||||
# ORDERING (see §13): run this with the agent STOPPED, then deploy agent >= v0.53.0, then start — the
|
||||
# scoped ACL and the pool-param agent are mutually dependent.
|
||||
run_rescope_acl() {
|
||||
log_step "RESCOPE-ACL — migrate to the pool-scoped token ACL"
|
||||
|
||||
local state_vmid vmid
|
||||
state_vmid=$(_state_get provisioned_vmid)
|
||||
if $VMID_EXPLICIT; then
|
||||
vmid="$VMID"
|
||||
elif [[ -n "$state_vmid" ]]; then
|
||||
vmid="$state_vmid"
|
||||
log_info " no --vmid given; using recorded provisioned_vmid=$vmid from $STATE_FILE"
|
||||
else
|
||||
die "pass --vmid N (state has no recorded vmid)"
|
||||
fi
|
||||
|
||||
_vmid_in_use "$vmid" || die "guest $vmid not found on this host"
|
||||
if pct config "$vmid" 2>/dev/null | grep -q 'mp=/etc/felhom-bootstrap'; then
|
||||
log_info " vmid $vmid looks like a Felhom guest (has the /etc/felhom-bootstrap mount)"
|
||||
elif $FORCE; then
|
||||
log_warn " vmid $vmid has NO /etc/felhom-bootstrap mount — --force given, rescoping anyway"
|
||||
else
|
||||
die "vmid $vmid does not look like a Felhom-provisioned guest (no /etc/felhom-bootstrap mount).
|
||||
Refusing to rescope. Pass --force to override."
|
||||
fi
|
||||
|
||||
# The guest MUST be a pool member before the scoped token can touch it — ensure it first.
|
||||
ensure_felhom_pool
|
||||
pool_add_guest "$vmid"
|
||||
# Apply the scoped grants, THEN remove the old broad grant (add-before-remove: never leave the token
|
||||
# with NO grant mid-migration).
|
||||
apply_scoped_acl
|
||||
remove_old_broad_acl
|
||||
|
||||
echo ""
|
||||
log_success "RESCOPE-ACL complete — token scoped to /pool/$PVE_POOL + /storage/[${PVE_STORAGES[*]}] + Sys.Audit/SDN.Use@/."
|
||||
log_warn " NOW deploy agent >= v0.53.0 (restore-into-pool) and (re)start felhom-agent — the scoped ACL needs it."
|
||||
$DRY_RUN && log_warn " DRY-RUN: nothing above was actually executed."
|
||||
return 0
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Arg parse
|
||||
#-------------------------------------------------------------------------------
|
||||
@@ -602,6 +732,8 @@ while [[ $# -gt 0 ]]; do
|
||||
--uninstall) UNINSTALL=true; shift ;;
|
||||
--remove-golden) REMOVE_GOLDEN=true; shift ;;
|
||||
--adopt-pool) ADOPT_POOL=true; shift ;;
|
||||
--rescope-acl) RESCOPE_ACL=true; shift ;;
|
||||
--acl-storages) read -ra PVE_STORAGES <<< "$2"; shift 2 ;;
|
||||
--dry-run) DRY_RUN=true; shift ;;
|
||||
--resume) RESUME=true; shift ;;
|
||||
-h|--help) usage ;;
|
||||
@@ -637,6 +769,20 @@ if $ADOPT_POOL; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
#===============================================================================
|
||||
# RESCOPE-ACL MODE — migrate an existing install to the pool-scoped token ACL (ACL-only, no hub, no
|
||||
# passphrase). Supervised: run with the agent stopped, then deploy agent >= v0.53.0 (see §13).
|
||||
#===============================================================================
|
||||
if $RESCOPE_ACL; then
|
||||
[[ $EUID -eq 0 ]] || die "must run as root"
|
||||
echo ""
|
||||
log_info "felhom-host-install v${SCRIPT_VERSION} — mode=rescope-acl"
|
||||
$DRY_RUN && log_warn "DRY-RUN: no mutations will be performed"
|
||||
echo ""
|
||||
run_rescope_acl
|
||||
exit 0
|
||||
fi
|
||||
|
||||
#===============================================================================
|
||||
# DR MODE — documented seam only (10D). NOT implemented.
|
||||
#===============================================================================
|
||||
@@ -841,13 +987,10 @@ step_token() {
|
||||
log_step "2/8 Proxmox API token"
|
||||
if should_skip token && [[ -n "$PVE_TOKEN" ]]; then return 0; fi
|
||||
|
||||
# role: create or modify to the exact 16 privs
|
||||
if pveum role list --output-format json 2>/dev/null | python3 -c "import json,sys;sys.exit(0 if any(r['roleid']=='$PVE_ROLE' for r in json.load(sys.stdin)) else 1)"; then
|
||||
log_info " role $PVE_ROLE exists — ensuring exact privileges"
|
||||
run pveum role modify "$PVE_ROLE" -privs "$PVE_PRIVS"
|
||||
else
|
||||
run pveum role add "$PVE_ROLE" -privs "$PVE_PRIVS"
|
||||
fi
|
||||
# Pool BEFORE the ACL: /pool/felhom must exist before apply_scoped_acl grants on it (3b). Always —
|
||||
# even under --skip-provision (the token exists now; a later provision-into-pool needs pool + grant).
|
||||
# The 3 scoped roles + grants are created by apply_scoped_acl below (AFTER the token exists).
|
||||
ensure_felhom_pool
|
||||
|
||||
# user: tolerate-exists
|
||||
if pveum user list --output-format json 2>/dev/null | python3 -c "import json,sys;sys.exit(0 if any(u['userid']=='$PVE_USER' for u in json.load(sys.stdin)) else 1)"; then
|
||||
@@ -887,11 +1030,12 @@ step_token() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# Both ACL grants — AFTER the token exists (the single most common 403 cause).
|
||||
# `pveum user token remove` PURGES the token's ACL, so re-applying here (post-rotate)
|
||||
# is mandatory; `acl modify` is idempotent so this is also safe on the reuse path.
|
||||
run pveum acl modify / -user "$PVE_USER" -role "$PVE_ROLE"
|
||||
run pveum acl modify / -token "${PVE_USER}!${PVE_TOKENID}" -role "$PVE_ROLE"
|
||||
# Scoped ACL grants — AFTER the token exists (`pveum user token remove` purges the token's ACL, so
|
||||
# re-applying post-rotate is mandatory; `acl modify` is idempotent so this is safe on the reuse path).
|
||||
apply_scoped_acl
|
||||
# If this box previously ran the pre-3b broad grant (re-install/upgrade), remove it — else the old
|
||||
# FelhomAgent role at / would UNION with the scoped grant and defeat containment. Tolerate-absent.
|
||||
remove_old_broad_acl
|
||||
_state_mark token
|
||||
}
|
||||
|
||||
@@ -1251,12 +1395,12 @@ step_provision() {
|
||||
local -a cap_args=()
|
||||
[[ -n "$CPU_CORES" ]] && cap_args+=(-cores "$CPU_CORES")
|
||||
[[ -n "$MEM_MIB" ]] && cap_args+=(-memory "$MEM_MIB")
|
||||
# felhom pool: ensure it exists (root@pam op — no agent/token/ACL change), then add the guest after
|
||||
# a successful provision. Fleet uniformity + the environment 3b will scope its ACL against.
|
||||
# felhom pool: ensure it exists before the restore (step_token already created it; this is a
|
||||
# belt-and-suspenders no-op that also covers a --resume path). The AGENT adds the guest to the pool
|
||||
# atomically via restore --pool (v0.53.0) — no separate script-side pool_add_guest.
|
||||
ensure_felhom_pool
|
||||
if $DRY_RUN; then
|
||||
log_dry "felhom-agent --config $AGENT_CONFIG --selftest=provision -archive $GOLDEN_VOLID -vmid $VMID -customer-id $CUSTOMER_ID -hub-password <pass> -rootfs-grow $ROOTFS_GROW -datavol-grow $DATAVOL_GROW -sysdata-grow $SYSDATA_GROW ${cap_args[*]}"
|
||||
log_dry "add guest $VMID to pool $PVE_POOL"
|
||||
log_dry "felhom-agent --config $AGENT_CONFIG --selftest=provision -archive $GOLDEN_VOLID -vmid $VMID -customer-id $CUSTOMER_ID -hub-password <pass> -rootfs-grow $ROOTFS_GROW -datavol-grow $DATAVOL_GROW -sysdata-grow $SYSDATA_GROW ${cap_args[*]} # agent restores INTO pool $PVE_POOL"
|
||||
log_dry "record provisioned_vmid=$VMID in $STATE_FILE (for a later automatic --uninstall)"
|
||||
_state_mark provision; return 0
|
||||
fi
|
||||
@@ -1271,8 +1415,7 @@ step_provision() {
|
||||
_state_mark provision
|
||||
# Record the provisioned vmid so a later --uninstall resolves the target automatically + safely.
|
||||
_state_put provisioned_vmid "$VMID"
|
||||
# Join the felhom pool (idempotent; skip-if-member).
|
||||
pool_add_guest "$VMID"
|
||||
# (No pool_add_guest here — the agent's restore --pool already made the guest a member.)
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user