# Architektur Dieses Dokument beschreibt den Aufbau der DDI-Suite (Arbeitstitel **ddi-suite**). Die Begründungen der einzelnen Entscheidungen stehen in den [ADRs](adr/README.md), der Auftrag in [PROJECT_BRIEF.md](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](adr/0014-konfigurationserzeugung.md)). 3. **Kein Anwenden ohne Prüfung:** Signatur → Syntaxtest → atomare Aktivierung → Health-Check → sonst Rollback. 4. **Ausgehende Verbindungen vom Agent**, mTLS mit eigener CA ([0007](adr/0007-server-agent-grpc-mtls.md), [0008](adr/0008-interne-ca-enrollment.md)). 5. **Alles Umgebungsspezifische aus Konfiguration**, Namen zentral ([0018](adr/0018-zentrale-benennung.md)). ## 2. Repository Monorepo mit einem Go-Modul `git.beugsiud.de/beug.k/DDI` ([0002](adr/0002-monorepo-ein-go-modul-lizenzgrenzen.md)). | 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](adr/0021-agent-strecke-m3.md). 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](adr/0022-ausrollen-pull-push-m4.md)), 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](adr/0025-dhcp-pro-adresse-pruefungen-ui.md), 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](adr/0009-signierte-config-bundles.md)). ### 3.4 Lizenz Zustandsautomat `trial → valid ⇄ grace → read_only` ([0016](adr/0016-lizenzierung-keygen.md)). `read_only` sperrt zentral alle schreibenden API-Aufrufe und das Ausrollen. Agents und Datenebene sind nie betroffen. ## 4. Agents ### 4.1 Kern und Rollen ```go // 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](adr/0010-agent-binaries-pro-rolle.md)). ### 4.2 Anwenden 1. Bundle empfangen, **Signatur** und Agent-ID prüfen, Revision > zuletzt angewendete. 2. Artefakte nach `revisions//` 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](adr/0026-eintraege-in-dynamischen-zonen.md)). Primary/Secondary mit NOTIFY und TSIG-gesichertem AXFR/IXFR (M5). Details: [0004](adr/0004-dns-engine-bind9.md). ### 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](adr/0005-dhcp-engine-kea.md)). ## 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](SECURITY.md#6-was-nicht-geschützt-ist)) | Vertrauenskette und Schlüssel: - **Master-Key** (Datei, nicht in der DB) verschlüsselt alle Geheimnisse in der DB ([0015](adr/0015-geheimnisse-verschluesselt.md)). - **CA-Schlüssel** signiert Server- und Agent-Zertifikate ([0008](adr/0008-interne-ca-enrollment.md)). - **Bundle-Signaturschlüssel** (Ed25519, getrennt von der CA) signiert Konfigurationen ([0009](adr/0009-signierte-config-bundles.md)). Bedrohungsmodell, Schlüsselverwaltung und Härtung: [SECURITY.md](SECURITY.md). Betrieb (Sicherung, Upgrade, Rotation, Fehlersuche): [OPERATIONS.md](OPERATIONS.md). Ports, Versionen und gemessene Richtwerte: [SYSTEM-REQUIREMENTS.md](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](adr/0011-agent-zustand-in-dateien.md)) | | `/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](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](adr/0024-auslieferung-installer-m6.md)): 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](adr/0003-git-host-gitea-actions.md)). 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](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](../README.md). ## 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](SYSTEM-REQUIREMENTS.md#4-messungen). 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 |