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

17 KiB
Raw Blame History

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

[ Ö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.
  1. 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.
  1. 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)

{
  "$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 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):

-- 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

  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.
  1. 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.
  1. 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).
  1. 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.
  1. 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:

# 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"