# 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 `____` 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.` und `traefik.tcp.routers.` 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). | Alle Werte außer der Kennung lassen sich später mit `tenant update` ändern (siehe unten). ### Mandant nachträglich ändern ```bash sultan token tenant update --id k10042 --add-domains "zweitshop.de,www.zweitshop.de" # weiterer Stack des Kunden sultan token tenant update --id k10042 --remove-domains alt-kunde.de # Routen darauf fallen weg sultan token tenant update --id k10042 --max-routes 10 sultan token tenant update --id k10042 --valid-until 2028-12-31 # Vertrag verlängern sultan token tenant update --id k10042 --tier pro --valid-until 2027-12-31 # von community auf pro sultan token tenant update --id k10042 --add-features websockets --remove-features tcp_stream ``` - Nur Angegebenes ändert sich. Es wirkt ohne Neustart beim nächsten Refresh (bis zu 1 Minute); Knoten und Einladungen bleiben. - **Verlängern:** Liegt das neue `--valid-until` in der Zukunft, wird der Status `expired` wieder `active`. Quarantäne und Zahlungsverzug bleiben bestehen. - **Wechsel von `community` auf `pro`/`enterprise`:** Liegt das bisherige Datum in der Vergangenheit, verlangt der Befehl `--valid-until`, sonst wären die Seiten sofort abgelaufen. - Die letzte Domain lässt sich nicht entfernen. Ein Tippfehler bei `--remove-domains` ist ein Fehler und ändert nichts. - Ein verkürztes Limit wirkt alphabetisch: Überzählige Router werden verworfen. ## 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://: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:`. 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://: # 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 (`:`), Container, Compose-Verzeichnis, Health und das Produkt aus `sultan.accountant.billing`. Im Störfall: ```bash sultan accountant where kunde-a.de # Backend :, Container, Compose-Verzeichnis, seit wann, wo vorher sultan accountant routes --tenant=k10042 ``` Einrichtung: `sultan accountant invite --payee=` 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 ` (WARN bei gestoppt, Policy-Verstoß, unhealthy, Knoten getrennt; Wartung bleibt OK) mit Ziel `http://:` und Compose-Verzeichnis im Text, je Ingress-Server `Sultan Payee ` (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 # 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 | |---|---|---| | 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 | – |