Files
DDI/docs/ARCHITECTURE.md

18 KiB
Raw Permalink Blame History

Architektur

Dieses Dokument beschreibt den Aufbau der DDI-Suite (Arbeitstitel ddi-suite). Die Begründungen der einzelnen Entscheidungen stehen in den ADRs, der Auftrag in PROJECT_BRIEF.md.

1. Überblick

 Browser ──HTTPS :8443──▶ Management-Server (Go)  ◀──▶ PostgreSQL
                            │ REST-API (OpenAPI), Web-UI (eingebettet), Auth/RBAC,
                            │ Audit, Lizenz (Keygen), Config-Generator, interne CA,
                            │ Bundle-Signatur
                            │
                            │ gRPC/mTLS :8444 (Agent verbindet sich ausgehend)
               ┌────────────┴─────────────────────┐
               ▼                                  ▼
   ddi-agent-dns + BIND 9               ddi-agent-dhcp + Kea DHCPv4
   - Bundle verifizieren                - Bundle verifizieren
   - prüfen, aktivieren, Health         - prüfen, aktivieren, Health
   - Rollback bei Fehler                - Rollback bei Fehler
                                        - Leases melden

Die DNS- und DHCP-Engines (BIND 9, Kea) sind bewährte Fremdsoftware. Die Suite erzeugt deren Konfiguration und steuert sie, ersetzt sie aber nicht.

Grundsätze

  1. Datenebene unabhängig. DNS und DHCP laufen weiter, wenn Server, Netzwerk oder Lizenz ausfallen. Ein Agent behält immer die letzte funktionierende Konfiguration.
  2. Deterministische, typisierte Konfigurationserzeugung ohne Text-Templates (0014).
  3. Kein Anwenden ohne Prüfung: Signatur → Syntaxtest → atomare Aktivierung → Health-Check → sonst Rollback.
  4. Ausgehende Verbindungen vom Agent, mTLS mit eigener CA (0007, 0008).
  5. Alles Umgebungsspezifische aus Konfiguration, Namen zentral (0018).

2. Repository

Monorepo mit einem Go-Modul git.beugsiud.de/beug.k/DDI (0002).

Pfad Inhalt Lizenz
proto/ gRPC-Definitionen (ddi/agent/v1), generierter Go-Code unter proto/gen/ Apache-2.0
internal/ Von Server und Agents gemeinsam genutzter Code (Benennung, Build-Info, Logging) Apache-2.0
agent/ Agent-Kern (core), Rollen (role/dns, role/dhcp), Binaries (cmd/…) Apache-2.0
server/ Management-Server: cmd/ddi-server, api/ (OpenAPI), internal/… (Domäne, Speicher, Generator, CA, Lizenz) proprietär
web/ Frontend (React, TS, Vite), eingebettet über web/embed.go proprietär
deploy/ systemd-Units, Beispielkonfigurationen, compose/ für Entwicklung und Tests Apache-2.0
scripts/ install.sh, Build-, Release- und Prüfskripte Apache-2.0
tools/ keygen-setup (Lizenzserver einrichten, nicht Teil der Auslieferung) proprietär
test/ Integrations- und End-to-End-Tests mit echten BIND-/Kea-Containern Apache-2.0
docs/ Dokumentation (Deutsch) und ADRs –

Abhängigkeitsregel (per depguard geprüft): agent/, internal/ und proto/ importieren nie server/.

3. Management-Server

3.1 Schichten

server/cmd/ddi-server          Befehle serve, migrate, bootstrap-admin, gen-master-key
server/api                     OpenAPI-Spezifikation (eingebettet)
server/internal/app            Verdrahtung, Hintergrundjobs, Metriken
server/internal/config         YAML-Datei + Umgebungsvariablen
server/internal/httpserver     Listener, Health, Middleware (Request-ID, Log, Header, Recover)
server/internal/httpapi        REST-API: Routentabelle, Sitzung/CSRF/Rechte/Schreibschutz, OIDC-Flow
server/internal/service        Anwendungsfälle; jede Änderung schreibt ihr Audit in derselben Transaktion
server/internal/ipam           Fachlogik Netze/DHCP-Bereiche/Adressen (rein): Validierung, effektive Pools,
                               Auslastung, Adresskarte, Änderungsvorschau
