From 78901793f0486c75cdece39384f05696f310afe5 Mon Sep 17 00:00:00 2001 From: mhoennig Date: Wed, 26 Aug 2026 18:37:46 +0200 Subject: [PATCH] fix(docs): Master-Passwort interaktiv hashen, nicht auf der Kommandozeile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Meine Anleitung schrieb `htpasswd -bnBC 12 "" PASSWORT` — und lieferte 401, obwohl Hash und Konfiguration nachweislich stimmten. Die Ursache liegt vor dem Hashen: Das Passwort steht dort ungeschuetzt in einer Kommandozeile, und die Shell fasst es an. `ge$heim` wird zu `ge`, `ge heim` zu `geheim`; gehasht wird etwas anderes als das, was man spaeter eintippt. Richtig ist `htpasswd -nBC 12 ''` ohne -b: Es fragt zweimal nach, das Passwort geht nie durch eine Shell und landet nicht in der History. Dazu eine direkte Probe (htpasswd -v gegen den gespeicherten Hash), weil der Fehler wie ein Konfigurationsfehler aussieht — alles Pruefbare stimmt, nur der Vergleich schlaegt fehl. Vier Stellen: deploy-backend.sh, beide READMEs, MasterPasswordProperties. Co-Authored-By: Claude Opus 5 --- README.md | 4 ++++ backend/README.md | 8 +++++++- .../service/MasterPasswordProperties.kt | 5 ++++- docs/DECISIONS.md | 17 +++++++++++++++++ scripts/deploy-backend.sh | 16 +++++++++++++--- 5 files changed, 45 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f12f16c..2de1f45 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,10 @@ Configuration lives in the git-ignored `.env` (template `.env.example`): - **The master password never leaves the server.** It goes into `/env` (mode 600), which the deploy creates empty on the first run. Until a hash is in it, the document list stays locked — deliberately. + Generate it **interactively** (`htpasswd -nBC 12 ''`, no `-b`): a password on + a command line lands in the shell history, and the shell mangles it on the + way — `ge$heim` becomes `ge`. What gets hashed is then not what you type + later, and the server answers 401 while everything looks right. - **Memory is the scarce resource on that host**, not CPU. The JVM flags are measured, not guessed: `-Xmx192m -Xms48m` plus heap free ratios lands at ~174 MB RSS, where the defaults take 291 MB. See diff --git a/backend/README.md b/backend/README.md index 3d906d6..0f9940c 100644 --- a/backend/README.md +++ b/backend/README.md @@ -171,9 +171,15 @@ auffindbar — das Zugriffsmodell „unerratbarer Link" wäre hinfällig. Dieser Endpunkt verlangt deshalb HTTP Basic mit dem Benutzer `werkbaum`: ```bash -export WERKBAUM_MASTER_PASSWORD_HASH="{bcrypt}$(htpasswd -bnBC 12 "" geheim | tr -d ':\n')" +export WERKBAUM_MASTER_PASSWORD_HASH="{bcrypt}$(htpasswd -nBC 12 '' | tr -d ':\n')" ``` +**Ohne `-b`, also mit Eingabeaufforderung.** Ein Passwort auf der Kommandozeile +landet in der Shell-History — und schlimmer: Die Shell fasst es vorher an. +`ge$heim` wird zu `ge`, `ge heim` zu `geheim`. Gehasht wird dann etwas anderes +als das, was man später eintippt, und der Server antwortet mit 401, obwohl +alles richtig aussieht. + - **Ohne gesetzten Hash ist die Liste gesperrt** (401), nicht offen. Ein vergessener Konfigurationsschritt darf nichts preisgeben; beim Start warnt das Log. diff --git a/backend/src/main/kotlin/de/werkbaum/service/MasterPasswordProperties.kt b/backend/src/main/kotlin/de/werkbaum/service/MasterPasswordProperties.kt index cb558b6..5e55015 100644 --- a/backend/src/main/kotlin/de/werkbaum/service/MasterPasswordProperties.kt +++ b/backend/src/main/kotlin/de/werkbaum/service/MasterPasswordProperties.kt @@ -22,7 +22,10 @@ data class MasterPasswordProperties( /** * Hash **mit Verfahrens-Präfix**, in Produktion `{bcrypt}$2a$…` - * (z. B. `htpasswd -bnBC 12 "" geheim | tr -d ':\n'`, davor `{bcrypt}`). + * Erzeugt mit `htpasswd -nBC 12 '' | tr -d ':\n'`, davor `{bcrypt}`. + * Bewusst **ohne** `-b`: Ein Passwort auf der Kommandozeile fasst die + * Shell an (`ge${'$'}heim` wird zu `ge`), und gehasht würde etwas anderes + * als das, was später eingetippt wird. */ val hash: String = "", diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index 2f26454..a4c9e79 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -6192,6 +6192,23 @@ Securitys `DelegatingPasswordEncoder`). So steht in der Konfiguration, womit gehasht wurde, ein Wechsel des Verfahrens bricht nichts — und die Tests dürfen `{noop}` benutzen, ohne dass dafür eine zweite Code-Bahn nötig wäre. +**Nachtrag beim ersten Einrichten: der Hash wird interaktiv erzeugt, nie mit +dem Passwort auf der Kommandozeile.** Die erste Anleitung schrieb +`htpasswd -bnBC 12 "" PASSWORT` — und lieferte prompt ein 401, obwohl Hash und +Konfiguration nachweislich in Ordnung waren (68 Zeichen, `{bcrypt}`, drei `$`, +im Prozess angekommen). Die Ursache liegt vor dem Hashen: Das Passwort steht +dort ungeschützt in einer Kommandozeile, und die Shell fasst es an — +`ge$heim` wird zu `ge`, `ge heim` zu `geheim`. Gehasht wird dann etwas anderes +als das, was man später eintippt. + +Richtig ist `htpasswd -nBC 12 ''` **ohne `-b`**: Es fragt zweimal nach, das +Passwort geht nie durch eine Shell und landet nicht in der History. Der Fehler +ist besonders unangenehm, weil er wie ein Konfigurationsfehler aussieht — alles +Prüfbare stimmt, nur der Vergleich schlägt fehl. Zum Auseinanderhalten gehört +deshalb eine direkte Probe in die Anleitung: `htpasswd -v` gegen den +gespeicherten Hash sagt in einem Schritt, ob Hash und Passwort zueinander +passen, ohne den Dienst zu befragen. + **Die neue Laufzeit-Abhängigkeit** (`spring-boot-starter-security`) ist in D76 ausdrücklich vorgesehen („geprüft über Spring Security") und damit von der Rückfragepflicht der Wurzel-CLAUDE.md gedeckt. Sie ist zugleich der Platz für diff --git a/scripts/deploy-backend.sh b/scripts/deploy-backend.sh index d04aead..b2213fa 100755 --- a/scripts/deploy-backend.sh +++ b/scripts/deploy-backend.sh @@ -151,9 +151,19 @@ if [ ! -f "$DIR/env" ]; then cat > "$DIR/env" <<'ENV' # Werkbaum-Backend — Umgebung des Dienstes. Modus 600, nie im Repository. # -# Hash MIT Verfahrens-Praefix, z. B.: -# WERKBAUM_MASTER_PASSWORD_HASH={bcrypt}$(htpasswd -bnBC 12 "" geheim | tr -d ':\n') -# Solange er leer ist, bleibt GET /api/v1/documents gesperrt (D76-Nachtrag 6). +# Hash MIT Verfahrens-Praefix. Erzeugen - INTERAKTIV, damit das Passwort weder +# in der Shell-History landet noch von der Shell veraendert wird: +# +# umask 077 +# printf 'WERKBAUM_MASTER_PASSWORD_HASH={bcrypt}%s\n' \ +# "$(htpasswd -nBC 12 '' | tr -d ':\n')" > ~/opt/werkbaum/env +# +# Pruefen, ob Hash und Passwort zueinander passen: +# printf 'werkbaum:%s\n' "$(sed 's/^[^=]*={bcrypt}//' ~/opt/werkbaum/env)" > /tmp/chk +# htpasswd -v /tmp/chk werkbaum; rm -f /tmp/chk +# +# Solange der Hash leer ist, bleibt GET /api/v1/documents gesperrt +# (D76-Nachtrag 6). WERKBAUM_MASTER_PASSWORD_HASH= ENV chmod 600 "$DIR/env"