final bis auf prio 2 Todos

This commit is contained in:
br committed 2026-10-03 13:22:51 +02:00
1 parent 64417d8353
commit 6a5cb95f92
6 files changed
+600 -1

No files matched your search

+166
View File
@@ -0,0 +1,166 @@
# Sultan für Hoster: Kunden-Onboarding (pro / enterprise)
Für Hoster, die Sultan als bezahlten Dienst anbieten: Du betreibst Ingress (Traefik + `sultan server`), der Kunde betreibt seine Backends mit `sultan agent`. Grundinstallation: siehe [community.md](community.md). Wartung: siehe [wartung.md](wartung.md).
> **Stand:** Der kommerzielle Teil (Lizenzen, Mahnwesen, Vertragsende) ist durch Tests abgedeckt, im echten Betrieb aber **noch nicht erprobt**. Bekannte Lücken stehen am Ende.
## Wer hat was?
| | Hoster | Kunde |
|---|---|---|
| Läuft dort | Traefik, `sultan server` | Docker-Host(s) mit `sultan agent` |
| Geheimnisse | `suldata/` des Servers: TLS-Schlüssel, **Master-Key** (signiert Lizenzen, Leases, Befehle) | `node.key` je Host; verlässt den Host nie, auch du siehst ihn nicht |
| Entscheidet | welche Domains, wie viele Routen, bis wann | welche Dienste wie geroutet werden (Labels) |
| Sieht | alle Routen, Knoten, Verstöße, Fristen | nur sein eigenes Agent-Log |
Der Kunde kann **nur Domains** aus seinem Mandanten beanspruchen und nur bis `max_routes`. Alles andere verwirft der Server mit Eintrag im Audit-Log. Kunden sehen sich gegenseitig nicht. Gleichnamige Router verschiedener Kunden kollidieren nicht, weil jeder Router in Traefik als `<mandant>__<knoten>__<router>` erscheint.
## 1. Vor dem Onboarding klären
Aus dem Vertrag gehen diese Werte in den Mandanten:
| Wert | Flag | Hinweise |
|---|---|---|
| Kennung | `--id` | `a–z 0–9 - _`, dauerhaft (erscheint in Routernamen, Logs, Monitoring). Z. B. Kundennummer. |
| Name, Kontakt | `--name`, `--email` | für dich (Listen, Benachrichtigungen) |
| Domains | `--domains` | `kunde.de` = genau diese; `*.kunde.de` = alle Subdomains, **ohne** `kunde.de` selbst → meist beides eintragen. Wildcard auf TLD (`*.de`) ist verboten. Umlaut-Domains als Punycode (`xn--…`). |
| Paket | `--tier pro` oder `enterprise` | Beide verhalten sich technisch derzeit **gleich** (Mahnwesen, Vertragsende). Der Unterschied ist vertraglich. `community` = kein Mahnwesen, läuft nicht ab. |
| Routen | `--max-routes` | Anzahl Router (jeder `traefik.http.routers.<name>` zählt, auch Redirect-Router). `-1` = unbegrenzt. Überzählige werden alphabetisch verworfen. |
| Vertragsende | `--valid-until JJJJ-MM-TT` | Default: heute + 1 Jahr. **Danach sehen Besucher die Landingpage „Hosting abgelaufen“.** |
| Zusatzfunktionen | `--features` | `custom_middleware`, `websockets`, `tcp_stream`. Ohne `custom_middleware` sind nur Standard-Middlewares erlaubt (Redirects, Pfad-Umschreibung, Header, Kompression). BasicAuth, Rate-Limit, IP-Allowlist usw. brauchen das Feature, sonst wird der Router mit Verstoß abgelehnt. `chain` und `errors` sind nie erlaubt. |
**Achtung:** Vertragsende, Domains und Paket lassen sich bisher **nicht nachträglich ändern** (`tenant update` fehlt, siehe Lücken). Deshalb `--valid-until` und `--domains` beim Anlegen sorgfältig setzen.
## 2. Netzwerk mit dem Kunden klären
Das ist der häufigste Grund für ein holpriges Onboarding. Zwei Wege müssen frei sein:
1. **Agent → Server:** ausgehend TCP 8443 vom Kunden-Host zu deiner `--public-url`. Meist ohnehin offen.
2. **Traefik → Kunden-Backend:** dein Ingress verbindet sich per HTTP auf den **veröffentlichten Port** des Containers. Dafür gibt es drei Möglichkeiten:
| Situation beim Kunden | Lösung |
|---|---|
| öffentliche IP / eigenes Rechenzentrum | Kunde gibt die Ports **nur für deine Ingress-IP(s)** frei |
| hinter NAT/CGNAT/DS-Lite | VPN/Overlay (z. B. NetBird): Agent mit `SULTAN_BACKEND_INTERFACE=wt0`; eventuell Einladung mit `--server-url https://<netbird-ip>:8443` |
| gleiches LAN wie der Ingress | nichts zu tun |
Vorgaben für den Kunden:
- Ports veröffentlichen (`"9080:80"`), **nicht** nur `127.0.0.1:9080:80`.
- Der Agent meldet als Backend die Adresse, über die er deinen Server erreicht. Das kann eine **IPv6-Adresse** sein. Dann muss dein Ingress IPv6 können, sonst setzt der Kunde `SULTAN_BACKEND_HOST`.
- Knoten-ID: Default ist der Hostname. Gleichnamige Hosts beim selben Kunden (z. B. `web01`) brauchen `SULTAN_NODE_ID`.
## 3. Mandant anlegen und einladen
```bash
cd /docker/sultan-server
docker compose exec sultan-server sultan token tenant add \
--id k10042 --name "Novesia Bonn" --email it@novesia-bonn.de \
--domains "novesia-bonn.de,www.novesia-bonn.de" \
--tier pro --max-routes 10 --valid-until 2027-10-31
docker compose exec sultan-server sultan token invite --tenant k10042 --ttl 7d
```
**Die Einladung (`sltn_enroll_…`):**
- **Ein Knoten pro Einladung.** Drei Hosts = drei Einladungen.
- Bis zur Benutzung geheim: Wer sie hat, kann einen Knoten für diesen Kunden anmelden. Danach ist sie wertlos. Übergabe über einen sicheren Kanal (Kundenportal, verschlüsselte Mail, Passwortmanager), **nicht** ins Ticket kopieren.
- Gespeichert wird nur ein Hash; du kannst sie später also nicht noch einmal anzeigen. Verloren oder abgelaufen → einfach neu erzeugen. Aufräumen: `sultan token purge`.
- Sie enthält Server-Adresse und Zertifikats-Fingerprint. Der Kunde braucht nichts weiter, kein CA-Zertifikat, keine IP-Liste. Mit einer gefälschten Adresse landet der Agent nie bei einem fremden Server (Pinning).
## 4. Was du dem Kunden schickst
Ein Paket aus:
1. Einladung (sicherer Kanal) und Gültigkeit.
2. Compose-Vorlage [deploy/compose/agent](../deploy/compose/agent) + Image `zot.chmln.de/sultan:<version>`.
3. Die Labels, passend zu **deinen** Traefik-Namen (Entrypoint, Certresolver):
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.shop.rule=Host(`www.novesia-bonn.de`) || Host(`novesia-bonn.de`)"
- "traefik.http.routers.shop.entrypoints=websecure"
- "traefik.http.routers.shop.tls.certresolver=le"
- "traefik.http.services.shop.loadbalancer.server.port=80"
```
4. DNS-Vorgabe: Domains per CNAME auf deinen Ingress (z. B. `web11.r25dc.de`). Erst dann klappt Let's Encrypt.
5. Freigabe aus Abschnitt 2 (Ports nur für deine Ingress-IP).
6. Die Wartungsregeln aus [wartung.md](wartung.md), vor allem: **`stop` statt `down`** und `sultan agent maintenance on`.
## 5. Abnahme
```bash
docker compose exec sultan-server sultan token node list --tenant k10042 # online, Lease bis …
curl -s 'http://127.0.0.1:8080/v1/traefik-config?format=yaml' | grep k10042 # Router + Backend-URL
curl -sI http://<backend-ip>:<port> # vom Ingress: Backend erreichbar?
curl -sI https://www.novesia-bonn.de # Zertifikat + Seite
docker compose logs sultan-server | grep -i verstoß # abgelehnte Domains/Routen?
```
Fehlt eine Route, steht der Grund im Server-Log („Policy-Verstoß“: fremde Domain, `HostRegexp`, Routenlimit, verbotene Middleware) oder im Agent-Log des Kunden („Container nicht übernommen“: Port nicht veröffentlicht).
## 6. Laufender Betrieb
### Zahlung und Mahnwesen
Sultan kennt keine Rechnungen, nur das **Fälligkeitsdatum einer offenen Zahlung**. Das trägst du (oder später dein Rechnungssystem) ein:
```bash
docker compose exec sultan-server sultan token tenant payment --id k10042 --overdue-since 2026-11-01
docker compose exec sultan-server sultan token tenant payment --id k10042 --paid
```
Daraus ergibt sich automatisch:
| Tage überfällig | Stufe | Besucher sehen |
|---|---|---|
| < 8 | – | normale Seite |
| ab 8 | watermark | Hinweisseite, nach 5 s weiter zur Seite (dann 60 min Ruhe) |
| ab 25 | firemark | wie oben, 10 s |
| ab 32 | paywall | **Seite offline**, Hinweisseite mit deinem Angebot (`--hoster-name`, `--hoster-url`) |
Mit `--paid` ist sofort (nächster Refresh, ≤ 1 min) alles normal. Bei Stufe 1 und 2 meldet Checkmk bzw. Slack/ntfy.
Sofort sperren, z. B. bei Zahlungsausfall oder Missbrauch:
```bash
sultan token tenant set-status --id k10042 --status suspended_payment # Paywall-Seite
sultan token tenant set-status --id k10042 --status quarantined # Routen weg, Agent-Anfragen abgelehnt
sultan token tenant set-status --id k10042 --status active # zurück
```
### Vertragsende
- Checkmk (`/metrics/checkmk`): **WARN 30**, **CRIT 14** Tage vor `valid_until`. Benachrichtigungen gehen einmal pro Frist raus.
- Ab `valid_until` wird der Mandant `expired`, Besucher sehen „Hosting abgelaufen“ (402). Die Leases enden spätestens zum Vertragsende.
- Verlängern: siehe Lücken.
### Monitoring
```bash
curl -s http://127.0.0.1:8080/metrics/checkmk # als Checkmk-Local-Check einbinden
```
Liefert pro kommerziellem Mandanten Vertragsfrist und Mahnstufe, pro Knoten online/offline/Wartung und Lease, dazu Policy-Verstöße der letzten 24 h. Zusätzlich Push per `SULTAN_NOTIFY_SLACK` / `SULTAN_NOTIFY_NTFY`: Knoten > 15 min offline, wieder online, Fristen, Mahnstufen. Policy-Verstöße erscheinen nur im Server-Log und als Zähler in Checkmk.
### Sicherheit
- Verdacht auf kompromittierten Kunden-Host: `sultan token node drain --id …` (Routen sofort weg), danach `node delete` und neue Einladung für einen sauberen Host.
- Schlüssel des Knotens tauschen, ohne Neuanmeldung: `sultan token node command --id … --cmd rotate_key`.
- Dein `suldata/` (Master-Key!) regelmäßig verschlüsselt sichern. Wer den Master-Key hat, kann Lizenzen und Befehle für alle Kunden ausstellen.
### Kunde kündigt
```bash
sultan token node drain --id <knoten> # Routen sofort weg (für jeden Knoten)
sultan token tenant delete --id k10042 # löscht Mandant, Knoten und offene Einladungen
```
## Bekannte Lücken (Stand heute)
| Lücke | Folge | Notlösung |
|---|---|---|
| **`tenant update` fehlt** | Vertragsende, Domains, Paket, Routenlimit nicht änderbar | Keine saubere: `tenant delete` + neu anlegen löscht auch die Knoten (neue Einladungen nötig). Erste Priorität für den kommerziellen Betrieb. |
| pro = enterprise | kein technischer Unterschied | vertraglich regeln |
| Keine Admin-API | Zahlungsstatus nur per CLI | Skript über `docker compose exec` |
| Kommerzieller Betrieb nicht im Feld getestet | – | mit einem Testmandanten und kurzen Fristen (`--valid-until` in wenigen Tagen, `payment --overdue-since` in der Vergangenheit) durchspielen |
| `public_reason` einsprachig | Wartungstext für de und en gleich | – |