server/internal/auth           Argon2id, TOTP, Tokens, Rate-Limiter, Rollen und Rechte
server/internal/license        LicenseProvider, KeygenProvider, Lizenzdateien, Zustandsautomat, Manager
server/internal/secrets        AES-256-GCM mit Master-Key (Schlüssel-IDs, Rotation)
server/internal/store          PostgreSQL-Repositories (pgx), Migrationen (goose, embed)
server/internal/configgen      reine Generatoren für BIND und Kea (derive, bindgen, keagen)
server/internal/agentapi       gRPC-Dienst für Agents (Enrollment, Stream, Bundles, Leases), mTLS-Prüfung
server/internal/agenthub       offene Agent-Streams: Push neuer Revisionen, Trennen beim Sperren
server/internal/events         Ereignisbus für Live-Aktualisierungen (SSE unter /api/v1/events)
server/internal/textdiff       Zeilen-Diff (Myers) im Unified-Format für Revisionen
server/internal/pki            Agent-CA, Bundle-Signaturschlüssel, Zertifikate ausstellen und prüfen
internal/bundle                Bundle-Manifest, Signatur und Prüfung (Ed25519), von Server und Agent genutzt
internal/agentpki              Agent-ID im Zertifikat, CA-Fingerprint, TLS-Einstellungen der Agents
agent/core                     Agent-Kern: Konfiguration, Zustand, Enrollment, Verbindung, Ablauf, Leases
agent/role/dns, agent/role/dhcp  Rollen für BIND und Kea

Fachlogik (ipam, auth, license) kennt weder HTTP noch SQL. service orchestriert Transaktionen; Handler und Repositories sind Adapter um sie herum.

3.2 Datenmodell (Kern)

Umgesetzt in M1 (server/internal/store/migrations): app_settings (u. a. Installationszeitpunkt, OIDC mit verschlüsseltem Secret), users, sessions (nur Token-Hash), audit_log, networks, dhcp_pools (bis Migration 00006), addresses (Typen static, reserved, manual_dhcp, dynamic), saved_filters, license_state (verschlüsselt).

Die Datenbank erzwingt die Kernregeln selbst:

  • keine überlappenden Netze (EXCLUDE USING gist (cidr inet_ops WITH &&)), Gateway im Netz
  • MAC Pflicht bei manual_dhcp, eine Reservierung pro MAC und Netz
  • IP eindeutig je Netz, Name (FQDN) eindeutig je Zone für Einträge mit DNS-Records
  • Adresse liegt im Netz (Trigger), Host-Adresse statt Präfix

Optimistisches Sperren über version; alle Änderungen an den Adressen eines Netzes sperren die Netzzeile und sind dadurch serialisiert.

M3 (Migration 00003): agents, enrollment_tokens, pki_keys (verschlüsselt), config_revisions und config_revision_files (unveränderlich per Trigger), deployments, dhcp_leases; Einzelheiten in ADR 0021. M4 (Migration 00004): agents.target_revision, offene Push-Deployments, config_revisions.source (Generator-Eingabe für Rollbacks); Entwürfe ergeben sich aus Audit-Log und Konfigurationsvergleich (ADR 0022), eine eigene pending_change-Tabelle entfällt. Ab M5 folgen zone (forward/reverse, Modus managed/dynamic), record und cluster (dns/dhcp).

DHCP pro Adresse (ADR 0025, Migration 00006): Pools werden nicht mehr gepflegt; dhcp_pools entfällt. Der DHCP-Bereich eines Netzes mit dhcp_enabled ist die Menge seiner dynamic-Einträge (nur IPv4, nicht die Gateway-Adresse); jede ununterbrochene Folge davon wird ein Kea-Pool (ipam.Ranges, in der API als abgeleitetes Network.pools). Damit liegen static-, reserved- und manual_dhcp-Einträge automatisch außerhalb dessen, was Kea vergibt.

