docs: Projektplan, Issues und Konventionen anlegen
Plan nach Freigabe vom 12.10.2026 mit den Antworten auf die sieben Rueckfragen. Issues fuer alle fuenf Phasen als Dateien, bis der Gitea-Zugang da ist (#030). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+32
@@ -0,0 +1,32 @@
|
|||||||
|
# Abhaengigkeiten
|
||||||
|
node_modules/
|
||||||
|
.pnpm-store/
|
||||||
|
|
||||||
|
# Next.js / Build
|
||||||
|
.next/
|
||||||
|
out/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
||||||
|
# Prisma-Client (wird erzeugt)
|
||||||
|
src/generated/
|
||||||
|
|
||||||
|
# Umgebung und Geheimnisse – niemals einchecken
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
geheim/
|
||||||
|
*.key
|
||||||
|
master.key
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
coverage/
|
||||||
|
test-results/
|
||||||
|
playwright-report/
|
||||||
|
|
||||||
|
# Editor / System
|
||||||
|
.DS_Store
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
Alle nennenswerten Änderungen an SSHKeyVault werden in dieser Datei dokumentiert.
|
||||||
|
|
||||||
|
Das Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.1.0/),
|
||||||
|
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/`
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# DEVLOG – SSHKeyVault
|
||||||
|
|
||||||
|
## Zeitübersicht
|
||||||
|
|
||||||
|
**Gesamt:** 0 h 25 min
|
||||||
|
|
||||||
|
| Issue | Zeit |
|
||||||
|
|---|---|
|
||||||
|
| Planung (Phase 0) | 0 h 25 min |
|
||||||
|
|
||||||
|
| Meilenstein | Zeit |
|
||||||
|
|---|---|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-10-12 · Sitzung 1 · 08:45–laufend
|
||||||
|
**Issue:** Planung, #001 ff.
|
||||||
|
**Version:** 0.0.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)
|
||||||
|
|
||||||
|
### 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.
|
||||||
+389
@@ -0,0 +1,389 @@
|
|||||||
|
# SSHKeyVault (`sshkv`) – Planung
|
||||||
|
|
||||||
|
Stand: 12.10.2026 · Status: **freigegeben** (12.10.2026)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Architektur
|
||||||
|
|
||||||
|
```
|
||||||
|
internes Netz / VPN
|
||||||
|
│ HTTPS (Reverse Proxy, z. B. Traefik/Caddy – nicht Teil von sshkv)
|
||||||
|
┌───────────────────────▼────────────────────────┐
|
||||||
|
│ sshkv-web (Next.js 16, App Router, Node 24) │
|
||||||
|
│ - UI (Server Components, Tailwind v4) │
|
||||||
|
│ - OIDC authentik + Notfall-Admin │
|
||||||
|
│ - Server Actions / Route Handlers │
|
||||||
|
│ - schreibt Jobs in pg-boss, liest Ergebnisse │
|
||||||
|
│ - KEINE SSH-Verbindungen, KEIN Master-Key* │
|
||||||
|
└───────────────┬────────────────────────────────┘
|
||||||
|
│ PostgreSQL 16 (Prisma + pg-boss-Schema)
|
||||||
|
┌───────────────▼────────────────────────────────┐
|
||||||
|
│ sshkv-worker (gleiches Image, anderer Start) │
|
||||||
|
│ - pg-boss-Consumer │
|
||||||
|
│ - Master-Key (Datei, read-only gemountet) │
|
||||||
|
│ - ssh2: Verbindungen zu Hosts, strikt geprüft │
|
||||||
|
│ - Schlüsselerzeugung, Ver-/Entschlüsselung │
|
||||||
|
│ - Scheduler: Abgleich, Ablaufwarnungen │
|
||||||
|
└───────────────┬────────────────────────────────┘
|
||||||
|
│ SSH (nur Deployment-Key)
|
||||||
|
┌──────▼──────┐ ┌─────────────┐
|
||||||
|
│ srv-pve01 │ │ srv-app01 … │
|
||||||
|
└─────────────┘ └─────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
\* Ausnahme „Private Key anzeigen/exportieren“: Der Web-Prozess fordert die Entschlüsselung als Job an und reicht das Ergebnis genau einmal an den Browser durch (kein Caching, kein Logging). Alternative: Master-Key auch im Web-Container – bewusst **nicht** vorgesehen, damit eine Lücke im Webprozess allein nicht reicht, um alle Private Keys zu entschlüsseln.
|
||||||
|
|
||||||
|
**Begründung Trennung Web/Worker:** Lange SSH-Vorgänge laufen nie im Request; nur der Worker hält Master-Key und Deployment-Key. Der Web-Container hat keinen Netzwerkpfad zu den Hosts nötig (per Docker-Netz trennbar).
|
||||||
|
|
||||||
|
### Bausteine
|
||||||
|
|
||||||
|
| Bereich | Wahl | Bemerkung |
|
||||||
|
|---|---|---|
|
||||||
|
| Framework | Next.js 16 (App Router), TypeScript strict | `output: "standalone"` für schlankes Image |
|
||||||
|
| DB | PostgreSQL 17 + Prisma 7 (adapter-pg) | Migrationen versioniert im Repo |
|
||||||
|
| Jobs | pg-boss 10 | kein Redis nötig; Cron-Schedules eingebaut |
|
||||||
|
| Auth | `openid-client` (OIDC, PKCE) + `jose` (Sitzungs-Cookie); Notfall-Admin mit argon2id + TOTP | kein Auth.js: v5 ist dauerhaft Beta (wie in Laufzettel/ProxLedger) |
|
||||||
|
| SSH | `ssh2` | `hostVerifier` gegen gespeicherten Host-Key |
|
||||||
|
| Schlüssel | Node `crypto` (ed25519, RSA-4096) + eigener OpenSSH-Serializer | kein `ssh-keygen` mit Temp-Dateien; Tests prüfen Kompatibilität gegen `ssh-keygen -y` |
|
||||||
|
| Krypto at rest | AES-256-GCM (Node `crypto`), Envelope Encryption | keine nativen Zusatz-Abhängigkeiten |
|
||||||
|
| UI | Tailwind v4 mit Design-Tokens als CSS-Variablen, `@fontsource` | keine externen CDNs |
|
||||||
|
| Validierung | zod | an allen Server-Grenzen |
|
||||||
|
| Tests | Vitest (Unit), Testcontainers + Debian-sshd-Image (Integration), Playwright (E2E + Screenshots) | |
|
||||||
|
| Betrieb | Docker Compose: `sshkv-web`, `sshkv-worker`, `sshkv-db` | `.env.example` mit Präfix `SSHKV_` |
|
||||||
|
|
||||||
|
### Remote-Operationen
|
||||||
|
|
||||||
|
Alle Änderungen auf Hosts laufen über **ein kleines POSIX-sh-Skript**, das per `exec` mit Inhalt über stdin gestartet wird (keine Dateien auf dem sshkv-Server, keine Abhängigkeit von SFTP-Rechten):
|
||||||
|
|
||||||
|
1. Ziel-Benutzer prüfen (`getent passwd`), Home ermitteln, `AuthorizedKeysFile` per `sshd -T` auslesen (abweichende Pfade werden erkannt).
|
||||||
|
2. Dateioperationen in fremden Home-Verzeichnissen **als Zielbenutzer** (`runuser -u <user> --`), damit Symlink-Tricks im Home eines Benutzers keine Root-Schreibrechte erlangen.
|
||||||
|
3. Backup `authorized_keys.sshkv.bak.<YYYYMMDD-HHMMSS>` (Aufbewahrung: letzte N, konfigurierbar).
|
||||||
|
4. Schreiben in `…/.sshkv.tmp.<rand>` + `chmod 0600` + `mv` (atomar), `~/.ssh` 0700.
|
||||||
|
5. Rückgabe von Vorher-Hash und Nachher-Inhalt → Audit-Diff.
|
||||||
|
6. Optimistische Sperre: Der Dry-Run merkt sich den SHA-256 der Datei; hat sich die Datei bis zur Ausführung geändert, bricht der Job ab und fordert einen neuen Dry-Run.
|
||||||
|
|
||||||
|
Private Keys werden ausschließlich über stdin derselben SSH-Verbindung übertragen.
|
||||||
|
|
||||||
|
### authorized_keys – verwalteter Block
|
||||||
|
|
||||||
|
```
|
||||||
|
# BEGIN SSHKV MANAGED – nicht manuell bearbeiten
|
||||||
|
restrict,from="10.0.0.12" ssh-ed25519 AAAA… sshkv:k_8f3a backup-srv-backup01
|
||||||
|
# END SSHKV MANAGED
|
||||||
|
```
|
||||||
|
|
||||||
|
- Parser (reine Funktion, umfangreich getestet): Optionen mit Anführungszeichen/Escapes/Kommas, Kommentare, Leerzeilen, CRLF, BOM, doppelte Keys, `cert-authority`, unbekannte Schlüsseltypen, kaputte Zeilen, fehlendes END-Marker, mehrere Blöcke.
|
||||||
|
- Merge: Alles außerhalb des Blocks bleibt bytegenau erhalten (inkl. Zeilenenden). Innerhalb wird der Block deterministisch aus dem Soll-Zustand neu erzeugt.
|
||||||
|
- Erkennung eines verwalteten Keys über Fingerprint (nicht über Kommentar); Kommentar `sshkv:<id> <name>` dient nur der Lesbarkeit.
|
||||||
|
- Duplikat außerhalb des Blocks → Hinweis im Dry-Run, keine automatische Löschung.
|
||||||
|
- **Aussperrschutz:** Der Deployment-Key ist in jedem `root`- bzw. Dienstbenutzer-Block unabänderlich enthalten; Aktionen, die ihn entfernen würden, werden serverseitig verweigert.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Datenmodell (Prisma, verkürzt)
|
||||||
|
|
||||||
|
> Umgesetzt mit **deutschen Bezeichnern** (Konvention, siehe `CLAUDE.md`); die Zuordnung steht dort. Die Skizze unten zeigt die Struktur.
|
||||||
|
|
||||||
|
```prisma
|
||||||
|
enum Role { ADMIN OPERATOR READER }
|
||||||
|
enum KeyType { ED25519 RSA4096 }
|
||||||
|
enum KeyCategory { PERSON SERVICE DEPLOYMENT }
|
||||||
|
enum KeyStatus { ACTIVE ROTATING REVOKED EXPIRED }
|
||||||
|
enum HostKeyStatus { PENDING TRUSTED CHANGED REJECTED }
|
||||||
|
enum UsageState { PLANNED DEPLOYED DRIFT REMOVING REMOVED FAILED }
|
||||||
|
|
||||||
|
model User { // Anmeldekonten der Oberfläche
|
||||||
|
id String @id @default(cuid())
|
||||||
|
oidcSub String? @unique
|
||||||
|
email String? @unique
|
||||||
|
name String
|
||||||
|
role Role
|
||||||
|
isBreakGlass Boolean @default(false) // lokaler Notfall-Admin
|
||||||
|
passwordHash String? // nur Notfall-Admin (argon2id)
|
||||||
|
totpSecretEnc Bytes? // nur Notfall-Admin, verschlüsselt
|
||||||
|
lastLoginAt DateTime?
|
||||||
|
person Person?
|
||||||
|
}
|
||||||
|
|
||||||
|
model Person { // Menschen, denen Personen-Keys gehören
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
email String?
|
||||||
|
organisation String? // z. B. Dienstleister
|
||||||
|
userId String? @unique // Verknüpfung für Self-Service
|
||||||
|
keys SshKey[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Customer { id String @id @default(cuid()); name String @unique; hosts Host[] }
|
||||||
|
|
||||||
|
model Host {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String @unique // srv-pve01
|
||||||
|
address String // Hostname/IP
|
||||||
|
port Int @default(22)
|
||||||
|
description String?
|
||||||
|
tags String[]
|
||||||
|
customerId String?
|
||||||
|
connectUser String @default("root") // siehe offene Frage 1
|
||||||
|
deploymentKeyId String? // null = Instanz-Default
|
||||||
|
bootstrapState String // NEW | BOOTSTRAPPED
|
||||||
|
reachable Boolean?
|
||||||
|
lastCheckAt DateTime?
|
||||||
|
lastSyncAt DateTime?
|
||||||
|
driftState String? // IN_SYNC | DRIFT | UNKNOWN
|
||||||
|
sshdFacts Json? // PermitRootLogin, PasswordAuthentication, AuthorizedKeysFile
|
||||||
|
groups HostGroupMember[]
|
||||||
|
hostKeys HostKey[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model HostGroup {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String @unique
|
||||||
|
description String?
|
||||||
|
deploymentKeyId String? // optional Deployment-Key je Gruppe (später)
|
||||||
|
members HostGroupMember[]
|
||||||
|
}
|
||||||
|
model HostGroupMember { hostId String; groupId String; @@id([hostId, groupId]) }
|
||||||
|
|
||||||
|
model HostKey { // TOFU
|
||||||
|
id String @id @default(cuid())
|
||||||
|
hostId String
|
||||||
|
algorithm String // ssh-ed25519, ecdsa-…, rsa-sha2-512
|
||||||
|
publicKey String
|
||||||
|
fingerprint String // SHA256:…
|
||||||
|
status HostKeyStatus
|
||||||
|
firstSeenAt DateTime
|
||||||
|
confirmedById String?
|
||||||
|
confirmedAt DateTime?
|
||||||
|
}
|
||||||
|
|
||||||
|
model SshKey {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String // Zweck, z. B. „Backup srv-backup01 → srv-pve01“
|
||||||
|
slug String @unique // für Dateinamen: backup-srv-backup01-srv-pve01
|
||||||
|
type KeyType
|
||||||
|
category KeyCategory
|
||||||
|
status KeyStatus
|
||||||
|
publicKey String // OpenSSH-Zeile ohne Optionen
|
||||||
|
fingerprint String @unique // SHA256
|
||||||
|
comment String // Default: sshkv:<slug>
|
||||||
|
hasPrivateKey Boolean
|
||||||
|
privateKeyEnc Bytes? // Envelope-Ciphertext, nie in Listen-Queries selektiert
|
||||||
|
dekEnc Bytes? // verschlüsselter Data-Key
|
||||||
|
masterKeyVersion Int?
|
||||||
|
passphraseProtected Boolean @default(false)
|
||||||
|
personId String?
|
||||||
|
templateId String?
|
||||||
|
tags String[]
|
||||||
|
expiresAt DateTime?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
lastUsedAt DateTime? // aus Verbindungstests/Abgleich
|
||||||
|
rotatedFromId String? // Rotationskette
|
||||||
|
publicUsages PublicKeyUsage[]
|
||||||
|
privateUsages PrivateKeyUsage[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model PublicKeyUsage { // Soll: Public Key in authorized_keys
|
||||||
|
id String @id @default(cuid())
|
||||||
|
keyId String
|
||||||
|
hostId String? // genau eins von hostId / groupId
|
||||||
|
groupId String?
|
||||||
|
targetUser String // root, deploy …
|
||||||
|
options String? // restrict,from="…",command="…"
|
||||||
|
state UsageState
|
||||||
|
lastAppliedAt DateTime?
|
||||||
|
}
|
||||||
|
|
||||||
|
model PrivateKeyUsage { // Soll: Private-Key-Datei auf Host
|
||||||
|
id String @id @default(cuid())
|
||||||
|
keyId String
|
||||||
|
hostId String
|
||||||
|
targetUser String
|
||||||
|
path String // ~/.ssh/id_ed25519_<slug>
|
||||||
|
sshConfigBlock Json? // { alias, hostName, user, port, identitiesOnly }
|
||||||
|
knownHostsFor String[] // Host-IDs, deren Host-Key in known_hosts gepflegt wird
|
||||||
|
state UsageState
|
||||||
|
onRevoke String @default("DELETE") // DELETE | RENAME
|
||||||
|
lastAppliedAt DateTime?
|
||||||
|
}
|
||||||
|
|
||||||
|
model ManagedAccount { // vom sshkv angelegte Benutzer (deploy) und sudoers-Dateien
|
||||||
|
id String @id @default(cuid())
|
||||||
|
hostId String
|
||||||
|
username String
|
||||||
|
createdBySshkv Boolean
|
||||||
|
sudoersFile String? // /etc/sudoers.d/sshkv-deploy
|
||||||
|
sudoersContent String?
|
||||||
|
}
|
||||||
|
|
||||||
|
model Template { // „Deployment-Zugang“, „Backup-Zugang“, …
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String @unique
|
||||||
|
builtin Boolean @default(false)
|
||||||
|
keyType KeyType
|
||||||
|
category KeyCategory
|
||||||
|
defaultTtlDays Int?
|
||||||
|
targetUser String
|
||||||
|
createTargetUser Boolean
|
||||||
|
sudoMode String? // NONE | COMMANDS | FULL
|
||||||
|
sudoCommands String[]
|
||||||
|
authorizedKeysOptions String? // mit Platzhaltern, z. B. from="{{sourceHostIp}}"
|
||||||
|
sshConfigAlias String? // z. B. deploy-{{targetHost}}
|
||||||
|
}
|
||||||
|
|
||||||
|
model Observation { // Ist-Zustand aus dem letzten Abgleich
|
||||||
|
id String @id @default(cuid())
|
||||||
|
hostId String
|
||||||
|
targetUser String
|
||||||
|
file String
|
||||||
|
sha256 String
|
||||||
|
entries Json // geparste Zeilen inkl. inBlock/fingerprint/options
|
||||||
|
observedAt DateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
model DriftFinding {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
hostId String
|
||||||
|
targetUser String
|
||||||
|
kind String // UNKNOWN_KEY | MISSING_KEY | EXTRA_IN_BLOCK | FILE_CHANGED | PERMS | HOSTKEY_CHANGED
|
||||||
|
fingerprint String?
|
||||||
|
detail Json
|
||||||
|
openedAt DateTime
|
||||||
|
resolvedAt DateTime?
|
||||||
|
}
|
||||||
|
|
||||||
|
model Operation { // fachliche Klammer um pg-boss-Jobs (für UI-Status)
|
||||||
|
id String @id @default(cuid())
|
||||||
|
kind String // DRY_RUN | APPLY | BOOTSTRAP | SYNC | TEST | ROTATE | REVOKE …
|
||||||
|
status String // QUEUED | RUNNING | AWAITING_CONFIRM | DONE | FAILED
|
||||||
|
requestedById String
|
||||||
|
plan Json? // Dry-Run-Ergebnis inkl. Datei-Hashes
|
||||||
|
result Json?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
finishedAt DateTime?
|
||||||
|
}
|
||||||
|
|
||||||
|
model AuditEvent { // append-only (DB-Trigger verbietet UPDATE/DELETE)
|
||||||
|
id BigInt @id @default(autoincrement())
|
||||||
|
at DateTime @default(now())
|
||||||
|
actorId String? // null = System/Scheduler
|
||||||
|
actorName String
|
||||||
|
action String // key.create, key.private.view, usage.apply, host.hostkey.confirm …
|
||||||
|
hostName String?
|
||||||
|
targetUser String?
|
||||||
|
fingerprint String?
|
||||||
|
diff String? // Vorher/Nachher (nur Public-Daten)
|
||||||
|
result String // SUCCESS | FAILURE | DENIED
|
||||||
|
detail Json?
|
||||||
|
prevHash String // Hash-Kette → Manipulation erkennbar
|
||||||
|
hash String
|
||||||
|
}
|
||||||
|
|
||||||
|
model Setting { key String @id; value Json }
|
||||||
|
```
|
||||||
|
|
||||||
|
Hinweise:
|
||||||
|
- `privateKeyEnc`/`dekEnc` liegen technisch in `SshKey`, werden aber nur über ein einziges Modul (`lib/secrets`) im Worker gelesen; Prisma-Queries nutzen sonst feste `select`-Listen ohne diese Felder (per Lint-Regel/Test abgesichert).
|
||||||
|
- Der Deployment-Key ist ein `SshKey` mit `category = DEPLOYMENT`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Sicherheitskonzept (Kurzfassung, Langfassung in `docs/security.md`)
|
||||||
|
|
||||||
|
- **Envelope Encryption:** pro Private Key ein zufälliger 256-bit-DEK, Key mit AES-256-GCM verschlüsselt (AAD = Key-ID + Fingerprint), DEK mit Master-Key (AES-256-GCM) verschlüsselt. Master-Key: 32 Byte aus Datei `SSHKV_MASTER_KEY_FILE` (Docker Secret), nur im Worker. `masterKeyVersion` erlaubt Master-Key-Rotation (CLI-Kommando `sshkv rewrap`).
|
||||||
|
- **Re-Auth** für kritische Aktionen: OIDC mit `prompt=login` + `max_age=0`, Prüfung von `auth_time` (≤ 5 min); Notfall-Admin: Passwort + TOTP erneut.
|
||||||
|
- **Web:** Sitzungs-Cookie (jose, HS256) (httpOnly, Secure, SameSite=Lax), CSRF: Server Actions (Origin-Prüfung) + Token für Route Handler, Rate-Limit (Postgres-basiert) auf Login/Re-Auth/kritische Aktionen, CSP mit Nonce, HSTS, `X-Frame-Options: DENY`, `Referrer-Policy: no-referrer`.
|
||||||
|
- **Logging:** strukturierter Logger mit Redaction (`privateKey`, `password`, `passphrase`, `-----BEGIN`); Test, der bewusst einen Key durch Fehlerpfade schickt und Logs prüft.
|
||||||
|
- **Bootstrap-Passwort:** nur im Arbeitsspeicher des Workers für genau einen Job, nie in DB/Job-Payload im Klartext → wird vom Web-Prozess mit einem kurzlebigen Job-Schlüssel verschlüsselt, Job-Datensatz wird nach Abschluss gelöscht.
|
||||||
|
- **Bedrohungsanalyse** (Doku): Kompromittierung sshkv = root auf allen Hosts. Gegenmaßnahmen: nur intern/VPN, Worker ohne eingehende Ports, Master-Key getrennt, Audit mit Hash-Kette + Syslog-Export, Notfallplan „Deployment-Key auf allen Hosts tauschen“ (Rotation des Deployment-Keys als eigener Ablauf), optional `from="<sshkv-IP>"` am Deployment-Key.
|
||||||
|
- **Backup:** `pg_dump` + Master-Key getrennt sichern; Doku mit Restore-Test.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Rollen & Rechte
|
||||||
|
|
||||||
|
| Aktion | Admin | Operator | Leser |
|
||||||
|
|---|:-:|:-:|:-:|
|
||||||
|
| Ansehen (Hosts, Keys, Matrix, Audit) | ✓ | ✓ | ✓ |
|
||||||
|
| Hosts anlegen/bootstrappen, Host-Key bestätigen | ✓ | ✓ | – |
|
||||||
|
| Public Key zuweisen/ausrollen/entfernen | ✓ | ✓ | – |
|
||||||
|
| Schlüssel erzeugen/importieren | ✓ | ✓ | – |
|
||||||
|
| Private Key ausrollen | ✓ | – | – |
|
||||||
|
| Private Key anzeigen/exportieren (Re-Auth) | ✓ | – | – |
|
||||||
|
| Deployment-Key ändern/rotieren (Re-Auth) | ✓ | – | – |
|
||||||
|
| Massenwiderruf (Re-Auth) | ✓ | – | – |
|
||||||
|
| Benutzer anlegen + sudoers (Re-Auth) | ✓ | – | – |
|
||||||
|
| Einstellungen, Vorlagen | ✓ | – | – |
|
||||||
|
| Eigene Public Keys hinterlegen (Self-Service) | ✓ | ✓ | ✓ |
|
||||||
|
|
||||||
|
Rollenzuordnung über authentik-Gruppen: `sshkv-admins`, `sshkv-operators`, `sshkv-readers` (Namen konfigurierbar per `SSHKV_OIDC_GROUP_*`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Phasenplan
|
||||||
|
|
||||||
|
Versionierung: `0.N.0` am Ende jeder Phase, `1.0.0` nach Phase 5 + Abnahme.
|
||||||
|
|
||||||
|
### Phase 0 – Planung (jetzt)
|
||||||
|
Plan, Freigabe, Repo + Gitea-Issues.
|
||||||
|
|
||||||
|
### Phase 1 – Grundgerüst → `v0.1.0`
|
||||||
|
- Repo, Next.js, Prisma-Schema, Docker Compose (web/worker/db), `.env.example`, CI-Basis (Lint, Typecheck, Tests).
|
||||||
|
- Design-System: Tokens, Schriften, Logo-Dateien + Favicon/PWA-Icons, Layout mit Seitenleiste.
|
||||||
|
- Auth: OIDC authentik, Notfall-Admin (argon2id + TOTP), Rollen, Re-Auth-Mechanik.
|
||||||
|
- Audit-Grundlage (append-only, Hash-Kette).
|
||||||
|
- Master-Key-Handling + `lib/secrets`.
|
||||||
|
- Deployment-Key erzeugen.
|
||||||
|
- Hosts/Hostgruppen CRUD, TOFU-Host-Key-Verifizierung, Erreichbarkeitsprüfung.
|
||||||
|
- Bootstrap: Einmal-Passwort-Login **oder** Copy-Paste-Befehl; sshd-Check (PermitRootLogin, PasswordAuthentication).
|
||||||
|
- Test-SSH-Container (Debian 12/13) für Integrationstests.
|
||||||
|
|
||||||
|
### Phase 2 – Schlüssel & Public-Key-Ausrollen → `v0.2.0`
|
||||||
|
- Erzeugen (ed25519, rsa-4096, optional Passphrase), Import (Public/Paar), Export.
|
||||||
|
- Personen, Kategorien, Tags, Ablaufdatum.
|
||||||
|
- Private Key anzeigen/Download mit Re-Auth + Audit.
|
||||||
|
- `authorized_keys`-Parser/Merger (Unit-Tests mit allen Randfällen).
|
||||||
|
- Zuweisungen (Host/Gruppe × Benutzer, Optionen), Dry-Run-Diff, Backup, atomares Schreiben, Aussperrschutz.
|
||||||
|
|
||||||
|
### Phase 3 – Private-Key-Ausrollen & Host-zu-Host → `v0.3.0`
|
||||||
|
- Private Key auf Host A, `~/.ssh/config`-Block (verwaltet, eigener Marker), `known_hosts`-Pflege.
|
||||||
|
- Benutzer anlegen + sudoers (Vorschau, `visudo -c`).
|
||||||
|
- Vorlagen (eingebaut „Deployment-Zugang“, „Backup-Zugang“, „Admin-Zugang Person“; eigene in Einstellungen).
|
||||||
|
- Host-zu-Host-Assistent inkl. Verbindungstest A → B.
|
||||||
|
- Option „Private Key nach Ausrollen löschen“.
|
||||||
|
|
||||||
|
### Phase 4 – Abgleich, Import, Rotation, Widerruf → `v0.4.0`
|
||||||
|
- Periodischer + manueller Abgleich, Drift-Befunde, Host-Status.
|
||||||
|
- Import bestehender `authorized_keys` (Zuordnung zu Person/Zweck).
|
||||||
|
- Geführte Rotation, Ein-Klick-Widerruf (inkl. Benutzer/sudoers entfernen), Massenwiderruf.
|
||||||
|
- Zuweisungsmatrix.
|
||||||
|
|
||||||
|
### Phase 5 – Dashboard & Feinschliff → `v0.5.0` / `v1.0.0`
|
||||||
|
- Übersicht laut Design, Audit-Ansicht + CSV-Export, Syslog-Weiterleitung.
|
||||||
|
- Benachrichtigungen (SMTP M365 / Webhook) bei Drift und Ablauf.
|
||||||
|
- Anleitung mit Screenshots (Installation, Bootstrap, Kern-Anwendungsfall), Bedrohungsanalyse, Backup-Konzept.
|
||||||
|
|
||||||
|
### Später (eigene Phasen)
|
||||||
|
- Heller Modus, SSH-CA (Zertifikate), Deployment-Key je Hostgruppe (Datenmodell ist vorbereitet).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Arbeitsweise
|
||||||
|
|
||||||
|
- Gitea: pro Phase ein Milestone, Issues je Arbeitspaket, Commits referenzieren Issues (`refs #12`), Conventional Commits.
|
||||||
|
- `devlog.md` (Datum, Dauer, Tätigkeit), `CHANGELOG.md` (Keep a Changelog), SemVer-Tags.
|
||||||
|
- Sub-Agenten parallel für Tests, Doku, UI-Komponenten.
|
||||||
|
- Nach jeder Phase Screenshots (Playwright) und kurzer Bericht, Freigabe der nächsten Phase.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Entscheidungen aus der Freigabe (12.10.2026)
|
||||||
|
|
||||||
|
1. Anmeldung auf den Hosts nur per Schlüssel. Standard: Dienstbenutzer `sshkv` mit `/etc/sudoers.d/sshkv` (NOPASSWD); `root` direkt ist pro Host wählbar.
|
||||||
|
2. Gitea-Zugang folgt; bis dahin liegen die Issues als Dateien in `docs/issues/` (#030).
|
||||||
|
3. Private Keys ausrollen: **nur Admin**.
|
||||||
|
4. Self-Service: Jeder angemeldete Benutzer darf eigene Public Keys hinterlegen; wirksam erst nach Zuweisung durch Operator/Admin.
|
||||||
|
5. Notfall-Admin: Passwort + verpflichtendes TOTP.
|
||||||
|
6. Passphrasen werden nie gespeichert; beim Ausrollen eines passphrasengeschützten Keys erscheint eine Warnung.
|
||||||
|
7. authentik vorerst über einen Mock-OIDC-Anbieter; echte Anbindung später.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# #001 Projekt-Setup
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.1.0
|
||||||
|
**Labels:** infrastruktur
|
||||||
|
**Geschätzt:** 3 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Lauffähiges Grundgerüst: Next.js 16, TypeScript strict, Prisma 7, pnpm, ESLint/Prettier/Vitest, Commit-Prüfung, Docker-Compose für die Entwicklungsdatenbank.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Next.js 16 (App Router), React 19, TypeScript strict
|
||||||
|
- [ ] pnpm 12, gepinnte Versionen, `.nvmrc`
|
||||||
|
- [ ] ESLint 9, Prettier, Vitest, commitlint + husky
|
||||||
|
- [ ] `docker-compose.dev.yml` mit PostgreSQL
|
||||||
|
- [ ] `.env.example` mit Präfix `SSHKV_`
|
||||||
|
- [ ] `CLAUDE.md` mit Konventionen
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] `pnpm lint && pnpm typecheck && pnpm test && pnpm build` grün
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
–
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# #002 Design-System und Layout
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.1.0
|
||||||
|
**Labels:** ui
|
||||||
|
**Geschätzt:** 4 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Design laut Vorgabe (Tresor, Graphit + Messing): Tokens, Schriften, Logo, Favicon/PWA-Icons, Grundlayout mit Seitenleiste.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Design-Tokens als CSS-Variablen + Tailwind-Theme
|
||||||
|
- [ ] Space Grotesk, IBM Plex Sans, JetBrains Mono selbst gehostet (`@fontsource`)
|
||||||
|
- [ ] `public/brand/` mit `logo.svg`, `logo-light.svg`, `logo-mono.svg`, `favicon.svg`; PNG-Icons + Manifest per Skript
|
||||||
|
- [ ] Seitenleiste (Logo, Navigation, Benutzer unten), einklappbar auf schmalen Bildschirmen
|
||||||
|
- [ ] Grundkomponenten: Karte, Button (primär/sekundär/Gefahr), Badge, Statuspunkt, Tabelle, Diff-Box, Kennzahl-Kachel
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] Kontrast ≥ 4,5:1, Fokusrahmen in Messing
|
||||||
|
- [ ] Kein horizontales Scrollen bei 390 px
|
||||||
|
- [ ] Klickflächen ≥ 44 px
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#001
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# #003 Datenmodell und Migrationen
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.1.0
|
||||||
|
**Labels:** datenbank
|
||||||
|
**Geschätzt:** 3 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Prisma-Schema für alle Phasen (Hosts, Gruppen, Personen, Schlüssel, Verwendungen, Host-Keys, Vorgänge, Protokoll), erste Migration, Seed.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Schema mit deutschen Bezeichnern (siehe `CLAUDE.md`)
|
||||||
|
- [ ] Datenbankregeln (CHECK, Trigger) für das, was auch an der Service-Schicht vorbei gelten muss
|
||||||
|
- [ ] Seed: Notfall-Admin, eingebaute Vorlagen, optional Demo-Daten
|
||||||
|
- [ ] Test-Datenbank-Setup für Vitest
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] Migration läuft auf leerer Datenbank
|
||||||
|
- [ ] Tests für die Datenbankregeln
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#001
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# #004 Anmeldung, Rollen und erneute Bestätigung
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.1.0
|
||||||
|
**Labels:** sicherheit
|
||||||
|
**Geschätzt:** 5 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Anmeldung per OIDC (authentik, in der Entwicklung Mock-Anbieter) und lokaler Notfall-Admin mit Passwort + TOTP. Rollen Admin/Operator/Leser, Re-Auth für kritische Aktionen.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Sitzungs-Cookie (`jose`), OIDC mit PKCE (`openid-client`)
|
||||||
|
- [ ] Rollen aus authentik-Gruppen, bei jeder Anmeldung neu
|
||||||
|
- [ ] Notfall-Admin: argon2id + TOTP (Pflicht), Einrichtung beim ersten Login
|
||||||
|
- [ ] Re-Auth: OIDC `prompt=login`/`max_age=0` + `auth_time`, Notfall-Admin Passwort + TOTP; Gültigkeit 5 min
|
||||||
|
- [ ] Rechtetabelle `darf()`, jede Seite/Route prüft selbst; struktureller Test
|
||||||
|
- [ ] Rate-Limit für Anmeldung und Re-Auth
|
||||||
|
- [ ] Mock-OIDC-Anbieter in `docker-compose.dev.yml`
|
||||||
|
- [ ] Proxy mit CSP-Nonce und Sicherheits-Kopfzeilen
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] Ohne Anmeldung keine Seite erreichbar
|
||||||
|
- [ ] Operator sieht keine Private-Key-Funktionen
|
||||||
|
- [ ] Re-Auth älter als 5 min wird abgelehnt
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#003
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# #005 Revisionssicheres Protokoll
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.1.0
|
||||||
|
**Labels:** sicherheit
|
||||||
|
**Geschätzt:** 2 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Jede Aktion landet im Protokoll; Einträge sind unveränderlich und über eine Hash-Kette verbunden.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Modell `Protokoll` mit Akteur, Aktion, Host, Zielbenutzer, Fingerprint, Diff, Ergebnis
|
||||||
|
- [ ] DB-Trigger verbietet UPDATE/DELETE
|
||||||
|
- [ ] Hash-Kette (SHA-256 über Vorgänger + Inhalt), Prüffunktion
|
||||||
|
- [ ] Redaction: Private Keys/Passwörter können nicht hineingeraten
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] Test: UPDATE/DELETE schlägt fehl
|
||||||
|
- [ ] Test: manipulierte Zeile wird von der Prüfung gefunden
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#003
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# #006 Verschlüsselte Ablage und OpenSSH-Schlüsselformat
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.1.0
|
||||||
|
**Labels:** sicherheit
|
||||||
|
**Geschätzt:** 4 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Envelope Encryption für Private Keys mit Master-Key aus Datei; eigene Erzeugung und Serialisierung von OpenSSH-Schlüsseln ohne temporäre Dateien; Deployment-Key der Instanz.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Master-Key aus `SSHKV_MASTER_KEY_FILE` (32 Byte), nur im Worker
|
||||||
|
- [ ] AES-256-GCM, DEK je Schlüssel, AAD = Schlüssel-ID + Fingerprint, Master-Key-Version
|
||||||
|
- [ ] ed25519 und RSA-4096 erzeugen (Node `crypto`), OpenSSH-Format schreiben und lesen (optional Passphrase: bcrypt-pbkdf + aes256-ctr)
|
||||||
|
- [ ] Fingerprint SHA256 wie `ssh-keygen -l`
|
||||||
|
- [ ] Deployment-Key beim ersten Start erzeugen
|
||||||
|
- [ ] Log-Redaction
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] `ssh-keygen -y -f` liest erzeugte Schlüssel (mit und ohne Passphrase)
|
||||||
|
- [ ] Fingerprints identisch zu `ssh-keygen -lf`
|
||||||
|
- [ ] Datenbank-Dump enthält keinen Klartext-Schlüssel
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#003
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# #007 Worker und Vorgänge
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.1.0
|
||||||
|
**Labels:** backend
|
||||||
|
**Geschätzt:** 3 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Eigener Worker-Prozess mit pg-boss; die Oberfläche legt Vorgänge an und zeigt deren Fortschritt.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Worker-Einstieg, gebündelt per esbuild
|
||||||
|
- [ ] pg-boss-Queues je Vorgangsart, Zeitpläne
|
||||||
|
- [ ] Modell `Vorgang` für Status/Ergebnis in der Oberfläche
|
||||||
|
- [ ] Docker-Compose produktiv: web, worker, db, migrate; Master-Key nur im Worker
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] Vorgang wird im Web angelegt, im Worker ausgeführt, Ergebnis sichtbar
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#003
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# #008 Hosts und Hostgruppen
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.1.0
|
||||||
|
**Labels:** backend, ui
|
||||||
|
**Geschätzt:** 3 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Hosts und Hostgruppen anlegen, bearbeiten, archivieren; Kunden und Tags.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Liste mit Status (erreichbar, letzter Abgleich, Drift)
|
||||||
|
- [ ] Formular: Name, Adresse, Port, Gruppen, Tags, Beschreibung, Kunde, Anmeldebenutzer (`sshkv`/`root`)
|
||||||
|
- [ ] Hostgruppen mit Mitgliedern
|
||||||
|
- [ ] Hostdetail-Gerüst
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] Leser sieht, Operator/Admin bearbeiten
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#004
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# #009 Host-Key-Prüfung (TOFU) und Erreichbarkeit
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.1.0
|
||||||
|
**Labels:** sicherheit
|
||||||
|
**Geschätzt:** 3 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Beim ersten Kontakt wird der Host-Key angezeigt und bestätigt; danach strikte Prüfung, Änderung = Warnung.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Vorgang „Host-Key abrufen“ (ohne Anmeldung)
|
||||||
|
- [ ] Bestätigen/Ablehnen mit Protokolleintrag
|
||||||
|
- [ ] `hostVerifier` in ssh2 gegen gespeicherten Key
|
||||||
|
- [ ] Geänderter Host-Key: Status GEAENDERT, keine Verbindung, deutliche Warnung
|
||||||
|
- [ ] Erreichbarkeitsprüfung
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] Integrationstest: geänderter Host-Key wird abgelehnt
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#006, #007
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# #010 Bootstrap neuer Hosts
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.1.0
|
||||||
|
**Labels:** backend, ui
|
||||||
|
**Geschätzt:** 4 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Deployment-Key auf neuen Host bringen: einmalige Passwortanmeldung (nie gespeichert) oder Copy-Paste-Befehl. Dienstbenutzer `sshkv` + sudoers.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Einmal-Passwort nur im Arbeitsspeicher des Workers, verschlüsselt im Job, danach gelöscht
|
||||||
|
- [ ] Bootstrap-Skript: Benutzer `sshkv`, `/etc/sudoers.d/sshkv` (geprüft mit `visudo -c`), Deployment-Key in verwaltetem Block
|
||||||
|
- [ ] Alternative: Befehl zum Kopieren
|
||||||
|
- [ ] Prüfung nach Bootstrap: Anmeldung per Schlüssel, sudo ohne Passwort
|
||||||
|
- [ ] sshd-Check: `PermitRootLogin`, `PasswordAuthentication` (nur Anzeige)
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] Integrationstest gegen Test-Container: Bootstrap per Passwort und per Befehl
|
||||||
|
- [ ] Passwort taucht nirgends in DB, Logs oder Protokoll auf
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#009
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# #011 Test-SSH-Container und Integrationstests
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.1.0
|
||||||
|
**Labels:** test
|
||||||
|
**Geschätzt:** 2 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Debian-Container mit sshd für Integrationstests (Bootstrap, Host-Key, später Ausrollen).
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Dockerfile `tests/ssh-ziel/` (Debian 13, sshd, sudo, Passwort für root/test)
|
||||||
|
- [ ] Compose-Datei für Tests, zwei Ziele (für Host-zu-Host)
|
||||||
|
- [ ] Vitest-Projekt `integration`
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] `pnpm test:integration` läuft lokal
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#001
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# #012 Schlüssel erzeugen, importieren, exportieren
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.2.0
|
||||||
|
**Labels:** backend, ui
|
||||||
|
**Geschätzt:** 4 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Schlüsselliste und -formular: erzeugen (ed25519, rsa-4096), importieren (Public oder Paar), exportieren; Kategorien, Tags, Ablaufdatum.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Liste mit Filter
|
||||||
|
- [ ] Erzeugen-Dialog
|
||||||
|
- [ ] Import mit Prüfung
|
||||||
|
- [ ] Export Public Key
|
||||||
|
- [ ] Schlüsseldetail-Gerüst mit Verwendungen
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] Private Keys erscheinen nie in Listen-Antworten
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#006
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# #013 Personen und Self-Service
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.2.0
|
||||||
|
**Labels:** backend, ui
|
||||||
|
**Geschätzt:** 2 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Personen verwalten; jeder angemeldete Benutzer kann eigene Public Keys hinterlegen, wirksam erst nach Zuweisung.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Personenliste/-detail
|
||||||
|
- [ ] Seite „Meine Schlüssel“
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] Leser kann nur eigene Keys hinterlegen, nicht zuweisen
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#012
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# #014 Private Key anzeigen und herunterladen
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.2.0
|
||||||
|
**Labels:** sicherheit
|
||||||
|
**Geschätzt:** 2 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Nur Admin, nur nach Re-Auth, immer protokolliert; Entschlüsselung im Worker.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Einmal-Abholung
|
||||||
|
- [ ] Option „Private Key nach Ausrollen löschen“
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] Kein Caching, kein Logging
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#012, #004
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# #015 authorized_keys: Parser und Merge
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.2.0
|
||||||
|
**Labels:** backend, test
|
||||||
|
**Geschätzt:** 3 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Reine Funktionen zum Lesen und Schreiben von `authorized_keys` mit verwaltetem Block.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Optionen mit Anführungszeichen
|
||||||
|
- [ ] CRLF, BOM, Kommentare, Duplikate
|
||||||
|
- [ ] Block erzeugen/ersetzen, Rest bytegenau
|
||||||
|
- [ ] Aussperrschutz Deployment-Key
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] Umfangreiche Unit-Tests der Randfälle
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#001
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# #016 Public Key ausrollen mit Dry-Run und Backup
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.2.0
|
||||||
|
**Labels:** backend, ui
|
||||||
|
**Geschätzt:** 4 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Zuweisungen Schlüssel × Host/Gruppe × Benutzer; Dry-Run-Diff, Backup, atomares Schreiben, Rechte.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Remote-Skript
|
||||||
|
- [ ] Optimistische Sperre über Datei-Hash
|
||||||
|
- [ ] Diff-Box im Schlüsseldetail
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] Integrationstest Ausrollen/Entfernen
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#015, #010
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# #017 Private Key ausrollen und ~/.ssh/config
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.3.0
|
||||||
|
**Labels:** backend, ui
|
||||||
|
**Geschätzt:** 4 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Private Key auf Zielhost/-benutzer/-pfad, verwalteter Block in `~/.ssh/config`, `known_hosts`.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Übertragung nur über stdin
|
||||||
|
- [ ] Warnung bei Passphrase
|
||||||
|
- [ ] nur Admin
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] Integrationstest
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#016
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# #018 Zielbenutzer anlegen und sudoers
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.3.0
|
||||||
|
**Labels:** backend, sicherheit
|
||||||
|
**Geschätzt:** 3 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Benutzer (z. B. `deploy`) anlegen, optional sudoers mit Vorschau und `visudo -c`.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Auswahl keine/bestimmte Befehle/voll
|
||||||
|
- [ ] Entfernen beim Widerruf
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] Integrationstest
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#016
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# #019 Vorlagen
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.3.0
|
||||||
|
**Labels:** backend, ui
|
||||||
|
**Geschätzt:** 2 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Eingebaute Vorlagen „Deployment-Zugang“, „Backup-Zugang“, „Admin-Zugang Person“; eigene in den Einstellungen.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Platzhalter `{{quellHostIp}}` usw.
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] Vorlage füllt Assistent vor
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#017, #018
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# #020 Host-zu-Host-Assistent und Verbindungstest
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.3.0
|
||||||
|
**Labels:** ui, backend
|
||||||
|
**Geschätzt:** 4 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Geführter Ablauf: Schlüssel erzeugen, Private auf A, Public auf B, Host-Key von B in known_hosts von A, Verbindungstest.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Schrittanzeige
|
||||||
|
- [ ] Zusammenfassung + Dry-Run
|
||||||
|
- [ ] `ssh -o BatchMode=yes … true` auf A
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] Kern-Anwendungsfall Ende-zu-Ende gegen zwei Test-Container
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#019
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# #021 Abgleich und Drift-Erkennung
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.4.0
|
||||||
|
**Labels:** backend
|
||||||
|
**Geschätzt:** 3 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Periodischer und manueller Abgleich, Befunde je Host/Benutzer.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] Zeitplan konfigurierbar
|
||||||
|
- [ ] Befundarten
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] Integrationstest
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#016
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# #022 Import bestehender Schlüssel
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.4.0
|
||||||
|
**Labels:** backend, ui
|
||||||
|
**Geschätzt:** 2 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Unbekannte Schlüssel eines Hosts übernehmen und zuordnen.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] wird zu Beginn der Phase verfeinert
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] wird zu Beginn der Phase verfeinert
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#021
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# #023 Geführte Rotation
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.4.0
|
||||||
|
**Labels:** backend, ui
|
||||||
|
**Geschätzt:** 3 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Neuer Schlüssel an allen Verwendungen, Test, alter entfernt.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] wird zu Beginn der Phase verfeinert
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] wird zu Beginn der Phase verfeinert
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#020
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# #024 Widerruf und Massenwiderruf
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.4.0
|
||||||
|
**Labels:** backend, ui, sicherheit
|
||||||
|
**Geschätzt:** 3 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Ein-Klick-Widerruf inkl. Benutzer/sudoers; Massenwiderruf mit Re-Auth.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] wird zu Beginn der Phase verfeinert
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] wird zu Beginn der Phase verfeinert
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#018
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# #025 Zuweisungsmatrix
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.4.0
|
||||||
|
**Labels:** ui
|
||||||
|
**Geschätzt:** 2 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Matrix Schlüssel × Host.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] wird zu Beginn der Phase verfeinert
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] wird zu Beginn der Phase verfeinert
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#016
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# #026 Übersicht (Dashboard)
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.5.0
|
||||||
|
**Labels:** ui
|
||||||
|
**Geschätzt:** 2 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Kennzahlen, Hosts mit Abweichung, bald ablaufend, letzte Aktionen.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] wird zu Beginn der Phase verfeinert
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] wird zu Beginn der Phase verfeinert
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#021
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# #027 Protokollansicht, CSV-Export, Syslog
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.5.0
|
||||||
|
**Labels:** ui, backend
|
||||||
|
**Geschätzt:** 2 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Filterbare Ansicht, CSV, optionale Weiterleitung an Syslog.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] wird zu Beginn der Phase verfeinert
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] wird zu Beginn der Phase verfeinert
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#005
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# #028 Benachrichtigungen
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.5.0
|
||||||
|
**Labels:** backend
|
||||||
|
**Geschätzt:** 2 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
E-Mail (M365 SMTP) und Webhook bei Drift und Ablauf.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] wird zu Beginn der Phase verfeinert
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] wird zu Beginn der Phase verfeinert
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
#021
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# #029 Handbuch und Betriebsdokumentation
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.5.0
|
||||||
|
**Labels:** docs
|
||||||
|
**Geschätzt:** 3 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Installation, Bootstrap, Kern-Anwendungsfall mit Bildern; Bedrohungsanalyse, Backup-Konzept.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] wird zu Beginn der Phase verfeinert
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] wird zu Beginn der Phase verfeinert
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
–
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# #030 Issues nach Gitea übertragen
|
||||||
|
**Status:** offen
|
||||||
|
**Meilenstein:** v0.1.0
|
||||||
|
**Labels:** chore
|
||||||
|
**Geschätzt:** 1 h **Tatsächlich:** –
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
Issue-Dateien, Labels und Meilensteine in Gitea anlegen (wartet auf Zugangsdaten).
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
- [ ] `scripts/gitea-issues.ts`
|
||||||
|
|
||||||
|
## Akzeptanzkriterien
|
||||||
|
- [ ] wird zu Beginn der Phase verfeinert
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
–
|
||||||
|
|
||||||
|
## Notizen / Entscheidungen
|
||||||
Reference in New Issue
Block a user