fix(deploy): reset-password schrieb lautlos nichts
Drei Fehler, alle drei ohne Fehlermeldung: Skript per Heredoc UND Passwort per Pipe geht nicht — kommt das Skript ueber stdin, frisst ein `cat` darin den Rest des eigenen Skripts. Die Fassung endete nach drei Zeilen, schrieb nichts und meldete Erfolg. Gemessen: Pipe+Heredoc -> 0 Zeichen kommen an, Skript als Argument -> alles. Jetzt geht beides ueber einen Strom: erste Zeile Passwort, dann das Skript. `case "$PW" in *"$(printf '\n')"*` lehnte jedes Passwort ab: Die Kommandosubstitution schneidet Zeilenumbrueche ab, das Muster ist leer, und `*""*` passt immer. `DIR=… read …` setzt DIR nur fuer das read; das folgende export exportierte eine leere Variable. Geprueft gegen ein Wegwerf-Verzeichnis auf dem echten Server, mit einem Passwort voller Sonderzeichen, und danach unabhaengig nachgehalten: richtiges Passwort akzeptiert, falsches abgelehnt. Das Verzeichnis ist wieder weg. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7816184fe3
commit
0e63165838
@@ -6448,6 +6448,33 @@ Host jeder lesen kann), hasht dort mit `htpasswd -i` und prüft anschließend
|
||||
selbst mit `htpasswd -vi`, ob Hash und Passwort zueinander passen. Genau diese
|
||||
Gegenprobe fehlte, als das erste 401 wie ein Konfigurationsfehler aussah.
|
||||
|
||||
**Drei Anläufe hat das Skript gebraucht, und alle drei Fehler waren lautlos.**
|
||||
Sie sind es wert, benannt zu werden, weil sie dieselbe Bauform haben — der
|
||||
Fehler meldet sich nicht, er tut einfach nichts:
|
||||
|
||||
1. **Skript per Heredoc UND Daten per Pipe geht nicht.** Kommt das Skript über
|
||||
`stdin` (`ssh … 'bash -s' <<'REMOTE'`), dann frisst ein `cat`/`read` darin
|
||||
den **Rest des eigenen Skripts**. Die erste Fassung endete nach drei Zeilen,
|
||||
schrieb nichts und meldete Erfolg. Gemessen: Pipe + Heredoc → 0 Zeichen
|
||||
kommen an, Skript als Argument → alles kommt an. Jetzt geht beides über
|
||||
**einen** Strom: erste Zeile Passwort, danach das Skript; die äußere
|
||||
Kommandozeile liest die Zeile weg und reicht sie als Umgebungsvariable
|
||||
weiter.
|
||||
2. **`case "$PW" in *"$(printf '\n')"*` lehnt alles ab.** Die
|
||||
Kommandosubstitution schneidet Zeilenumbrüche am Ende ab — das Muster ist
|
||||
leer, und `*""*` passt auf jede Zeichenkette. Richtig ist `[[ "$PW" ==
|
||||
*$'\n'* ]]`.
|
||||
3. **`DIR=… read …` setzt `DIR` nur für das `read`.** Eine Zuweisung als
|
||||
Kommando-Präfix gilt für dieses eine Kommando; das nachfolgende `export`
|
||||
exportierte eine leere Variable. Semikolons statt Präfix.
|
||||
|
||||
Gefunden hat sie ein Testlauf gegen ein **Wegwerf-Verzeichnis** auf dem echten
|
||||
Server (`BACKEND_DIR=opt/werkbaum-probe`, Terminal per `script -qec`, Passwort
|
||||
mit `$` und `!`), gefolgt von einer **unabhängigen** Gegenprobe: den
|
||||
gespeicherten Hash mit `htpasswd -vi` gegen das richtige *und* ein falsches
|
||||
Passwort halten. Dem Skript zu glauben, dass es funktioniert hat, wäre nach
|
||||
drei stillen Fehlschlägen die falsche Sorte Vertrauen gewesen.
|
||||
|
||||
**Nicht getestet, weil es nicht zu testen war:** Alles bis zur SSH-Grenze ist
|
||||
gemessen — die erzeugte Unit ist mit `systemd-analyze verify` gültig, das Jar
|
||||
startet mit **genau** den Flags der Unit in einer Sekunde, antwortet auf die
|
||||
|
||||
+38
-14
@@ -81,23 +81,38 @@ if [ "${#PASSWORT}" -lt 8 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Das Passwort geht über **stdin** an den Server, nie als Argument: Argumente
|
||||
# stehen in der Prozessliste, die auf einem geteilten Host jeder lesen kann.
|
||||
echo "==> hashen und speichern"
|
||||
printf '%s' "$PASSWORT" | ssh "$SSH_TARGET" DIR="$DIR_SH" RESTART="$RESTART" 'bash -s' <<'REMOTE'
|
||||
# Ein Zeilenumbruch im Passwort ginge unten beim `read` verloren — lieber hier
|
||||
# ablehnen als dort still abschneiden.
|
||||
# `$'\n'` und nicht `"$(printf '\n')"`: Die Kommandosubstitution schneidet
|
||||
# Zeilenumbrüche am Ende ab, das Muster wäre also leer — und `*""*` passt auf
|
||||
# jede Zeichenkette. Die erste Fassung lehnte damit jedes Passwort ab.
|
||||
if [[ "$PASSWORT" == *$'\n'* ]]; then
|
||||
echo "Zeilenumbruch im Passwort — nicht unterstützt." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Der Fernteil als Text, NICHT als Heredoc am ssh-Aufruf. Der Grund ist eine
|
||||
# Falle, die lautlos zuschlägt: Kommt das Skript über stdin (`bash -s` mit
|
||||
# Heredoc), dann frisst ein `cat`/`read` DARIN den Rest des eigenen Skripts.
|
||||
# Die erste Fassung endete so nach drei Zeilen — ohne etwas zu schreiben und
|
||||
# ohne Fehlermeldung.
|
||||
#
|
||||
# Deshalb geht beides über **einen** Strom: erst die Passwortzeile, dann das
|
||||
# Skript. Die äußere Kommandozeile liest die erste Zeile weg und reicht sie
|
||||
# als Umgebungsvariable weiter; `bash -s` bekommt den Rest als Skript.
|
||||
# Das Passwort steht damit nie in `argv` (das liest auf einem geteilten Host
|
||||
# jeder); in der Umgebung des kurzlebigen Prozesses steht es — dort kommt nur
|
||||
# derselbe Benutzer heran, und `htpasswd` bekommt es über eine Pipe.
|
||||
FERN=$(cat <<'REMOTE'
|
||||
set -euo pipefail
|
||||
DIR="$(eval echo "$DIR")"
|
||||
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
|
||||
|
||||
PASSWORT="$(cat)"
|
||||
[ -n "$PASSWORT" ] || { echo "Kein Passwort angekommen." >&2; exit 1; }
|
||||
|
||||
[ -n "${WERKBAUM_PW:-}" ] || { echo "Kein Passwort angekommen." >&2; exit 1; }
|
||||
command -v htpasswd >/dev/null || {
|
||||
echo "htpasswd fehlt auf dem Server (Paket apache2-utils)." >&2; exit 1; }
|
||||
|
||||
# htpasswd -i liest das Passwort von stdin - es steht also weder in der
|
||||
# Prozessliste noch in einer History.
|
||||
HASH="$(printf '%s' "$PASSWORT" | htpasswd -niBC 12 '' | tr -d ':\n')"
|
||||
HASH="$(printf '%s' "$WERKBAUM_PW" | htpasswd -niBC 12 '' | tr -d ':\n')"
|
||||
case "$HASH" in
|
||||
\$2*) : ;;
|
||||
*) echo "htpasswd hat keinen bcrypt-Hash geliefert." >&2; exit 1 ;;
|
||||
@@ -110,14 +125,14 @@ mv "$DIR/env.neu" "$DIR/env"
|
||||
chmod 600 "$DIR/env"
|
||||
echo " gespeichert in $DIR/env (Modus 600)"
|
||||
|
||||
# Gegenprobe, bevor irgendjemand sich wundert: Passen Hash und Passwort?
|
||||
# Gegenprobe, bevor sich jemand wundert: Passen Hash und Passwort zueinander?
|
||||
CHK="$(mktemp)"
|
||||
trap 'rm -f "$CHK"' EXIT
|
||||
printf 'werkbaum:%s\n' "$HASH" > "$CHK"
|
||||
if printf '%s' "$PASSWORT" | htpasswd -vi "$CHK" werkbaum >/dev/null 2>&1; then
|
||||
if printf '%s' "$WERKBAUM_PW" | htpasswd -vi "$CHK" werkbaum >/dev/null 2>&1; then
|
||||
echo " Gegenprobe: Hash und Passwort passen zueinander."
|
||||
else
|
||||
echo " ! Gegenprobe FEHLGESCHLAGEN - der Hash passt nicht zum Passwort." >&2
|
||||
echo " ! Gegenprobe FEHLGESCHLAGEN — der Hash passt nicht zum Passwort." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -126,10 +141,19 @@ if [ "$RESTART" = "1" ]; then
|
||||
systemctl --user restart werkbaum-backend.service
|
||||
echo " Dienst neu gestartet."
|
||||
else
|
||||
echo " ! Dienst noch nicht eingerichtet - scripts/deploy-backend.sh ausführen."
|
||||
echo " ! Dienst noch nicht eingerichtet — scripts/deploy-backend.sh ausführen."
|
||||
fi
|
||||
fi
|
||||
REMOTE
|
||||
)
|
||||
|
||||
echo "==> hashen und speichern"
|
||||
{ printf '%s\n' "$PASSWORT"; printf '%s\n' "$FERN"; } \
|
||||
| ssh "$SSH_TARGET" "DIR='$DIR_SH'; RESTART='$RESTART'; \
|
||||
IFS= read -r WERKBAUM_PW; export WERKBAUM_PW DIR RESTART; bash -s"
|
||||
# Semikolons, keine Zuweisungen als Kommando-Präfix: `DIR=… read …` setzt DIR
|
||||
# nur für das `read` und danach ist es wieder weg — das `export` exportierte
|
||||
# eine leere Variable, und der Fernteil brach mit „unbound variable" ab.
|
||||
|
||||
unset PASSWORT PASSWORT2
|
||||
|
||||
|
||||
Reference in New Issue
Block a user