18 KiB
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
- Datenebene unabhängig. DNS und DHCP laufen weiter, wenn Server, Netzwerk oder Lizenz ausfallen. Ein Agent behält immer die letzte funktionierende Konfiguration.
- Deterministische, typisierte Konfigurationserzeugung ohne Text-Templates (0014).
- Kein Anwenden ohne Prüfung: Signatur → Syntaxtest → atomare Aktivierung → Health-Check → sonst Rollback.
- Ausgehende Verbindungen vom Agent, mTLS mit eigener CA (0007, 0008).
- 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
- Bundle empfangen, Signatur und Agent-ID prüfen, Revision > zuletzt angewendete.
- Artefakte nach
revisions/<n>/schreiben (atomar), SHA-256 gegen Manifest prüfen. - Staging erzeugen, Syntaxprüfung (
named-checkconf -z,named-checkzone,kea-dhcp4 -t). - Aktivieren: Dateien atomar ersetzen,
rndc reconfig/reloadbzw. Keaconfig-reloadüber den Control-Socket. - Health-Check: DNS-Testabfrage gegen eine gerade ausgerollte Zone bzw. Kea
status-get/config-get-Vergleich. - 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
Makefileals 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ürlinux/amd64undlinux/arm64, Tarballs,.deb/.rpm(nfpm),SHA256SUMSund – mit SecretRELEASE_SIGNING_KEY– die Ed25519-SignaturSHA256SUMS.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 |