import os import re from pathlib import Path from jinja2 import Environment, FileSystemLoader class ConfigManager: def __init__(self, template_dir, output_dir): self.template_dir = template_dir self.output_dir = output_dir self.env = Environment(loader=FileSystemLoader(template_dir)) self.server_conf_path = Path(output_dir) / "server.conf" def read_server_config(self): """Parse existing server config into a dictionary""" if not self.server_conf_path.exists(): return {} config = {} try: with open(self.server_conf_path, 'r') as f: content = f.read() # Regex mappings for simple key-value pairs mappings = { 'port': r'^port\s+(\d+)', 'proto': r'^proto\s+(\w+)', 'dev': r'^dev\s+(\w+)', 'server_network': r'^server\s+([\d\.]+)', 'server_netmask': r'^server\s+[\d\.]+\s+([\d\.]+)', 'topology': r'^topology\s+(\w+)', 'cipher': r'^cipher\s+([\w\-]+)', 'data_ciphers': r'^data-ciphers\s+([\w\-:]+)', 'data_ciphers_fallback': r'^data-ciphers-fallback\s+([\w\-]+)', 'status_log': r'^status\s+(.+)', 'log_file': r'^log-append\s+(.+)', 'ipp_path': r'^ifconfig-pool-persist\s+(.+)', 'auth_algo': r'^auth\s+(\w+)', 'tun_mtu': r'^tun-mtu\s+(\d+)', 'mssfix': r'^mssfix\s+(\d+)' } for key, pattern in mappings.items(): match = re.search(pattern, content, re.MULTILINE) if match: config[key] = match.group(1) # Boolean flags config['client_to_client'] = bool(re.search(r'^client-to-client', content, re.MULTILINE)) # redirect-gateway is usually pushed config['redirect_gateway'] = bool(re.search(r'push "redirect-gateway', content, re.MULTILINE)) config['crl_verify'] = bool(re.search(r'^crl-verify', content, re.MULTILINE)) # DNS # push "dhcp-option DNS 8.8.8.8" dns_matches = re.findall(r'push "dhcp-option DNS ([\d\.]+)"', content) if dns_matches: config['dns_servers'] = dns_matches # Routes # push "route 192.168.1.0 255.255.255.0" route_matches = re.findall(r'push "route ([\d\.]+ [\d\.]+)"', content) if route_matches: config['routes'] = route_matches return config except Exception as e: print(f"Error reading config: {e}") return {} def generate_server_config(self, params): """Generate server.conf from template""" # Defaults defaults = { 'port': 1194, 'proto': 'udp', 'server_network': '10.8.0.0', 'server_netmask': '255.255.255.0', 'topology': 'subnet', 'cipher': 'AES-256-GCM', 'auth_algo': 'SHA256', 'data_ciphers': 'AES-256-GCM:AES-128-GCM', 'data_ciphers_fallback': None, 'status_log': '/var/log/openvpn/openvpn-status.log', 'log_file': '/var/log/openvpn/openvpn.log', 'crl_verify': True, 'client_to_client': False, 'redirect_gateway': True, 'dns_servers': ['8.8.8.8', '8.8.4.4'], 'routes': [], 'tun_mtu': None, 'mssfix': None } # Merge params ctx = {**defaults, **params} try: template = self.env.get_template('server.conf.j2') output = template.render(ctx) with open(self.server_conf_path, 'w') as f: f.write(output) return True, str(self.server_conf_path) except Exception as e: return False, str(e) def generate_client_config(self, client_name, pki_path, server_config=None, extra_params=None): """Generate client .ovpn content server_config: dict of server security/network settings extra_params: dict of specific overrides (remote_host, port, proto) """ # Checks pki = Path(pki_path) ca_path = pki / "ca.crt" cert_path = pki / "issued" / f"{client_name}.crt" key_path = pki / "private" / f"{client_name}.key" ta_path = pki / "ta.key" if not (ca_path.exists() and cert_path.exists() and key_path.exists()): return False, "Certificate files missing" try: # Read contents ca = ca_path.read_text().strip() cert = cert_path.read_text().strip() # Cert file often contains text before -----BEGIN CERTIFICATE----- if "-----BEGIN CERTIFICATE-----" in cert: cert = cert[cert.find("-----BEGIN CERTIFICATE-----"):] key = key_path.read_text().strip() ta = ta_path.read_text().strip() if ta_path.exists() else None ctx = { 'client_name': client_name, 'ca': ca, 'cert': cert, 'key': key, 'tls_auth': ta } # Merge server config if present if server_config: ctx.update(server_config) # Merge extra params (host, port, proto) - takes precedence if extra_params: ctx.update(extra_params) template = self.env.get_template('client.ovpn.j2') output = template.render(ctx) return True, output except Exception as e: return False, str(e)