Merge branch 'phase/029-handbuch'
# Conflicts: # src/worker/index.ts
This commit is contained in:
@@ -18,7 +18,15 @@ SSHKeyVault hat **root-Zugriff auf alle verwalteten Hosts**. Deshalb:
|
||||
- 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.
|
||||
Mehr dazu in [`docs/betrieb/sicherheit.md`](docs/betrieb/sicherheit.md) (mit Bedrohungsanalyse und
|
||||
Notfallplan) und [`docs/betrieb/sicherung.md`](docs/betrieb/sicherung.md).
|
||||
|
||||
## Dokumentation
|
||||
|
||||
- [Handbuch](docs/anleitung/README.md) – Bedienung Schritt für Schritt mit Bildern
|
||||
- [Installation und Betrieb](docs/betrieb/installation.md) – Docker Compose, authentik, Reverse Proxy
|
||||
- [Sicherung und Wiederherstellung](docs/betrieb/sicherung.md) – `deploy/sichern.sh`, `deploy/wiederherstellen.sh`
|
||||
- [Sicherheit](docs/betrieb/sicherheit.md) – Aufbau, Bedrohungsanalyse, Notfallplan
|
||||
|
||||
## Installation (Docker Compose)
|
||||
|
||||
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env bash
|
||||
# SSHKeyVault sichern (#029).
|
||||
#
|
||||
# bash deploy/sichern.sh # nach /var/backups/sshkv
|
||||
# bash deploy/sichern.sh /mnt/sicherung/sshkv # anderes Ziel
|
||||
#
|
||||
# Sichert die Datenbank (pg_dump mit --clean, damit sie sich auch in eine nicht leere
|
||||
# Datenbank zurueckspielen laesst) und legt einen Pruefwert des Master-Keys daneben.
|
||||
#
|
||||
# Den MASTER-KEY selbst sichert dieses Skript absichtlich NICHT mit: Datenbank und Master-Key
|
||||
# zusammen geben alle Private Keys preis. Den Master-Key einmalig und GETRENNT aufbewahren
|
||||
# (Passwortmanager, Tresor, zweites Medium) – ohne ihn ist jede Sicherung wertlos.
|
||||
# Siehe docs/betrieb/sicherung.md.
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
VERZEICHNIS=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
cd "$VERZEICHNIS"
|
||||
|
||||
gruen() { printf '\033[1;32m✓ %s\033[0m\n' "$*"; }
|
||||
gelb() { printf '\033[1;33m! %s\033[0m\n' "$*"; }
|
||||
fehler() {
|
||||
printf '\033[1;31m✗ %s\033[0m\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
ZIEL="${1:-/var/backups/sshkv}"
|
||||
BEHALTEN="${SSHKV_SICHERUNGEN_BEHALTEN:-14}"
|
||||
STAND=$(date +%Y%m%d-%H%M%S)
|
||||
|
||||
mkdir -p "$ZIEL"
|
||||
chmod 0700 "$ZIEL"
|
||||
|
||||
# Datenbank: als Klartext-SQL, gzip-komprimiert. Enthaelt nur verschluesselte Private Keys.
|
||||
# Nur das Schema public: Die Warteschlange (Schema pgboss) legt der Worker beim Start neu an,
|
||||
# und ihre partitionierten Tabellen lassen sich mit --clean nicht zurueckspielen (in der
|
||||
# Wiederherstellungsprobe aufgefallen).
|
||||
docker compose exec -T db pg_dump -U sshkv --clean --if-exists --no-owner --schema=public sshkv \
|
||||
| gzip -9 > "${ZIEL}/datenbank-${STAND}.sql.gz" \
|
||||
|| fehler "pg_dump ist fehlgeschlagen. Läuft der Dienst db?"
|
||||
[ -s "${ZIEL}/datenbank-${STAND}.sql.gz" ] || fehler "Die Sicherung ist leer."
|
||||
chmod 0600 "${ZIEL}/datenbank-${STAND}.sql.gz"
|
||||
gruen "Datenbank gesichert: ${ZIEL}/datenbank-${STAND}.sql.gz"
|
||||
|
||||
# Pruefwert des Master-Keys: verraet nichts ueber den Schluessel, zeigt beim Zurueckspielen
|
||||
# aber, ob der richtige Master-Key bereitliegt.
|
||||
ENV_DATEI="${SSHKV_ENV_DATEI:-.env}"
|
||||
SCHLUESSEL="$(grep -E '^SSHKV_MASTER_KEY_HOST_FILE=' "$ENV_DATEI" 2>/dev/null | cut -d= -f2- || true)"
|
||||
SCHLUESSEL="${SCHLUESSEL:-./geheim/master.key}"
|
||||
if [ -r "$SCHLUESSEL" ]; then
|
||||
sha256sum "$SCHLUESSEL" | cut -d' ' -f1 > "${ZIEL}/master-key-pruefwert-${STAND}.txt"
|
||||
chmod 0600 "${ZIEL}/master-key-pruefwert-${STAND}.txt"
|
||||
gruen "Prüfwert des Master-Keys abgelegt (nicht der Schlüssel selbst)."
|
||||
else
|
||||
gelb "Master-Key ${SCHLUESSEL} nicht lesbar – kein Prüfwert abgelegt (mit sudo ausführen?)."
|
||||
fi
|
||||
|
||||
# Alte Sicherungen aufraeumen (samt Pruefwert).
|
||||
ls -1t "${ZIEL}"/datenbank-*.sql.gz 2>/dev/null | tail -n +"$((BEHALTEN + 1))" | while read -r alt; do
|
||||
alter_stand=$(basename "$alt" .sql.gz)
|
||||
alter_stand=${alter_stand#datenbank-}
|
||||
rm -f -- "$alt" "${ZIEL}/master-key-pruefwert-${alter_stand}.txt"
|
||||
done
|
||||
|
||||
gelb "Den Master-Key getrennt aufbewahren – er gehört NICHT neben diese Sicherung."
|
||||
Executable
+80
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env bash
|
||||
# SSHKeyVault aus einer Sicherung wiederherstellen (#029).
|
||||
#
|
||||
# bash deploy/wiederherstellen.sh /var/backups/sshkv # jüngste Sicherung
|
||||
# bash deploy/wiederherstellen.sh /var/backups/sshkv 20261012-170000
|
||||
# bash deploy/wiederherstellen.sh … --ja # ohne Rückfrage
|
||||
#
|
||||
# Voraussetzung: Der zur Sicherung gehoerende Master-Key liegt an der in .env genannten Stelle
|
||||
# (SSHKV_MASTER_KEY_HOST_FILE). Das Skript vergleicht ihn mit dem Pruefwert der Sicherung und
|
||||
# bricht ab, wenn er nicht passt – sonst laeuft sshkv ohne lesbare Private Keys an.
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
VERZEICHNIS=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
cd "$VERZEICHNIS"
|
||||
|
||||
blau() { printf '\033[1;36m› %s\033[0m\n' "$*"; }
|
||||
gruen() { printf '\033[1;32m✓ %s\033[0m\n' "$*"; }
|
||||
fehler() {
|
||||
printf '\033[1;31m✗ %s\033[0m\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
QUELLE="${1:-/var/backups/sshkv}"
|
||||
STAND=""
|
||||
OHNE_RUECKFRAGE="nein"
|
||||
for argument in "${@:2}"; do
|
||||
if [ "$argument" = "--ja" ]; then OHNE_RUECKFRAGE="ja"; else STAND="$argument"; fi
|
||||
done
|
||||
|
||||
[ -d "$QUELLE" ] || fehler "Kein Sicherungsordner unter ${QUELLE}."
|
||||
if [ -z "$STAND" ]; then
|
||||
DATENBANK=$(ls -1t "${QUELLE}"/datenbank-*.sql.gz 2>/dev/null | head -n1 || true)
|
||||
else
|
||||
DATENBANK="${QUELLE}/datenbank-${STAND}.sql.gz"
|
||||
fi
|
||||
[ -n "${DATENBANK:-}" ] && [ -f "$DATENBANK" ] || fehler "Keine Datenbanksicherung in ${QUELLE} gefunden."
|
||||
PRUEFWERT="${DATENBANK/datenbank-/master-key-pruefwert-}"
|
||||
PRUEFWERT="${PRUEFWERT/.sql.gz/.txt}"
|
||||
|
||||
ENV_DATEI="${SSHKV_ENV_DATEI:-.env}"
|
||||
SCHLUESSEL="$(grep -E '^SSHKV_MASTER_KEY_HOST_FILE=' "$ENV_DATEI" 2>/dev/null | cut -d= -f2- || true)"
|
||||
SCHLUESSEL="${SCHLUESSEL:-./geheim/master.key}"
|
||||
if [ -f "$PRUEFWERT" ]; then
|
||||
[ -r "$SCHLUESSEL" ] || fehler "Master-Key ${SCHLUESSEL} nicht lesbar (mit sudo ausführen?)."
|
||||
[ "$(sha256sum "$SCHLUESSEL" | cut -d' ' -f1)" = "$(cat "$PRUEFWERT")" ] \
|
||||
|| fehler "Der Master-Key unter ${SCHLUESSEL} gehört NICHT zu dieser Sicherung. Abbruch."
|
||||
gruen "Master-Key passt zur Sicherung."
|
||||
else
|
||||
printf '\033[1;33m! Kein Prüfwert zur Sicherung – der Worker prüft den Master-Key beim Start.\033[0m\n'
|
||||
fi
|
||||
|
||||
printf '\033[1;33m! Achtung: Der aktuelle Datenbestand wird durch die Sicherung ersetzt.\033[0m\n'
|
||||
echo " Sicherung: $(basename "$DATENBANK")"
|
||||
if [ "$OHNE_RUECKFRAGE" != "ja" ]; then
|
||||
read -r -p "Wirklich zurückspielen? [tippen Sie ja] " antwort
|
||||
[ "$antwort" = "ja" ] || fehler "Abgebrochen."
|
||||
fi
|
||||
|
||||
blau "Weboberfläche und Worker anhalten …"
|
||||
docker compose stop web worker
|
||||
blau "Datenbank zurückspielen …"
|
||||
# Eine Transaktion: scheitert etwas, bleibt der alte Stand vollstaendig erhalten (in der
|
||||
# Wiederherstellungsprobe hatte ein Abbruch ohne Transaktion das Schema pgboss halb geloescht).
|
||||
gunzip -c "$DATENBANK" | docker compose exec -T db psql -U sshkv -d sshkv -v ON_ERROR_STOP=1 --single-transaction -q >/dev/null \
|
||||
|| fehler "Zurückspielen fehlgeschlagen. Web und Worker sind angehalten – nach Klärung: docker compose up -d"
|
||||
# Die Warteschlange gehoert nicht zur Sicherung: frisch anlegen lassen (macht der Worker beim
|
||||
# Start), offene Vorgaenge abbrechen – ihre Jobs sind damit weg.
|
||||
docker compose exec -T db psql -U sshkv -d sshkv -q -c "DROP SCHEMA IF EXISTS pgboss CASCADE" >/dev/null
|
||||
docker compose exec -T db psql -U sshkv -d sshkv -q -c \
|
||||
"UPDATE \"Vorgang\" SET status = 'ABGEBROCHEN', fehler = 'Durch Wiederherstellung abgebrochen.', \"beendetAm\" = now() WHERE status IN ('WARTEND','LAEUFT')" >/dev/null
|
||||
blau "Dienste starten …"
|
||||
docker compose up -d --wait web worker
|
||||
sleep 3
|
||||
if docker compose logs --tail 20 worker 2>&1 | grep -q "passt nicht zur Datenbank"; then
|
||||
fehler "Der Worker meldet: Master-Key passt nicht zur Datenbank. Siehe docker compose logs worker."
|
||||
fi
|
||||
gruen "Wiederhergestellt aus $(basename "$DATENBANK")."
|
||||
@@ -0,0 +1,216 @@
|
||||
# SSHKeyVault – Handbuch
|
||||
|
||||
SSHKeyVault (`sshkv`) verwaltet SSH-Schlüssel zentral: Schlüsselpaare erzeugen, Public Keys in
|
||||
`authorized_keys` verteilen, Private Keys gezielt auf andere Hosts legen, Ist und Soll abgleichen –
|
||||
alles protokolliert. Installation und Betrieb: [../betrieb/installation.md](../betrieb/installation.md),
|
||||
Sicherung: [../betrieb/sicherung.md](../betrieb/sicherung.md), Sicherheit:
|
||||
[../betrieb/sicherheit.md](../betrieb/sicherheit.md).
|
||||
|
||||
**Grundsatz:** Auf einem Host ändert sich nur etwas, nachdem Sie einen **Dry-Run** gesehen und
|
||||
„Änderung anwenden“ geklickt haben. Alles, was sshkv in `authorized_keys`, `~/.ssh/config` und
|
||||
`known_hosts` schreibt, steht in einem verwalteten Block:
|
||||
|
||||
```
|
||||
# BEGIN SSHKV MANAGED – nicht manuell bearbeiten
|
||||
…
|
||||
# END SSHKV MANAGED
|
||||
```
|
||||
|
||||
Zeilen außerhalb dieses Blocks bleiben unverändert – außer Sie übernehmen einen Schlüssel ausdrücklich
|
||||
in die Verwaltung.
|
||||
|
||||
1. [Anmelden](#1-anmelden)
|
||||
2. [Übersicht](#2-übersicht)
|
||||
3. [Einen Host aufnehmen](#3-einen-host-aufnehmen)
|
||||
4. [Kern-Anwendungsfall: Host A meldet sich bei Host B an](#4-kern-anwendungsfall-host-a-meldet-sich-bei-host-b-an)
|
||||
5. [Deployment-Zugang für Claude Code mit dem Assistenten](#5-deployment-zugang-für-claude-code-mit-dem-assistenten)
|
||||
6. [Abgleich, Drift und Übernahme](#6-abgleich-drift-und-übernahme)
|
||||
7. [Rotation und Widerruf](#7-rotation-und-widerruf)
|
||||
8. [Personen, Self-Service und Zuweisungsmatrix](#8-personen-self-service-und-zuweisungsmatrix)
|
||||
9. [Vorlagen](#9-vorlagen)
|
||||
10. [Private Key anzeigen oder löschen](#10-private-key-anzeigen-oder-löschen)
|
||||
11. [Audit-Log](#11-audit-log)
|
||||
|
||||
---
|
||||
|
||||
## 1. Anmelden
|
||||
|
||||

|
||||
|
||||
- **Mit authentik anmelden** – der Normalfall. Ihre Rolle (Leser, Operator, Admin) kommt aus den
|
||||
authentik-Gruppen und wird bei jeder Anmeldung neu bestimmt.
|
||||
- **Notfallzugang** – nur, wenn authentik ausfällt. Anmeldename, Passwort und ein Code aus einer
|
||||
Authenticator-App. Bei der ersten Anmeldung richtet der Notfall-Admin den zweiten Faktor ein
|
||||
(QR-Code scannen, Code bestätigen).
|
||||
|
||||
Kritische Aktionen (Private Key anzeigen, Benutzer mit sudo anlegen, Massenwiderruf, Host-Key
|
||||
ersetzen) verlangen eine **erneute Bestätigung**; sie gilt fünf Minuten.
|
||||
|
||||
## 2. Übersicht
|
||||
|
||||

|
||||
|
||||
Kennzahlen, Hosts mit Abweichung, bald ablaufende Schlüssel und die letzten Aktionen. Von hier aus:
|
||||
**Jetzt abgleichen** (alle Hosts), **Host-zu-Host-Assistent**, **Neuer Schlüssel**.
|
||||
|
||||
## 3. Einen Host aufnehmen
|
||||
|
||||
**Hosts → + Host anlegen:** Name (z. B. `srv-pve01`), Adresse, Port, Gruppen, Tags. Bei
|
||||
**Anmeldung** ist „Dienstbenutzer `sshkv` mit sudo“ empfohlen – im auth.log des Hosts ist dann
|
||||
eindeutig zu sehen, was von sshkv kam, und root-Login kann aus bleiben.
|
||||
|
||||
**Host-Key bestätigen.** „Host-Key abrufen“ holt den Fingerprint, ohne sich anzumelden. Vergleichen
|
||||
Sie ihn direkt am Host:
|
||||
|
||||
```bash
|
||||
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
|
||||
```
|
||||
|
||||

|
||||
|
||||
Erst nach „Fingerprint stimmt – vertrauen“ verbindet sich sshkv. Ändert sich der Host-Key später,
|
||||
bricht jede Verbindung ab und die Hostseite warnt deutlich – annehmen nur, wenn der Host wirklich neu
|
||||
installiert wurde.
|
||||
|
||||
**Bootstrap.** Zwei Wege, den Deployment-Key von sshkv auf den Host zu bringen:
|
||||
|
||||
- **Einmalig per Passwort** (root oder ein Benutzer mit sudo). Das Passwort wird nicht gespeichert.
|
||||
- **Per Befehl:** den angezeigten Befehl auf dem Host einfügen, danach „Einrichtung prüfen“.
|
||||
|
||||

|
||||
|
||||
Danach zeigt die Hostseite den **sshd-Befund**. Steht dort „root darf sich per Passwort anmelden“,
|
||||
empfiehlt sich `PermitRootLogin prohibit-password` in `/etc/ssh/sshd_config` – sshkv ändert das nicht
|
||||
von sich aus.
|
||||
|
||||
## 4. Kern-Anwendungsfall: Host A meldet sich bei Host B an
|
||||
|
||||
Beispiel: Der Backup-Server `srv-backup01` soll sich als `root` per SSH auf `srv-pve01` anmelden.
|
||||
Beide Hosts sind aufgenommen (Kapitel 3).
|
||||
|
||||
**1. Schlüssel erzeugen.** *Schlüssel → + Schlüssel anlegen → Erzeugen*: Zweck „Backup srv-backup01 →
|
||||
srv-pve01“, Typ ed25519, optional Ablaufdatum. Keine Passphrase – ein Cronjob kann sie nicht eingeben.
|
||||
|
||||

|
||||
|
||||
**2a. Private Key auf A.** In der Karte *Private Key ausgerollt auf* → **+ Ausrollen**: Quell-Host
|
||||
`srv-backup01`, Benutzer `root`, Pfad `~/.ssh/id_ed25519_…`. Mit **Eintrag in ~/.ssh/config** und
|
||||
Ziel-Host `srv-pve01` legt sshkv einen Alias an und trägt den (bereits bestätigten) Host-Key von B in
|
||||
`known_hosts` von A ein. *HostName* ist die Adresse, unter der **A** den Host B erreicht.
|
||||
|
||||
**2b. Public Key auf B.** In der Karte *Public Key ausgerollt auf* → **+ Ausrollen**: Ziel
|
||||
`srv-pve01`, Benutzer `root`, optional Einschränkungen wie `from="10.0.0.12",no-pty`.
|
||||
|
||||
2a und 2b sind unabhängig und jederzeit nachholbar.
|
||||
|
||||
**3. Dry-Run und anwenden.** *Dry-Run starten* zeigt je Host, was sich ändert – beim Private Key nur
|
||||
„wird angelegt“, nie den Inhalt.
|
||||
|
||||

|
||||
|
||||
„Änderung anwenden“ schreibt genau das, was Sie gesehen haben (mit Sicherung der Dateien). Hat sich
|
||||
in der Zwischenzeit etwas auf dem Host geändert, bricht sshkv ab – dann den Dry-Run neu starten.
|
||||
|
||||
**4. Verbindung testen.** In der Private-Karte „Verbindung testen“ ruft auf A
|
||||
`ssh -o BatchMode=yes <alias> true` auf – genau das, was später der Cronjob braucht.
|
||||
|
||||

|
||||
|
||||
## 5. Deployment-Zugang für Claude Code mit dem Assistenten
|
||||
|
||||
Szenario: Claude Code auf `srv-rz-claude-linux` soll eine Anwendung per SSH auf `srv-app01`
|
||||
ausrollen. **Übersicht → Host-zu-Host-Assistent** (nur Admins):
|
||||
|
||||
1. **Vorlage „Deployment-Zugang“** wählen, Zweck „Claude Code Deployment → srv-app01“. Die Vorlage
|
||||
setzt Benutzer `deploy`, 90 Tage Laufzeit, `from="<IP von A>"` und den Alias `deploy-<Ziel>`.
|
||||
2. **Quelle:** `srv-rz-claude-linux`, Benutzer, unter dem Claude Code läuft.
|
||||
3. **Ziel:** `srv-app01`, Benutzer `deploy` – „Benutzer anlegen“ ist vorgewählt; sudo nach Bedarf
|
||||
(keine, bestimmte Befehle mit Vorschau der sudoers-Datei, oder voll). Fingerprint von B bestätigen.
|
||||
|
||||

|
||||
|
||||
4. **Prüfen** und „Vorbereiten“: sshkv erzeugt den Schlüssel, legt `deploy` an (frische Bestätigung
|
||||
nötig – sie öffnet sich in einem neuen Tab, Ihre Eingaben bleiben stehen) und legt die
|
||||
Zuweisungen an.
|
||||
5. **Ausrollen:** Dry-Run, anwenden, „Verbindung von A nach B testen“.
|
||||
|
||||

|
||||
|
||||
Claude Code kann danach einfach `ssh deploy-srv-app01` benutzen. Wählen Sie `root` als Ziel, warnt
|
||||
der Assistent: Ein KI-Agent hätte dann uneingeschränkten Zugriff.
|
||||
|
||||
**Nach Projektende** (oder Ablaufdatum): auf der Schlüsselseite **Widerrufen** – entfernt Public und
|
||||
Private Key, config- und known_hosts-Eintrag und auf Wunsch den Benutzer `deploy` samt sudoers-Datei.
|
||||
|
||||
## 6. Abgleich, Drift und Übernahme
|
||||
|
||||
Nachts um 02:30 (einstellbar) und auf Knopfdruck vergleicht sshkv jeden Host mit dem Soll. Gefunden
|
||||
werden fehlende Schlüssel, von Hand geänderte Blöcke, **unbekannte Schlüssel** außerhalb des Blocks,
|
||||
falsche Rechte von `~/.ssh` und veränderte Private Keys. Der Abgleich ändert auf dem Host nichts.
|
||||
|
||||

|
||||
|
||||
Ein unbekannter Schlüssel lässt sich **übernehmen**: nur als bekannt aufnehmen (der Befund
|
||||
verschwindet) oder „in den verwalteten Block holen“ – dann wandert die Zeile beim nächsten Ausrollen
|
||||
(mit Dry-Run) in den Block.
|
||||
|
||||
## 7. Rotation und Widerruf
|
||||
|
||||
**Rotieren** (Schlüsselseite): Ein neuer Schlüssel kommt an alle Stellen des alten, der Alias in
|
||||
`~/.ssh/config` zeigt auf die neue Datei, die Verbindungen werden getestet – **erst dann** wird der
|
||||
alte entfernt. Die Rotation lässt sich jederzeit fortsetzen.
|
||||
|
||||

|
||||
|
||||
**Widerrufen** (Schlüsselseite, Gefahr-Knopf): Der Dialog zeigt, was entfernt wird; danach Dry-Run
|
||||
und anwenden. Ist ein Host nicht erreichbar, bleibt der Schlüssel als „Widerruf unvollständig“
|
||||
markiert, bis auch dort alles entfernt ist. **Massenwiderruf:** in der Schlüsselliste mehrere
|
||||
auswählen (nur Admin).
|
||||
|
||||

|
||||
|
||||
## 8. Personen, Self-Service und Zuweisungsmatrix
|
||||
|
||||
- **Personen:** Admins, Mitarbeiter, Dienstleister mit ihren Schlüsseln. Private Keys von Personen
|
||||
speichert sshkv nie.
|
||||
- **Meine Schlüssel:** Jede angemeldete Person kann eigene Public Keys hinterlegen. Wirksam werden
|
||||
sie erst, wenn ein Operator oder Admin sie einem Host zuweist.
|
||||
|
||||

|
||||
|
||||
- **Zuweisungen:** Matrix Schlüssel × Host mit Filtern; ein Klick auf eine Zelle zeigt die
|
||||
Zuweisungen und erlaubt neue. Export als CSV.
|
||||
|
||||

|
||||
|
||||
## 9. Vorlagen
|
||||
|
||||
Vorlagen legen Schlüsseltyp, Laufzeit, Zielbenutzer (samt Anlegen und sudo), `authorized_keys`-
|
||||
Optionen und das Alias-Muster vor. Platzhalter: `{{quellHost}}`, `{{quellHostIp}}`, `{{zielHost}}`,
|
||||
`{{zielHostIp}}`. Eingebaut: „Deployment-Zugang“, „Backup-Zugang“, „Admin-Zugang Person“.
|
||||
|
||||

|
||||
|
||||
## 10. Private Key anzeigen oder löschen
|
||||
|
||||
Nur Admins, nur nach erneuter Bestätigung: „anzeigen (Re-Auth)“ in der Info-Leiste eines Schlüssels.
|
||||
Der Key wird genau einmal ausgeliefert und nirgends zwischengespeichert; jede Anzeige steht im
|
||||
Protokoll. **Private Key löschen** entfernt ihn endgültig aus sshkv – auf den Hosts bleibt er; neu
|
||||
ausrollen geht danach nicht mehr.
|
||||
|
||||
## 11. Audit-Log
|
||||
|
||||
Jede Aktion steht im **Audit-Log**: Zeitpunkt, Benutzer, Host, Zielbenutzer, Fingerprint, das Diff
|
||||
der geänderten Datei und das Ergebnis – nie ein Private Key oder Passwort. Einträge lassen sich
|
||||
weder ändern noch löschen; jeder Eintrag ist per Hash mit seinem Vorgänger verkettet.
|
||||
|
||||

|
||||
|
||||
- **Filter** nach Zeitraum, Aktionsgruppe, Benutzer, Host, Ergebnis und Freitext; eine Zeile
|
||||
aufklappen zeigt Diff, Details und Hashes.
|
||||
- **Kette prüfen** rechnet alle Hashes nach. Wurde eine Zeile an sshkv vorbei in der Datenbank
|
||||
verändert, zeigt die Seite die erste betroffene Zeile.
|
||||
- **CSV-Export** mit denselben Filtern (der Export selbst wird protokolliert).
|
||||
- **Syslog:** Mit `SSHKV_SYSLOG_HOST` geht jeder Eintrag zusätzlich an einen zentralen Syslog-Server
|
||||
(z. B. für Zabbix) – eine Kopie außerhalb von sshkv.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 151 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
@@ -0,0 +1,113 @@
|
||||
# Installation und Betrieb
|
||||
|
||||
SSHKeyVault läuft als Docker-Compose-Stapel aus drei Diensten plus einem einmaligen Migrationslauf:
|
||||
|
||||
| Dienst | Aufgabe | Netz | Geheimnisse |
|
||||
|---|---|---|---|
|
||||
| `db` | PostgreSQL 17 | nur intern (`internal: true`) | Datenbankpasswort |
|
||||
| `migrate` | Schema einspielen, Notfall-Admin und Vorlagen anlegen (läuft bei jedem Start einmal) | intern | Notfall-Passwort beim ersten Start |
|
||||
| `web` | Weboberfläche (Port 3400) | intern + nach außen zum OIDC-Anbieter | `SSHKV_APP_SECRET`, OIDC-Client-Secret – **kein Master-Key** |
|
||||
| `worker` | SSH zu den Hosts, Ver-/Entschlüsselung, Zeitpläne | intern + zu den Hosts | Master-Key (Docker-Secret), `SSHKV_APP_SECRET` |
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- Linux-Host mit Docker und Docker Compose v2, **im internen Netz bzw. nur per VPN erreichbar**.
|
||||
- Der Worker muss die zu verwaltenden Hosts per SSH erreichen (Port 22 oder den jeweiligen Port).
|
||||
- authentik (oder ein anderer OIDC-Anbieter) für die Anmeldung.
|
||||
- Ein Reverse Proxy mit TLS vor der Weboberfläche.
|
||||
|
||||
## Schritt für Schritt
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /opt/sshkv && cd /opt/sshkv
|
||||
git clone <repo> . # oder Release entpacken
|
||||
cp .env.example .env && chmod 600 .env
|
||||
```
|
||||
|
||||
In `.env` eintragen (Kommentare in der Datei erklären jede Variable):
|
||||
|
||||
- `POSTGRES_PASSWORD` – `openssl rand -hex 24`
|
||||
- `SSHKV_APP_SECRET` – `openssl rand -base64 48`
|
||||
- `SSHKV_APP_URL` – die Adresse, unter der Benutzer sshkv aufrufen, z. B. `https://sshkv.intern.example.de`
|
||||
- `SSHKV_BREAKGLASS_PASSWORD` – mindestens 16 Zeichen; Notfall-Admin `notfall`
|
||||
- `SSHKV_OIDC_*` – siehe unten
|
||||
|
||||
Master-Key erzeugen und nur für den Worker lesbar machen:
|
||||
|
||||
```bash
|
||||
sudo mkdir -p geheim
|
||||
sudo sh -c 'head -c 32 /dev/urandom > geheim/master.key'
|
||||
sudo chown 1001:1001 geheim/master.key && sudo chmod 400 geheim/master.key
|
||||
```
|
||||
|
||||
Starten:
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
docker compose logs -f worker # „sshkv-worker … bereit“
|
||||
```
|
||||
|
||||
Beim ersten Start legt `migrate` das Schema, den Notfall-Admin und die eingebauten Vorlagen an; der
|
||||
Worker erzeugt den **Deployment-Key** der Instanz. Danach sofort den Master-Key sichern
|
||||
([sicherung.md](sicherung.md)).
|
||||
|
||||
## authentik einrichten
|
||||
|
||||
1. **Provider** „OAuth2/OpenID Provider“ anlegen: Client-Typ *confidential*, Redirect-URI
|
||||
`https://sshkv.intern.example.de/api/auth/oidc/rueckruf`, Scopes `openid profile email` und ein
|
||||
Scope-Mapping, das die Gruppen als Claim `groups` liefert (in authentik Standard).
|
||||
2. **Application** „SSHKeyVault“ mit diesem Provider anlegen.
|
||||
3. **Gruppen** `sshkv-admins`, `sshkv-operators`, `sshkv-readers` anlegen und Benutzer zuordnen
|
||||
(Namen änderbar über `SSHKV_OIDC_GROUP_*`). Wer in keiner Gruppe ist, kommt nicht hinein.
|
||||
4. In `.env`: `SSHKV_OIDC_ISSUER=https://authentik.example.de/application/o/sshkv/`,
|
||||
`SSHKV_OIDC_CLIENT_ID`, `SSHKV_OIDC_CLIENT_SECRET`, `SSHKV_OIDC_NAME=authentik`.
|
||||
|
||||
Erneute Bestätigung vor kritischen Aktionen schickt `prompt=login` und `max_age=0`; sshkv prüft
|
||||
`auth_time` im ID-Token. authentik liefert `auth_time` standardmäßig.
|
||||
|
||||
| Rolle | darf |
|
||||
|---|---|
|
||||
| Leser | alles ansehen, eigene Public Keys hinterlegen |
|
||||
| Operator | zusätzlich Hosts verwalten, Host-Keys bestätigen, Schlüssel erzeugen, Public Keys ausrollen, abgleichen |
|
||||
| Admin | zusätzlich Private Keys ausrollen/anzeigen/löschen, Zielkonten mit sudo, Massenwiderruf, Einstellungen, Vorlagen |
|
||||
|
||||
## Reverse Proxy (Beispiel Caddy)
|
||||
|
||||
```caddyfile
|
||||
sshkv.intern.example.de {
|
||||
tls internal # oder das interne Zertifikat
|
||||
@extern not remote_ip 10.0.0.0/8 192.168.0.0/16
|
||||
respond @extern 403
|
||||
reverse_proxy 127.0.0.1:3400
|
||||
}
|
||||
```
|
||||
|
||||
Wichtig: `SSHKV_APP_URL` muss die Adresse mit `https://` sein – davon hängen Secure-Cookie und
|
||||
OIDC-Rückruf ab.
|
||||
|
||||
## Hosts vorbereiten
|
||||
|
||||
- **Debian/Ubuntu** mit `openssh-server`, `sudo`, `runuser` (util-linux), `base64`, `sha256sum`,
|
||||
`visudo` (bei Dienstbenutzer) – auf Standardinstallationen vorhanden.
|
||||
- Für den Bootstrap per Passwort einmalig ein Konto mit Passwort-Login (root oder ein Benutzer mit
|
||||
sudo). Danach kann Passwort-Login aus; sshkv zeigt den sshd-Befund an.
|
||||
|
||||
## Aktualisieren
|
||||
|
||||
```bash
|
||||
cd /opt/sshkv
|
||||
sudo bash deploy/sichern.sh
|
||||
git pull
|
||||
docker compose up -d --build # migrate spielt neue Migrationen ein
|
||||
```
|
||||
|
||||
## Variablen (Auswahl)
|
||||
|
||||
| Variable | Bedeutung |
|
||||
|---|---|
|
||||
| `SSHKV_SYNC_CRON` | Zeitplan des Abgleichs (Standard `30 2 * * *`, Europe/Berlin) |
|
||||
| `SSHKV_MELDUNG_CRON` | tägliche Zusammenfassung der Benachrichtigungen |
|
||||
| `SSHKV_SYSLOG_HOST`/`_PORT`/`_PROTOKOLL` | Kopie des Protokolls an einen Syslog-Server (RFC 5424, UDP oder TCP) |
|
||||
| `SSHKV_SYSLOG_FACILITY` / `_APPNAME` | Facility (Standard `authpriv`) und App-Name (Standard `sshkv`) |
|
||||
| `SSHKV_WORKER_USER` | Benutzer des Worker-Containers, falls die Master-Key-Datei nicht UID 1001 gehört |
|
||||
| `SSHKV_BIND` / `SSHKV_PORT` | Bindeadresse/Port der Weboberfläche (Standard `127.0.0.1:3400`) |
|
||||
@@ -0,0 +1,84 @@
|
||||
# Sicherheit und Bedrohungsanalyse
|
||||
|
||||
SSHKeyVault (`sshkv`) verwaltet die SSH-Zugänge zu Servern – und hat dafür selbst
|
||||
**root-gleichen Zugriff auf alle verwalteten Hosts**. Wer sshkv übernimmt, übernimmt die
|
||||
Infrastruktur. Diese Seite beschreibt, wie sshkv aufgebaut ist, um das zu erschweren, was
|
||||
trotzdem passieren kann und was im Ernstfall zu tun ist.
|
||||
|
||||
## Was sshkv besitzt
|
||||
|
||||
| Wert | Wo | Schutz |
|
||||
|---|---|---|
|
||||
| **Deployment-Key** (Private Key) | Datenbank, verschlüsselt | Envelope Encryption mit Master-Key; nur der Worker entschlüsselt |
|
||||
| **Private Keys** von Service-Schlüsseln | Datenbank, verschlüsselt | wie oben; Anzeige nur für Admins mit frischer Bestätigung, protokolliert |
|
||||
| **Master-Key** | Datei auf dem Docker-Host, nur im Worker eingebunden | Dateirechte 0400, nicht in der Datenbank, nicht im Image, nicht im Repo |
|
||||
| `SSHKV_APP_SECRET` | `.env` | signiert Sitzungen, verschlüsselt TOTP-Geheimnis, SMTP-Passwort, Webhook-Geheimnis und Job-Transporte |
|
||||
| Host-Keys der Hosts | Datenbank | öffentlich; schützen vor Man-in-the-Middle (strikte Prüfung) |
|
||||
| Protokoll | Datenbank | append-only per Trigger, Hash-Kette, optional Syslog |
|
||||
|
||||
**Personen-Keys** speichert sshkv nur als Public Key – ein Datenbankregel verhindert, dass dort je ein
|
||||
Private Key steht. Bootstrap-Passwörter werden nie gespeichert.
|
||||
|
||||
## Aufbau, der Angriffe erschwert
|
||||
|
||||
- **Web und Worker getrennt.** Der Webprozess kennt den Master-Key nicht und spricht kein SSH. Eine
|
||||
Lücke in der Weboberfläche allein entschlüsselt keine Private Keys; ein Test hält strukturell fest,
|
||||
dass der Webprozess nichts aus `src/worker` importiert und nur eine einzige Datei die
|
||||
verschlüsselten Felder liest.
|
||||
- **Envelope Encryption** (AES-256-GCM): je Private Key ein eigener Datenschlüssel, gebunden an ID und
|
||||
Fingerprint des Schlüssels. Ein Datenbank-Dump allein gibt keinen Schlüssel preis (Test).
|
||||
- **Strikte Host-Key-Prüfung** bei jeder Verbindung. Ein geänderter Host-Key bricht die Verbindung vor
|
||||
dem Senden von Zugangsdaten ab; Ersetzen nur mit Häkchen und frischer Bestätigung.
|
||||
- **Dateien auf den Hosts** schreibt sshkv als jeweiliger Zielbenutzer (`runuser`): Ein Symlink im
|
||||
Home eines Benutzers kann root nicht dazu bringen, `/etc/shadow` zu lesen oder zu überschreiben
|
||||
(Integrationstest).
|
||||
- **Dry-Run mit optimistischer Sperre:** Ausgerollt wird nur, was ein Mensch gesehen hat. Hat sich die
|
||||
Datei oder die Zuweisung seitdem geändert, bricht der Vorgang ab.
|
||||
- **Rollen:** Private Keys anzeigen, ausrollen und löschen, Deployment-Key, Zielkonten mit sudo und
|
||||
Massenwiderruf nur für Admins; kritische Aktionen zusätzlich mit erneuter Anmeldung (5 Minuten).
|
||||
Ohne passende authentik-Gruppe gibt es keinen Zugang, auch keinen lesenden.
|
||||
- **Notfall-Admin** nur mit Passwort **und** TOTP; Rate-Limit gegen Raten.
|
||||
- **Weboberfläche:** Content-Security-Policy mit Nonce, `X-Frame-Options: DENY`, `Referrer-Policy:
|
||||
no-referrer`, HttpOnly-/SameSite-Cookies, Secure hinter HTTPS; Server Actions mit Origin-Prüfung.
|
||||
|
||||
## Was passiert, wenn sshkv kompromittiert wird?
|
||||
|
||||
| Angreifer hat … | Folge | Gegenmaßnahme |
|
||||
|---|---|---|
|
||||
| nur ein **Datenbank-Backup** | Hosts, Zuweisungen, Public Keys, Protokoll sichtbar; **keine** Private Keys | Backup trotzdem vertraulich behandeln (verrät die Struktur der Infrastruktur) |
|
||||
| **Datenbank + Master-Key** | alle Private Keys inkl. Deployment-Key → root auf allen Hosts | Master-Key getrennt aufbewahren; Notfallplan unten |
|
||||
| die **Weboberfläche** als Admin (gestohlene Sitzung) | kann Schlüssel zuweisen und ausrollen; Private Keys nur nach erneuter Anmeldung | kurze Sitzungen (8 h), Re-Auth, Protokoll + Syslog zeigen jede Aktion |
|
||||
| den **Webprozess** (Codeausführung) | Datenbankzugriff, aber kein Master-Key | Worker getrennt; Master-Key nur im Worker-Container |
|
||||
| den **Worker** oder Docker-Host | alles | Host härten, nur internes Netz, Updates, Zugriff auf den Docker-Host streng begrenzen |
|
||||
| einen **verwalteten Host** | kann sshkv nicht angreifen; sieht nur den Public Key des Deployment-Keys | — |
|
||||
|
||||
### Notfallplan „sshkv kompromittiert“
|
||||
|
||||
1. **Sofort:** Netzwerkzugang zu sshkv sperren (Firewall/VPN), Container anhalten
|
||||
(`docker compose stop`). Die Hosts bleiben erreichbar – sshkv ist kein Teil des Logins.
|
||||
2. **Deployment-Key auf allen Hosts entfernen:** Auf jedem Host in `~sshkv/.ssh/authorized_keys`
|
||||
(bzw. `/root/.ssh/authorized_keys` bei Anmeldeart root) die Zeile mit `sshkv-deployment@…` löschen
|
||||
und `/etc/sudoers.d/sshkv` entfernen. Mit einem Konfigurationsmanagement oder einer Schleife über
|
||||
die Hostliste (CSV-Export der Zuweisungen bzw. Hostliste).
|
||||
3. **Ausgerollte Service-Keys tauschen:** Jeder Private Key, den sshkv kannte, gilt als bekannt.
|
||||
Nach dem Wiederaufbau alle Service-Schlüssel rotieren (geführte Rotation) und alte widerrufen.
|
||||
4. **Neu aufsetzen:** neuer Master-Key, neues `SSHKV_APP_SECRET`, frische Datenbank oder geprüftes
|
||||
Backup; der Worker erzeugt einen neuen Deployment-Key; Hosts per Bootstrap neu aufnehmen.
|
||||
5. **Protokoll auswerten:** Syslog-Kopie (unveränderlich außerhalb von sshkv) mit dem Protokoll
|
||||
vergleichen; `protokoll_kette_pruefen()` zeigt manipulierte Zeilen.
|
||||
|
||||
> Eine eingebaute **Rotation des Deployment-Keys** gibt es noch nicht (siehe `docs/issues`). Bis dahin
|
||||
> ist Schritt 2 Handarbeit.
|
||||
|
||||
## Betriebsregeln
|
||||
|
||||
- **Nur intern betreiben.** Die Weboberfläche gehört ins interne Netz oder hinter ein VPN, nie ins
|
||||
Internet. Compose bindet standardmäßig nur an `127.0.0.1`; ein Reverse Proxy mit TLS steht davor
|
||||
(siehe [installation.md](installation.md)).
|
||||
- **Docker-Host wie einen Bastion-Host behandeln:** wenige Admins, SSH nur mit Schlüssel, Updates,
|
||||
keine anderen Dienste.
|
||||
- **Master-Key** getrennt sichern, Rechte 0400, nur für den Worker-Benutzer lesbar.
|
||||
- **Syslog** einschalten (`SSHKV_SYSLOG_*`), damit eine Kopie des Protokolls außerhalb von sshkv liegt.
|
||||
- **Hosts:** `PermitRootLogin prohibit-password` (sshkv zeigt das als Hinweis an), Anmeldeart
|
||||
„Dienstbenutzer“ bevorzugen – dann kann root-Login ganz aus bleiben.
|
||||
- **Benachrichtigungen** für Drift und geänderte Host-Keys aktivieren, damit Abweichungen auffallen.
|
||||
@@ -0,0 +1,92 @@
|
||||
# Sicherung und Wiederherstellung
|
||||
|
||||
Eine Sicherung von SSHKeyVault besteht aus **zwei Teilen, die getrennt aufbewahrt werden**:
|
||||
|
||||
| Teil | Inhalt | Wie oft | Wohin |
|
||||
|---|---|---|---|
|
||||
| **Datenbank** | Hosts, Schlüssel (Private Keys nur verschlüsselt), Zuweisungen, Protokoll, Einstellungen | täglich | normales Backup-Ziel (`/var/backups/sshkv`, von dort auf das Sicherungssystem) |
|
||||
| **Master-Key** | 32 Byte, entschlüsselt alle Private Keys | einmalig nach der Installation und nach jeder Master-Key-Rotation | **getrennt:** Passwortmanager, Tresor, verschlüsselter Datenträger |
|
||||
| `.env` | `SSHKV_APP_SECRET`, OIDC-Zugang, Datenbankpasswort | nach jeder Änderung | wie der Master-Key |
|
||||
|
||||
> **Ohne Master-Key ist jedes Datenbank-Backup wertlos** – die Private Keys lassen sich nicht mehr
|
||||
> entschlüsseln. **Mit Master-Key neben dem Backup** gibt ein gestohlenes Backup alle Private Keys
|
||||
> preis. Deshalb niemals beides am selben Ort.
|
||||
>
|
||||
> Ohne das richtige `SSHKV_APP_SECRET` gehen TOTP des Notfall-Admins, SMTP-Passwort und
|
||||
> Webhook-Geheimnis verloren (neu einrichten genügt); Private Keys hängen nur am Master-Key.
|
||||
|
||||
## Sichern
|
||||
|
||||
```bash
|
||||
cd /opt/sshkv # Verzeichnis mit docker-compose.yml und .env
|
||||
sudo bash deploy/sichern.sh # nach /var/backups/sshkv
|
||||
sudo bash deploy/sichern.sh /mnt/sicherung/sshkv
|
||||
```
|
||||
|
||||
Das Skript
|
||||
|
||||
- schreibt `datenbank-<Stand>.sql.gz` (`pg_dump --clean --if-exists`, nur das Schema `public` – die
|
||||
Warteschlange legt der Worker selbst neu an),
|
||||
- legt `master-key-pruefwert-<Stand>.txt` daneben: den SHA-256 der Master-Key-Datei. Er verrät nichts
|
||||
über den Schlüssel, zeigt beim Zurückspielen aber, ob der richtige bereitliegt,
|
||||
- behält die letzten 14 Sicherungen (`SSHKV_SICHERUNGEN_BEHALTEN`),
|
||||
- sichert den Master-Key **nicht** mit.
|
||||
|
||||
Als täglicher Cronjob auf dem Docker-Host:
|
||||
|
||||
```cron
|
||||
15 3 * * * root cd /opt/sshkv && bash deploy/sichern.sh >/var/log/sshkv-sicherung.log 2>&1
|
||||
```
|
||||
|
||||
## Master-Key sichern (einmalig)
|
||||
|
||||
```bash
|
||||
sudo base64 /opt/sshkv/geheim/master.key # Ausgabe in den Passwortmanager, Eintrag „sshkv Master-Key“
|
||||
sudo sha256sum /opt/sshkv/geheim/master.key # Prüfwert dazuschreiben
|
||||
```
|
||||
|
||||
Zurück in eine Datei: `echo '<base64>' | base64 -d > geheim/master.key && chmod 400 geheim/master.key`
|
||||
und den Eigentümer setzen (Container-Benutzer 1001 oder `SSHKV_WORKER_USER`).
|
||||
|
||||
## Wiederherstellen
|
||||
|
||||
```bash
|
||||
cd /opt/sshkv
|
||||
sudo bash deploy/wiederherstellen.sh /var/backups/sshkv # jüngste Sicherung
|
||||
sudo bash deploy/wiederherstellen.sh /var/backups/sshkv 20261012-163958
|
||||
```
|
||||
|
||||
Das Skript
|
||||
|
||||
1. prüft den Master-Key unter `SSHKV_MASTER_KEY_HOST_FILE` gegen den Prüfwert der Sicherung und
|
||||
**bricht ab, wenn er nicht passt**,
|
||||
2. fragt nach (`--ja` überspringt),
|
||||
3. hält Weboberfläche und Worker an,
|
||||
4. spielt die Datenbank **in einer Transaktion** zurück – scheitert etwas, bleibt der alte Stand
|
||||
vollständig erhalten,
|
||||
5. setzt die Warteschlange zurück und bricht offene Vorgänge ab (ihre Jobs gehören nicht zur
|
||||
Sicherung),
|
||||
6. startet die Dienste und prüft, dass der Worker den Master-Key akzeptiert.
|
||||
|
||||
Zusätzlich prüft der **Worker bei jedem Start**, ob er den Deployment-Key mit dem eingebundenen
|
||||
Master-Key entschlüsseln kann, und startet sonst nicht („Der Master-Key passt nicht zur Datenbank“).
|
||||
|
||||
### Geprobt am 12.10.2026
|
||||
|
||||
Auf einem Wegwerf-Stapel (gleiches `docker-compose.yml`):
|
||||
|
||||
1. Markierung angelegt (Host `srv-probe`, Protokolleintrag), gesichert.
|
||||
2. Markierung gelöscht, weiterer Protokolleintrag geschrieben.
|
||||
3. Zurückgespielt → Host wieder da, Protokoll wieder auf dem gesicherten Stand, Hash-Kette
|
||||
unversehrt (`protokoll_kette_pruefen()`), Weboberfläche gesund, Worker bereit.
|
||||
4. Mit **falschem Master-Key** zurückgespielt → Abbruch vor jeder Änderung.
|
||||
|
||||
Dabei fielen zwei Fehler auf, die jetzt behoben sind: Das Schema der Warteschlange ließ sich mit
|
||||
`--clean` nicht zurückspielen, und ein Abbruch ohne Transaktion hatte es halb gelöscht.
|
||||
|
||||
## Nach einer Wiederherstellung
|
||||
|
||||
- **„Jetzt abgleichen“** auf der Übersicht: Der Stand der Hosts kann neuer sein als die Sicherung.
|
||||
- Zuweisungen, die nach der Sicherung ausgerollt wurden, kennt sshkv nicht mehr – der Abgleich zeigt
|
||||
sie als unbekannte Schlüssel; übernehmen oder entfernen.
|
||||
- Wurde nach der Sicherung ein Schlüssel widerrufen, steht er wieder als aktiv da: erneut widerrufen.
|
||||
@@ -219,3 +219,21 @@ export async function deploymentKeySicherstellen(): Promise<string> {
|
||||
});
|
||||
return neu.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passt der Master-Key zur Datenbank? (#029) Oeffnet probeweise den Deployment-Key. Schlaegt
|
||||
* das fehl, ist entweder die falsche Master-Key-Datei eingebunden oder eine Sicherung ohne den
|
||||
* dazugehoerigen Master-Key zurueckgespielt worden – beides soll beim Start auffallen, nicht
|
||||
* erst beim ersten Ausrollen.
|
||||
*/
|
||||
export async function masterKeyPruefen(deploymentKeyId: string): Promise<void> {
|
||||
try {
|
||||
const klartext = await privateKeyLaden(deploymentKeyId);
|
||||
klartext.fill(0);
|
||||
} catch {
|
||||
throw new Error(
|
||||
"Der Master-Key passt nicht zur Datenbank: Der Deployment-Key lässt sich nicht entschlüsseln. " +
|
||||
"Ist SSHKV_MASTER_KEY_FILE die Datei, die zu dieser Datenbank gehört (Sicherung)?",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ import {
|
||||
import { vorgangAusfuehren } from "@/worker/ausfuehren";
|
||||
import { abholungenAufraeumen } from "@/worker/ablaeufe/privatekey";
|
||||
import { taktVersenden, zusammenfassungVersenden } from "@/worker/benachrichtigung/versand";
|
||||
import { deploymentKeySicherstellen } from "@/worker/geheimnisse";
|
||||
import { deploymentKeySicherstellen, masterKeyPruefen } from "@/worker/geheimnisse";
|
||||
import { HANDLER } from "@/worker/handler";
|
||||
|
||||
/**
|
||||
@@ -23,7 +23,7 @@ import { HANDLER } from "@/worker/handler";
|
||||
async function starten(): Promise<void> {
|
||||
// Zuerst: ohne lesbaren Master-Key und Deployment-Key kann der Worker nichts tun, und das
|
||||
// soll beim Start auffallen, nicht beim ersten Vorgang.
|
||||
await deploymentKeySicherstellen();
|
||||
await masterKeyPruefen(await deploymentKeySicherstellen());
|
||||
const boss = await warteschlangeHolen();
|
||||
|
||||
await boss.work<VorgangsJob>(
|
||||
|
||||
@@ -48,6 +48,7 @@ test("neuen Host einrichten", async ({ page }) => {
|
||||
await expect(page.getByRole("button", { name: "Fingerprint stimmt – vertrauen" })).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
await page.screenshot({ path: "docs/anleitung/bilder/hostkey-bestaetigen-desktop.png", fullPage: true });
|
||||
await page.getByRole("button", { name: "Fingerprint stimmt – vertrauen" }).click();
|
||||
await expect(page.getByText("Einmalig per Passwort")).toBeVisible({ timeout: 15_000 });
|
||||
await expect(page.getByLabel("Bootstrap-Befehl", { exact: true })).toContainText("SSHKV_BOOTSTRAP");
|
||||
|
||||
@@ -73,3 +73,22 @@ describe("Deployment-Key", () => {
|
||||
expect(dump).not.toContain(kern.slice(0, 40));
|
||||
});
|
||||
});
|
||||
|
||||
describe("Master-Key-Pruefung beim Start (#029)", () => {
|
||||
it("meldet einen falschen Master-Key verstaendlich", async () => {
|
||||
const { masterKeyPruefen } = await import("@/worker/geheimnisse");
|
||||
const id = await deploymentKeySicherstellen();
|
||||
await expect(masterKeyPruefen(id)).resolves.toBeUndefined();
|
||||
const falsch = path.join(verzeichnis, "falsch.key");
|
||||
writeFileSync(falsch, randomBytes(32), { mode: 0o600 });
|
||||
const richtig = process.env["SSHKV_MASTER_KEY_FILE"];
|
||||
process.env["SSHKV_MASTER_KEY_FILE"] = falsch;
|
||||
masterKeyVergessen();
|
||||
try {
|
||||
await expect(masterKeyPruefen(id)).rejects.toThrow(/passt nicht zur Datenbank/);
|
||||
} finally {
|
||||
process.env["SSHKV_MASTER_KEY_FILE"] = richtig;
|
||||
masterKeyVergessen();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user