openvpn-endpoint-server/server/app/models/vpn_server.py

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}