Drift, ablaufende und abgelaufene Schluessel, unerreichbare Hosts und geaenderte Host-Keys sollen auffallen, ohne dass jemand taeglich in die Oberflaeche schaut. Versand per SMTP (Microsoft 365, STARTTLS) und per Webhook mit HMAC-Signatur, eingestellt unter /einstellungen. Die Anlaesse liest der Worker aus der Datenbank, statt sie im Abgleich auszuloesen: der Abgleich-Ablauf bleibt unberuehrt, und ein verpasster Lauf holt sich der naechste. Ein Versandprotokoll (Zustellung) mit Anlass-Fingerabdruck verhindert Doppelmeldungen und haelt Fehlschlaege fuer begrenzte Wiederholungen fest. Taeglich eine Zusammenfassung, geaenderte Host-Keys im Fuenf-Minuten-Takt sofort. Versand und Testnachricht laufen im Worker, damit der Webprozess keinen Weg zu Mailserver oder Webhook-Ziel braucht. Passwort und Geheimnis liegen nur verschluesselt in der Datenbank; die Seite zeigt "gesetzt". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
126 lines
4.6 KiB
YAML
126 lines
4.6 KiB
YAML
# SSHKeyVault – Betrieb mit Weboberflaeche, Worker und Datenbank.
|
||
# 1. cp .env.example .env und Werte eintragen (mindestens SSHKV_APP_SECRET, POSTGRES_PASSWORD,
|
||
# SSHKV_APP_URL, SSHKV_BREAKGLASS_PASSWORD)
|
||
# 2. Master-Key erzeugen (nur fuer den Worker lesbar, Container-Benutzer 1001):
|
||
# mkdir -p geheim && head -c 32 /dev/urandom > geheim/master.key
|
||
# sudo chown 1001:1001 geheim/master.key && sudo chmod 400 geheim/master.key
|
||
# (oder SSHKV_WORKER_USER auf den Eigentuemer der Datei setzen)
|
||
# 3. docker compose up -d
|
||
# Nur im internen Netz/VPN betreiben und einen Reverse Proxy mit TLS davorsetzen (docs/betrieb).
|
||
|
||
name: sshkv
|
||
|
||
services:
|
||
db:
|
||
image: postgres:17-alpine
|
||
container_name: sshkv-db
|
||
restart: unless-stopped
|
||
environment:
|
||
POSTGRES_USER: sshkv
|
||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD fehlt in der .env}
|
||
POSTGRES_DB: sshkv
|
||
volumes:
|
||
- datenbank:/var/lib/postgresql/data
|
||
networks: [datenbank]
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "pg_isready -U sshkv"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 10
|
||
|
||
# Laeuft einmal je Start: Schema einspielen, Notfall-Admin und Vorlagen anlegen.
|
||
migrate:
|
||
build:
|
||
context: .
|
||
target: bauen
|
||
image: sshkv-migrate
|
||
restart: "no"
|
||
depends_on:
|
||
db:
|
||
condition: service_healthy
|
||
command: ["sh", "-c", "pnpm prisma migrate deploy && pnpm prisma db seed"]
|
||
environment:
|
||
DATABASE_URL: postgresql://sshkv:${POSTGRES_PASSWORD}@db:5432/sshkv?schema=public
|
||
SSHKV_BREAKGLASS_USER: ${SSHKV_BREAKGLASS_USER:-notfall}
|
||
SSHKV_BREAKGLASS_PASSWORD: ${SSHKV_BREAKGLASS_PASSWORD:-}
|
||
networks: [datenbank]
|
||
|
||
web:
|
||
build: .
|
||
image: sshkv
|
||
container_name: sshkv-web
|
||
restart: unless-stopped
|
||
depends_on:
|
||
migrate:
|
||
condition: service_completed_successfully
|
||
environment:
|
||
DATABASE_URL: postgresql://sshkv:${POSTGRES_PASSWORD}@db:5432/sshkv?schema=public
|
||
SSHKV_APP_URL: ${SSHKV_APP_URL:?SSHKV_APP_URL fehlt in der .env}
|
||
SSHKV_APP_SECRET: ${SSHKV_APP_SECRET:?SSHKV_APP_SECRET fehlt in der .env}
|
||
SSHKV_OIDC_ISSUER: ${SSHKV_OIDC_ISSUER:-}
|
||
SSHKV_OIDC_CLIENT_ID: ${SSHKV_OIDC_CLIENT_ID:-}
|
||
SSHKV_OIDC_CLIENT_SECRET: ${SSHKV_OIDC_CLIENT_SECRET:-}
|
||
SSHKV_OIDC_NAME: ${SSHKV_OIDC_NAME:-authentik}
|
||
SSHKV_OIDC_GROUP_ADMIN: ${SSHKV_OIDC_GROUP_ADMIN:-sshkv-admins}
|
||
SSHKV_OIDC_GROUP_OPERATOR: ${SSHKV_OIDC_GROUP_OPERATOR:-sshkv-operators}
|
||
SSHKV_OIDC_GROUP_READER: ${SSHKV_OIDC_GROUP_READER:-sshkv-readers}
|
||
# Nur zur Anzeige unter /einstellungen; ausgefuehrt werden die Zeitplaene im Worker.
|
||
SSHKV_SYNC_CRON: ${SSHKV_SYNC_CRON:-30 2 * * *}
|
||
SSHKV_MELDUNG_CRON: ${SSHKV_MELDUNG_CRON:-0 7 * * *}
|
||
# Absichtlich KEIN Master-Key: eine Luecke im Webprozess allein entschluesselt keine
|
||
# Private Keys. Nach aussen braucht er nur den OIDC-Anbieter.
|
||
ports:
|
||
- "${SSHKV_BIND:-127.0.0.1}:${SSHKV_PORT:-3400}:3400"
|
||
networks: [datenbank, aussen]
|
||
healthcheck:
|
||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3400/api/gesundheit"]
|
||
interval: 30s
|
||
timeout: 5s
|
||
retries: 5
|
||
start_period: 20s
|
||
|
||
worker:
|
||
image: sshkv
|
||
container_name: sshkv-worker
|
||
restart: unless-stopped
|
||
command: ["./start.sh", "worker"]
|
||
# Muss die Master-Key-Datei lesen duerfen (0400). Standard ist der Benutzer im Abbild;
|
||
# alternativ den Eigentuemer der Datei eintragen, z. B. SSHKV_WORKER_USER=1000:1000.
|
||
user: "${SSHKV_WORKER_USER:-1001:1001}"
|
||
depends_on:
|
||
migrate:
|
||
condition: service_completed_successfully
|
||
web:
|
||
condition: service_started
|
||
environment:
|
||
DATABASE_URL: postgresql://sshkv:${POSTGRES_PASSWORD}@db:5432/sshkv?schema=public
|
||
SSHKV_APP_SECRET: ${SSHKV_APP_SECRET}
|
||
SSHKV_MASTER_KEY_FILE: /run/secrets/master_key
|
||
SSHKV_SYNC_CRON: ${SSHKV_SYNC_CRON:-30 2 * * *}
|
||
# Benachrichtigungen (#028): taegliche Zusammenfassung; Links in den Nachrichten zeigen
|
||
# auf SSHKV_APP_URL. Den Weg zu Mailserver und Webhook-Ziel hat der Worker ueber "aussen".
|
||
SSHKV_MELDUNG_CRON: ${SSHKV_MELDUNG_CRON:-0 7 * * *}
|
||
SSHKV_APP_URL: ${SSHKV_APP_URL}
|
||
secrets:
|
||
- master_key
|
||
# Der Worker braucht die Datenbank und den Weg zu den Hosts (und zu Mailserver/Webhook).
|
||
networks: [datenbank, aussen]
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "pgrep -f dist/worker.mjs > /dev/null"]
|
||
interval: 60s
|
||
timeout: 5s
|
||
retries: 3
|
||
|
||
secrets:
|
||
master_key:
|
||
file: ${SSHKV_MASTER_KEY_HOST_FILE:-./geheim/master.key}
|
||
|
||
networks:
|
||
# Die Datenbank hat keinen Weg nach draussen.
|
||
datenbank:
|
||
internal: true
|
||
aussen: {}
|
||
|
||
volumes:
|
||
datenbank:
|