Files
sultan/docs/hoster.md
T

12 KiB
Raw Permalink Blame History

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. Wartung: siehe 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

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 + Image zot.chmln.de/sultan:<version>.
  3. Die Labels, passend zu deinen Traefik-Namen (Entrypoint, Certresolver):
    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, vor allem: stop statt down und sultan agent maintenance on.

5. Abnahme

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:

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:

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

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:

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

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 –