Skip to content

Commit ee865f9

Browse files
authored
Merge pull request #481 from p2pool-starter-stack/release/v1.4.0-envfix
fix(#33): restore operator ownership of .env after root control-apply (v1.4 blocker)
2 parents 9168c19 + 725b0a2 commit ee865f9

2 files changed

Lines changed: 38 additions & 0 deletions

File tree

pithead

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4202,6 +4202,30 @@ control_preview() { # <request-file> <id> <actor> <control-dir>
42024202
rm -f "$errf"
42034203
}
42044204

4205+
# Hand the operator-facing stack files that the ROOT control-runner just wrote back to the stack
4206+
# owner (#33 v1.4). control_run_pending is root (User=root in pithead-control.service), so its
4207+
# `apply` renders `.env` under `umask 077` as root:root 0600 and rewrites the Caddyfile as root —
4208+
# but pithead runs a NON-ROOT operator model ($REAL_USER), and a normal operator-run apply leaves
4209+
# these files owned by the operator. Without this, the operator's next `status`/`apply` can't even
4210+
# read .env (Permission denied), which is what the tier-4 gate caught. The target owner is DERIVED
4211+
# from config.json's on-disk owner — an operator-owned file the dashboard container CANNOT write
4212+
# (its raw config.json mount was dropped in #440; control_commit's `cp` also preserves its inode/
4213+
# owner), so nothing from the request or spool can steer the chown. $USER/$SUDO_USER are NOT usable
4214+
# here — the runner is root, so they read as root. The control-dir (staged/results/audit) is
4215+
# deliberately host-owned and is NOT touched: that rw/ro split is the #33 trust boundary.
4216+
control_reown_operator_files() {
4217+
local owner f
4218+
# GNU stat first, BSD fallback (see the provision_onion_client_auth note). No owner → skip.
4219+
owner=$(stat -c '%u:%g' "$CONFIG_FILE" 2>/dev/null || stat -f '%u:%g' "$CONFIG_FILE" 2>/dev/null) || owner=""
4220+
[ -n "$owner" ] || return 0
4221+
for f in "$ENV_FILE" "Caddyfile" "${CONFIG_FILE}.bak-control"; do
4222+
[ -e "$f" ] || continue
4223+
# Fail safe: a chown that can't complete leaves the pre-existing bug, never corrupts state.
4224+
chown "$owner" "$f" 2>/dev/null ||
4225+
warn "Could not re-own $f to $owner after the control apply — the operator may need to chown it by hand (#33)."
4226+
done
4227+
}
4228+
42054229
# Commit: apply the HOST-SIDE staged copy from the matching preview. A tampered second request
42064230
# can't swap the config — commit carries only the id; the config it applies is the one previewed.
42074231
control_commit() { # <id> <actor> <control-dir>
@@ -4235,6 +4259,7 @@ control_commit() { # <id> <actor> <control-dir>
42354259
cp "$staged" "$CONFIG_FILE"
42364260
"$0" apply -y >"$logf" 2>&1 || rc=$?
42374261
if [ "$rc" -eq 0 ]; then
4262+
control_reown_operator_files # the root apply wrote .env/Caddyfile as root — give them back (#33)
42384263
control_write_result "$cdir/results" "$id" "$(jq -n '{status:"applied",ts:(now|floor)}')"
42394264
control_audit "$cdir/audit/control.log" "$id" "$actor" "commit" "applied" "$keys"
42404265
else

tests/stack/run.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2572,6 +2572,7 @@ echo "== unit+black-box: secret files are owner-only from creation (#368) =="
25722572
# reverse order is wrong — on Linux `stat -f` is a VALID flag (filesystem status) that succeeds
25732573
# with the wrong output, so the fallback never runs and CI (Linux) reads garbage.
25742574
file_mode() { stat -c %a "$1" 2>/dev/null || stat -f %Lp "$1" 2>/dev/null; }
2575+
file_uid() { stat -c %u "$1" 2>/dev/null || stat -f %u "$1" 2>/dev/null; }
25752576
PB="$SANDBOX/perm368"
25762577
mkdir -p "$PB/bin"
25772578
printf '{ "monero": {} }\n' >"$PB/config.json"
@@ -3552,6 +3553,18 @@ assert_contains "commit ran the real apply (containers recreated)" "$(cat "$CTRL
35523553
assert_contains "commit audited with the actor" "$(cat "$AUDIT")" "\"actor\":\"admin\",\"action\":\"commit\",\"status\":\"applied\""
35533554
[ ! -f "$STAGED/$UUID1.json" ] && ok "staged intent consumed on commit" || bad "staged intent consumed on commit" "still staged"
35543555

3556+
# Operator keeps ownership of the stack files the root runner's apply wrote (#33 v1.4): control_run_pending
3557+
# is root, so its apply would render .env root:root 0600 — unreadable to the non-root operator. The
3558+
# re-own derives the owner from config.json (operator-owned, container can't write it) so a commit
3559+
# matches a normal apply. Assert every operator-facing file is owned by config.json's owner, so a
3560+
# non-root operator can still read .env / re-render on the next apply.
3561+
cfg_uid="$(file_uid "$C/config.json")"
3562+
for reowned in ".env" "Caddyfile" "config.json.bak-control"; do
3563+
[ -e "$C/$reowned" ] &&
3564+
assert_eq "$reowned owned by the config.json owner after a control commit" "$(file_uid "$C/$reowned")" "$cfg_uid" ||
3565+
bad "$reowned present after a control commit" "missing"
3566+
done
3567+
35553568
echo "== black-box: audit log records names, never values (#349) =="
35563569
# WHAT changed rides in the audit entry as env-key NAMES (main -> mini touches the p2pool keys);
35573570
# no config or secret VALUE may ever land in the log — it is mounted into the dashboard container.

0 commit comments

Comments
 (0)