Typ DHCP DNS (A/AAAA, PTR)
static nein ja
reserved nein (gesperrt) nein
manual_dhcp Reservierung MAC → IP ja
dynamic IP gehört zum DHCP-Bereich (ohne MAC) ja (ab Generator-Eingabe Version 3)

dynamic-Einträge ohne Hostnamen erhalten beim Anlegen (Formular, CSV, Bereichsoperation, Typwechsel, Massenänderung) einen zufälligen Namen dyn- + 8 Zeichen [a-z0-9]; der eindeutige FQDN-Index umfasst static, manual_dhcp und dynamic. Leases ändern nur vorhandene Einträge (Lease-Felder, vom Client gemeldeter Name in lease_hostname, bei dynamic die MAC), nie den Hostnamen, und legen keine Einträge an. Bereichsoperationen (POST /networks/{id}/range) legen bzw. löschen bis zu 65.536 Einträge in einer Anweisung mit einem Audit-Eintrag.

3.3 Änderungsfluss

UI/API-Änderung ──▶ Validierung ──▶ DB (Fachdaten + pending_change + Audit)
                                         │  „X Änderungen warten auf Ausrollen“
Ausrollen (Benutzer) ───────────────────▶│
                                         ▼
            Generator (rein) ──▶ ConfigRevision N (strukturiert + Artefakte + Diff zu N-1)
                                         ▼
            Bundle je Agent (Manifest + Artefakte, Ed25519-signiert)
                                         ▼
  auto_pull: Agent fragt im Intervall nach    │    Push: Kommando „apply N“ über Stream
                                         ▼
            Agent wendet an ──▶ Deployment-Ergebnis (Erfolg/Fehler/Rollback) ──▶ Server ──▶ UI live

Ein Rollback im UI erzeugt eine neue Revision mit altem Inhalt (0009).

3.4 Lizenz

Zustandsautomat trial → valid ⇄ grace → read_only (0016). read_only sperrt zentral alle schreibenden API-Aufrufe und das Ausrollen. Agents und Datenebene sind nie betroffen.

4. Agents

4.1 Kern und Rollen

// Vereinfachte Schnittstellen (Implementierung ab M3, Standalone ab M7).
type ConfigSource interface {          // woher kommt die Soll-Konfiguration?
    Desired(ctx context.Context) (<-chan VerifiedBundle, error)
    Report(ctx context.Context, r DeploymentResult) error
}

type Role interface {                  // wie wird sie angewendet?
    Validate(ctx context.Context, stagingDir string) error   // named-checkconf / kea-dhcp4 -t
    Activate(ctx context.Context, stagingDir string) error   // atomar umschalten + reload
    Healthy(ctx context.Context) error                       // Testabfrage / Control-Socket
}

ServerSource (gRPC) kommt in M3, FileSource (YAML, Standalone) in M7. Der Kern ist für beide identisch (0010).

4.2 Anwenden

  1. Bundle empfangen, Signatur und Agent-ID prüfen, Revision > zuletzt angewendete.
  2. Artefakte nach revisions/<n>/ schreiben (atomar), SHA-256 gegen Manifest prüfen.
  3. Staging erzeugen, Syntaxprüfung (named-checkconf -z, named-checkzone, kea-dhcp4 -t).
  4. Aktivieren: Dateien atomar ersetzen, rndc reconfig/reload bzw. Kea config-reload über den Control-Socket.
  5. Health-Check: DNS-Testabfrage gegen eine gerade ausgerollte Zone bzw. Kea status-get/config-get-Vergleich.
  6. Fehler in 3–5 → vorige funktionierende Revision wiederherstellen, erneut aktivieren, Fehler mit Details melden.

4.3 DNS-Rolle

Eine generierte Include-Datei plus Zonendateien der managed Zonen. Dynamische Zonen werden angelegt, aber deren Daten nie überschrieben; die Records, die DDI darin verwaltet (Adresstabelle, Records der Zone), gleicht der DNS-Agent per nsupdate mit dem Systemschlüssel ddi-agent-update ab (0026). Primary/Secondary mit NOTIFY und TSIG-gesichertem AXFR/IXFR (M5). Details: 0004.

