Fix #45: Beispielkonfiguration entschärfen #51

Merged
florian.wilde merged 1 commits from fix/45-env-beispiel-standard into main 2026-09-01 14:16:47 +00:00
3 changed files with 116 additions and 3 deletions
Showing only changes of commit 5c6fac59b2 - Show all commits
+13 -3
View File
@@ -103,7 +103,17 @@ VERTRAUE_PROXY=true
# --- Objektspeicher (S3-kompatibel, EU!) ------------------------------------ # --- Objektspeicher (S3-kompatibel, EU!) ------------------------------------
# Wird ab Etappe 3 verwendet. Beispiel: Hetzner Object Storage, Falkenstein. # Wird ab Etappe 3 verwendet. Beispiel: Hetzner Object Storage, Falkenstein.
# ACHTUNG: Mit dem Anbieter muss ein AV-Vertrag nach Art. 28 DSGVO bestehen. # 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_REGION=eu-central
S3_BUCKET=betreuungs-cockpit S3_BUCKET=betreuungs-cockpit
S3_ACCESS_KEY_ID= S3_ACCESS_KEY_ID=
@@ -131,5 +141,5 @@ SMTP_FROM="Betreuungs-Cockpit <cockpit@example.de>"
# --- Erstanmeldung ---------------------------------------------------------- # --- Erstanmeldung ----------------------------------------------------------
# Wird nur vom Seed benutzt, um den ersten Betreuer-Zugang anzulegen. # Wird nur vom Seed benutzt, um den ersten Betreuer-Zugang anzulegen.
SEED_ADMIN_EMAIL=fw@wildeflorian.de SEED_ADMIN_EMAIL=
SEED_ADMIN_PASSWORT=bitte-beim-ersten-login-aendern SEED_ADMIN_PASSWORT=
+3
View File
@@ -218,6 +218,9 @@ INDEX_KEY=<aus Schritt 2>
# --- Objektspeicher --- # --- Objektspeicher ---
S3_ACCESS_KEY_ID=<aus Schritt 2> S3_ACCESS_KEY_ID=<aus Schritt 2>
S3_SECRET_ACCESS_KEY=<aus Schritt 2> S3_SECRET_ACCESS_KEY=<aus Schritt 2>
# 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 ---
SICHERUNG_PASSWORT=<aus Schritt 2> SICHERUNG_PASSWORT=<aus Schritt 2>
+100
View File
@@ -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)
})
})