Release / verify (push) Canceled after 0s
CI / checks (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / backend (push) Canceled after 0s
Release / test (push) Canceled after 0s
Release / publish (backend) (push) Canceled after 0s
Release / publish (frontend) (push) Canceled after 0s
Release / publish (proxy) (push) Canceled after 0s
Netzwerk-Verwaltung und Übernahme von Hand angelegter Regeln. Alles gegen die OPNsense-Core-Quellen geprüft und live auf drei Test-Firewalls (26.7.1 und 26.7.6) getestet, siehe Handbuch „Netzwerk, DNS und DHCP“: - **Neuer Bereich „Netzwerk“:** Inventar je Firewall (Schnittstellen mit Adressen und CARP-Zustand, VLANs, virtuelle IPs, Gateways, Routen, DNS-Einträge, DHCP) und Änderungen daran: anlegen, bearbeiten, löschen für VLANs, virtuelle IPs, Gateways, statische Routen, Schnittstellen-Zuweisung samt IP-Konfiguration, Unbound-DNS-Einträge, dnsmasq-Hosts sowie Kea-Subnetze und -Reservierungen. - **Abgesichert:** Jede Änderung läuft über eine Vorschau mit Diff und einem Token, der den Firewall-Stand erfasst. Vor dem Schreiben wird ein Konfig-Backup gespeichert (ohne Backup keine Änderung), danach angewendet und zurückgelesen. OPNexus erkennt, über welche Schnittstelle es die Firewall erreicht; Änderungen, die diese Verbindung kappen können (Verwaltungs-Schnittstelle, Standard-Gateway, Standardroute, Route über die Firewall-Adresse), brauchen eine ausdrückliche Bestätigung mit Eingabe des Firewall-Namens. Es gibt bewusst **keinen** automatischen Rollback: OPNexus kann eine Änderung nicht zurücknehmen, wenn sie seine eigene Verbindung trennt. Das Backup vor der Änderung ist der Rückweg. - **Schnittstellen-Löschen:** OPNsense entfernt dabei selbst alle Regeln, die die Schnittstelle nutzen. Die Vorschau listet sie, die Bestätigung ist Pflicht. Bereits auf der Firewall vorgemerkte Änderungen blockieren eine neue Änderung. - **IP-Konfiguration per API gibt es erst ab OPNsense 26.7.6.** Auf 26.7.1 ist die Zuweisungsliste nur lesbar; OPNexus erkennt das an den Daten und sperrt die Änderung mit Begründung. - **Von der Firewall übernehmen:** Regeln, die direkt in OPNsense angelegt wurden, lassen sich in OPNexus übernehmen, bearbeiten und ausrollen. Die Übernahme schreibt nichts auf die Firewall. Regeln, die das Modell verfälschen würde (invertierte Quelle/Ziel, mehrere Schnittstellen, keine Quick-Regel, …), werden mit Grund abgelehnt. Neu: Regeln haben ein Protokollieren-Flag (`log`, Migration 0003), damit es beim Bearbeiten nicht still verloren geht; nicht modellierte Felder (Gateway, Zustandsoptionen, …) bleiben auf der Firewall unverändert. - **Geheimnisse:** Das CARP-Passwort (liefert OPNsense im Klartext) und der Kea-DDNS-Schlüssel werden nie zurückgegeben, gespeichert oder auditiert. - **Host-Adresse änderbar:** Hosts › Bearbeiten hat jetzt das Feld „Adresse“. Nach einer Änderung der Verwaltungs-IP muss der Host nicht mehr gelöscht und neu angelegt werden; Verlauf und Zuordnungen bleiben erhalten. Meldet eine Änderung „angewendet, aber nicht prüfbar“, weist die Oberfläche darauf hin. - **Fehler aus den Live-Tests, die Mocks nicht gezeigt haben:** Jede Änderung einer virtuellen IP muss die Adresse (`network`) wiederholen, sonst leert OPNsense sie bzw. lehnt ab; die Prüfung nach dem Anwenden vergleicht jetzt den vollständigen Soll-Zustand statt nur der gesendeten Felder. Dazu: Gateway-Priorität als Zahl, Routen mit `enabled` statt `disabled`, Beschreibungsmuster der Schnittstellen, gesperrte Schnittstellen, Pflicht-Backup vor jeder Änderung. - **Hinweis zu den Versionen:** Die IP-Konfiguration von Schnittstellen per API gibt es erst ab OPNsense 26.7.6. Mit 26.7.5 (die Prod-Firewalls) sind VLANs, virtuelle IPs, Gateways, Routen, DNS und DHCP verwaltbar, Schnittstellen nur lesbar.
155 lines
4.2 KiB
Python
155 lines
4.2 KiB
Python
import uuid
|
|
from datetime import datetime
|
|
from typing import Literal
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
|
|
from app.services.firewall_url import validate_base_url
|
|
|
|
|
|
class HostCreate(BaseModel):
|
|
name: str = Field(min_length=1, max_length=120)
|
|
base_url: str = Field(min_length=1, max_length=255)
|
|
api_key: str = Field(min_length=1, max_length=255)
|
|
api_secret: str = Field(min_length=1)
|
|
verify_tls: bool = True
|
|
# Pinned certificate of the firewall (PEM), obtained via POST /hosts/tls-probe.
|
|
tls_ca_pem: str | None = Field(default=None, max_length=32768)
|
|
enabled: bool = True
|
|
cluster_name: str | None = Field(default=None, max_length=120)
|
|
policy_hierarchy_group_id: uuid.UUID | None = None
|
|
|
|
@field_validator("base_url")
|
|
@classmethod
|
|
def _base_url_is_safe(cls, value: str) -> str:
|
|
return validate_base_url(value)
|
|
|
|
|
|
class TlsProbeRequest(BaseModel):
|
|
base_url: str = Field(min_length=1, max_length=255)
|
|
|
|
|
|
class TlsProbeResult(BaseModel):
|
|
pem: str
|
|
sha256: str
|
|
subject: str
|
|
issuer: str
|
|
self_signed: bool
|
|
not_before: datetime
|
|
not_after: datetime
|
|
expired: bool
|
|
san: list[str]
|
|
|
|
|
|
class HostUpdate(BaseModel):
|
|
"""Partial update — everything optional, only provided fields change.
|
|
Credential rotation (api_key/api_secret) isn't included yet; delete +
|
|
recreate is still the way to rotate a host's API key for now."""
|
|
|
|
name: str | None = Field(default=None, min_length=1, max_length=120)
|
|
# New address of the firewall, e.g. after its management IP was changed.
|
|
base_url: str | None = Field(default=None, min_length=1, max_length=255)
|
|
verify_tls: bool | None = None
|
|
tls_ca_pem: str | None = Field(default=None, max_length=32768)
|
|
clear_tls_pin: bool = False
|
|
enabled: bool | None = None
|
|
cluster_name: str | None = Field(default=None, max_length=120)
|
|
clear_cluster_name: bool = False # explicit flag, since cluster_name=None is ambiguous with "don't touch it"
|
|
policy_hierarchy_group_id: uuid.UUID | None = None
|
|
clear_policy_hierarchy_group_id: bool = False # same ambiguity as clear_cluster_name above
|
|
|
|
@field_validator("base_url")
|
|
@classmethod
|
|
def _base_url_is_safe(cls, value: str | None) -> str | None:
|
|
return validate_base_url(value) if value is not None else None
|
|
|
|
|
|
class HostRead(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: uuid.UUID
|
|
name: str
|
|
base_url: str
|
|
verify_tls: bool
|
|
tls_pinned: bool = False
|
|
tls_fingerprint: str | None = None
|
|
enabled: bool
|
|
cluster_name: str | None
|
|
policy_hierarchy_group_id: uuid.UUID | None = None
|
|
created_at: datetime
|
|
status: Literal["online", "offline", "disabled"]
|
|
carp_status: str | None = None
|
|
status_detail: str | None = None
|
|
|
|
|
|
class LiveRule(BaseModel):
|
|
uuid: str
|
|
description: str
|
|
action: str
|
|
action_label: str
|
|
direction: str
|
|
direction_label: str
|
|
interface: str | None
|
|
interface_label: str | None
|
|
protocol: str | None
|
|
protocol_label: str | None
|
|
source_net: str
|
|
source_port: str | None
|
|
destination_net: str
|
|
destination_port: str | None
|
|
enabled: bool
|
|
is_automatic: bool
|
|
|
|
|
|
class FirmwareInfo(BaseModel):
|
|
product_id: str
|
|
product_version: str
|
|
|
|
|
|
class InterfaceOption(BaseModel):
|
|
name: str
|
|
label: str
|
|
|
|
|
|
class AliasOption(BaseModel):
|
|
name: str
|
|
type: str
|
|
description: str
|
|
content: str
|
|
enabled: bool
|
|
|
|
|
|
class HostBackup(BaseModel):
|
|
id: str
|
|
created_at: datetime
|
|
size: int
|
|
|
|
|
|
CertStatus = Literal["valid", "expiring_soon", "expired", "unknown"]
|
|
|
|
|
|
class HostCertificate(BaseModel):
|
|
uuid: str
|
|
# OPNsense's own cross-references (OpenVPN cert/ca, Trust-Store caref) use
|
|
# this id, not the row UUID.
|
|
refid: str = ""
|
|
descr: str
|
|
common_name: str | None
|
|
issuer: str
|
|
valid_from: datetime | None
|
|
valid_to: datetime | None
|
|
days_until_expiry: int | None
|
|
status: CertStatus
|
|
in_use: bool
|
|
|
|
|
|
class HostCa(BaseModel):
|
|
uuid: str
|
|
refid: str = ""
|
|
descr: str
|
|
common_name: str | None
|
|
valid_from: datetime | None
|
|
valid_to: datetime | None
|
|
days_until_expiry: int | None
|
|
status: CertStatus
|