Fix #45: Beispielkonfiguration entschärfen #51
+13
-3
@@ -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=
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user