diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a45430..b81d7e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,15 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/). ### Sicherheit +## [0.19.0] – 2026-09-19 + +### Hinzugefügt +- `docs/betrieb/`: Inbetriebnahme, Reverse Proxy (nginx Proxy Manager und netbird), OIDC mit authentik und Sicherung – jeweils Schritt für Schritt (#029) + +### Behoben +- Die OIDC-Rückkehradresse berücksichtigte die im Dashboard eingetragene Adresse nicht, sondern nur `APP_URL` (#029) +- Beim Einlösen des OIDC-Codes wurde hinter einem Reverse Proxy die interne Adresse verwendet; der Tausch wäre am Anbieter gescheitert (#029) + ## [0.18.0] – 2026-09-19 ### Hinzugefügt diff --git a/DEVLOG.md b/DEVLOG.md index 0475c27..257f9da 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -2,7 +2,7 @@ ## Zeitübersicht -**Gesamt:** 14 h 23 min +**Gesamt:** 15 h 03 min | Issue | Zeit | |---|---| @@ -34,6 +34,7 @@ | #026 Auslieferung | 0 h 40 min | | #027 Rollen und Benutzerverwaltung | 1 h 20 min | | #028 Passkeys | 0 h 45 min | +| #029 Einrichtungsdokumentation | 0 h 40 min | | Meilenstein | Zeit | |---|---| @@ -56,7 +57,7 @@ ## 2026-09-17 · Sitzung 1 · 19:36–laufend **Issue:** #001–#007 (Meilensteine v0.1.0 bis v0.4.0) -**Version:** 0.18.0 +**Version:** 0.19.0 ### Erledigt - Auftrag gelesen, Rückfragen gestellt @@ -158,6 +159,9 @@ - Dabei aufgefallen: Neu angelegte OIDC-Konten hätten die Modellvorgabe ADMIN geerbt – behoben, mit eigener Einstellung und Test - Passkeys (#028): passwortlos, mehrere je Konto, Aufgabe in einem kurzlebigen Cookie. Nur über HTTPS nutzbar – die Seite sagt es, statt still zu scheitern +- Einrichtungsdokumentation (#029): Beim Aufschreiben des OIDC-Wegs fielen zwei Fehler auf, die nur hinter einem Reverse Proxy auftreten – Rückkehradresse und Code-Einlösung nahmen die interne Adresse. Beides behoben +- Erkenntnis: Eine Anleitung Schritt für Schritt aufzuschreiben findet Fehler, die kein Test findet, weil sie nur in einer bestimmten Betriebsform auftreten + ### Nächster Schritt - #016 Benutzerhandbuch mit Screenshots, danach #017 Favicons und #019 CI-Pipeline - Für 1.0.0 fehlt noch: PMG (kein Host verfügbar) und der Fall „aktive Subscription auf PBS“ (#023) diff --git a/README.md b/README.md index ec3715e..e6af813 100644 --- a/README.md +++ b/README.md @@ -122,13 +122,23 @@ sie sich mit eigenen Tokens ausweisen. - **Lokaler Zugang:** E-Mail und Passwort aus dem Seed (`ADMIN_EMAIL`, `ADMIN_PASSWORD`). Fünf Fehlversuche je Kennung und Viertelstunde, danach Sperre (einstellbar unter *Einstellungen → System*). Benutzer sperren oder Passwort setzen: `npm run benutzer -- sperren `. -- **OIDC (z. B. authentik):** `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET` setzen. Rückkehradresse +- **Passkey:** passwortlos über WebAuthn, mehrere je Konto, Verwaltung unter *Meine Passkeys*. Setzt HTTPS voraus (außer `localhost`). +- **OIDC (z. B. authentik):** `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET` setzen – Schritt für Schritt in [docs/betrieb/authentik.md](docs/betrieb/authentik.md). Rückkehradresse ist `/api/auth/oidc/callback`. Ohne diese Werte erscheint nur der lokale Zugang – der bleibt absichtlich immer nutzbar, falls der Anmeldedienst ausfällt. Unter *Einstellungen → System* lässt sich einschränken, welche E-Mail-Domänen zugelassen sind und ob unbekannte Konten angelegt werden dürfen. - **`APP_SECRET`** (mindestens 32 Zeichen) signiert die Sitzungs-Cookies. Ändert sich der Wert, sind alle Sitzungen ungültig. +## Einrichtung im Betrieb + +| Anleitung | Inhalt | +|---|---| +| [Inbetriebnahme](docs/betrieb/inbetriebnahme.md) | von null zum meldenden Host, in der richtigen Reihenfolge | +| [Reverse Proxy](docs/betrieb/reverse-proxy.md) | HTTPS mit nginx Proxy Manager, privates Netz mit netbird | +| [authentik](docs/betrieb/authentik.md) | Anmeldung über OIDC, samt Fehlerbildern | +| [Sicherung](docs/betrieb/sicherung.md) | was gesichert wird – und wie man es zurückspielt | + ## Einstellungen Die meisten Betriebswerte stehen unter *Einstellungen → System* und wirken sofort: Dashboard-Adresse, diff --git a/docs/anleitung/README.md b/docs/anleitung/README.md index d5b6137..50ff29a 100644 --- a/docs/anleitung/README.md +++ b/docs/anleitung/README.md @@ -24,6 +24,9 @@ auch mit Hosts hinter NAT. --- +> **Einrichtung statt Bedienung?** Reverse Proxy, OIDC und Sicherung stehen in +> [docs/betrieb/](../betrieb/README.md). Dieses Handbuch beschreibt die tägliche Arbeit. + ## 1. Installation ### Mit Docker diff --git a/docs/betrieb/README.md b/docs/betrieb/README.md new file mode 100644 index 0000000..0c17ef9 --- /dev/null +++ b/docs/betrieb/README.md @@ -0,0 +1,13 @@ +# Betrieb + +Anleitungen für alles, was rund um ProxLedger eingerichtet wird. + +| Dokument | Inhalt | +|---|---| +| [inbetriebnahme.md](inbetriebnahme.md) | Von null zum laufenden Dashboard – Docker oder LXC, erster Agent, erste Zuordnung | +| [reverse-proxy.md](reverse-proxy.md) | HTTPS mit nginx Proxy Manager, privates Netz mit netbird | +| [authentik.md](authentik.md) | Anmeldung über OIDC | +| [sicherung.md](sicherung.md) | Was gesichert werden muss und wie man es zurückspielt | + +Für die tägliche Arbeit **mit** ProxLedger ist das [Handbuch](../anleitung/README.md) gedacht. +Was gegen Angriffe getan wird (und was bewusst nicht), steht in [sicherheit.md](../sicherheit.md). diff --git a/docs/betrieb/authentik.md b/docs/betrieb/authentik.md new file mode 100644 index 0000000..e209078 --- /dev/null +++ b/docs/betrieb/authentik.md @@ -0,0 +1,118 @@ +# Anmeldung über authentik (OIDC) + +ProxLedger kann die Anmeldung an einen OIDC-Anbieter abgeben. Getestet ist authentik; mit jedem +anderen Anbieter, der OpenID Connect Discovery beherrscht, funktioniert es genauso. + +**Der lokale Zugang bleibt daneben immer bestehen.** Das ist Absicht: Fällt authentik aus, kommt man +trotzdem noch ins Dashboard. + +--- + +## 1. In authentik: Provider anlegen + +*Applications → Providers → Create → OAuth2/OpenID Provider* + +| Feld | Wert | +|---|---| +| Name | `ProxLedger` | +| Authorization flow | der übliche (z. B. *default-provider-authorization-implicit-consent*) | +| Client type | **Confidential** | +| Client ID | übernehmen, wird gleich gebraucht | +| Client Secret | übernehmen, wird gleich gebraucht | +| Redirect URIs | `https://proxledger.firma.de/api/auth/oidc/callback` | +| Scopes | `openid`, `email`, `profile` | +| Signing Key | ein vorhandenes Zertifikat auswählen | + +Zur Rückkehradresse zwei Hinweise, an denen die meisten Einrichtungen scheitern: + +- Sie muss **wortgleich** übereinstimmen – einschließlich `https`, Portangabe und ohne Schrägstrich am + Ende. ProxLedger bildet sie aus der Dashboard-Adresse unter *Einstellungen → System*, ersatzweise aus + `APP_URL`. +- authentik erlaubt auch reguläre Ausdrücke. Für eine einzelne Installation ist die genaue Adresse die + bessere Wahl; ein zu weiter Ausdruck öffnet eine Umleitung auf fremde Ziele. + +## 2. In authentik: Application anlegen + +*Applications → Applications → Create* + +| Feld | Wert | +|---|---| +| Name | `ProxLedger` | +| Slug | `proxledger` | +| Provider | der eben angelegte | + +Der Slug bestimmt die Discovery-Adresse: + +``` +https://authentik.firma.de/application/o/proxledger/.well-known/openid-configuration +``` + +Diese Adresse **ohne** den Teil `/.well-known/openid-configuration` ist der Aussteller +(`OIDC_ISSUER`) – also `https://authentik.firma.de/application/o/proxledger/`. + +Prüfen lässt sie sich mit: + +```bash +curl -fsS https://authentik.firma.de/application/o/proxledger/.well-known/openid-configuration | head +``` + +Wer darf sich anmelden, entscheidet in authentik die **Policy-Bindung** an der Application +(*Applications → ProxLedger → Policy / Group / User Bindings*). Ohne Bindung darf jeder, der bei +authentik ein Konto hat. + +## 3. In ProxLedger: Werte eintragen + +In die `.env` (Docker) oder `/opt/proxledger/.env` (LXC): + +```bash +OIDC_ISSUER="https://authentik.firma.de/application/o/proxledger/" +OIDC_CLIENT_ID="…" +OIDC_CLIENT_SECRET="…" +OIDC_NAME="authentik" # Beschriftung des Knopfes auf der Anmeldeseite +APP_URL="https://proxledger.firma.de" +``` + +Danach neu starten: + +```bash +docker compose up -d # Docker +systemctl restart proxledger # LXC +``` + +Auf der Anmeldeseite erscheint jetzt **Mit authentik anmelden**. + +## 4. Rollen festlegen + +ProxLedger liest **keine** Gruppen aus authentik. Wer sich zum ersten Mal anmeldet, bekommt die Rolle +aus *Einstellungen → System → Rolle für neue OIDC-Konten* – Vorgabe ist **Techniker**. Die endgültige +Rolle vergibt ein Administrator unter *Einstellungen → Benutzer*. + +Das ist bewusst so: Eine Gruppenzuordnung, die stillschweigend Administratorrechte verteilt, ist +schwerer zu überblicken als eine Liste, in der jeder Zugang sichtbar ist. + +Zwei Einstellungen helfen dabei, den Kreis klein zu halten: + +- **Erlaubte E-Mail-Domänen** – etwa `firma.de`. Alles andere wird abgewiesen, auch wenn die Anmeldung + bei authentik geklappt hat. +- **Unbekannte OIDC-Anmeldungen legen ein Konto an** – abgeschaltet kommt nur herein, wer hier bereits + ein Konto hat. Dann legt man Konten vorab an (ohne Passwort) und die Anmeldung läuft trotzdem über + authentik. + +## 5. Prüfen + +1. Abmelden, dann **Mit authentik anmelden**. +2. Unter *Einstellungen → Protokoll* steht `anmeldung.erfolgreich` mit `weg: oidc`. +3. Unter *Einstellungen → Benutzer* steht das Konto mit der vergebenen Rolle und der Anmeldeart `OIDC`. + +## Wenn es nicht klappt + +| Meldung | Ursache | +|---|---| +| „Die Anmeldung über den Anbieter hat nicht geklappt.“ | Sammelmeldung – die Einzelheiten stehen im Protokoll des Containers (`docker compose logs web`). | +| authentik zeigt *Redirect URI Error* | Die Adresse stimmt nicht wortgleich. Häufig: `http` statt `https`, Schrägstrich am Ende, oder die interne statt der öffentlichen Adresse. | +| `invalid_client` | Client-ID oder -Secret falsch, oder der Provider steht auf *Public* statt *Confidential*. | +| Anmeldung klappt, aber ProxLedger meldet „keine E-Mail-Adresse“ | Der Scope `email` fehlt am Provider. | +| Nach der Anmeldung landet man auf einer unerreichbaren Adresse | `APP_URL` bzw. die Dashboard-Adresse zeigt nicht auf die öffentliche Adresse. | + +Siehe auch: [Reverse Proxy](reverse-proxy.md) – ohne HTTPS gibt es weder sinnvolle OIDC-Anmeldung noch +Passkeys. diff --git a/docs/betrieb/inbetriebnahme.md b/docs/betrieb/inbetriebnahme.md new file mode 100644 index 0000000..4c3eb34 --- /dev/null +++ b/docs/betrieb/inbetriebnahme.md @@ -0,0 +1,81 @@ +# Inbetriebnahme + +Der kürzeste Weg von null zu einem Dashboard, das Hosts meldet. Rechnen Sie mit einer halben Stunde. + +## Reihenfolge + +Die Reihenfolge ist nicht beliebig – zwei Schritte hängen an der endgültigen Adresse: + +1. Dashboard installieren +2. **Adresse festlegen** (Reverse Proxy, DNS) +3. OIDC einrichten (falls gewünscht) +4. Passkeys anlegen +5. Agents ausrollen + +Wer Schritt 4 oder 5 vorzieht, macht sie nach einem Adresswechsel noch einmal: Passkeys gelten nur für +die Domäne, unter der sie entstanden sind, und die Adresse steckt im Installations-Einzeiler. + +## 1. Installieren + +**Docker** (beliebiger Host mit Docker): + +```bash +git clone proxledger && cd proxledger +cp .env.example .env +# Pflicht: APP_SECRET, POSTGRES_PASSWORD, ADMIN_PASSWORD, APP_URL +docker compose up -d +``` + +**LXC auf Proxmox**, zwei Möglichkeiten: + +```bash +bash deploy/lxc-installer.sh # Container mit Docker darin +``` + +```bash +# Nach Art der Proxmox VE Helper-Scripts, ohne Docker: +bash -c "$(curl -fsSL https:///ProxLedger/raw/branch/main/deploy/community-scripts/ct/proxledger.sh)" +``` + +Der erste Administrator entsteht beim Einrichten aus `ADMIN_EMAIL` und `ADMIN_PASSWORD`; beim +LXC-Weg stehen die erzeugten Zugangsdaten in `/opt/proxledger/.credentials`. + +**`APP_SECRET` gehört sofort in die Sicherung.** Er signiert die Sitzungen und verschlüsselt die +gespeicherten Zugangsdaten für SMTP und Webhooks. Ohne ihn sind beide unbrauchbar. + +## 2. Adresse festlegen + +Siehe [reverse-proxy.md](reverse-proxy.md). Danach unter *Einstellungen → System* die **Adresse des +Dashboards** eintragen – sie landet im Installations-Einzeiler und in den Benachrichtigungen. + +## 3. Anmeldung einrichten + +- Optional OIDC: [authentik.md](authentik.md) +- Weitere Konten unter *Einstellungen → Benutzer* (Administrator, Techniker, Kunde) +- Jeder legt sich unter *Meine Passkeys* einen Passkey an – danach entfällt das Passwort + +## 4. Agents ausrollen + +1. *Einstellungen → Agent-Tokens* → Enrollment-Token erzeugen (mit Kundenbindung spart es den + Zuordnungsschritt) +2. Auf jedem Proxmox-Host als root: + ```bash + curl -fsSL https://proxledger.firma.de/install.sh | bash -s -- --token + ``` +3. **In einem Cluster auf jedem Knoten** – jeder Knoten hat eine eigene Subscription. Das Dashboard + warnt von selbst, wenn ein Knoten fehlt. +4. Danach den Enrollment-Token widerrufen. + +## 5. Benachrichtigungen + +*Einstellungen → Benachrichtigungen*: SMTP-Zugang oder Webhook eintragen, Testnachricht schicken, +Zusammenfassung auf täglich oder wöchentlich stellen. + +## Prüfliste + +- [ ] `https://…/api/gesundheit` antwortet mit 200 +- [ ] Anmeldung klappt, die Adresszeile bleibt auf der öffentlichen Adresse +- [ ] Ein Host meldet sich (*Hosts* zeigt ihn mit Ablaufdatum) +- [ ] Testnachricht ist angekommen +- [ ] Sicherung läuft und wurde **einmal zurückgespielt** ([sicherung.md](sicherung.md)) +- [ ] `APP_SECRET` liegt an einem zweiten Ort diff --git a/docs/betrieb/reverse-proxy.md b/docs/betrieb/reverse-proxy.md new file mode 100644 index 0000000..b179279 --- /dev/null +++ b/docs/betrieb/reverse-proxy.md @@ -0,0 +1,144 @@ +# HTTPS einrichten: nginx Proxy Manager oder netbird + +ProxLedger spricht selbst kein HTTPS – davor gehört ein Reverse Proxy. Ohne ihn fehlen drei Dinge: + +- **Passkeys** funktionieren nur im sicheren Kontext (HTTPS oder `localhost`). +- Das Sitzungs-Cookie bekommt kein `Secure`-Kennzeichen. +- Die Agent-Kommunikation läuft unverschlüsselt durchs Netz. + +Fertige Beispiele für nginx, Caddy und Traefik liegen unter [`deploy/reverse-proxy/`](../../deploy/reverse-proxy/). +Dieses Dokument beschreibt die beiden Wege, die in der Praxis am häufigsten vorkommen. + +--- + +## Weg A: nginx Proxy Manager + +### 1. Proxy Host anlegen + +*Hosts → Proxy Hosts → Add Proxy Host*, Reiter **Details**: + +| Feld | Wert | +|---|---| +| Domain Names | `proxledger.firma.de` | +| Scheme | `http` | +| Forward Hostname / IP | IP des Docker-Hosts oder des LXC | +| Forward Port | `3000` | +| Cache Assets | aus | +| Block Common Exploits | an | +| Websockets Support | **an** | + +Reiter **SSL**: *Request a new SSL Certificate*, dazu **Force SSL** und **HTTP/2 Support**. + +### 2. Kopfzeilen ergänzen + +Reiter **Advanced**, in das Feld *Custom Nginx Configuration*: + +```nginx +# Ohne diese Angaben baut ProxLedger seine Weiterleitungen aus der internen Adresse +# (etwa http://172.18.0.4:3000) – im Browser landet man dann im Nichts. +proxy_set_header Host $host; +proxy_set_header X-Forwarded-Host $host; +proxy_set_header X-Forwarded-Proto $scheme; +proxy_set_header X-Real-IP $remote_addr; +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + +# Rechnungs-PDFs sind größer als die Standardgrenze von 1 MB. +client_max_body_size 12m; +``` + +### 3. In ProxLedger eintragen + +```bash +APP_URL="https://proxledger.firma.de" +``` + +und unter *Einstellungen → System* dieselbe Adresse als **Adresse des Dashboards**. Sie steht danach +im Installations-Einzeiler für die Agents und ist die Grundlage für Passkeys und OIDC. + +### 4. Prüfen + +```bash +curl -fsS -o /dev/null -w "%{http_code}\n" https://proxledger.firma.de/api/gesundheit +curl -fsSI https://proxledger.firma.de/login | grep -i "content-security-policy\|strict-transport" +``` + +Danach einmal anmelden und darauf achten, dass die Adresszeile auf `proxledger.firma.de` bleibt. + +--- + +## Weg B: netbird (kein offener Port) + +netbird spannt ein privates Netz über WireGuard. ProxLedger ist dann **nur** für Geräte im Netz +erreichbar – kein Port im Internet, kein Zertifikat von außen nötig. + +Das passt gut zu diesem Werkzeug: Die Oberfläche braucht niemand aus dem Internet, und die Proxmox- +Hosts stehen ohnehin meist in Kundennetzen mit netbird oder VPN. + +### Aufbau + +``` +Proxmox-Host (netbird-Client) ──┐ +Proxmox-Host (netbird-Client) ──┼── netbird-Netz ──► ProxLedger (netbird-Client, Port 3000) +Arbeitsplatz (netbird-Client) ──┘ +``` + +### 1. netbird auf dem ProxLedger-Host + +```bash +curl -fsSL https://pkgs.netbird.io/install.sh | sh +netbird up --setup-key +netbird status # zeigt die vergebene Adresse, z. B. 100.x.y.z +``` + +### 2. netbird auf jedem Proxmox-Host + +Gleiche Schritte. Danach von dort prüfen: + +```bash +curl -fsS -o /dev/null -w "%{http_code}\n" http://100.x.y.z:3000/api/gesundheit +``` + +### 3. Zugriff einschränken + +In der netbird-Verwaltung eine Access-Policy anlegen: Gruppe `proxmox-hosts` darf Gruppe +`proxledger` auf Port 3000 erreichen, sonst nichts. Damit können die Hosts melden, aber nicht +untereinander sprechen. + +### 4. HTTPS trotzdem einrichten + +netbird verschlüsselt den Transport – der Browser weiß davon aber nichts, und **Passkeys bleiben ohne +HTTPS gesperrt**. Zwei Möglichkeiten: + +- **Empfohlen:** Zusätzlich einen kleinen Reverse Proxy im netbird-Netz betreiben (nginx oder Caddy) + mit einem Zertifikat für einen internen Namen, ausgestellt über DNS-01 – dafür muss nichts nach + außen offen sein. +- Oder ein eigenes CA-Zertifikat verteilen. Dann brauchen auch die Agents das CA-Zertifikat: + `curl … | bash -s -- --token … --ca-cert /pfad/zur/ca.crt`. + +Wer auf Passkeys verzichtet, kann ProxLedger im netbird-Netz auch schlicht über `http://100.x.y.z:3000` +betreiben. In dem Fall `APP_URL` auf genau diese Adresse setzen – sonst setzt ProxLedger ein +`Secure`-Cookie, das der Browser über `http` verwirft, und die Anmeldung läuft ins Leere. + +--- + +## Beides zusammen + +Der häufigste Aufbau in der Praxis: + +| Zugang | Weg | +|---|---| +| Oberfläche für Mitarbeiter und Kunden | nginx Proxy Manager mit Let's-Encrypt-Zertifikat | +| Agents aus Kundennetzen | netbird, oder dieselbe öffentliche Adresse | + +Wichtig ist nur, dass die Agents dieselbe Adresse erreichen, die im Installations-Einzeiler steht. +Ändert sich die Adresse später, genügt der Einzeiler erneut auf jedem Host – die Zuordnung bleibt +erhalten, weil sie an der Maschinenkennung hängt, nicht an der Adresse. + +## Merkposten + +1. **Adresse zuerst festlegen, dann Passkeys anlegen.** Ein Passkey gilt nur für die Domäne, unter der + er registriert wurde. +2. **`APP_URL` und die Dashboard-Adresse müssen auf die öffentliche Adresse zeigen** – daraus entstehen + Weiterleitungen, OIDC-Rückkehradresse und der Agent-Einzeiler. +3. **`client_max_body_size` nicht vergessen**, sonst scheitert das Hochladen von Rechnungs-PDFs mit + einem nichtssagenden 413. diff --git a/docs/betrieb/sicherung.md b/docs/betrieb/sicherung.md new file mode 100644 index 0000000..04a5e94 --- /dev/null +++ b/docs/betrieb/sicherung.md @@ -0,0 +1,40 @@ +# Sicherung und Wiederherstellung + +## Was gesichert werden muss + +| Sache | Wo | Ohne sie | +|---|---|---| +| Datenbank | PostgreSQL | alles weg: Hosts, Kunden, Laufzeiten, Rechnungen | +| `APP_SECRET` | `.env` | alle Sitzungen ungültig, SMTP- und Webhook-Zugangsdaten unlesbar | +| Hochgeladene PDFs | `/data/uploads` (Docker-Volume) bzw. `/opt/proxledger/data/uploads` | Rechnungsanhänge fehlen | + +Die Agents brauchen **keine** Sicherung: Sie registrieren sich neu, und die Zuordnung hängt an der +Maschinenkennung, nicht an einem Token. + +## Sichern + +```bash +bash deploy/backup.sh # Datenbank und Uploads +``` + +Beim LXC-Weg reicht zusätzlich die gewohnte Proxmox-Sicherung des Containers. + +## Zurückspielen + +```bash +# Docker +docker compose down +docker compose up -d postgres +gunzip -c sicherung.sql.gz | docker compose exec -T postgres psql -U proxledger proxledger +docker compose up -d +``` + +Danach die `.env` mit **demselben** `APP_SECRET` zurücklegen wie zum Zeitpunkt der Sicherung. Ein +anderer Wert lässt die gespeicherten Zugangsdaten unlesbar werden; das Dashboard sagt das beim +nächsten Versand deutlich, aber die Werte müssen dann neu eingetragen werden. + +## Das Wichtigste + +**Eine Sicherung, die nie zurückgespielt wurde, ist keine Sicherung.** Spielen Sie sie einmal in eine +leere Testumgebung ein und melden Sie sich dort an. Erst dann wissen Sie, dass Sicherung und +`APP_SECRET` zusammenpassen. diff --git a/docs/issues/029-betriebsdokumentation.md b/docs/issues/029-betriebsdokumentation.md new file mode 100644 index 0000000..70b3559 --- /dev/null +++ b/docs/issues/029-betriebsdokumentation.md @@ -0,0 +1,30 @@ +# #029 Einrichtungsdokumentation +**Status:** erledigt +**Meilenstein:** v0.19.0 +**Labels:** doku +**Geschätzt:** 3 h **Tatsächlich:** 0 h 40 min + +## Ziel +Anleitungen für alles, was rund um ProxLedger eingerichtet wird: HTTPS, OIDC mit authentik, Sicherung – +und die richtige Reihenfolge, in der das geschehen muss. + +## Aufgaben +- [x] `docs/betrieb/inbetriebnahme.md` – von null zum meldenden Host +- [x] `docs/betrieb/reverse-proxy.md` – nginx Proxy Manager und netbird +- [x] `docs/betrieb/authentik.md` – OIDC Schritt für Schritt, mit Fehlerbildern +- [x] `docs/betrieb/sicherung.md` – sichern und **zurückspielen** +- [x] Verweise aus README und Handbuch + +## Beim Schreiben gefunden +Zwei Stellen taugten für einen Betrieb hinter einem Reverse Proxy nicht: + +1. Die **OIDC-Rückkehradresse** kam allein aus `APP_URL` und übersah die Dashboard-Adresse aus den + Einstellungen. Wer nur letztere gesetzt hatte, bekam vom Anbieter einen *Redirect URI Error*. +2. Beim **Einlösen des Codes** wurde die Adresse der eingehenden Anfrage verwendet – hinter einem + Proxy also die interne. Der Anbieter hat aber die öffentliche hinterlegt; der Tausch wäre + gescheitert. Jetzt wird dieselbe öffentliche Adresse gebildet wie für die Weiterleitungen. + +## Entscheidung +Die Reihenfolge steht bewusst am Anfang der Inbetriebnahme: **Adresse festlegen, dann Passkeys und +Agents.** Ein Passkey gilt nur für die Domäne, unter der er entstanden ist, und die Adresse steckt im +Installations-Einzeiler. Wer das übergeht, macht beides zweimal. diff --git a/docs/issues/README.md b/docs/issues/README.md index 49d2316..255cbd6 100644 --- a/docs/issues/README.md +++ b/docs/issues/README.md @@ -32,4 +32,5 @@ Details und Architektur: [../architektur.md](../architektur.md). Zeiten werden a | [#026](026-auslieferung.md) | Auslieferung finalisieren | erledigt | v0.17.0 | 3 h | 0 h 40 min | | [#027](027-rollen-benutzerverwaltung.md) | Rollen und Benutzerverwaltung | erledigt | v0.18.0 | 6 h | 1 h 20 min | | [#028](028-passkeys.md) | Anmeldung mit Passkeys | erledigt | v0.18.0 | 4 h | 0 h 45 min | -| | **Summe** | | | **143 h** | **14 h 23 min** | +| [#029](029-betriebsdokumentation.md) | Einrichtungsdokumentation | erledigt | v0.19.0 | 3 h | 0 h 40 min | +| | **Summe** | | | **146 h** | **15 h 03 min** | diff --git a/package.json b/package.json index 4fc44aa..4c446bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "proxledger", - "version": "0.18.0", + "version": "0.19.0", "private": true, "scripts": { "dev": "next dev", diff --git a/src/app/api/auth/oidc/callback/route.ts b/src/app/api/auth/oidc/callback/route.ts index d5ed64e..e240e58 100644 --- a/src/app/api/auth/oidc/callback/route.ts +++ b/src/app/api/auth/oidc/callback/route.ts @@ -26,7 +26,12 @@ export async function GET(request: Request) { } try { - const angaben = await anmeldungAbschliessen(einstellungen, new URL(request.url), pruefwert, zustand); + // Hinter einem Reverse Proxy ist `request.url` die interne Adresse. Der Anbieter hat aber die + // öffentliche als Rückkehradresse hinterlegt – mit der internen scheitert der Tausch des Codes. + const angekommen = new URL(request.url); + const oeffentlich = eigeneAdresse(request, `${angekommen.pathname}${angekommen.search}`, dashboardUrl); + + const angaben = await anmeldungAbschliessen(einstellungen, oeffentlich, pruefwert, zustand); const benutzer = await mitOidcAnmelden(angaben.subject, angaben.email, angaben.name); const { sitzungsdauerStunden } = await systemEinstellungen(); diff --git a/src/server/auth/oidc.ts b/src/server/auth/oidc.ts index 10a0f11..2139fa1 100644 --- a/src/server/auth/oidc.ts +++ b/src/server/auth/oidc.ts @@ -1,4 +1,5 @@ import * as client from "openid-client"; +import { einstellungen as systemEinstellungen } from "@/server/einstellungen"; /** * Anmeldung über OIDC (getestet mit authentik). Die Einrichtung erfolgt über Umgebungsvariablen, @@ -28,8 +29,13 @@ export function oidcEinstellungen(): OidcEinstellungen | null { }; } -export function rueckkehrAdresse(): string { - const basis = (process.env.APP_URL ?? "http://localhost:3000").replace(/\/$/, ""); +/** + * Adresse, an die der Anbieter zurückschickt. Sie muss beim Anbieter **wortgleich** hinterlegt sein. + * Vorrang hat die eingetragene Dashboard-Adresse, dann `APP_URL` (#029). + */ +export async function rueckkehrAdresse(): Promise { + const { dashboardUrl } = await systemEinstellungen(); + const basis = (dashboardUrl || process.env.APP_URL || "http://localhost:3000").replace(/\/$/, ""); return `${basis}/api/auth/oidc/callback`; } @@ -55,7 +61,7 @@ export async function anmeldungStarten(einstellungen: OidcEinstellungen): Promis const zustand = client.randomState(); const url = client.buildAuthorizationUrl(konfig, { - redirect_uri: rueckkehrAdresse(), + redirect_uri: await rueckkehrAdresse(), scope: "openid email profile", code_challenge: herausforderung, code_challenge_method: "S256",