From 5c6fac59b2962c8230ab5c529cb8dfb31773ff63 Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Tue, 1 Sep 2026 16:16:12 +0200 Subject: [PATCH] Beispielkonfiguration entschaerfen .env.example belegte S3_ENDPOINT mit der Adresse eines externen Anbieters. Gedacht war das als Beispiel - in der kopierten .env stand es aber aktiv, und damit sprach die Anwendung mit einem fremden Host statt mit dem Objektspeicher nebenan. Verschaerfend: Die docker-compose.yml hatte laengst einen brauchbaren Standard, ${S3_ENDPOINT:-http://minio:9000}. Der greift nur bei leerer Variable - die Beispieldatei hebelte ihn aus. Der Mechanismus, der genau diesen Fehler verhindern sollte, war durch die Beispieldatei wirkungslos. Auf dem Livesystem hat das monatelang jede Ablage scheitern lassen, und die Zugangs-ID des lokalen Speichers ging bei jedem Versuch nach draussen. Die Zeile ist jetzt auskommentiert, mit einer Erklaerung, wann man sie braucht. Nebenbei stand dort SEED_ADMIN_EMAIL mit der Adresse des Betreibers. In einer mitgelieferten Beispieldatei hat eine persoenliche Adresse nichts zu suchen - jetzt leer. Der Test prueft beides und dazu die allgemeine Regel: Kein aktiver Wert in der Beispieldatei zeigt auf einen fremden Rechner. Er prueft auch, dass ueberhaupt Zuweisungen gefunden werden - sonst staende er gruen da und pruefte nichts. Gegen zwei Mutationen geprueft: Endpunkt wieder aktiv, persoenliche Adresse wieder eingetragen - beide werden rot. Refs #45 --- .env.example | 16 ++++-- docs/INSTALLATION.md | 3 ++ tests/env-beispiel.test.ts | 100 +++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 tests/env-beispiel.test.ts diff --git a/.env.example b/.env.example index ca90ca9..107b97b 100644 --- a/.env.example +++ b/.env.example @@ -103,7 +103,17 @@ VERTRAUE_PROXY=true # --- Objektspeicher (S3-kompatibel, EU!) ------------------------------------ # Wird ab Etappe 3 verwendet. Beispiel: Hetzner Object Storage, Falkenstein. # ACHTUNG: Mit dem Anbieter muss ein AV-Vertrag nach Art. 28 DSGVO bestehen. -S3_ENDPOINT=https://fsn1.your-objectstorage.com +# Bleibt AUSKOMMENTIERT, solange das mitgelieferte MinIO benutzt wird. +# +# Die docker-compose.yml setzt dann von selbst http://minio:9000 - aber +# nur, wenn die Variable leer ist. Steht hier ein Wert, gewinnt er, und +# die Anwendung spricht mit einem fremden Host statt mit dem Speicher +# nebenan. Genau das ist am 31.08.2026 passiert: Jede Ablage scheiterte +# monatelang mit "NoSuchBucket", und die Zugangs-ID ging bei jedem Versuch +# nach draussen. +# +# Nur setzen, wenn ein EXTERNER Objektspeicher benutzt wird, z.B.: +# S3_ENDPOINT=https://fsn1.your-objectstorage.com S3_REGION=eu-central S3_BUCKET=betreuungs-cockpit S3_ACCESS_KEY_ID= @@ -131,5 +141,5 @@ SMTP_FROM="Betreuungs-Cockpit " # --- Erstanmeldung ---------------------------------------------------------- # Wird nur vom Seed benutzt, um den ersten Betreuer-Zugang anzulegen. -SEED_ADMIN_EMAIL=fw@wildeflorian.de -SEED_ADMIN_PASSWORT=bitte-beim-ersten-login-aendern +SEED_ADMIN_EMAIL= +SEED_ADMIN_PASSWORT= diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index 9aed948..9bf54fd 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -218,6 +218,9 @@ INDEX_KEY= # --- Objektspeicher --- S3_ACCESS_KEY_ID= S3_SECRET_ACCESS_KEY= +# S3_ENDPOINT bleibt AUSKOMMENTIERT, solange du das mitgelieferte MinIO +# benutzt. Nur dann setzt die docker-compose.yml von selbst +# http://minio:9000 - steht dort ein Wert, gewinnt er. # --- Sicherung --- SICHERUNG_PASSWORT= diff --git a/tests/env-beispiel.test.ts b/tests/env-beispiel.test.ts new file mode 100644 index 0000000..ec78855 --- /dev/null +++ b/tests/env-beispiel.test.ts @@ -0,0 +1,100 @@ +import { readFileSync } from 'node:fs' +import { describe, expect, it } from 'vitest' + +/** + * Die Beispielkonfiguration darf keine Falle enthalten. + * + * `.env.example` wird nach `.env` kopiert und dann Zeile für Zeile + * ausgefüllt. Was schon einen Wert hat, wird dabei leicht überlesen — und + * genau das ist am 31.08.2026 passiert: + * + * S3_ENDPOINT=https://fsn1.your-objectstorage.com + * + * Die Zeile war als Beispiel gedacht. Sie stand aber aktiv in der `.env`, + * und damit sprach die Anwendung monatelang mit einem fremden Anbieter + * statt mit dem Objektspeicher nebenan. Jede Ablage scheiterte mit + * `NoSuchBucket`, und die Zugangs-ID des lokalen Speichers ging bei jedem + * Versuch nach draußen. + * + * Verschärfend: Die `docker-compose.yml` hatte längst einen brauchbaren + * Standard (`${S3_ENDPOINT:-http://minio:9000}`). Der greift aber nur bei + * leerer Variable — die Beispieldatei hebelte ihn aus. Der Mechanismus, + * der den Fehler verhindern sollte, war durch die Beispieldatei + * wirkungslos. + * + * Diese Tests halten fest: Ein Wert steht in der Beispieldatei nur, wenn + * er für die mitgelieferte Zusammenstellung auch richtig ist. + */ + +const beispiel = readFileSync('.env.example', 'utf8') + +/** Alle aktiven Zuweisungen - Kommentarzeilen zählen nicht. */ +function zuweisungen(): [string, string][] { + return beispiel + .split('\n') + .map((zeile) => zeile.trim()) + .filter((zeile) => zeile !== '' && !zeile.startsWith('#')) + .map((zeile) => { + const stelle = zeile.indexOf('=') + return [zeile.slice(0, stelle), zeile.slice(stelle + 1)] as [string, string] + }) +} + +/** + * Rechner, die zur mitgelieferten Zusammenstellung gehören. Alles andere + * ist ein fremder Host und hat in der Beispieldatei nichts verloren. + */ +const EIGENE_HOSTS = ['localhost', '127.0.0.1', '0.0.0.0', 'minio', 'db'] + +describe('Beispielkonfiguration', () => { + it('zeigt mit keinem aktiven Wert auf einen fremden Host', () => { + const fremde = zuweisungen() + .filter(([, wert]) => /https?:\/\//.test(wert)) + .filter(([, wert]) => { + const treffer = wert.match(/https?:\/\/([^:/"']+)/) + return treffer ? !EIGENE_HOSTS.includes(treffer[1]!) : false + }) + .map(([name, wert]) => `${name}=${wert}`) + + expect( + fremde, + 'Diese Zeilen zeigen nach draußen. Wer die Datei kopiert und die ' + + 'Zeile übersieht, schickt seine Daten an einen fremden Rechner.', + ).toEqual([]) + }) + + it('lässt S3_ENDPOINT auskommentiert, damit der Compose-Standard greift', () => { + const aktiv = zuweisungen().some(([name]) => name === 'S3_ENDPOINT') + + expect( + aktiv, + 'Mit gesetztem S3_ENDPOINT greift ${S3_ENDPOINT:-http://minio:9000} ' + + 'aus der docker-compose.yml nicht mehr.', + ).toBe(false) + }) + + it('erklärt trotzdem, wann man S3_ENDPOINT braucht', () => { + // Auskommentieren allein hilft nicht - dann sucht jemand mit externem + // Speicher vergeblich nach der Einstellung. + expect(beispiel).toMatch(/#\s*S3_ENDPOINT=/) + expect(beispiel).toMatch(/EXTERNER Objektspeicher/) + }) + + it('enthält keine persönliche E-Mail-Adresse', () => { + // Stand einmal drin: die Adresse des Betreibers als Vorgabe fuer den + // Seed. In einer mitgelieferten Beispieldatei hat sie nichts zu suchen. + const adressen = zuweisungen() + // Verbindungszeichenfolgen tragen ein "@" vor dem Rechnernamen - + // das ist keine E-Mail-Adresse. + .filter(([, wert]) => !wert.includes('://')) + .filter(([, wert]) => /[\w.+-]+@[\w-]+\.[a-z]{2,}/i.test(wert)) + .filter(([, wert]) => !/example\.(de|com|org)/.test(wert)) + .map(([name, wert]) => `${name}=${wert}`) + + expect(adressen).toEqual([]) + }) + + it('findet überhaupt Zuweisungen - sonst prüft der Test nichts', () => { + expect(zuweisungen().length).toBeGreaterThan(15) + }) +})