- **Zum Update:** Das erste Update auf diese Version übernimmt auch die `docker-compose.yml` (siehe unten; braucht wie die
CLI-Aktualisierung `cosign` und `cosign.pub`), migriert die Datenbank (Revision 0032; ein Rollback spielt die Sicherung davor
zurück) und stellt das Backend auf einen unprivilegierten Benutzer um. Nichts davon braucht einen Handgriff.
- **Nur ein Backend führt die Hintergrunddienste aus (HA, Stufe H0):** Laufen zwei Backends an derselben Datenbank, verschickten
beide jeden Alarm und brachen Zyklen mit `UniqueViolation` ab. Jetzt führt nur der Prozess mit der Leader-Sperre in der
Datenbank die neun Hintergrundschleifen aus; die anderen warten in Bereitschaft und übernehmen innerhalb einer halben Minute,
wenn der Leader ausfällt. Verliert der Leader seine Datenbankverbindung, hält er alle Schleifen sofort an (geprüft alle 5 Sekunden
und vor jedem Durchlauf). An einer Replica startet kein Leader. Rolle in `GET /api/health` und als Metrik `opnexus_leader`.
Zwei gleichzeitige Erststarts legen das Admin-Konto nur einmal an.
- **Umzug und Kaltreserve (HA, Stufe H1):** `opnexus backup --full --with-env --with-ca` nimmt zusätzlich die `.env` und die
interne CA des Proxys mit, `opnexus restore DATEI --with-env --with-ca` spielt sie zurück (ohne Datenbank-Zugang, Version und
Adressen des Zielservers; die CA bleibt dieselbe, Browser vertrauen weiter). Neu: `opnexus migrate-host BENUTZER@ZIEL` zieht
eine Installation in einem Schritt auf einen anderen Server um (Vorprüfung, Quelle anhalten, Sicherung mit Zufalls-Passphrase,
Restore, Kontrolle). Im Lab 90 Sekunden mit drei Firewalls. `opnexus status` zeigt das Alter der letzten Vollsicherung.
Handbuch: „Umzug und Notfall“.
- **Betriebseinstellungen im Webinterface:** Was bisher nur in der `.env` oder gar nur im Code stand, lässt sich unter
Einstellungen › Betriebseinstellungen ändern: Sitzungsdauer und Anmeldesperren, Bestätigungsfrist beim Ausrollen, Zeitlimit für
Firewall-Anfragen, Messintervalle und Aufbewahrung, Intervall der Versionsprüfung, Heartbeat-Adresse, CA des Mailservers (PEM
einfügen statt Datei einbinden), Zeitlimit Mailversand. Mit Grenzen, Standardwert, Zurücksetzen und Audit-Eintrag. Ein Wert in der
`.env` hat weiter Vorrang und wird als „vom Server“ angezeigt. Adressen, TLS und Datenbank-Zugang bleiben bewusst in der `.env`.
- **Heartbeat für einen externen Überwacher:** Die Heartbeat-Adresse (nur `https://`) wird nach jedem erfolgreichen Prüfzyklus
aufgerufen; bleibt der Aufruf aus, schlägt der Überwacher Alarm. Die Adresse wird nie angezeigt oder protokolliert.
- **Updates bringen die Compose-Datei mit:** Bisher tauschte `opnexus update` nur die Images; Änderungen an der
`docker-compose.yml` (neue Variablen, Härtung) kamen bei bestehenden Installationen nie an. Jetzt übernimmt das Update die Datei
des Zielreleases aus dem signierten Updater-Image, sichert die alte als `docker-compose.yml.bak-<version>` und legt sie beim
Rollback zurück. Eine hier angepasste Datei wird nicht überschrieben (neue Fassung als `docker-compose.yml.new`); eigene
Anpassungen gehören in `docker-compose.override.yml`.
- **Alarm „Platte des OPNexus-Servers fast voll“** (standardmäßig an, ab 90 %, einstellbar): eine volle Platte hält die Datenbank
an und damit jeden anderen Alarm. Auch im Dashboard und als Metrik `opnexus_server_disk_used_percent`.
- **Härtung aus einem externen Audit:**
- **Kein root mehr in den Containern:** Backend (uid 10001) und Frontend (`node`) laufen unprivilegiert, ohne neue Rechte (`no-new-privileges`), das Frontend zusätzlich
ohne Capabilities. Das Backend startet über `backend/scripts/start.sh` noch kurz als root, stellt die vorhandenen Datenvolumes einmalig um und gibt dann alle Rechte ab:
ein Update braucht keinen Handgriff, ein Rückweg auf eine ältere Version bleibt möglich. Notausgang für Sonderfälle: `OPNEXUS_RUN_AS_ROOT=1` in der `.env`.
`wg-hub` (braucht root für WireGuard) und `gui-gateway` (läuft schon als nobody) sind unverändert. Der Proxy (Caddy) und der optionale Updater (Docker-Socket) laufen weiter wie bisher.
- **Vollsicherung konsistent:** Ändern sich Geheimnisse, während die Sicherung läuft (jemand legt gerade ein Objekt an oder löscht eines), wird sie bis zu dreimal wiederholt;
gelingt das nicht, bricht sie mit einer klaren Meldung ab, statt ein unstimmiges Archiv zu schreiben.
- **Zieladress-Schutz überall:** Audit-Export (Webhook und Syslog), Offsite-S3 und der Update-Feed verbinden nur noch zu Adressen, die die Prüfung bestanden haben
(kein Loopback, Link-Local, Metadaten-Dienst; auch nicht über DNS-Rebinding oder Weiterleitungen). Adressen im eigenen Netz bleiben erlaubt. Der Update-Feed wird beim Lesen auf 1 MB begrenzt, nicht erst danach.
- **Fehlende Basis-Rechte werden sichtbar:** Fehlen dem API-Schlüssel schon die Rechte für die Grundmessungen (Speicher, Last, Platte), schlug das ganze Sampling still fehl und der
Rechte-Alarm konnte nie auslösen. Jetzt entsteht ein Befund „Grundwerte“ (Recht „Lobby: Dashboard“), der sich nach Korrektur von selbst schließt.
- **Installation:** Die `INSTALL.txt` im Bundle und die Anleitung auf der Website legen die `.env` jetzt mit `install -m 600` an (nur für dich lesbar), statt mit `cp`.
- **Oberfläche:** Die Aktionsknöpfe der Backup-Tabelle auf der Host-Seite werden auf schmalen Bildschirmen als Symbole gezeigt, statt abgeschnitten zu werden.
118 lines
5.7 KiB
Python
118 lines
5.7 KiB
Python
from app.models.host import OpnsenseHost
|
|
from app.models.rule_target import rule_targets
|
|
from app.models.rule import FirewallRule
|
|
from app.models.rule_deployment import RuleDeployment
|
|
from app.models.alias_target import alias_targets
|
|
from app.models.alias import FirewallAlias
|
|
from app.models.alias_deployment import AliasDeployment
|
|
from app.models.port_forward_target import port_forward_targets
|
|
from app.models.port_forward import PortForwardRule
|
|
from app.models.port_forward_deployment import PortForwardDeployment
|
|
from app.models.user import AdminUser, Session
|
|
|
|
__all__ = [
|
|
"OpnsenseHost", "FirewallRule", "RuleDeployment", "rule_targets",
|
|
"FirewallAlias", "AliasDeployment", "alias_targets",
|
|
"PortForwardRule", "PortForwardDeployment", "port_forward_targets",
|
|
"AdminUser", "Session",
|
|
]
|
|
|
|
from app.models.policy import DeviceGroup, RuleRevision, AliasRevision, PortForwardRevision
|
|
|
|
from app.models.one_to_one import OneToOneRule
|
|
from app.models.one_to_one_deployment import OneToOneDeployment
|
|
from app.models.one_to_one_target import one_to_one_targets
|
|
from app.models.policy import OneToOneRevision
|
|
__all__ += ["OneToOneRule", "OneToOneDeployment", "one_to_one_targets", "OneToOneRevision"]
|
|
|
|
from app.models.outbound_nat import OutboundNatRule
|
|
from app.models.outbound_nat_deployment import OutboundNatDeployment
|
|
from app.models.outbound_nat_target import outbound_nat_targets
|
|
from app.models.policy import OutboundNatRevision
|
|
__all__ += ["OutboundNatRule", "OutboundNatDeployment", "outbound_nat_targets", "OutboundNatRevision"]
|
|
|
|
from app.models.audit import AuditEvent
|
|
__all__ += ["AuditEvent"]
|
|
|
|
from app.models.host_operation import HostOperation
|
|
__all__ += ["HostOperation"]
|
|
|
|
from app.models.preferences import UserPreferences
|
|
__all__ += ["UserPreferences"]
|
|
|
|
from app.models.notification import NotificationSettings, NotificationProblem
|
|
__all__ += ["NotificationSettings", "NotificationProblem"]
|
|
|
|
from app.models.policy_hierarchy_group import PolicyHierarchyGroup
|
|
from app.models.policy_hierarchy_rule import PolicyHierarchyRule
|
|
from app.models.policy_hierarchy_rule_deployment import PolicyHierarchyRuleDeployment
|
|
from app.models.policy import PolicyHierarchyRuleRevision
|
|
__all__ += ["PolicyHierarchyGroup", "PolicyHierarchyRule", "PolicyHierarchyRuleDeployment", "PolicyHierarchyRuleRevision"]
|
|
|
|
from app.models.log_entry import FirewallLogEntry
|
|
from app.models.log_cursor import HostLogCursor
|
|
from app.models.log_settings import LogSettings
|
|
__all__ += ["FirewallLogEntry", "HostLogCursor", "LogSettings"]
|
|
|
|
from app.models.ipsec_tunnel_target import ipsec_tunnel_targets
|
|
from app.models.ipsec_tunnel import IpsecTunnel
|
|
from app.models.ipsec_tunnel_deployment import IpsecTunnelDeployment
|
|
from app.models.policy import ipsec_tunnel_groups, IpsecTunnelRevision
|
|
|
|
from app.models.openvpn_instance_target import openvpn_instance_targets
|
|
from app.models.openvpn_instance import OpenVpnInstance
|
|
from app.models.openvpn_instance_deployment import OpenVpnInstanceDeployment
|
|
from app.models.policy import openvpn_instance_groups, OpenVpnInstanceRevision
|
|
__all__ += ["IpsecTunnel", "IpsecTunnelDeployment", "ipsec_tunnel_targets", "ipsec_tunnel_groups", "IpsecTunnelRevision"]
|
|
__all__ += ["OpenVpnInstance", "OpenVpnInstanceDeployment", "openvpn_instance_targets", "openvpn_instance_groups", "OpenVpnInstanceRevision"]
|
|
|
|
from app.models.wireguard_tunnel_target import wireguard_tunnel_targets
|
|
from app.models.wireguard_tunnel import WireguardTunnel
|
|
from app.models.wireguard_tunnel_deployment import WireguardTunnelDeployment
|
|
from app.models.policy import wireguard_tunnel_groups, WireguardTunnelRevision
|
|
__all__ += ["WireguardTunnel", "WireguardTunnelDeployment", "wireguard_tunnel_targets", "wireguard_tunnel_groups", "WireguardTunnelRevision"]
|
|
|
|
# Trust-Store + ACME certificate management — deliberately per-host (plain
|
|
# host_id FK, no DeviceGroup/effective_hosts targeting) — see
|
|
# ManagedCertificate/AcmeAccount/AcmeCertificateRequest's own docstrings
|
|
# for why.
|
|
from app.models.managed_certificate import ManagedCertificate
|
|
from app.models.acme_account import AcmeAccount
|
|
from app.models.acme_certificate_request import AcmeCertificateRequest
|
|
__all__ += ["ManagedCertificate", "AcmeAccount", "AcmeCertificateRequest"]
|
|
|
|
from app.models.restore_operation import RestoreOperation
|
|
__all__ += ["RestoreOperation"]
|
|
|
|
from app.models.carp_event import CarpEvent
|
|
from app.models.carp_maintenance_window import CarpMaintenanceWindow
|
|
__all__ += ["CarpEvent", "CarpMaintenanceWindow"]
|
|
|
|
from app.models.resource_sample import HostResourceSample
|
|
__all__ += ["HostResourceSample"]
|
|
|
|
from app.models.scheduled_maintenance import ScheduledMaintenance
|
|
__all__ += ["ScheduledMaintenance"]
|
|
|
|
from app.models.maintenance_window import MaintenanceWindow, MaintenanceWindowException
|
|
__all__ += ["MaintenanceWindow", "MaintenanceWindowException"]
|
|
|
|
from app.models.rollout import RolloutPlan, RolloutPlanWave, RolloutRun, RolloutRunHost, RolloutRunWave, rollout_wave_groups
|
|
__all__ += ["RolloutPlan", "RolloutPlanWave", "RolloutRun", "RolloutRunHost", "RolloutRunWave", "rollout_wave_groups"]
|
|
|
|
from app.models.gui_access import GuiAccessSettings, GuiSession, GuiTicket
|
|
__all__ += ["GuiAccessSettings", "GuiSession", "GuiTicket"]
|
|
|
|
from app.models.rule_history import LiveRuleState, RuleChangeEvent
|
|
__all__ += ["LiveRuleState", "RuleChangeEvent"]
|
|
|
|
from app.models.rule_cleanup import RuleCleanupOperation, RuleCleanupItem
|
|
__all__ += ["RuleCleanupOperation", "RuleCleanupItem"]
|
|
|
|
# Every mapped class must be registered here: a script that imports only app.models (python -m app.reset_password,
|
|
# app.reset_2fa) otherwise fails at the first query because OpnsenseHost refers to classes nobody imported.
|
|
from app.models.host_variable import HostVariable
|
|
__all__ += ["HostVariable"]
|
|
from app.models.runtime_setting import RuntimeSetting
|
|
__all__ += ["RuntimeSetting"]
|