132 lines
4.6 KiB
Python
132 lines
4.6 KiB
Python
"""VPN Server model for managing multiple OpenVPN instances."""
|
|
|
|
from datetime import datetime
|
|
from enum import Enum as PyEnum
|
|
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, Enum, Text
|
|
from sqlalchemy.orm import relationship
|
|
from ..database import Base
|
|
|
|
|
|
class VPNProtocol(str, PyEnum):
|
|
"""VPN protocol type."""
|
|
UDP = "udp"
|
|
TCP = "tcp"
|
|
|
|
|
|
class VPNCipher(str, PyEnum):
|
|
"""VPN cipher options."""
|
|
AES_256_GCM = "AES-256-GCM"
|
|
AES_128_GCM = "AES-128-GCM"
|
|
AES_256_CBC = "AES-256-CBC"
|
|
CHACHA20_POLY1305 = "CHACHA20-POLY1305"
|
|
|
|
|
|
class VPNAuth(str, PyEnum):
|
|
"""VPN auth digest options."""
|
|
SHA256 = "SHA256"
|
|
SHA384 = "SHA384"
|
|
SHA512 = "SHA512"
|
|
|
|
|
|
class VPNCompression(str, PyEnum):
|
|
"""VPN compression options."""
|
|
NONE = "none"
|
|
LZ4 = "lz4"
|
|
LZ4_V2 = "lz4-v2"
|
|
LZO = "lzo"
|
|
|
|
|
|
class VPNServerStatus(str, PyEnum):
|
|
"""VPN Server status."""
|
|
PENDING = "pending" # Server created but not started
|
|
STARTING = "starting" # Container starting
|
|
RUNNING = "running" # Server running
|
|
STOPPED = "stopped" # Server stopped
|
|
ERROR = "error" # Error state
|
|
|
|
|
|
class VPNServer(Base):
|
|
"""VPN Server instance configuration."""
|
|
|
|
__tablename__ = "vpn_servers"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
tenant_id = Column(Integer, ForeignKey("tenants.id"), nullable=True) # NULL for global
|
|
ca_id = Column(Integer, ForeignKey("certificate_authorities.id"), nullable=False)
|
|
|
|
# Basic info
|
|
name = Column(String(255), nullable=False)
|
|
description = Column(Text, nullable=True)
|
|
|
|
# Network configuration
|
|
hostname = Column(String(255), nullable=False) # External hostname/IP
|
|
port = Column(Integer, default=1194, nullable=False)
|
|
protocol = Column(Enum(VPNProtocol), default=VPNProtocol.UDP, nullable=False)
|
|
|
|
# VPN network
|
|
vpn_network = Column(String(18), default="10.8.0.0", nullable=False) # CIDR or IP
|
|
vpn_netmask = Column(String(15), default="255.255.255.0", nullable=False)
|
|
|
|
# Server certificate (PEM encoded)
|
|
server_cert = Column(Text, nullable=True)
|
|
server_key = Column(Text, nullable=True)
|
|
ta_key = Column(Text, nullable=True) # TLS-Auth key
|
|
|
|
# Security settings
|
|
cipher = Column(Enum(VPNCipher), default=VPNCipher.AES_256_GCM)
|
|
auth = Column(Enum(VPNAuth), default=VPNAuth.SHA256)
|
|
tls_version_min = Column(String(10), default="1.2")
|
|
tls_auth_enabled = Column(Boolean, default=True)
|
|
|
|
# Performance settings
|
|
compression = Column(Enum(VPNCompression), default=VPNCompression.NONE)
|
|
max_clients = Column(Integer, default=100)
|
|
keepalive_interval = Column(Integer, default=10) # seconds
|
|
keepalive_timeout = Column(Integer, default=60) # seconds
|
|
|
|
# Docker settings
|
|
docker_container_name = Column(String(255), nullable=True)
|
|
management_port = Column(Integer, default=7505)
|
|
|
|
# Status
|
|
status = Column(Enum(VPNServerStatus), default=VPNServerStatus.PENDING)
|
|
is_active = Column(Boolean, default=True)
|
|
is_primary = Column(Boolean, default=False) # Primary server for tenant
|
|
auto_start = Column(Boolean, default=True) # Start on system boot
|
|
|
|
# Statistics (updated periodically)
|
|
connected_clients = Column(Integer, default=0)
|
|
last_status_check = Column(DateTime, nullable=True)
|
|
|
|
# Audit
|
|
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
# Relationships
|
|
tenant = relationship("Tenant", back_populates="vpn_servers")
|
|
certificate_authority = relationship("CertificateAuthority", back_populates="vpn_servers")
|
|
vpn_profiles = relationship("VPNProfile", back_populates="vpn_server")
|
|
client_vpn_profiles = relationship("ClientVPNProfile", back_populates="vpn_server")
|
|
|
|
def __repr__(self):
|
|
return f"<VPNServer(id={self.id}, name='{self.name}', {self.hostname}:{self.port}/{self.protocol.value})>"
|
|
|
|
@property
|
|
def is_ready(self) -> bool:
|
|
"""Check if server is ready to accept connections."""
|
|
return (
|
|
self.server_cert is not None and
|
|
self.server_key is not None and
|
|
self.certificate_authority is not None and
|
|
self.certificate_authority.is_ready
|
|
)
|
|
|
|
@property
|
|
def connection_string(self) -> str:
|
|
"""Get connection string for display."""
|
|
return f"{self.hostname}:{self.port}/{self.protocol.value.upper()}"
|
|
|
|
def get_docker_port_mapping(self) -> dict:
|
|
"""Get Docker port mapping for this server."""
|
|
return {f"{self.port}/{self.protocol.value}": self.port}
|