4.4 DHCP-Rolle

Vollständige kea-dhcp4.conf, Pools aus den Folgen von dynamic-Einträgen (ADR 0025), Reservierungen aus manual_dhcp. Leases über lease_cmds (lease4-get-page), daraus Änderungsereignisse an den Server. HA über libdhcp_ha.so (M5). Nur frei verfügbare Kea-Bestandteile (0005).

5. Kommunikation und Sicherheit

Verbindung Richtung Port (Standard) Schutz
Web-UI/REST-API Browser → Server 8443/tcp TLS 1.2+, Sitzungscookie (Secure, HttpOnly, SameSite=Lax), CSRF-Token
Agent-Protokoll Agent → Server 8444/tcp TLS 1.3, mTLS (interne CA), Enrollment per Einmal-Token + CA-Hash
Lizenzprüfung Server → Keygen 443/tcp TLS, signierte Antworten (sofern Edition unterstützt)
DNS Clients → BIND 53/udp+tcp –
Zonentransfer Secondary ↔ Primary 53/tcp TSIG
DHCP Clients/Relays → Kea 67/udp (Antwort 68/udp) –
Kea-HA Kea ↔ Kea 8001/tcp (ha_port) HTTP mit Basic-Auth, noch ohne TLS (siehe SECURITY.md)

Vertrauenskette und Schlüssel:

  • Master-Key (Datei, nicht in der DB) verschlüsselt alle Geheimnisse in der DB (0015).
  • CA-Schlüssel signiert Server- und Agent-Zertifikate (0008).
  • Bundle-Signaturschlüssel (Ed25519, getrennt von der CA) signiert Konfigurationen (0009).

Bedrohungsmodell, Schlüsselverwaltung und Härtung: SECURITY.md. Betrieb (Sicherung, Upgrade, Rotation, Fehlersuche): OPERATIONS.md. Ports, Versionen und gemessene Richtwerte: SYSTEM-REQUIREMENTS.md.

6. Dateisystem auf den Zielsystemen

Pfad Zweck
/usr/local/bin/ddi-server, ddi-agent-dns, ddi-agent-dhcp Binaries
/etc/ddi-suite/ Konfiguration (server.yaml, agent-dns.yaml, …), master.key (nur Server)
/var/lib/ddi-suite/ Zustand (Agent-Identität und Revisionen, 0011)
/etc/bind/ddi-suite/ bzw. /var/named/ddi-suite/ vom DNS-Agent erzeugte BIND-Dateien
/etc/kea/kea-dhcp4.conf vom DHCP-Agent erzeugte Kea-Konfiguration

Jeder Dienst läuft unter einem eigenen Systembenutzer mit systemd-Härtung. Alle Namen kommen aus der zentralen Benennung.

7. Konfiguration

Server und Agents lesen eine YAML-Datei (Server: Pfad aus DDI_CONFIG_FILE, Beispiel deploy/config/server.dev.yaml). Unbekannte Schlüssel sind ein Fehler. Umgebungsvariablen mit Präfix DDI_ überschreiben einzelne Werte, damit Container und Tests ohne Datei auskommen.

Server-Variable Bedeutung
DDI_HTTP_LISTEN, DDI_TLS_CERT_FILE, DDI_TLS_KEY_FILE, DDI_PUBLIC_URL Listener, TLS, externe URL (OIDC-Redirect)
DDI_DATABASE_URL bzw. DDI_DATABASE_URL_FILE PostgreSQL
DDI_MASTER_KEY_FILE, DDI_STATE_DIR Master-Key, Zustandsverzeichnis
DDI_LOG_LEVEL, DDI_LOG_FORMAT Logging
DDI_SESSION_LIFETIME, DDI_SESSION_IDLE_TIMEOUT Sitzungen
DDI_LICENSE_GRACE_PERIOD, KEYGEN_URL, KEYGEN_PRODUCT_ID Lizenz (siehe LICENSING.md)
DDI_RETENTION_AUDIT_DAYS, DDI_RETENTION_REVISIONS_KEEP Aufbewahrung (retention.audit_days, retention.revisions_keep; 0 = alles behalten)

