fix(de-sudo): route container-tree writes through the privileged path
Two more cases of the manager writing directly into the container-owned /libreportal-containers tree (same class as the regen-poll stamp), both masked by a '✓ Success' that printed anyway: - Password replacers (config/password/*): used 'runInstallOp sed -i' (manager) on app configs copied into the container tree, so sed -i EACCES'd its temp file and the substitution silently failed — the adguard.config 'couldn't open temporary file', leaving the literal RANDOMIZEDPASSWORD placeholder. Added runCfgOp (picks runFileOp vs runInstallOp by the target file's location) and routed every $file grep/sed/awk through it: password, username, hex, vapid, appkey, and bcrypt. - Updater generator (webui_updater_scan): 'runFileOp cp <manager-tmp>' can't read the manager's 0600 mktemp as the container user, so it fell through to a manager 'cp' that EACCES'd on the container-owned out_dir. Switched the three writes to 'runFileWrite < tmp' (manager shell reads the tmp; container user tees the write). Both deploy via the normal quick path (relocatable scripts) — no footprint bump, no reinstall. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
parent
b83b647d16
commit
6089eb0882
@ -20,7 +20,7 @@ exportBcryptPassword()
|
||||
|
||||
# Extract the correct variable name (e.g., PASSWORD_HASH) before the placeholder
|
||||
local variable_name
|
||||
variable_name=$(runInstallOp awk -F= '/'"$placeholder"'/ { gsub(/^[ \t-]+/, "", $1); print $1; exit }' "$file")
|
||||
variable_name=$(runCfgOp awk -F= '/'"$placeholder"'/ { gsub(/^[ \t-]+/, "", $1); print $1; exit }' "$file")
|
||||
|
||||
if [ -n "$variable_name" ]; then
|
||||
# Remove old password entries for this app & variable
|
||||
|
||||
@ -8,7 +8,7 @@ processBcryptPassword()
|
||||
|
||||
# Extract the variable name before the placeholder
|
||||
local variable_name
|
||||
variable_name=$(runInstallOp awk -F= '/'"$placeholder"'/ { gsub(/^[ \t-]+/, "", $1); print $1; exit }' "$file")
|
||||
variable_name=$(runCfgOp awk -F= '/'"$placeholder"'/ { gsub(/^[ \t-]+/, "", $1); print $1; exit }' "$file")
|
||||
|
||||
if [ -z "$variable_name" ]; then
|
||||
isError " Could not extract variable name before $placeholder."
|
||||
@ -35,11 +35,11 @@ processBcryptPassword()
|
||||
# Remove any single quotes from the bcrypt hash
|
||||
bcrypt_password=$(echo "$bcrypt_password" | tr -d "'")
|
||||
|
||||
local result=$(runInstallOp sed -i -E "s#$placeholder#$bcrypt_password#g" "$file")
|
||||
local result=$(runCfgOp sed -i -E "s#$placeholder#$bcrypt_password#g" "$file")
|
||||
checkSuccess "Use sed to replace placeholder with bcrypt hash"
|
||||
|
||||
# Verify replacement
|
||||
if runInstallOp grep -q "$bcrypt_password" "$file"; then
|
||||
if runCfgOp grep -q "$bcrypt_password" "$file"; then
|
||||
isSuccessful "Updated $variable_name in $(basename "$file")."
|
||||
else
|
||||
isError "ERROR: sed failed to replace $placeholder in $file."
|
||||
|
||||
@ -7,7 +7,7 @@ replaceBcryptPasswords()
|
||||
app_name=$(basename "$(dirname "$file")")
|
||||
|
||||
# Only scan for bcrypt placeholders that actually exist in the file
|
||||
local existing_placeholders=$(runInstallOp grep -oE 'RANDOMIZEDBCRYPTPASSWORD[0-9]*' "$file" 2>/dev/null | sort -u)
|
||||
local existing_placeholders=$(runCfgOp grep -oE 'RANDOMIZEDBCRYPTPASSWORD[0-9]*' "$file" 2>/dev/null | sort -u)
|
||||
|
||||
if [[ -n "$existing_placeholders" ]]; then
|
||||
while IFS= read -r placeholder; do
|
||||
|
||||
@ -5,7 +5,7 @@ replaceHexKeys()
|
||||
local file="$1"
|
||||
|
||||
# Only scan for hex placeholders that actually exist in the file
|
||||
local existing_placeholders=$(runInstallOp grep -oE 'RANDOMIZEDHEX[0-9]*' "$file" 2>/dev/null | sort -u)
|
||||
local existing_placeholders=$(runCfgOp grep -oE 'RANDOMIZEDHEX[0-9]*' "$file" 2>/dev/null | sort -u)
|
||||
|
||||
if [[ -n "$existing_placeholders" ]]; then
|
||||
while IFS= read -r placeholder; do
|
||||
@ -13,7 +13,7 @@ replaceHexKeys()
|
||||
local hex_key
|
||||
hex_key=$(openssl rand -hex 32)
|
||||
|
||||
runInstallOp sed -i "s/${placeholder}/${hex_key}/g" "$file"
|
||||
runCfgOp sed -i "s/${placeholder}/${hex_key}/g" "$file"
|
||||
checkSuccess "Updated ${placeholder} in $(basename "$file") with a new hex key."
|
||||
fi
|
||||
done <<< "$existing_placeholders"
|
||||
|
||||
@ -5,7 +5,7 @@ replaceVAPIDKeys()
|
||||
local file="$1"
|
||||
|
||||
# Only scan for VAPID placeholders that actually exist in the file
|
||||
local existing_placeholders=$(runInstallOp grep -oE 'RANDOMIZEDVAPID[0-9]*' "$file" 2>/dev/null | sort -u)
|
||||
local existing_placeholders=$(runCfgOp grep -oE 'RANDOMIZEDVAPID[0-9]*' "$file" 2>/dev/null | sort -u)
|
||||
|
||||
if [[ -n "$existing_placeholders" ]]; then
|
||||
while IFS= read -r placeholder; do
|
||||
@ -13,7 +13,7 @@ replaceVAPIDKeys()
|
||||
local vapid_key
|
||||
vapid_key=$(openssl rand -base64 32 | tr -d '+/=' | tr -cd '[:alnum:]')
|
||||
|
||||
runInstallOp sed -i "s/${placeholder}/${vapid_key}/g" "$file"
|
||||
runCfgOp sed -i "s/${placeholder}/${vapid_key}/g" "$file"
|
||||
checkSuccess "Updated ${placeholder} in $(basename "$file") with a new VAPID key."
|
||||
fi
|
||||
done <<< "$existing_placeholders"
|
||||
|
||||
@ -5,13 +5,13 @@ replacePlainPasswords()
|
||||
local file="$1"
|
||||
|
||||
# Only scan for placeholders that actually exist in the file
|
||||
local existing_placeholders=$(runInstallOp grep -oE 'RANDOMIZEDPASSWORD[0-9]+' "$file" 2>/dev/null | sort -u)
|
||||
local existing_placeholders=$(runCfgOp grep -oE 'RANDOMIZEDPASSWORD[0-9]+' "$file" 2>/dev/null | sort -u)
|
||||
|
||||
if [[ -n "$existing_placeholders" ]]; then
|
||||
while IFS= read -r password_placeholder; do
|
||||
if [[ -n "$password_placeholder" ]]; then
|
||||
local random_password=$(generateRandomPassword)
|
||||
runInstallOp sed -i 's/'"${password_placeholder}"'/'"${random_password}"'/g' "$file"
|
||||
runCfgOp sed -i 's/'"${password_placeholder}"'/'"${random_password}"'/g' "$file"
|
||||
checkSuccess "Updated ${password_placeholder} in $(basename "$file")."
|
||||
fi
|
||||
done <<< "$existing_placeholders"
|
||||
|
||||
@ -8,14 +8,14 @@ replaceLaravelAppKeys()
|
||||
{
|
||||
local file="$1"
|
||||
|
||||
local existing_placeholders=$(runInstallOp grep -oE 'RANDOMIZEDAPPKEY[0-9]*' "$file" 2>/dev/null | sort -u)
|
||||
local existing_placeholders=$(runCfgOp grep -oE 'RANDOMIZEDAPPKEY[0-9]*' "$file" 2>/dev/null | sort -u)
|
||||
|
||||
if [[ -n "$existing_placeholders" ]]; then
|
||||
while IFS= read -r placeholder; do
|
||||
if [[ -n "$placeholder" ]]; then
|
||||
local app_key
|
||||
app_key="base64:$(openssl rand -base64 32)"
|
||||
runInstallOp sed -i "s#${placeholder}#${app_key}#g" "$file"
|
||||
runCfgOp sed -i "s#${placeholder}#${app_key}#g" "$file"
|
||||
checkSuccess "Updated ${placeholder} in $(basename "$file") with a new Laravel APP_KEY."
|
||||
fi
|
||||
done <<< "$existing_placeholders"
|
||||
|
||||
@ -5,13 +5,13 @@ replaceRandomUsernames()
|
||||
local file="$1"
|
||||
|
||||
# Only scan for placeholders that actually exist in the file
|
||||
local existing_placeholders=$(runInstallOp grep -oE 'RANDOMIZEDUSERNAME[0-9]+' "$file" 2>/dev/null | sort -u)
|
||||
local existing_placeholders=$(runCfgOp grep -oE 'RANDOMIZEDUSERNAME[0-9]+' "$file" 2>/dev/null | sort -u)
|
||||
|
||||
if [[ -n "$existing_placeholders" ]]; then
|
||||
while IFS= read -r username_placeholder; do
|
||||
if [[ -n "$username_placeholder" ]]; then
|
||||
local random_username=$(generateRandomUsername)
|
||||
runInstallOp sed -i 's/'"${username_placeholder}"'/'"${random_username}"'/g' "$file"
|
||||
runCfgOp sed -i 's/'"${username_placeholder}"'/'"${random_username}"'/g' "$file"
|
||||
checkSuccess "Updated ${username_placeholder} in $(basename "$file")."
|
||||
fi
|
||||
done <<< "$existing_placeholders"
|
||||
|
||||
@ -77,6 +77,23 @@ runInstallWrite() {
|
||||
runAsManager tee "${append_flag[@]}" "$dest" >/dev/null
|
||||
}
|
||||
|
||||
# Run a read/edit op against a CONFIG FILE, auto-selecting elevation by where the
|
||||
# file lives: the container data-plane (/libreportal-containers, install-user-owned
|
||||
# in rootless) -> runFileOp; the manager-owned control plane (configs/, the clone,
|
||||
# backup-location configs) -> runInstallOp. The target file must be the LAST arg
|
||||
# (true for the grep/sed/awk calls in the password replacers). Without this,
|
||||
# sed -i EACCES'd its own temp file whenever the manager edited an app config
|
||||
# copied into the container tree (the adguard.config "couldn't open temporary
|
||||
# file" bug — the substitution silently failed, leaving the placeholder).
|
||||
runCfgOp() {
|
||||
local _file="${!#}"
|
||||
if [[ -n "$containers_dir" && "$_file" == "$containers_dir"* ]]; then
|
||||
runFileOp "$@"
|
||||
else
|
||||
runInstallOp "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Backup-engine command (borg/restic/kopia) run AS the dedicated backup user
|
||||
# ($docker_install_user), with the environment preserved (-E) so the repo
|
||||
# password and BORG_/RESTIC_/KOPIA_ env vars reach the tool. Never root — the
|
||||
|
||||
@ -742,6 +742,7 @@ declare -gA LP_FN_MAP=(
|
||||
[runAsManager]="docker/command/run_privileged.sh"
|
||||
[runBackupOp]="docker/command/run_privileged.sh"
|
||||
[runBinInstall]="docker/command/run_privileged.sh"
|
||||
[runCfgOp]="docker/command/run_privileged.sh"
|
||||
[runCrowdsec]="docker/command/run_privileged.sh"
|
||||
[runFileOp]="docker/command/run_privileged.sh"
|
||||
[runFileWrite]="docker/command/run_privileged.sh"
|
||||
@ -1648,6 +1649,7 @@ declare -gA LP_FN_ROOT=(
|
||||
[runAsManager]="scripts"
|
||||
[runBackupOp]="scripts"
|
||||
[runBinInstall]="scripts"
|
||||
[runCfgOp]="scripts"
|
||||
[runCrowdsec]="scripts"
|
||||
[runFileOp]="scripts"
|
||||
[runFileWrite]="scripts"
|
||||
@ -2574,6 +2576,7 @@ runAppCfg() { source "${install_scripts_dir}docker/command/run_privileged.sh"; r
|
||||
runAsManager() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runAsManager "$@"; }
|
||||
runBackupOp() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runBackupOp "$@"; }
|
||||
runBinInstall() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runBinInstall "$@"; }
|
||||
runCfgOp() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runCfgOp "$@"; }
|
||||
runCrowdsec() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runCrowdsec "$@"; }
|
||||
runFileOp() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runFileOp "$@"; }
|
||||
runFileWrite() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runFileWrite "$@"; }
|
||||
|
||||
@ -68,7 +68,12 @@ EOF
|
||||
{ "generated_at": "$now", "apps": [${entries}
|
||||
] }
|
||||
EOF
|
||||
runFileOp cp "$tmp" "$out_dir/updates.json" 2>/dev/null || cp "$tmp" "$out_dir/updates.json"
|
||||
# Write as the container user that owns out_dir. cp'ing the manager-owned
|
||||
# mktemp would fail (the container user can't read a 600 /tmp file), so the
|
||||
# old `runFileOp cp || cp` fell through to a manager cp that EACCES'd on the
|
||||
# container-owned dir. runFileWrite reads the tmp in this (manager) shell and
|
||||
# tees it as the container user — works in both modes.
|
||||
runFileWrite "$out_dir/updates.json" < "$tmp"
|
||||
rm -f "$tmp"
|
||||
|
||||
# CVE data — pluggable. Wire trivy/grype per image here and emit per-app
|
||||
@ -76,14 +81,14 @@ EOF
|
||||
if [ ! -f "$out_dir/cves.json" ]; then
|
||||
local ctmp; ctmp="$(mktemp)"
|
||||
printf '{ "generated_at": "%s", "apps": [], "totals": { "critical": 0, "high": 0, "medium": 0, "low": 0 } }\n' "$now" > "$ctmp"
|
||||
runFileOp cp "$ctmp" "$out_dir/cves.json" 2>/dev/null || cp "$ctmp" "$out_dir/cves.json"
|
||||
runFileWrite "$out_dir/cves.json" < "$ctmp"
|
||||
rm -f "$ctmp"
|
||||
fi
|
||||
# Ensure a valid (possibly empty) history file exists for the WebUI.
|
||||
if [ ! -f "$out_dir/history.json" ]; then
|
||||
local htmp; htmp="$(mktemp)"
|
||||
printf '{ "entries": [] }\n' > "$htmp"
|
||||
runFileOp cp "$htmp" "$out_dir/history.json" 2>/dev/null || cp "$htmp" "$out_dir/history.json"
|
||||
runFileWrite "$out_dir/history.json" < "$htmp"
|
||||
rm -f "$htmp"
|
||||
fi
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user