diff --git a/docs/BACKEND.md b/docs/BACKEND.md index 400ef26..7733780 100644 --- a/docs/BACKEND.md +++ b/docs/BACKEND.md @@ -82,7 +82,8 @@ listen_addr: ":8443" tls_cert: "certs/server.crt" tls_key: "certs/server.key" api_keys: - - "SICHERER_API_KEY" + - "SICHERER_API_KEY" # beliebig lang, wird in X-API-Key Header mitgegeben +jwt_secret: "LANGER_ZUFAELLIGER_STRING" # fuer Frontend-Login (JWT-Signierung) database: host: "127.0.0.1" @@ -93,6 +94,12 @@ database: sslmode: "disable" # "require" wenn PG ueber Netz ``` +Zufälligen JWT-Secret generieren: + +```bash +openssl rand -hex 32 +``` + Alternativ per Environment-Variablen: `RMM_LISTEN_ADDR`, `RMM_TLS_CERT`, `RMM_TLS_KEY`, `RMM_DB_HOST`, `RMM_DB_PASSWORD`. ## 6. Backend deployen und starten @@ -141,27 +148,285 @@ Beim ersten Start: - Retention Policy (90 Tage) und Compression Policy (nach 7 Tagen) werden automatisch gesetzt - Default-Admin `admin` / `YOUR_PASSWORD` wird erstellt -## Datenbank-Schema (automatisch) +## Datenbank-Schema (vollständig) -| Tabelle | Typ | Beschreibung | -|---------|-----|-------------| -| `agents` | Relational | Agent-Registry (ID, Name, Hostname, IP, Version, Heartbeat, customer_id, update_requested) | -| `system_data` | Relational | Letzte Systemdaten pro Agent (JSONB) | -| `config_backups` | Relational | OPNsense Config-Backups (dedupliziert per SHA256) | -| `agent_events` | Relational | Online/Offline/Connected Events | -| `customers` | Relational | Kundenstammdaten (Nummer, Name) | -| `users` | Relational | Benutzer (Username, bcrypt-Hash, Display-Name) | -| `agent_firmware` | Relational | Agent-Binaries pro Plattform+Version (BYTEA, SHA256) | -| `metrics` | TimescaleDB Hypertable | Time-Series Metriken (CPU, RAM, Disk, Network, Gateway) | +Alle Tabellen werden beim Backend-Start **automatisch angelegt** (Auto-Migration inkl. ALTER TABLE für neue Spalten). Kein manuelles Schema-Setup nötig. -### TimescaleDB Policies +--- -- **Retention**: Rohdaten werden nach 90 Tagen automatisch geloescht -- **Compression**: Daten aelter als 7 Tage werden komprimiert (segmentby: agent_id+metric) +### `agents` — Agent-Registry + +```sql +CREATE TABLE agents ( + id TEXT PRIMARY KEY, -- UUID des Agents (persistent) + name TEXT NOT NULL, -- Anzeigename (z.B. "OPN.intra.kunde.de") + hostname TEXT NOT NULL, -- Hostname der Firewall + ip TEXT NOT NULL, -- IP-Adresse des Agents + opnsense_version TEXT NOT NULL DEFAULT '', -- OPNsense-Version (z.B. "25.1.1") + agent_version TEXT NOT NULL DEFAULT '', -- Agent-Binary-Version (z.B. "1.0.3") + platform TEXT NOT NULL DEFAULT 'freebsd', -- Plattform: "freebsd", "linux" + registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- Erstregistrierung + last_heartbeat TIMESTAMPTZ, -- Letzter Heartbeat + customer_id INTEGER REFERENCES customers(id), -- Kundenzuordnung (nullable) + update_requested BOOLEAN NOT NULL DEFAULT FALSE -- Update-Flag fuer Updater-Daemon +); +CREATE INDEX idx_agents_customer ON agents(customer_id); +``` + +**Status-Berechnung** (kein DB-Feld, dynamisch im Backend): + +| Status | Bedingung | +|--------|-----------| +| `online` | `last_heartbeat` < 2 Minuten | +| `stale` | `last_heartbeat` < 5 Minuten | +| `offline` | `last_heartbeat` > 5 Minuten | +| `unknown` | `last_heartbeat` ist NULL | + +--- + +### `customers` — Kundenstammdaten + +```sql +CREATE TABLE customers ( + id SERIAL PRIMARY KEY, + number TEXT NOT NULL UNIQUE, -- RMM-Kundennummer (z.B. "K05068") + name TEXT NOT NULL, -- Kundenname + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +--- + +### `users` — Benutzer (Frontend-Login) + +```sql +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + username TEXT NOT NULL UNIQUE, + password_hash TEXT NOT NULL, -- bcrypt-Hash + display_name TEXT NOT NULL DEFAULT '', + role TEXT NOT NULL DEFAULT 'admin', -- aktuell nur "admin" + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + last_login TIMESTAMPTZ -- nullable +); +``` + +Standard-Admin wird beim ersten Start angelegt: `admin` / `Start!123` (sofort ändern). + +--- + +### `system_data` — Aktueller Systemzustand pro Agent + +```sql +CREATE TABLE system_data ( + agent_id TEXT PRIMARY KEY REFERENCES agents(id) ON DELETE CASCADE, + data_json JSONB NOT NULL, -- Vollständiger Heartbeat-Payload des Agents + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +--- + +### `config_backups` — Versionierte OPNsense-Configs + +```sql +CREATE TABLE config_backups ( + id SERIAL PRIMARY KEY, + agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE, + hash TEXT NOT NULL, -- SHA256 der config.xml (Deduplizierung) + size INTEGER NOT NULL, -- Grösse in Bytes + config_xml TEXT NOT NULL, -- Vollständige config.xml + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +CREATE INDEX idx_config_backups_agent ON config_backups(agent_id, created_at DESC); +CREATE INDEX idx_config_backups_hash ON config_backups(agent_id, hash); +``` + +Deduplizierung: gleicher SHA256 → kein neuer Eintrag. + +--- + +### `agent_events` — Online/Offline-Ereignisse + +```sql +CREATE TABLE agent_events ( + id SERIAL PRIMARY KEY, + agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE, + event_type TEXT NOT NULL, -- "online", "offline", "stale", "connected", "disconnected" + message TEXT, -- optionale Zusatzinfo + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +CREATE INDEX idx_agent_events_agent ON agent_events(agent_id, created_at DESC); +``` + +--- + +### `agent_firmware` — Agent-Binaries pro Plattform + +```sql +CREATE TABLE agent_firmware ( + version TEXT NOT NULL, -- z.B. "1.0.3" + platform TEXT NOT NULL DEFAULT 'freebsd', -- "freebsd", "linux", "windows" + hash TEXT NOT NULL, -- SHA256 des Binaries + binary_data BYTEA NOT NULL, -- Das Binary selbst + uploaded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE (version, platform) +); +``` + +--- + +### `tasks` — Task-Scheduler (geplante Aufgaben) + +```sql +CREATE TABLE tasks ( + id SERIAL PRIMARY KEY, + agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE, + name TEXT DEFAULT '', -- Anzeigename der Aufgabe + action TEXT NOT NULL, -- "update", "backup", "reboot", ... + status TEXT NOT NULL DEFAULT 'scheduled', -- "scheduled","running","completed","failed" + schedule_type TEXT DEFAULT 'once', -- "once", "daily", "weekly", "monthly" + schedule_time TEXT DEFAULT '02:00', -- Uhrzeit (HH:MM) + schedule_weekday INT, -- 0=So, 1=Mo, ... 6=Sa (nur bei "weekly") + schedule_monthday INT, -- 1-31 (nur bei "monthly") + reboot BOOLEAN DEFAULT FALSE, -- Nach Update neu starten + health_check BOOLEAN DEFAULT FALSE, -- Nach Aufgabe Erreichbarkeit prüfen + health_check_timeout INT DEFAULT 600, -- Sekunden bis Timeout des Health-Checks + active BOOLEAN DEFAULT TRUE, -- Recurring: aktiv/inaktiv + scheduled_at TIMESTAMPTZ NOT NULL, -- Nächste/erste Ausführung (once) + started_at TIMESTAMPTZ, -- Tatsächlicher Start + finished_at TIMESTAMPTZ, -- Ende der Ausführung + last_run TIMESTAMPTZ, -- Letzter Lauf (recurring) + next_run TIMESTAMPTZ, -- Nächster Lauf (recurring) + run_count INT DEFAULT 0, -- Anzahl Ausführungen (recurring) + result JSONB, -- Ergebnis-Payload + webhook_url TEXT, -- URL für Webhook nach Abschluss + webhook_sent BOOLEAN DEFAULT FALSE, + webhook_response TEXT, -- HTTP-Response des Webhooks + created_by TEXT, -- Username des Erstellers + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +CREATE INDEX idx_tasks_status_scheduled ON tasks(status, scheduled_at); +CREATE INDEX idx_tasks_active_next_run ON tasks(active, next_run) WHERE active = TRUE; +``` + +**Webhook-Payload** (gesendetes JSON bei `webhook_url` gesetzt): + +```json +{ + "customer_id": "K05068", + "hostname": "opnsense.kunde.de", + "opnsense_version": "25.1.1", + "timestamp": "2026-03-06T08:00:00Z", + "trigger": "update_completed", + "reachable": true, + "update_status": "completed" +} +``` + +--- + +### `api_keys` — API-Keys (zusätzlich zu config.yaml) + +```sql +CREATE TABLE api_keys ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + key TEXT NOT NULL UNIQUE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + last_used TIMESTAMPTZ +); +``` + +--- + +### `installers` — OPNsense Plugin-Pakete + +```sql +CREATE TABLE installers ( + id SERIAL PRIMARY KEY, + platform TEXT NOT NULL UNIQUE, -- "freebsd", "linux" + filename TEXT NOT NULL, + data BYTEA NOT NULL, -- ZIP-Paket (install.sh + Plugin) + sha256 TEXT NOT NULL, + size INTEGER NOT NULL, + uploaded_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +--- + +### `audit_log` — Audit-Protokoll + +```sql +CREATE TABLE audit_log ( + id SERIAL PRIMARY KEY, + timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), + user_name TEXT NOT NULL, + action TEXT NOT NULL, -- z.B. "login", "delete_agent", "update_customer" + target_type TEXT, -- z.B. "agent", "customer", "user" + target_id TEXT, + target_name TEXT, + details TEXT, + source_ip TEXT +); +CREATE INDEX idx_audit_log_timestamp ON audit_log(timestamp DESC); +``` + +--- + +### `system_settings` — Backend-Einstellungen (Key/Value) + +```sql +CREATE TABLE system_settings ( + key TEXT PRIMARY KEY, -- "backend_url_internal", "backend_url_external", "api_key" + value TEXT NOT NULL, + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +--- + +### `metrics` — TimescaleDB Hypertable (Zeitreihen) + +```sql +CREATE TABLE metrics ( + time TIMESTAMPTZ NOT NULL, + agent_id TEXT NOT NULL, + metric TEXT NOT NULL, -- Name der Metrik + value DOUBLE PRECISION NOT NULL, + tags JSONB -- z.B. {"interface":"igb0"} +); +-- In Hypertable umwandeln: +SELECT create_hypertable('metrics', 'time', if_not_exists => TRUE); +CREATE INDEX idx_metrics_agent_metric ON metrics(agent_id, metric, time DESC); +``` + +**Verfügbare Metriken:** + +| Metrik | Einheit | Tags | +|--------|---------|------| +| `cpu_usage` | Prozent | — | +| `memory_used_percent` | Prozent | — | +| `memory_used_bytes` | Bytes | — | +| `memory_total_bytes` | Bytes | — | +| `uptime_seconds` | Sekunden | — | +| `disk_used_percent` | Prozent | `{"mountpoint":"/","dataset":"zroot/ROOT"}` | +| `disk_used_bytes` | Bytes | `{"mountpoint":"/"}` | +| `interface_rx_bytes` | Bytes | `{"interface":"igb0"}` | +| `interface_tx_bytes` | Bytes | `{"interface":"igb0"}` | +| `gateway_rtt_ms` | Millisekunden | `{"gateway":"WAN_GW"}` | +| `gateway_loss_percent` | Prozent | `{"gateway":"WAN_GW"}` | + +**TimescaleDB Policies** (automatisch gesetzt): +- **Retention**: Rohdaten nach 90 Tagen gelöscht +- **Compression**: Daten älter als 7 Tage komprimiert (`segmentby: agent_id, metric`) + +--- ### DB-Funktionen -- `GetAgentByName`: Sucht Agent nach Name — verwendet fuer Pre-Registration Matching bei Agent-Registrierung +- **`GetAgentByName`**: Sucht Agent nach Name — für Pre-Registration Matching (Agent registriert sich, Backend findet vorregistrierten Platzhalter mit `hostname="(ausstehend)"`). ---