17 KiB
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:
- In-Service-Deklaration: Backends deklarieren ihr Routing direkt am Dienst (native Traefik-Labels in Docker oder Drop-in-Konfigurationen unter
/etc/sultan.d/). - Dynamische Registrierung: Ein leichtgewichtiger Agent überträgt diese Konfiguration kryptografisch gesichert über einen langlebigen, ressourcenschonenden Keepalive-Stream an den zentralen Ingress.
- 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.
- Integrierte Autorisierung & Monetarisierung: Mandantensichere Capability-Lizenzen via Ed25519-Kryptografie, Lease-System und automatisierte Überwachung von Lizenzfristen.
2. Netzwerkmodell (Network Model)
2.1 Topologie
[ Ö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
- 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.
- 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.
- 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:
- Admin erzeugt kurzlebigen Einladungs-Token auf dem Server (
sltn_token_...). - 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). - Server speichert den Public Key des Agenten, weist ihm eine Mandanten-ID zu und signiert das initiale Berechtigungszertifikat (Capability Lease).
- Der Einladungs-Token wird entwertet; alle künftigen Heartbeats werden kryptografisch vom Agenten mit
node.keysigniert.
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, obkunde.deim Feldallowed_domainsder 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)
{
"$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:
{
"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 wiere_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):
-- 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:
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
- Watcher Engine:
- Docker Engine: Lauscht am
/var/run/docker.sockvia Docker Events API. Filtert nach Labels mit Präfixtraefik.. - File Provider: Überwacht Verzeichnis
/etc/sultan.d/*.confvia Inotify auf Konfigurationsdateien im INI/YAML-Format.
- 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.
- Transport Engine:
- Verwaltet den permanenten TLS-Stream mit 45s-Keepalive.
- Führt automatischen Reconnect mit exponentiellem Backoff durch.
5.2 Server-Subsysteme
- Stream Hub:
- Hält offene Sockets aller Agents im RAM.
- Verarbeitet State-Pushes und führt Dead-Node-Detection durch (Timeout: 90s).
- 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-configbereit.
- 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 mitRetry-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:
# Syntax: <Status> <ServiceName> <Metriken> <Nachrichtentext>
# 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
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)
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"