fix: Final-Review-Findings (GUI-Login, FK-Kaskaden, Validierung, Duplikat-Recheck, Views)
F1: GUI-Login-Hash gegen $-Korruption gehaertet. .env-Werte einfach-gequotet
geschrieben + Sanity-Check nach Sourcing. Zusaetzlich systemd-Ebene:
podman escapt $->$$ in Environment=, systemd 252 kollabiert das nicht ->
sed-Post-Processing der Unit-Dateien. Live-.env: GUI-Passwort rotiert.
F2: Explizite Dependent-Deletes gegen FK-500 (delete_scenario/loan), 409 bei
bestaetigtem Import-Delete. Test-Engine erzwingt PRAGMA foreign_keys=ON.
F3: Enum/Range-Validierung (Literal + Field ge/le) fuer Recurring, Loan,
Modifier inkl. PATCH-Pfade.
F4: Confirm nur fuer draft-Statements (sonst 409) + Dedup-Re-Check gegen
bestaetigte Buchungen vor dem Promoten.
F5: v_balance_history/v_balance_total addieren pro Konto den opening_balance
des fruehesten bestaetigten Statements (approved deviation vom Plan-SQL).
F6: patch_scenario Namenskollision -> 409; create_transaction unbekanntes
Konto -> 404 (statt 500).
F7: Generierte systemd-Unit-Dateien chmod 600 (enthalten Env-Secrets).
F8: Globaler htmx:responseError-Handler in base.html.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -53,15 +53,26 @@ GRAFANA_DASHBOARDS_DIR="$FINANCE_DIR/grafana/dashboards"
|
||||
# unchanged, so re-running this script never rotates secrets under existing
|
||||
# data.
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
# Compute all values into variables first, then write them SINGLE-QUOTED.
|
||||
# The GUI password hash has the form salt$digest; if written unquoted, the
|
||||
# later `set -a; . "$ENV_FILE"` would shell-expand the "$digest" part and
|
||||
# corrupt the hash, breaking GUI login. Single quotes make every value a
|
||||
# literal string on sourcing.
|
||||
GUI_PASSWORD=$(openssl rand -base64 12)
|
||||
POSTGRES_PASSWORD_VAL=$(openssl rand -hex 16)
|
||||
FINANCE_READ_PASSWORD_VAL=$(openssl rand -hex 16)
|
||||
FB_API_KEY_VAL=$(openssl rand -hex 32)
|
||||
FB_SESSION_SECRET_VAL=$(openssl rand -hex 32)
|
||||
FB_GUI_PASSWORD_HASH_VAL=$(python3 -c "import hashlib,secrets,sys;s=secrets.token_hex(16);pw=sys.argv[1];print(s+'\$'+hashlib.pbkdf2_hmac('sha256',pw.encode(),s.encode(),200000).hex())" "$GUI_PASSWORD")
|
||||
GRAFANA_ADMIN_PASSWORD_VAL=$(openssl rand -base64 12)
|
||||
cat > "$ENV_FILE" <<EOF
|
||||
POSTGRES_PASSWORD=$(openssl rand -hex 16)
|
||||
FINANCE_READ_PASSWORD=$(openssl rand -hex 16)
|
||||
FB_API_KEY=$(openssl rand -hex 32)
|
||||
FB_SESSION_SECRET=$(openssl rand -hex 32)
|
||||
FB_GUI_USER=admin
|
||||
FB_GUI_PASSWORD_HASH=$(python3 -c "import hashlib,secrets;s=secrets.token_hex(16);print(s+'\$'+hashlib.pbkdf2_hmac('sha256',b'$GUI_PASSWORD',s.encode(),200000).hex())")
|
||||
GRAFANA_ADMIN_PASSWORD=$(openssl rand -base64 12)
|
||||
POSTGRES_PASSWORD='$POSTGRES_PASSWORD_VAL'
|
||||
FINANCE_READ_PASSWORD='$FINANCE_READ_PASSWORD_VAL'
|
||||
FB_API_KEY='$FB_API_KEY_VAL'
|
||||
FB_SESSION_SECRET='$FB_SESSION_SECRET_VAL'
|
||||
FB_GUI_USER='admin'
|
||||
FB_GUI_PASSWORD_HASH='$FB_GUI_PASSWORD_HASH_VAL'
|
||||
GRAFANA_ADMIN_PASSWORD='$GRAFANA_ADMIN_PASSWORD_VAL'
|
||||
EOF
|
||||
chmod 600 "$ENV_FILE"
|
||||
echo "NEU ERZEUGT: GUI-Login admin / $GUI_PASSWORD (jetzt notieren!)"
|
||||
@@ -69,6 +80,16 @@ EOF
|
||||
fi
|
||||
set -a; . "$ENV_FILE"; set +a
|
||||
|
||||
# Sanity check: the GUI password hash must survive shell sourcing intact. An
|
||||
# unquoted value containing a literal '$' gets mangled by parameter expansion,
|
||||
# which would silently break GUI login. Require the canonical salt$digest form
|
||||
# (32 hex chars, '$', 64 hex chars).
|
||||
if ! printf '%s' "$FB_GUI_PASSWORD_HASH" | grep -Eq '^[0-9a-f]{32}\$[0-9a-f]{64}$'; then
|
||||
echo "ERROR: FB_GUI_PASSWORD_HASH is malformed after sourcing $ENV_FILE." >&2
|
||||
echo " All values in $ENV_FILE must be single-quoted." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Stop existing systemd-managed pod if present, to avoid conflicts on rerun
|
||||
echo "Stopping systemd-managed pod 'pod-$POD_NAME.service' if it exists..."
|
||||
if systemctl --user list-units --type=service --all 2>/dev/null | \
|
||||
@@ -169,6 +190,24 @@ cd "$USER_SYSTEMD_DIR"
|
||||
podman generate systemd --name --new --files "$POD_NAME"
|
||||
echo "Generated systemd service files (rc=$?)"
|
||||
|
||||
# The --new units embed the full `podman run` commands, including container
|
||||
# environment secrets (POSTGRES_PASSWORD, GF_SECURITY_ADMIN_PASSWORD,
|
||||
# FB_GUI_PASSWORD_HASH, ...). Post-process each generated unit file:
|
||||
# 1. chmod 600 so only the owner can read the embedded secrets.
|
||||
# 2. Collapse '$$' -> '$' on Environment= lines. `podman generate systemd`
|
||||
# escapes every '$' as '$$' (correct for ExecStart command lines, where
|
||||
# systemd performs variable expansion). But systemd does NOT un-escape
|
||||
# '$$' inside Environment= *values* - it passes them through literally.
|
||||
# The GUI password hash has the form salt$digest, so without this fix the
|
||||
# container receives salt$$digest and GUI login fails. Restrict the
|
||||
# substitution to Environment= lines so ExecStart escaping stays intact.
|
||||
for svc in "pod-${POD_NAME}.service" "container-${DB_CTR_NAME}.service" \
|
||||
"container-${API_CTR_NAME}.service" "container-${GRAFANA_CTR_NAME}.service"; do
|
||||
[ -f "$svc" ] || continue
|
||||
chmod 600 "$svc"
|
||||
sed -i '/^Environment=/ s/\$\$/$/g' "$svc"
|
||||
done
|
||||
|
||||
# Stop & remove live pod and containers
|
||||
podman pod stop --ignore --time 15 "$POD_NAME"
|
||||
podman pod rm -f --ignore "$POD_NAME"
|
||||
|
||||
Reference in New Issue
Block a user