KEYGEN_ACCOUNT_ID und KEYGEN_PUBLIC_KEY gelten nur für Builds ohne eingebettete Werte (Entwicklung). Dauerangaben verstehen Go-Notation und Tage (14d).

Aufbewahrung (0024): Ist retention.audit_days oder retention.revisions_keep gesetzt, räumt der Server fünf Minuten nach dem Start und danach täglich auf. Gelöscht werden Audit-Einträge, die älter als die angegebene Zahl Tage sind, und Revisionen jenseits der neuesten revisions_keep (samt Dateien). Nie gelöscht werden die neueste Revision, Revisionen, die ein Agent anwendet, als letzte funktionierende kennt oder als Ziel hat, und Revisionen mit offenen Deployments. Ein Aufräumlauf, der etwas löscht, schreibt den Audit-Eintrag retention.cleanup mit den Anzahlen.

8. Build, CI und Auslieferung

  • Makefile als Einstieg: build, test, lint, fmt, generate, web, dev-up/dev-down, release.
  • CI: Gitea Actions unter .gitea/workflows/ (0003). Geprüft werden Go (Format, vet, golangci-lint, Tests, govulncheck), Protobuf (buf lint, generierter Code aktuell), Web (ESLint, Typecheck, Build, Tests, npm audit), Shell (shellcheck), SPDX-Header und die Entwicklungsumgebung (Compose-Smoke-Test).
  • Release bei Tag v*: Binaries für linux/amd64 und linux/arm64, Tarballs, .deb/.rpm (nfpm), SHA256SUMS und – mit Secret RELEASE_SIGNING_KEY – die Ed25519-Signatur SHA256SUMS.sig, Upload an das Gitea-Release (RELEASE.md).
  • Installer scripts/install.sh (M6) lädt die Artefakte und prüft die Prüfsummen.

9. Entwicklungsumgebung

deploy/compose/compose.dev.yaml startet PostgreSQL 17, BIND 9.20 und Kea DHCPv4 3.2 mit Beispielkonfigurationen. Server und Agents laufen in der Entwicklung direkt auf dem Host: make run-server (erzeugt beim ersten Mal local/master.key), make dev-admin legt den ersten Administrator an, cd web && npm run dev startet die Oberfläche mit Hot Reload. Siehe README.

10. Skalierung

Zielgröße der ersten Version sind bis ca. 50 Agents und einige zehntausend Adressen. Gemessene Werte für 5.000 und 50.000 Adressen stehen in SYSTEM-REQUIREMENTS.md. Damit es nach oben nicht verbaut ist, gilt:

  • Tabellen werden serverseitig paginiert und gefiltert. Indizes auf ip, mac, hostname.
  • Revisionen speichern strukturierte Daten plus Artefakte. Die Aufbewahrung ist konfigurierbar.
  • Agent-Streams sind leichtgewichtig (ein Stream pro Agent). Lease-Meldungen laufen gebündelt.
  • Mögliche spätere Schritte: Katalogzonen für Secondaries, inkrementelle Kea-Änderungen per host_cmds (frei ab Kea 3.0), mehrere Server-Instanzen hinter einem Load-Balancer (Sitzungen und Stream-Zuordnung über die Datenbank).

11. Meilensteine

Meilenstein Inhalt
M0 Fundament: Repo, Makefile, Lint, CI, Compose, ADRs, dieses Dokument
M1 Server-Kern: Schema, Auth, RBAC, Audit, Lizenz, REST-API, Netze/Adressen, UI-Grundgerüst
M2 Config-Generator mit Golden- und Integrationstests
M3 Agent verwaltet: Enrollment, mTLS, Signatur, Anwenden, Rollback, Leases
M4 Pull/Push, Entwürfe, Diff, Live-Status, Rollback im UI
M5 Cluster, Zonen, Records, dynamische Zonen, Kea-HA, Lease-Erfassung
M6 Installer, Release, Pakete, Systemvoraussetzungen mit Messungen, Betriebs- und Sicherheitsdoku
M7 (später) Standalone-Agent, DHCPv6, DNSSEC, Discovery