# CLAUDE.md # Sultan: High-Performance Distributed Ingress & ZeroTrust Service Discovery Dieses Dokument beschreibt das vollständige Architektur-, Daten-, Netzwerk- und Anwendungsmodell des Projekts **Sultan**. Es dient als Kontext und Referenz für Software-Architekten und AI-gestützte Coding-Agenten (z. B. via Continue in VSCodium). --- ## 1. Executive Summary & Problemstellung Klassische Ingress-Lösungen wie Nginx Proxy Manager (NPM) erfordern eine manuelle, GUI-basierte Konfiguration des Routings. In verteilten Umgebungen (Mehrere Proxmox-Cluster, Docker-Hosts, Bare-Metal-LAMP/Apache-Server, Kunden-Setups hinter DS-Lite/CGNAT) führt dies zu Dokumentationsbrüchen, toten Routen und hohem Wartungsaufwand. **Sultan** löst dies durch eine entkoppelte, zweigeteilte Architektur: 1. **In-Service-Deklaration:** Backends deklarieren ihr Routing direkt am Dienst (native Traefik-Labels in Docker oder Drop-in-Konfigurationen unter `/etc/sultan.d/`). 2. **Dynamische Registrierung:** Ein leichtgewichtiger Agent überträgt diese Konfiguration kryptografisch gesichert über einen langlebigen, ressourcenschonenden Keepalive-Stream an den zentralen Ingress. 3. **ZeroTrust & Public Reachability:** Optionale Anbindung über ein WireGuard-Mesh (z. B. NetBird), um auch Hosts ohne statische IPv4/IPv6 oder hinter NAT/Firewalls öffentlich mit TLS bereitzustellen. 4. **Integrierte Autorisierung & Monetarisierung:** Mandantensichere Capability-Lizenzen via Ed25519-Kryptografie, Lease-System und automatisierte Überwachung von Lizenzfristen. --- ## 2. Netzwerkmodell (Network Model) ### 2.1 Topologie ```text [ Öffentliches Internet ] │ │ HTTP (80) / HTTPS (443) / TCP-Streams ▼ ┌────────────────────────────────────────────────────────┐ │ Ingress-Knoten (Zentrales Rechenzentrum / Public IP) │ │ │ │ ┌────────────────────────────────────────────────┐ │ │ │ Traefik v3 (Ingress / ACME DNS-01 Validierung) │ │ │ └───────────────────────▲────────────────────────┘ │ │ │ HTTP Poll (:8080) │ │ │ (/v1/traefik-config) │ │ ┌───────────────────────┴────────────────────────┐ │ │ │ Sultan-Server (Registry / Policy Engine) │ │ │ └───────────────────────▲────────────────────────┘ │ │ │ │ │ │ Control Plane: │ │ │ TLS-Stream (:8443) │ │ │ (45s Heartbeat, │ │ │ Instant Event Push) │ │ │ │ │ ┌───────────────────────┴────────────────────────┐ │ │ │ NetBird / WireGuard Gateway (100.64.0.1) │ │ │ └───────────────────────▲────────────────────────┘ │ └───────────────────────────┼────────────────────────────┘ │ ZeroTrust WireGuard Tunnel (Traffic) │ ┌──────────────────┴──────────────────┐ │ │ ┌────────┴──────────────┐ ┌──────────┴─────────────┐ │ Client-Host A │ │ Client-Host B │ │ (Bare-Metal / LAMP) │ │ (Docker-Compose Stack) │ │ │ │ │ │ ┌───────────────────┐ │ │ ┌────────────────────┐ │ │ │ sultan-agent │ │ │ │ sultan-agent │ │ │ │ (File-Watcher) │ │ │ │ (Docker-Socket) │ │ │ └───────────────────┘ │ │ └────────────────────┘ │ │ ┌───────────────────┐ │ │ ┌────────────────────┐ │ │ │ Apache / VHost │ │ │ │ Web-Container │ │ │ └───────────────────┘ │ │ └────────────────────┘ │ └───────────────────────┘ └────────────────────────┘ ``` ### 2.2 Datenfluss & Verbindungstypen 1. **Control Plane (Agent -> Server):** * **Transport:** Ausgehende Verbindung vom Agenten zum Server über TCP `:8443` (TLS). * **Firewall-Freundlichkeit:** Reine Outbound-Verbindung; funktioniert hinter DS-Lite, Mobilfunk, Carrier-Grade-NAT und Unternehmens-Firewalls ohne Portweiterleitung. * **Keepalive-Mechanik:** Persistenter HTTP/2- oder SSE-Stream. Alle 45 Sekunden wird ein minimales Keepalive-Byte (`:\n\n`) ausgetauscht. Verhindert das Schließen von State-Tables in NAT-Routern (Conntrack Timeout). * **Event-Driven Push:** Änderungen an Containern (Start/Stop) oder Notfall-Sperren werden verzögerungsfrei (Latenz < 5 ms) über den bestehenden Stream übertragen. 2. **Data Plane (Traffic: Besucher -> Backend):** * **Direkt:** Liegen Ingress und Backend im selben VLAN/LAN, routet Traefik direkt auf die interne LAN-IP. * **ZeroTrust Overlay (NetBird):** Sitzt das Backend im Fremdnetz/Homelab, routet Traefik auf die WireGuard-Overlay-IP (`100.64.x.y`). End-to-End verschlüsselt. 3. **Ingress zu Traefik:** * Traefik pollt den lokalen Sultan-Server via HTTP-Provider: `GET http://127.0.0.1:8080/v1/traefik-config`. * Intervall: 2–5 Sekunden. Traefik verarbeitet die In-Memory-Konfiguration ohne Neustart und ohne Verbindungsabbrüche. --- ## 3. Sicherheits- und Vertrauensmodell ### 3.1 Transport-Verschlüsselung (TLS ohne CA-Zirkus) * **Server-Zertifikat:** Generiert sich beim ersten Start selbst (Self-Signed / Snakeoil ECDSA P-256). * **Vertrauensmodell:** Trust-on-First-Use (TOFU) analog zu SSH. Der Agent verifiziert beim ersten Connect den SHA256-Fingerprint des Servers und speichert ihn in `known_servers`. * **Optional:** Unterstützung für explizit gemountete Root-CA-Zertifikate. ### 3.2 Authentifizierung & Enrollment (CSR-Handshake) * Private Keys werden **ausschließlich lokal auf dem Ziel-Host** generiert (Ed25519) und verlassen diesen niemals. * **Enrollment:** 1. Admin erzeugt kurzlebigen Einladungs-Token auf dem Server (`sltn_token_...`). 2. Agent generiert lokalen Key (`node.key`) und sendet seinen Public Key (`node.pub`) zusammen mit dem Einladungs-Token an den Server (`POST /api/v1/enroll`). 3. Server speichert den Public Key des Agenten, weist ihm eine Mandanten-ID zu und signiert das initiale Berechtigungszertifikat (Capability Lease). 4. Der Einladungs-Token wird entwertet; alle künftigen Heartbeats werden kryptografisch vom Agenten mit `node.key` signiert. ### 3.3 Autorisierung & Domain-Ownership-Schutz Jeder Payload vom Agenten an den Server muss vom Server gegen die zugewiesene Policy geprüft werden: * **Domain Spoofing Protection:** Beansprucht ein Agent ein Label `Host('kunde.de')`, prüft der Server, ob `kunde.de` im Feld `allowed_domains` der Mandanten-Lizenz hinterlegt ist. Falls nicht: Ablehnung mit Sicherheits-Audit-Log. * **Route Limits:** Übersteigt die Anzahl der deklarierten Routen das Limit (`max_routes`), werden überzählige Routen verworfen. --- ## 4. Datenmodell (Data Specifications) ### 4.1 Lizenz & Capabilities (`license.json`) ```json { "$schema": "[http://json-schema.org/draft-07/schema#](http://json-schema.org/draft-07/schema#)", "title": "SultanCapabilities", "type": "object", "properties": { "tenant_id": { "type": "string" }, "tier": { "type": "string", "enum": ["community", "pro", "enterprise"] }, "max_routes": { "type": "integer", "description": "-1 für unbegrenzt" }, "allowed_domains": { "type": "array", "items": { "type": "string" } }, "features": { "type": "array", "items": { "type": "string", "enum": ["websockets", "tcp_stream", "custom_middleware"] } }, "valid_until": { "type": "string", "format": "date-time" }, "signature": { "type": "string", "description": "Ed25519 Signatur des Server-Master-Keys über den Payload" } }, "required": ["tenant_id", "tier", "max_routes", "allowed_domains", "valid_until", "signature"] } ``` ### 4.2 Stream Event Protocol (Agent <-> Server) Das bidirektionale Streaming-Format nutzt standardisierte JSON-Events: ```json { "event_id": "evt_01J8Z9X2Y3...", "timestamp": 1727618531, "type": "service_state | lease_renew | node_command | error", "node_id": "node-proxmox-vm-42", "payload": {} } ``` * **Typ `service_state` (Agent -> Server):** Enthält die vollständige Liste der aktuell aktiven Traefik-Labels des Knotens. * **Typ `node_command` (Server -> Agent):** Enthält Aktionen wie `re_enroll`, `drain`, `maintenance`. * **Typ `lease_renew` (Server -> Agent):** Liefert ein verlängertes Krypto-Lease an den Agenten zurück. ### 4.3 SQLite Datenbankschema (Server State) Der Server persistiert Mandanten, Tokens und Richtlinien lokal in SQLite (`/var/lib/sultan/sultan.db`): ```sql -- Mandanten & Abonnements CREATE TABLE tenants ( id TEXT PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL, tier TEXT NOT NULL DEFAULT 'community', max_routes INTEGER NOT NULL DEFAULT 5, allowed_domains TEXT NOT NULL, -- JSON-Array: ["*.firma.de", "shop.de"] valid_until DATETIME NOT NULL, -- Ablaufdatum des Vertrags / Abos status TEXT NOT NULL DEFAULT 'active', -- active, suspended_payment, quarantined created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- Registrierte Agenten / Knoten CREATE TABLE nodes ( id TEXT PRIMARY KEY, tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, hostname TEXT NOT NULL, public_key TEXT NOT NULL UNIQUE, -- Ed25519 Public Key des Agenten last_seen DATETIME NOT NULL, ip_address TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'online', -- online, offline, drained FOREIGN KEY(tenant_id) REFERENCES tenants(id) ); -- Einmalige Enrollment-Tokens CREATE TABLE enrollment_tokens ( token_hash TEXT PRIMARY KEY, tenant_id TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, expires_at DATETIME NOT NULL, used INTEGER NOT NULL DEFAULT 0 ); ``` --- ## 5. Anwendungsmodell (Application Model) Das gesamte System ist als **Single-Binary-Architektur** in Go implementiert: ```text sultan ├── server # Führt Registry, Stream-Hub, Policy-Engine & Traefik-Provider aus ├── agent # Führt Docker-/File-Watcher aus und verbindet sich zum Server └── token # CLI-Tools zur Schlüsselverwaltung, Enrollment- & Lizenzausstellung ``` ### 5.1 Agenten-Subsysteme 1. **Watcher Engine:** * **Docker Engine:** Lauscht am `/var/run/docker.sock` via Docker Events API. Filtert nach Labels mit Präfix `traefik.`. * **File Provider:** Überwacht Verzeichnis `/etc/sultan.d/*.conf` via Inotify auf Konfigurationsdateien im INI/YAML-Format. 2. **State Serializer:** * Extrahiert Services, Host-Ports und Routen. * Injiziert automatisch die Ziel-IP (LAN-IP oder NetBird-IP) in die Server-URL-Definitionen für Traefik. 3. **Transport Engine:** * Verwaltet den permanenten TLS-Stream mit 45s-Keepalive. * Führt automatischen Reconnect mit exponentiellem Backoff durch. ### 5.2 Server-Subsysteme 1. **Stream Hub:** * Hält offene Sockets aller Agents im RAM. * Verarbeitet State-Pushes und führt Dead-Node-Detection durch (Timeout: 90s). 2. **Dynamic Config Aggregator:** * Führt die flachen Traefik-Labels aller aktiven Knoten zusammen. * Wandelt die Labels in das verschachtelte Traefik-JSON-Format um. * Stellt `GET /v1/traefik-config` bereit. 3. **Policy & Interception Engine:** * **Sperrung bei Zahlungsverzug:** Modifiziert den Traefik-Router auf einen internen Redirect zu einer Bezahlseite (`HTTP 402 / Payment Required`). * **Notfall-Wartung:** Schlägt das Backend fehl oder meldet der Agent `service_down`, liefert Traefik eine statische Statusseite mit `Retry-After: 60` (`HTTP 503`). --- ## 6. Überwachung, Lizenzfristen & Alerting (Checkmk-Integration) Um zu verhindern, dass Kunden-Instanzen bei Ablauf von Quartals- oder Jahresabonnements unerwartet in den Wartungsmodus fallen, implementiert der Sultan-Server Schnittstellen für **Checkmk** und externe Alerting-Pipelines. ### 6.1 Checkmk Local Check Protocol Der Server stellt einen internen Management-Endpunkt bereit: `GET /metrics/checkmk` (oder auslesbar über ein lokales Checkmk-Agent-Plugin-Script `/usr/lib/check_mk_agent/local/sultan_licenses`). Das Ausgabeformat entspricht der nativen Checkmk Local-Check-Syntax: ```text # Syntax: # 0 = OK, 1 = WARN, 2 = CRIT, 3 = UNKNOWN 0 "Sultan_Tenant_kunde_schmidt" days_left=74;30;14 OK - Abo aktiv bis 2026-12-15 (74 Tage verbleibend) 1 "Sultan_Tenant_meier_gmbh" days_left=12;30;14 WARN - Abo laeuft in 12 Tagen ab! Verlaengerung erforderlich. 2 "Sultan_Tenant_alt_ag" days_left=0;30;14 CRITICAL - Abo seit 1 Tagen abgelaufen! Status: suspended_payment ``` ### 6.2 Schwellenwerte & Alarmierungskaskade * **30 Tage vor Ablauf:** Warnung im Checkmk-Dashboard (Monitoring-Status: `WARN`). * **14 Tage vor Ablauf:** Kritischer Alert (`CRIT`). Automatischer E-Mail-Dispatch / Ticket-Erstellung an Kunden und Admin: *„Abo-Verlängerung erforderlich – bitte Rechnung begleichen“*. * **Tag 0 (Ablaufzeitpunkt):** * Status wechselt in SQLite auf `expired`. * Traefik leitet Domain auf Informations-Landingpage um. * Checkmk triggert Alarm: `CRITICAL - Service offline geschaltet`. --- ## 7. Referenzkonfigurationen ### 7.1 Compose-Setup: Ingress-Knoten ```yaml services: traefik: image: traefik:v3.1 container_name: traefik-ingress restart: unless-stopped network_mode: host command: - "--providers.http.endpoint=[http://127.0.0.1:8080/v1/traefik-config](http://127.0.0.1:8080/v1/traefik-config)" - "--providers.http.pollInterval=3s" - "--entrypoints.web.address=:80" - "--entrypoints.web.http.redirections.entryPoint.to=websecure" - "--entrypoints.web.http.redirections.entryPoint.scheme=https" - "--entrypoints.websecure.address=:443" - "--certificatesresolvers.dns-resolver.acme.dnschallenge=true" - "--certificatesresolvers.dns-resolver.acme.dnschallenge.provider=rfc2136" volumes: - ./acme.json:/etc/traefik/acme.json sultan-server: image: sultan:latest container_name: sultan-server restart: unless-stopped command: ["sultan", "server", "--data-dir=/var/lib/sultan"] ports: - "8443:8443" # Agent Inbound (TLS Stream) - "127.0.0.1:8080:8080" # Traefik Config Provider & Checkmk Metrics volumes: - ./sultan-data:/var/lib/sultan ``` ### 7.2 Compose-Setup: Kunden-Host (Backend mit Docker) ```yaml services: sultan-agent: image: sultan:latest container_name: sultan-agent restart: unless-stopped command: - "sultan" - "agent" - "--server=[https://ingress.dein-netz.de:8443](https://ingress.dein-netz.de:8443)" - "--token=sltn_enroll_01J8Z9..." volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./agent-data:/var/lib/sultan kunde-wordpress: image: wordpress:latest restart: unless-stopped labels: - "traefik.enable=true" - "traefik.http.routers.wp.rule=Host(`kunde.de`) || Host(`www.kunde.de`)" - "traefik.http.routers.wp.entrypoints=websecure" - "traefik.http.routers.wp.tls=true" - "traefik.http.routers.wp.tls.certresolver=dns-resolver" - "traefik.http.middlewares.wp-www.redirectregex.regex=^https?://kunde\\.de/(.*)" - "traefik.http.middlewares.wp-www.redirectregex.replacement=[https://www.kunde.de/$$](https://www.kunde.de/$$){1}" - "traefik.http.middlewares.wp-www.redirectregex.permanent=true" - "traefik.http.routers.wp.middlewares=wp-www" - "traefik.http.services.wp.loadbalancer.server.port=80" ```