Dashboard hinter Caddy: TLS (100 Jahre selbstsigniert) + eigene Basic-Auth auf Port 443

Stefans Entscheidung: 0.0.0.0-Bind mit unverschluesseltem Basic-Auth war nur
Testzustand. Jetzt bindet hermes-agent-dashboard wieder auf 127.0.0.1, ein
neuer Caddy-Service (network_mode: host) terminiert TLS auf 443 mit einem
manuell erzeugten selbstsignierten Zertifikat (100 Jahre Laufzeit, kein
Let's-Encrypt-Domain-Zwang) und erzwingt eine zweite, eigene Basic-Auth-Schicht
(bcrypt-Hash in CADDY_BASIC_AUTH_HASH, getrennt von Hermes' pbkdf2-Hash) -
noetig weil Hermes' eigenes Auth-Gate laut Fehlermeldung nur bei 0.0.0.0-Binds
"engagiert". README + .env.example um die Setup-Schritte (Cert-Erzeugung,
Caddy-Hash, $-Escaping-Falle) ergaenzt.
This commit is contained in:
ARIA
2026-07-19 22:07:17 +00:00
parent 24e09a46ce
commit 66bcd0dfb1
4 changed files with 169 additions and 31 deletions
+79 -27
View File
@@ -153,26 +153,57 @@ aussen — kein anderer Rechner kommt mehr direkt ran.
```
Danach neu hochziehen: `docker compose up -d --build hermes-agent-dashboard`
5. **Stack starten:**
5. **TLS-Reverse-Proxy (Caddy) einrichten — macht Port 443 von aussen
erreichbar, Dashboard bleibt intern auf `127.0.0.1`:**
a. Selbstsigniertes Zertifikat erzeugen, 100 Jahre gueltig (bewusst kein
Let's Encrypt — kein Domain-/Port-80-Zwang, kein Renewal je wieder
noetig). Einmalig auf der Maschine:
```bash
mkdir -p hermes-data/caddy-certs
openssl req -x509 -nodes -newkey rsa:4096 \
-keyout hermes-data/caddy-certs/dashboard.key \
-out hermes-data/caddy-certs/dashboard.crt \
-days 36500 \
-subj "/CN=hermes-dashboard"
```
`-days 36500` ≈ 100 Jahre. `/CN` ist egal (kein Public-DNS-Zwang), kann
auch die Server-IP sein wenn Du magst.
b. Caddy-Basic-Auth-Hash erzeugen (EIGENER Hash, nicht derselbe wie bei
Schritt 4 — Caddy will bcrypt, Hermes' Hash ist pbkdf2):
```bash
docker run --rm caddy:2-alpine caddy hash-password --plaintext 'DEIN-PASSWORT'
```
Output in `.env` bei `CADDY_BASIC_AUTH_HASH` eintragen, danach —
**gleiche Dollarzeichen-Falle wie bei Schritt 4** (bcrypt-Hashes haben
auch rohe `$`-Zeichen drin) — escapen:
```bash
sed -i '/^CADDY_BASIC_AUTH_HASH=/ s/\$/$$/g' .env
```
`HERMES_DASHBOARD_USER` aus Schritt 4 wird wiederverwendet (dasselbe
Login gilt fuer beide Auth-Schichten).
6. **Stack starten:**
```bash
docker compose up -d
```
Baut beim ersten Mal das Hermes-Image aus `hermes-agent-src` (dauert ein
paar Minuten), startet dann `hermes-proxy`, `hermes-gateway`,
`hermes-agent` (Gateway/Messaging-Prozess) und `hermes-agent-dashboard`.
Falls `HERMES_DASHBOARD_PASSWORD_HASH` noch leer ist (Schritt 4 noch nicht
gemacht): `hermes-agent-dashboard` bricht beim Start ab, die anderen drei
Services laufen trotzdem — Hash nachtragen, dann
`docker compose up -d hermes-agent-dashboard`.
`hermes-agent` (Gateway/Messaging-Prozess), `hermes-agent-dashboard` und
`caddy`. Falls `HERMES_DASHBOARD_PASSWORD_HASH` oder `CADDY_BASIC_AUTH_HASH`
noch leer sind: der jeweilige Service bricht beim Start ab, die anderen
laufen trotzdem — Werte nachtragen, dann
`docker compose up -d hermes-agent-dashboard caddy`.
6. **Einmalig Claude-CLI-Login** (oeffnet Browser-OAuth mit deiner Claude-Max-Subscription):
7. **Einmalig Claude-CLI-Login** (oeffnet Browser-OAuth mit deiner Claude-Max-Subscription):
```bash
docker exec -it hermes-proxy claude
```
Danach liegen die Credentials persistent in `hermes-data/claude-config/`
— ueberlebt Container-Restarts.
7. **Testen, dass der Proxy laeuft:**
8. **Testen, dass der Proxy laeuft:**
```bash
curl -s http://127.0.0.1:${HERMES_GATEWAY_PORT:-8447}/v1/chat/completions \
-H "Content-Type: application/json" \
@@ -181,15 +212,17 @@ aussen — kein anderer Rechner kommt mehr direkt ran.
```
Ohne oder mit falschem Bearer-Token gibt's `401 unauthorized`.
8. **Mit Hermes chatten:**
9. **Mit Hermes chatten:**
```bash
docker exec -it hermes-agent hermes chat
```
oder das Dashboard direkt im Browser oeffnen: `http://<diese-maschine>:9119`
— bindet auf `0.0.0.0`, kein SSH-Tunnel mehr noetig, dafuer fragt es jetzt
den Basic-Auth-Login aus Schritt 4 ab. Traffic ist weiterhin unverschluesseltes
HTTP (siehe Sicherheitshinweis unten) — fuer mehr als "kurz testen" gehoert
ein TLS-Reverse-Proxy davor.
oder das Dashboard im Browser oeffnen: `https://<diese-maschine>` (Port
443, kein `:9119` mehr — der ist jetzt nur noch intern). Browser meldet
beim ersten Aufruf "Zertifikat nicht vertrauenswuerdig" (weil selbstsigniert,
nicht von einer oeffentlichen CA) — einmalig als Ausnahme akzeptieren
("Trotzdem fortfahren" o.ae.), danach fragt Caddy den Basic-Auth-Login aus
Schritt 5b ab. Traffic ist ab hier durchgehend TLS-verschluesselt, kein
SSH-Tunnel mehr noetig.
## Mobiler Client (Android)?
@@ -212,20 +245,34 @@ Zwei GRUNDVERSCHIEDENE Richtungen, nicht verwechseln:
ran, solange Du nicht selbst was aendertst (z.B. fuer den mobilen API-Server
oben, oder falls Hermes doch mal auf eine zweite Maschine zieht).
`hermes-agent-dashboard` bindet seit 19.07.2026 bewusst auf `0.0.0.0` (Stefans
Entscheidung — ein SSH-Tunnel machte mit dem SSL-Setup Probleme) und ist per
Basic-Auth gesichert (siehe Setup-Schritt 4, Hermes verweigert den 0.0.0.0-Bind
sonst ohnehin hart). Das ist Testzustand, kein Dauerbetrieb-Haerten: das
Passwort geht als HTTP Basic-Auth unverschluesselt raus (kein TLS auf Port
9119), Keys/Config/Sessions liegen dahinter offen. Fuer Dauerbetrieb gehoert
ein TLS-Reverse-Proxy (Caddy/nginx) davor, der Basic-Auth + Zertifikat
uebernimmt — Dashboard-Port dann wieder `127.0.0.1`, nur der Proxy von aussen
erreichbar.
`hermes-agent-dashboard` bindet seit dem 19.07.2026, spaeter Abend, wieder
auf `127.0.0.1` — von aussen nicht mehr direkt erreichbar. Davor steht seitdem
`caddy` (Service in `docker-compose.yml`, Config in `Caddyfile`): terminiert
TLS auf Port 443 mit einem selbstsignierten 100-Jahre-Zertifikat (Setup-Schritt
5a) und erzwingt eine EIGENE Basic-Auth-Schicht (Setup-Schritt 5b, eigener
bcrypt-Hash in `CADDY_BASIC_AUTH_HASH` — nicht derselbe wie Hermes' pbkdf2-Hash).
Zwei Gruende fuer die doppelte Auth statt nur auf Hermes' eigene zu vertrauen:
1. Hermes' Auth-Gate "engagiert" laut eigener Fehlermeldung nur bei einem
0.0.0.0-Bind — auf 127.0.0.1 (das Caddy anspricht) ist nicht garantiert,
dass `dashboard.basic_auth` noch durchgesetzt wird.
2. Selbst wenn doch: zwei unabhaengige Schichten sind ohnehin robuster als
eine, falls in Zukunft mal eine davon falsch konfiguriert wird.
Das war vorher (kurzzeitig, testweise) `0.0.0.0` OHNE TLS — Passwort ging als
Basic-Auth im Klartext raus. Ist jetzt behoben: einziger von aussen offene
Port ist 443, durchgehend verschluesselt.
Das Zertifikat ist selbstsigniert (keine oeffentliche CA dahinter) — Browser
zeigen beim ersten Aufruf eine Warnung, die Du einmalig als Ausnahme
akzeptierst (Details siehe Setup-Schritt 9). Wer eine "echte", warnungsfreie
CA-Kette will, braucht dafuer eine oeffentliche Domain + Let's Encrypt — war
hier bewusst nicht der Plan.
Generell gilt: wenn Du `HERMES_GATEWAY_PORT`, `API_SERVER_HOST` oder den
Dashboard-Port doch mal direkt nach aussen exposest, ist der Traffic
unverschluesseltes HTTP, kein TLS. Nicht direkt ins offene Internet haengen —
SSH-Tunnel, WireGuard/Tailscale oder einen TLS-Reverse-Proxy davorschalten.
Dashboard-Port (`9119`) doch mal direkt nach aussen exposest (statt ueber
Caddy), ist der Traffic unverschluesseltes HTTP, kein TLS. Nicht direkt ins
offene Internet haengen — SSH-Tunnel, WireGuard/Tailscale oder eben den
Caddy-Reverse-Proxy davorschalten.
## Verzeichnisse (nicht committet, siehe .gitignore)
@@ -234,7 +281,12 @@ SSH-Tunnel, WireGuard/Tailscale oder einen TLS-Reverse-Proxy davorschalten.
- `hermes-data/agent-home/` — Hermes' `~/.hermes`-Aequivalent: Config
(`config.yaml`, aus `hermes-agent-config/config.yaml.example` geseedet),
Sessions, Memory, Skills, `.env`
- `.env` — Secrets (Token, ggf. API_SERVER_KEY)
- `hermes-data/caddy-certs/` — selbstsigniertes 100-Jahre-Zertifikat
(`dashboard.crt`/`dashboard.key`, Setup-Schritt 5a) — Private Key, niemals
committen
- `hermes-data/caddy-data/`, `hermes-data/caddy-config/` — Caddys eigener
Runtime-State (nicht von Dir angefasst)
- `.env` — Secrets (Token, ggf. API_SERVER_KEY, Basic-Auth-Hashes)
## Wo deployen?