156 lines
5.8 KiB
Python
156 lines
5.8 KiB
Python
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)
|