diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b22adb..6b6f634 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,5 +8,33 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/). ## [Unveröffentlicht] ### Hinzugefügt -- Projektplan mit Architektur, Datenmodell, Sicherheitskonzept und Phasenplan (`docs/PLAN.md`) -- Issues für alle fünf Phasen in `docs/issues/` + +### Geändert + +### Behoben + +### Sicherheit + +## [0.1.0] – 2026-10-12 + +Phase 1: Grundgerüst, Anmeldung, Hosts und Bootstrap. + +### Hinzugefügt +- Projektplan mit Architektur, Datenmodell, Sicherheitskonzept und Phasenplan (`docs/PLAN.md`), Issues für alle fünf Phasen +- Projektgerüst: Next.js 16, Prisma 7, PostgreSQL 17, pnpm, ESLint/Prettier/Vitest/Playwright, commitlint (#001) +- Design „Tresor“: Design-Tokens, selbst gehostete Schriften, Logo, Favicon und PWA-Icons, Seitenleiste, Grundkomponenten (#002) +- Datenmodell für alle Phasen mit Datenbankregeln (CHECK, partielle Indizes, Trigger) und Seed mit Notfall-Admin und eingebauten Vorlagen „Deployment-Zugang“, „Backup-Zugang“, „Admin-Zugang Person“ (#003) +- Anmeldung per OIDC (authentik) mit Rollen Admin/Operator/Leser aus Gruppen; Notfall-Admin mit Passwort und Pflicht-TOTP; erneute Bestätigung vor kritischen Aktionen (#004) +- Revisionssicheres Protokoll: append-only, Hash-Kette, Prüffunktion (#005) +- OpenSSH-Schlüssel (ed25519, RSA-4096) erzeugen, lesen und schreiben ohne `ssh-keygen`, kompatibel geprüft; Envelope Encryption mit Master-Key; Deployment-Key der Instanz (#006) +- Worker mit pg-boss und Vorgängen mit Fortschrittsanzeige; Dockerfile und Docker Compose für den Betrieb (#007) +- Hosts und Hostgruppen anlegen, bearbeiten, archivieren, filtern; Demo-Daten mit `SSHKV_SEED_DEMO=1` (#008) +- Host-Key-Prüfung nach TOFU: abrufen, Fingerprint bestätigen oder ablehnen, strikte Prüfung bei jeder Verbindung, Warnung bei Änderung (#009) +- Bootstrap neuer Hosts per Einmal-Passwort oder Befehl zum Kopieren, mit Dienstbenutzer `sshkv` und sudoers; sshd-Hinweise (#010) +- SSH-Testziele (Debian 13) und Mock-OIDC für Integrations- und E2E-Tests (#011) + +### Sicherheit +- Private Keys nur verschlüsselt in der Datenbank; nur der Worker kann sie öffnen – strukturell getestet +- Ein `pg_dump` enthält keinen Klartext-Schlüssel (Test) +- Bootstrap-Passwort wird nirgends gespeichert (Test); Dateien im Home des Zielbenutzers werden als dieser Benutzer geschrieben (Symlink-Test gegen `/etc/shadow`) +- Content-Security-Policy mit Nonce, Sicherheits-Kopfzeilen, Rate-Limit für den Notfallzugang diff --git a/DEVLOG.md b/DEVLOG.md index 23ba281..e231466 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -2,29 +2,56 @@ ## Zeitübersicht -**Gesamt:** 0 h 25 min +**Gesamt (Wandzeit):** 1 h 25 min +**Summe der Issue-Zeiten:** 4 h 23 min – Sub-Agenten arbeiteten parallel an #002, #006, #008 und #011. | Issue | Zeit | |---|---| -| Planung (Phase 0) | 0 h 25 min | +| Planung (Phase 0) | 0 h 20 min | +| #001 Projekt-Setup | 0 h 15 min | +| #002 Design-System und Layout | 0 h 18 min | +| #003 Datenmodell und Migrationen | 0 h 10 min | +| #004 Anmeldung, Rollen und Bestätigung | 0 h 45 min | +| #005 Revisionssicheres Protokoll | 0 h 10 min | +| #006 Verschlüsselte Ablage und Schlüsselformat | 0 h 25 min | +| #007 Worker und Vorgänge | 0 h 15 min | +| #008 Hosts und Hostgruppen | 0 h 30 min | +| #009 Host-Key-Prüfung | 0 h 25 min | +| #010 Bootstrap | 0 h 35 min | +| #011 Test-SSH-Container | 0 h 15 min | | Meilenstein | Zeit | |---|---| +| v0.1.0 | 1 h 25 min (Wandzeit) | **abgeschlossen** --- -## 2026-10-12 · Sitzung 1 · 08:45–laufend -**Issue:** Planung, #001 ff. -**Version:** 0.0.0 +## 2026-10-12 · Sitzung 1 · 08:45–10:10 +**Issue:** Planung, #001–#011 +**Version:** 0.1.0 ### Erledigt - Auftrag gelesen, Architektur, Datenmodell, Sicherheitskonzept und Phasenplan entworfen (`docs/PLAN.md`) - Sieben Rückfragen gestellt und beantwortet, Plan freigegeben - Konventionen aus Laufzettel und ProxLedger übernommen; Git-Repository mit lokaler Identität angelegt - 30 Issues für Phase 1–5 als Dateien angelegt (Gitea folgt mit #030) +- #001 Grundgerüst; #003 Datenmodell mit Datenbankregeln und Protokoll-Hash-Kette als Trigger +- #002, #006 (Kern), #011 parallel von Sub-Agenten in eigenen Worktrees, danach gemergt +- #004 Anmeldung: OIDC gegen Mock-Anbieter, Notfall-Admin mit TOTP, Re-Auth; E2E im gebauten Stand +- #005 Protokoll-Service mit Schwärzen; #006 Teil 2: Versiegeln im Worker, Deployment-Key +- #007 Worker mit pg-boss; vollständiger Docker-Stapel geprüft (migrate, web, worker gesund) +- #008 Hosts und Hostgruppen (Sub-Agent) +- #009 strikte Host-Key-Prüfung und TOFU, #010 Bootstrap – Integrationstests gegen echtes sshd, E2E im Browser bis zur Verbindungsprüfung +- Stand: 287 Unit-/DB-Tests, 17 Integrationstests, 5 E2E-Abläufe grün ### Entscheidungen - **Kein Auth.js:** v5 steht dauerhaft auf Beta – wie in Laufzettel `openid-client` + `jose`. - **Web und Worker getrennt, Master-Key nur im Worker:** Eine Lücke im Webprozess allein entschlüsselt keine Private Keys. - **Schlüsselerzeugung ohne `ssh-keygen`:** Node `crypto` + eigener OpenSSH-Serializer, damit Private Keys nie als Datei auf dem sshkv-Server liegen. `ssh-keygen` prüft in den Tests die Kompatibilität. -- **Dienstbenutzer `sshkv` mit sudo als Standard,** `root` pro Host wählbar: Im auth.log der Hosts ist eindeutig zu sehen, was von sshkv kam, und root-Login kann ganz aus bleiben. +- **Dienstbenutzer `sshkv` mit sudo als Standard,** `root` pro Host wählbar. +- **Ohne sshkv-Gruppe kein Zugang** – auch kein Lesezugriff. +- **Protokoll-Hash im Datenbank-Trigger:** Niemand kann eine Zeile ohne Verkettung einfügen; die ID wird erst unter Sperre vergeben, damit die Kette auch bei parallelem Schreiben hält. +- **Ein Bootstrap-Skript für Passwort- und Copy-Paste-Weg,** Dateizugriffe als Zielbenutzer (Symlink-Schutz). +- **Eigene Testdatenbank je parallelem Lauf:** Sub-Agent und Hauptsitzung leerten sich gegenseitig die Tabellen (in `CLAUDE.md` festgehalten). +- **Prisma-Client in den Worker gebündelt:** Im Standalone-Abbild fand die ESM-Auflösung `@prisma/client` sonst nicht (erst im echten Docker-Stapel aufgefallen). +- **Worker-Benutzer in Compose wählbar** (`SSHKV_WORKER_USER`), weil Docker-Secrets ohne Swarm die Rechte der Host-Datei tragen. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c5e7861 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# SSHKeyVault (`sshkv`) + +Selbst gehostete Weboberfläche zur zentralen Verwaltung von SSH-Schlüsseln: Schlüsselpaare +erzeugen und verwalten, Public Keys in `authorized_keys` verteilen, Private Keys gezielt auf +andere Hosts ausrollen, Ist- und Soll-Zustand abgleichen – alles revisionssicher protokolliert. + +> **Stand 0.1.0 (Phase 1):** Grundgerüst, Anmeldung (OIDC + Notfall-Admin mit TOTP), Hosts und +> Hostgruppen, Host-Key-Prüfung (TOFU) und Bootstrap neuer Hosts. Schlüsselverwaltung und +> Ausrollen folgen in Phase 2 und 3 – siehe [`docs/PLAN.md`](docs/PLAN.md). + +## Sicherheit vorab + +SSHKeyVault hat **root-Zugriff auf alle verwalteten Hosts**. Deshalb: + +- nur im internen Netz bzw. VPN betreiben, mit Reverse Proxy und TLS davor; +- den **Master-Key** (`SSHKV_MASTER_KEY_FILE`) getrennt von der Datenbank sichern – ohne ihn + ist ein Datenbank-Backup wertlos, und mit ihm zusammen gibt es alle Private Keys preis; +- Private Keys liegen nur verschlüsselt in der Datenbank (AES-256-GCM, Envelope Encryption); + entschlüsseln kann ausschließlich der Worker, der Webprozess hat den Master-Key nicht. + +Mehr dazu in [`docs/PLAN.md`](docs/PLAN.md), Abschnitt 3. + +## Installation (Docker Compose) + +```bash +cp .env.example .env # Werte eintragen, siehe Kommentare +openssl rand -base64 48 # → SSHKV_APP_SECRET +mkdir -p geheim && head -c 32 /dev/urandom > geheim/master.key +sudo chown 1001:1001 geheim/master.key && sudo chmod 400 geheim/master.key +docker compose up -d +``` + +Die Oberfläche lauscht auf `127.0.0.1:3400` (`SSHKV_BIND`, `SSHKV_PORT`). Beim ersten Start legt +der Dienst `migrate` das Schema, den Notfall-Admin (`SSHKV_BREAKGLASS_*`) und die eingebauten +Vorlagen an; der Worker erzeugt den Deployment-Key der Instanz. + +**Anmeldung:** über authentik (`SSHKV_OIDC_*`, Rollen über die Gruppen `sshkv-admins`, +`sshkv-operators`, `sshkv-readers`). Der Notfall-Admin richtet bei der ersten Anmeldung TOTP ein. + +## Einen Host aufnehmen + +1. **Hosts → + Host anlegen** (Name, Adresse, Port; Anmeldung als Dienstbenutzer `sshkv` mit sudo + – empfohlen – oder direkt als root). +2. **Host-Key abrufen**, den Fingerprint am Host vergleichen + (`ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub`) und vertrauen. +3. **Bootstrap:** einmalig per Passwort (wird nicht gespeichert) oder den angezeigten Befehl auf + dem Host einfügen und „Einrichtung prüfen“. + +## Entwicklung + +Node 24, pnpm 12, Docker. Konventionen und Befehle: [`CLAUDE.md`](CLAUDE.md). + +```bash +pnpm install +pnpm db:up && pnpm db:migrate && pnpm db:seed +pnpm test:ziele:up # SSH-Testziele + Mock-OIDC +pnpm build && pnpm start # http://localhost:3400 +pnpm worker # zweites Terminal +pnpm test && pnpm test:integration && pnpm test:e2e +``` diff --git a/package.json b/package.json index f0ec25b..9612b07 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sshkv", - "version": "0.0.0", + "version": "0.1.0", "private": true, "type": "module", "description": "SSHKeyVault – zentrale Verwaltung von SSH-Schluesseln mit Weboberflaeche",