S3: Aus der Kontosperre wird eine Wartezeit. Die alte Sperre wies auch das richtige Passwort ab — wer die Adresse eines Administrators kannte, konnte ihn alle 15 Minuten neu aussperren. Jetzt bleiben vier Fehlversuche folgenlos, danach verdoppelt sich die Wartezeit bis zu einer Minute. Der Besitzer kommt spaetestens nach einer Minute hinein, ein Fremder bekommt einen Versuch pro Minute. Ausgewertet wird sie vor der Passwortpruefung, damit ein ohnehin abgewiesener Versuch keine 128 MiB kostet. S4: Drei Stufen statt einer. Die Bremse je Absender liest X-Forwarded-For nur noch mit TRUST_PROXY=true — ohne Proxy waehlt den Wert jeder Aufrufer selbst, und eine so umgehbare Bremse ist eine Behauptung. Dazu die Wartezeit je Konto, die an der Datenbank haengt, und eine Obergrenze fuer die ganze Installation: 60 Passwortpruefungen und 120 Rueckmeldungen je Minute. Die letzte Stufe ist die einzige, die sich nicht ueber Kopfzeilen umgehen laesst, und sie schuetzt den Speicher. CSP: Eine vollstaendige Policy mit Nonce je Anfrage, strict-dynamic, default-src self, object-src none, base-uri und form-action self. Skripte ohne den Nonce dieser Anfrage laufen nicht mehr. Zwei Lockerungen bleiben und stehen begruendet in der Doku: style-src erlaubt unsafe-inline, weil ein Nonce keine style-Attribute deckt, und media-src erlaubt https, weil der Typ Video ausdruecklich Direktlinks annimmt. Protokoll: Sicherheitsereignisse als JSON auf die Standardausgabe — Fehlversuche, OIDC-Verknuepfungen samt abgelehnten, Vergabe von Administratorrechten. Ohne Passwoerter, Token und Adressen. Refs #20
65 lines
2.4 KiB
YAML
65 lines
2.4 KiB
YAML
services:
|
|
db:
|
|
image: postgres:18-alpine
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_USER: ${POSTGRES_USER:-qrcode}
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD fehlt — bitte .env aus .env.example anlegen}
|
|
POSTGRES_DB: ${POSTGRES_DB:-qrcode}
|
|
volumes:
|
|
# Ab PostgreSQL 18 liegt das Datenverzeichnis in einem Unterordner mit
|
|
# Versionsnummer — der Mountpunkt ist deshalb /var/lib/postgresql und
|
|
# nicht mehr /var/lib/postgresql/data.
|
|
- db-data:/var/lib/postgresql
|
|
healthcheck:
|
|
# Die App startet erst, wenn die Datenbank wirklich Anfragen annimmt.
|
|
test:
|
|
["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-qrcode} -d ${POSTGRES_DB:-qrcode}"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 12
|
|
start_period: 10s
|
|
|
|
app:
|
|
build:
|
|
context: .
|
|
restart: unless-stopped
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
environment:
|
|
# Achtung beim Ergänzen: Compose liest die .env nur, um Platzhalter wie
|
|
# ${BASE_URL} zu ersetzen. In den Container gelangt ausschließlich, was
|
|
# hier aufgeführt ist — eine neue Variable in der .env allein bleibt
|
|
# wirkungslos.
|
|
#
|
|
# Öffentliche Adresse der App — landet in jedem dynamischen QR-Code.
|
|
BASE_URL: ${BASE_URL:-http://localhost:3000}
|
|
DATABASE_URL: ${DATABASE_URL}
|
|
# Anmeldung mit Benutzern, Gruppen und Rollen.
|
|
# Ohne Angabe: an. Abgeschaltet wird nur, wer ausdruecklich
|
|
# AUTH_ENABLED=false setzt — siehe .env.example.
|
|
AUTH_ENABLED: ${AUTH_ENABLED:-true}
|
|
# Nur einschalten, wenn wirklich ein Proxy davorsteht — siehe
|
|
# .env.example.
|
|
TRUST_PROXY: ${TRUST_PROXY:-false}
|
|
MAX_UPLOAD_MB: ${MAX_UPLOAD_MB:-10}
|
|
SESSION_IDLE_DAYS: ${SESSION_IDLE_DAYS:-30}
|
|
SESSION_MAX_DAYS: ${SESSION_MAX_DAYS:-90}
|
|
OIDC_ENABLED: ${OIDC_ENABLED:-false}
|
|
OIDC_ISSUER: ${OIDC_ISSUER:-}
|
|
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID:-}
|
|
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET:-}
|
|
OIDC_BUTTON_LABEL: ${OIDC_BUTTON_LABEL:-Mit Authentik anmelden}
|
|
OIDC_SCOPES: ${OIDC_SCOPES:-openid email profile}
|
|
OIDC_ONLY: ${OIDC_ONLY:-false}
|
|
OIDC_DEFAULT_ROLE: ${OIDC_DEFAULT_ROLE:-VIEWER}
|
|
OIDC_ADMIN_GROUP: ${OIDC_ADMIN_GROUP:-}
|
|
OIDC_GROUPS_CLAIM: ${OIDC_GROUPS_CLAIM:-groups}
|
|
ports:
|
|
# Direktes Port-Mapping, bewusst ohne Reverse Proxy.
|
|
- "${APP_PORT:-3000}:3000"
|
|
|
|
volumes:
|
|
db-data:
|