from lib import config as config_module from lib import logging as logging_lib from lib import utils import os import aiofiles.os config = config_module.config log = logging_lib.log CLIENT_CONFIGS_LOCATION = "/etc/openvpn/client" PARTNER_CONFIG_NAME = "clore_partner.conf" def generate_openvpn_config( local_ip='10.1.0.2', server_ip='10.1.0.1', server_hostname='example.com', udp_port=1194, vpn_secret_key='YOUR_VPN_SECRET_KEY' ): openvpn_config = f"""nobind proto udp4 remote {server_hostname} {udp_port} resolv-retry infinite auth SHA256 cipher AES-256-CBC dev {config.openvpn_forwarding_tun_device} ifconfig {local_ip} {server_ip} -----BEGIN OpenVPN Static key V1----- {vpn_secret_key} -----END OpenVPN Static key V1----- fragment 1300 mssfix 1300 sndbuf 524288 rcvbuf 524288 user nobody group nogroup ping 15 ping-restart 45 ping-timer-rem persist-tun persist-key verb 0""" return openvpn_config async def get_iptables_forward_rules(): code, stdout, stderr = await utils.async_run_command( f"LC_ALL=C {'sudo ' if config.run_iptables_with_sudo else ''}iptables -t nat -L PREROUTING -n -v --line-numbers" ) rules = [] if code == 0: collumns = [] for idx, line in enumerate(stdout.split('\n')): if "num" in collumns and "target" in collumns and "in" in collumns: items = line.split(maxsplit=len(collumns)+1) rule = {} for idx, name in enumerate(collumns): rule[name]=items[idx] rule["desc"] = items[len(collumns)+1] rules.append(rule) else: collumns = line.split() return rules async def remove_iptables_rule(rule_dict): cmd = f"{'sudo ' if config.run_iptables_with_sudo else ''}iptables" if rule_dict.get('target') == 'DNAT': cmd += " -t nat" cmd += " -D PREROUTING" if rule_dict.get('prot') and rule_dict['prot'] != '--': cmd += f" -p {rule_dict['prot']}" if rule_dict.get('in') and rule_dict['in'] != '*': cmd += f" -i {rule_dict['in']}" if rule_dict.get('out') and rule_dict['out'] != '*': cmd += f" -o {rule_dict['out']}" if rule_dict.get('source') and rule_dict['source'] != '0.0.0.0/0': cmd += f" -s {rule_dict['source']}" if rule_dict.get('destination') and rule_dict['destination'] != '0.0.0.0/0': cmd += f" -d {rule_dict['destination']}" if rule_dict.get('target') == 'DNAT': if 'dports' in rule_dict.get('desc', ''): port_info = rule_dict['desc'].split('dports ')[1].split(' ')[0] if ':' in port_info: cmd += f" -m multiport --dports {port_info}" else: cmd += f" --dport {port_info}" if 'to:' in rule_dict.get('desc', ''): dest_ip = rule_dict['desc'].split('to:')[1].split()[0] cmd += f" -j DNAT --to-destination {dest_ip}" await utils.async_run_command(cmd) async def clore_partner_configure(clore_partner_config): try: if clore_partner_config: docker_restart_required = False needed_openvpn_config = generate_openvpn_config( local_ip=clore_partner_config["provider"], server_ip=clore_partner_config["forwarding"], server_hostname=clore_partner_config["openvpn_host"], udp_port=clore_partner_config["openvpn_port"], vpn_secret_key=clore_partner_config["secret"] ) saved_config='' config_exists = await aiofiles.os.path.exists(os.path.join(CLIENT_CONFIGS_LOCATION, PARTNER_CONFIG_NAME)) if config_exists: async with aiofiles.open(os.path.join(CLIENT_CONFIGS_LOCATION, PARTNER_CONFIG_NAME), mode='r') as file: saved_config = await file.read() if saved_config != needed_openvpn_config: async with aiofiles.open(os.path.join(CLIENT_CONFIGS_LOCATION, PARTNER_CONFIG_NAME), mode='w') as file: await file.write(needed_openvpn_config) is_active_code, is_active_stdout, is_active_stderr = await utils.async_run_command( f"systemctl is-active openvpn-client@{PARTNER_CONFIG_NAME.replace('.conf','')}" ) if is_active_code == 0 and saved_config != needed_openvpn_config: code, stdout, stderr = await utils.async_run_command( f"systemctl restart openvpn-client@{PARTNER_CONFIG_NAME.replace('.conf','')}" ) docker_restart_required = False if code != 0 else True elif is_active_code != 0: code, stdout, stderr = await utils.async_run_command( f"systemctl start openvpn-client@{PARTNER_CONFIG_NAME.replace('.conf','')}" ) docker_restart_required = False if code != 0 else True code, stdout, stderr = await utils.async_run_command( f"{'sudo ' if config.run_iptables_with_sudo else ''}ip route show table {str(config.forwarding_ip_route_table_id)}" ) ip_route_configured = False if code == 0: for line in stdout.split('\n'): items = line.split(' ') if clore_partner_config["provider"] in items and config.openvpn_forwarding_tun_device in items: ip_route_configured = True break if not ip_route_configured: code, stdout, stderr = await utils.async_run_command( f"{'sudo ' if config.run_iptables_with_sudo else ''}ip route add 0.0.0.0/0 dev {config.openvpn_forwarding_tun_device} src {clore_partner_config['provider']} table {config.forwarding_ip_route_table_id} && ip rule add from {clore_partner_config['provider']} table {config.forwarding_ip_route_table_id}" ) ip_tables_configured = False rules = await get_iptables_forward_rules() for rule in rules: try: if rule["in"] == config.openvpn_forwarding_tun_device and rule["target"].lower()=="dnat" and f"{clore_partner_config['ports'][0]}:{clore_partner_config['ports'][1]}" in rule["desc"] and f"to:{clore_partner_config['provider']}" in rule["desc"]: ip_tables_configured = True elif rule["in"] == config.openvpn_forwarding_tun_device and rule["target"].lower()=="dnat" and "to:10." in rule["desc"] and "dports " in rule["desc"]: print("REMOVE RULE", rule) await remove_iptables_rule(rule) except Exception as ei: log.error(f"clore_partner_configure() | ei | {ei}") if ip_tables_configured == False: code, stdout, stderr = await utils.async_run_command( f"{'sudo ' if config.run_iptables_with_sudo else ''}iptables -t nat -A PREROUTING -i {config.openvpn_forwarding_tun_device} -p tcp -m multiport --dports {clore_partner_config['ports'][0]}:{clore_partner_config['ports'][1]} -j DNAT --to-destination {clore_partner_config['provider']} && {'sudo ' if config.run_iptables_with_sudo else ''}iptables -t nat -A PREROUTING -i {config.openvpn_forwarding_tun_device} -p udp -m multiport --dports {clore_partner_config['ports'][0]}:{clore_partner_config['ports'][1]} -j DNAT --to-destination {clore_partner_config['provider']}" ) if docker_restart_required: async with aiofiles.open(config.restart_docker_flag_file, mode='w') as file: await file.write("") os._exit(0) # We close clore hosting, because it's mandatory to restart docker after starting the up to date version of VPN, docker will be restarted on next start of clore hosting else: code, stdout, stderr = await utils.async_run_command( f"systemctl stop openvpn-client@{PARTNER_CONFIG_NAME.replace('.conf','')}" ) return True except Exception as e: log.error(f"FAIL | openvpn.clore_partner_configure | {e}") return False