Files
sultan/.ai/sultan-system-architecture.md
T
br 9861c25949 init des architektur beschreibenden MD...
noch kein code, nur REadme, ai.agent md s
2026-09-29 22:45:38 +02:00

369 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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: <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
```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"
```