stuff
This commit is contained in:
191
models.py
191
models.py
@@ -40,12 +40,45 @@ class VPNType(Enum):
|
||||
IPSEC = "IPSec"
|
||||
|
||||
|
||||
@dataclass
|
||||
class NetworkSegment:
|
||||
"""Represents a network segment with metadata."""
|
||||
name: str # "LAN", "DMZ", "Management"
|
||||
cidr: str # "192.168.1.0/24"
|
||||
vlan_id: Optional[int] = None # VLAN 100
|
||||
zone: str = "general" # "production", "dmz", "management", "guest"
|
||||
gateway: Optional[str] = None # "192.168.1.1"
|
||||
description: str = "" # "Main office network"
|
||||
|
||||
|
||||
@dataclass
|
||||
class PortForwarding:
|
||||
"""Represents a port forwarding rule for external access."""
|
||||
external_port: int # Port on external address (e.g., 8080)
|
||||
# Target internal IP (e.g., "192.168.1.10")
|
||||
internal_ip: str
|
||||
internal_port: int # Target internal port (e.g., 80)
|
||||
protocol: str = "tcp" # "tcp", "udp", or "both"
|
||||
description: str = "" # "Web server access"
|
||||
enabled: bool = True # Whether the forwarding is active
|
||||
|
||||
|
||||
@dataclass
|
||||
class HostIP:
|
||||
"""IP address with network segment context."""
|
||||
ip_address: str
|
||||
network_segment: str # References NetworkSegment.name
|
||||
is_primary: bool = False # Primary interface for this host
|
||||
|
||||
|
||||
@dataclass
|
||||
class Host:
|
||||
"""Represents a physical or virtual host at a location."""
|
||||
name: str
|
||||
ip_address: str
|
||||
host_type: HostType
|
||||
ip_addresses: List[HostIP] = field(default_factory=list)
|
||||
host_type: HostType = HostType.LINUX
|
||||
# Icon name without extension (e.g., 'ubuntu', 'windows')
|
||||
icon: Optional[str] = None
|
||||
description: str = ""
|
||||
services: List[Service] = field(default_factory=list)
|
||||
sub_hosts: List['Host'] = field(
|
||||
@@ -62,6 +95,38 @@ class Host:
|
||||
"""Check if this host has sub-hosts (VMs)."""
|
||||
return len(self.sub_hosts) > 0
|
||||
|
||||
def get_primary_ip(self) -> str:
|
||||
"""Get the primary IP address, or first IP if no primary set."""
|
||||
if not self.ip_addresses:
|
||||
return ""
|
||||
|
||||
# Look for explicitly marked primary
|
||||
for host_ip in self.ip_addresses:
|
||||
if host_ip.is_primary:
|
||||
return host_ip.ip_address
|
||||
|
||||
# Fall back to first IP
|
||||
return self.ip_addresses[0].ip_address
|
||||
|
||||
def get_ip_display(self) -> str:
|
||||
"""Get a display string for IP addresses."""
|
||||
if not self.ip_addresses:
|
||||
return "No IP"
|
||||
elif len(self.ip_addresses) == 1:
|
||||
return self.ip_addresses[0].ip_address
|
||||
else:
|
||||
primary_ip = self.get_primary_ip()
|
||||
return f"{primary_ip} (+{len(self.ip_addresses)-1} more)"
|
||||
|
||||
def get_all_ips(self) -> List[str]:
|
||||
"""Get all IP addresses as a simple list."""
|
||||
return [host_ip.ip_address for host_ip in self.ip_addresses]
|
||||
|
||||
def get_ips_in_segment(self, segment_name: str) -> List[str]:
|
||||
"""Get all IP addresses in a specific network segment."""
|
||||
return [host_ip.ip_address for host_ip in self.ip_addresses
|
||||
if host_ip.network_segment == segment_name]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Location:
|
||||
@@ -72,11 +137,24 @@ class Location:
|
||||
active: bool = False
|
||||
vpn_config: str = "" # Path to VPN config or connection details
|
||||
hosts: List[Host] = field(default_factory=list)
|
||||
|
||||
|
||||
# Enhanced network configuration
|
||||
network_segments: List[NetworkSegment] = field(
|
||||
default_factory=list) # Network segments with rich metadata
|
||||
external_addresses: List[str] = field(
|
||||
default_factory=list) # External VPN endpoints
|
||||
port_forwardings: List[PortForwarding] = field(
|
||||
default_factory=list) # Port forwarding rules
|
||||
|
||||
# Legacy field for backward compatibility (will be deprecated)
|
||||
# Simple network list (legacy)
|
||||
networks: List[str] = field(default_factory=list)
|
||||
|
||||
# VPN connection management fields
|
||||
nmcli_connection_name: Optional[str] = None # NetworkManager connection name
|
||||
# NetworkManager connection name
|
||||
nmcli_connection_name: Optional[str] = None
|
||||
auto_import: bool = True # Auto-import .ovpn file if not in NetworkManager
|
||||
|
||||
|
||||
# Credential storage - can be:
|
||||
# - Passbolt UUID string (for future use)
|
||||
# - Dict with 'username' and 'password' keys
|
||||
@@ -112,6 +190,109 @@ class Location:
|
||||
"""Get all hosts that have sub-hosts (hypervisors)."""
|
||||
return [host for host in self.get_all_hosts_flat() if host.is_hypervisor()]
|
||||
|
||||
def get_segment_by_name(self, segment_name: str) -> Optional[NetworkSegment]:
|
||||
"""Get a network segment by its name."""
|
||||
return next((seg for seg in self.network_segments if seg.name == segment_name), None)
|
||||
|
||||
def get_hosts_in_segment(self, segment_name: str) -> List[Host]:
|
||||
"""Get all hosts that have IPs in the specified network segment."""
|
||||
hosts = []
|
||||
for host in self.get_all_hosts_flat():
|
||||
if any(host_ip.network_segment == segment_name for host_ip in host.ip_addresses):
|
||||
hosts.append(host)
|
||||
return hosts
|
||||
|
||||
def get_segments_by_zone(self, zone: str) -> List[NetworkSegment]:
|
||||
"""Get all network segments in a specific zone."""
|
||||
return [seg for seg in self.network_segments if seg.zone == zone]
|
||||
|
||||
def get_port_forwardings_for_host(self, host_ip: str) -> List[PortForwarding]:
|
||||
"""Get all port forwardings targeting a specific host IP."""
|
||||
return [pf for pf in self.port_forwardings if pf.internal_ip == host_ip and pf.enabled]
|
||||
|
||||
def get_externally_accessible_services(self) -> List[tuple]:
|
||||
"""Get all services accessible from external addresses via port forwarding.
|
||||
|
||||
Returns list of tuples: (external_address, external_port, host, service, port_forwarding)
|
||||
"""
|
||||
accessible_services = []
|
||||
|
||||
for external_addr in self.external_addresses:
|
||||
for port_forward in self.port_forwardings:
|
||||
if not port_forward.enabled:
|
||||
continue
|
||||
|
||||
# Find the host that owns the target IP
|
||||
target_host = None
|
||||
target_service = None
|
||||
|
||||
for host in self.get_all_hosts_flat():
|
||||
host_ips = [hip.ip_address for hip in host.ip_addresses]
|
||||
if port_forward.internal_ip in host_ips:
|
||||
target_host = host
|
||||
|
||||
# Find matching service on this host
|
||||
for service in host.services:
|
||||
if service.port == port_forward.internal_port:
|
||||
target_service = service
|
||||
break
|
||||
break
|
||||
|
||||
if target_host:
|
||||
accessible_services.append((
|
||||
external_addr,
|
||||
port_forward.external_port,
|
||||
target_host,
|
||||
target_service, # May be None if no matching service defined
|
||||
port_forward
|
||||
))
|
||||
|
||||
return accessible_services
|
||||
|
||||
def is_service_externally_accessible(self, host_ip: str, service_port: int) -> bool:
|
||||
"""Check if a specific service is accessible from external addresses."""
|
||||
for pf in self.port_forwardings:
|
||||
if (pf.enabled and
|
||||
pf.internal_ip == host_ip and
|
||||
pf.internal_port == service_port):
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_service_reachable(self, host: 'Host', service: Service) -> bool:
|
||||
"""Check if a service is reachable (either via VPN connection or port forwarding).
|
||||
|
||||
Returns True if:
|
||||
- VPN is connected (all internal services become reachable)
|
||||
- Service has a port forwarding rule enabled
|
||||
"""
|
||||
# If VPN is connected, all services are reachable
|
||||
if self.connected:
|
||||
return True
|
||||
|
||||
# Check if service is externally accessible via port forwarding
|
||||
for host_ip in host.ip_addresses:
|
||||
if self.is_service_externally_accessible(host_ip.ip_address, service.port):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_external_url_for_service(self, host: 'Host', service: Service) -> Optional[str]:
|
||||
"""Get the external URL for a service if it has port forwarding.
|
||||
|
||||
Returns the external URL (e.g., "https://vpn.example.com:8006") or None.
|
||||
"""
|
||||
for host_ip in host.ip_addresses:
|
||||
for pf in self.port_forwardings:
|
||||
if (pf.enabled and
|
||||
pf.internal_ip == host_ip.ip_address and
|
||||
pf.internal_port == service.port):
|
||||
# Use first external address if available
|
||||
if self.external_addresses:
|
||||
protocol = "https" if service.port in [
|
||||
443, 8006, 8080] else "http"
|
||||
return f"{protocol}://{self.external_addresses[0]}:{pf.external_port}"
|
||||
return None
|
||||
|
||||
|
||||
@dataclass
|
||||
class CustomerService:
|
||||
|
||||
Reference in New Issue
Block a user