184 lines
12 KiB
Markdown
184 lines
12 KiB
Markdown
# 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; je Container Compose-Projekt und -Verzeichnis, Startzeit, Health; je Host FQDN, Betriebssystem, Docker-Version, NetBird-IP | nur sein eigenes Agent-Log |
|
||
|
||
Die Host- und Container-Angaben sammelt der Agent automatisch für Inventar und Störfall (wo läuft welche URL, in welchem Compose-Verzeichnis). Sag das dem Kunden; enthält ein Verzeichnisname Vertrauliches, sieht der Hoster es.
|
||
|
||
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>` und `traefik.tcp.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. `tcp_stream` erlaubt TLS-Passthrough (Kunde mit eigener CA/TLS, z. B. tinyCA): nur `HostSNI` mit einzelnen Hosts aus den Domains, nur Port 443; ab Paywall oder bei Wartung ist die Route ganz weg (keine Hinweisseite möglich). |
|
||
|
||
**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.
|
||
|
||
### Inventar (Accountant)
|
||
|
||
Betreibst du mehrere Ingress-Server, melden sie ihr Inventar an deinen `sultan accountant`: Mandanten, Knoten und je URL das Ziel (`<fqdn>:<port>`), Container, Compose-Verzeichnis, Health und das Produkt aus `sultan.accountant.billing`. Im Störfall:
|
||
|
||
```bash
|
||
sultan accountant where kunde-a.de # Backend <fqdn>:<port>, Container, Compose-Verzeichnis, seit wann, wo vorher
|
||
sultan accountant routes --tenant=k10042
|
||
```
|
||
|
||
Einrichtung: `sultan accountant invite --payee=<server>` auf dem Accountant, die Einladung als `SULTAN_ACCOUNTANT_TOKEN` in die `.env` des Ingress-Servers, Neustart. Danach liegen `payee.key` und `accountant.json` in `suldata/` (mitsichern).
|
||
|
||
Abrechnung: Kunden legt der Accountant aus den Mandanten automatisch an; du ergänzt Zahlweise, Pauschale, Gruppe und Notiz (`customer set`), pflegst den Produktkatalog (`product add|set|price`, Preise optional) und ordnest Posten zu (Label `sultan.accountant.billing` am Container oder `assign --host`). `sultan accountant billing --month=JJJJ-MM --out=…csv` liefert die Monatsliste; die Spalte „Hinweis“ zeigt Lücken (kein Kunde, nicht zugeordnet, Zahlweise offen) und Kündigungen mit tagesgenauer Gegenrechnung.
|
||
|
||
Checkmk auf dem Accountant-Host: `curl -s http://127.0.0.1:8082/metrics/checkmk` als Local Check einbinden. Je URL ein Service `Sultan URL <host>` (WARN bei gestoppt, Policy-Verstoß, unhealthy, Knoten getrennt; Wartung bleibt OK) mit Ziel `http://<fqdn>:<port>` und Compose-Verzeichnis im Text, je Ingress-Server `Sultan Payee <id>` (WARN ab 2 h ohne Bericht, CRIT ab 6 h). Den Text passt du mit `sultan accountant dump-checkmk` an.
|
||
|
||
### 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 | – |
|