summaryrefslogtreecommitdiff
path: root/src/conf_mode/interfaces-openvpn.py
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2023-12-30 23:25:20 +0100
committerChristian Breunig <christian@breunig.cc>2023-12-31 23:49:48 +0100
commit4ef110fd2c501b718344c72d495ad7e16d2bd465 (patch)
treee98bf08f93c029ec4431a3b6ca078e7562e0cc58 /src/conf_mode/interfaces-openvpn.py
parent2286b8600da6c631b17e1d5b9b341843e50f9abf (diff)
downloadvyos-1x-4ef110fd2c501b718344c72d495ad7e16d2bd465.tar.gz
vyos-1x-4ef110fd2c501b718344c72d495ad7e16d2bd465.zip
T5474: establish common file name pattern for XML conf mode commands
We will use _ as CLI level divider. The XML definition filename and also the Python helper should match the CLI node. Example: set interfaces ethernet -> interfaces_ethernet.xml.in set interfaces bond -> interfaces_bond.xml.in set service dhcp-server -> service_dhcp-server-xml.in
Diffstat (limited to 'src/conf_mode/interfaces-openvpn.py')
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py732
1 files changed, 0 insertions, 732 deletions
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
deleted file mode 100755
index bdeb44837..000000000
--- a/src/conf_mode/interfaces-openvpn.py
+++ /dev/null
@@ -1,732 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2019-2023 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-import re
-import tempfile
-
-from cryptography.hazmat.primitives.asymmetric import ec
-from glob import glob
-from sys import exit
-from ipaddress import IPv4Address
-from ipaddress import IPv4Network
-from ipaddress import IPv6Address
-from ipaddress import IPv6Network
-from ipaddress import summarize_address_range
-from netifaces import interfaces
-from secrets import SystemRandom
-from shutil import rmtree
-
-from vyos.base import DeprecationWarning
-from vyos.config import Config
-from vyos.configdict import get_interface_dict
-from vyos.configdict import is_node_changed
-from vyos.configverify import verify_vrf
-from vyos.configverify import verify_bridge_delete
-from vyos.configverify import verify_mirror_redirect
-from vyos.configverify import verify_bond_bridge_member
-from vyos.ifconfig import VTunIf
-from vyos.pki import load_dh_parameters
-from vyos.pki import load_private_key
-from vyos.pki import sort_ca_chain
-from vyos.pki import verify_ca_chain
-from vyos.pki import wrap_certificate
-from vyos.pki import wrap_crl
-from vyos.pki import wrap_dh_parameters
-from vyos.pki import wrap_openvpn_key
-from vyos.pki import wrap_private_key
-from vyos.template import render
-from vyos.template import is_ipv4
-from vyos.template import is_ipv6
-from vyos.utils.dict import dict_search
-from vyos.utils.dict import dict_search_args
-from vyos.utils.list import is_list_equal
-from vyos.utils.file import makedir
-from vyos.utils.file import read_file
-from vyos.utils.file import write_file
-from vyos.utils.kernel import check_kmod
-from vyos.utils.kernel import unload_kmod
-from vyos.utils.process import call
-from vyos.utils.permission import chown
-from vyos.utils.process import cmd
-from vyos.utils.network import is_addr_assigned
-
-from vyos import ConfigError
-from vyos import airbag
-airbag.enable()
-
-user = 'openvpn'
-group = 'openvpn'
-
-cfg_dir = '/run/openvpn'
-cfg_file = '/run/openvpn/{ifname}.conf'
-otp_path = '/config/auth/openvpn'
-otp_file = '/config/auth/openvpn/{ifname}-otp-secrets'
-secret_chars = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567')
-service_file = '/run/systemd/system/openvpn@{ifname}.service.d/20-override.conf'
-
-def get_config(config=None):
- """
- Retrive CLI config as dictionary. Dictionary can never be empty, as at least the
- interface name will be added or a deleted flag
- """
- if config:
- conf = config
- else:
- conf = Config()
- base = ['interfaces', 'openvpn']
-
- ifname, openvpn = get_interface_dict(conf, base)
- openvpn['auth_user_pass_file'] = '/run/openvpn/{ifname}.pw'.format(**openvpn)
-
- if 'deleted' in openvpn:
- return openvpn
-
- openvpn['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)
-
- if is_node_changed(conf, base + [ifname, 'openvpn-option']):
- openvpn.update({'restart_required': {}})
- if is_node_changed(conf, base + [ifname, 'enable-dco']):
- openvpn.update({'restart_required': {}})
-
- # We have to get the dict using 'get_config_dict' instead of 'get_interface_dict'
- # as 'get_interface_dict' merges the defaults in, so we can not check for defaults in there.
- tmp = conf.get_config_dict(base + [openvpn['ifname']], get_first_key=True)
-
- # We have to cleanup the config dict, as default values could enable features
- # which are not explicitly enabled on the CLI. Example: server mfa totp
- # originate comes with defaults, which will enable the
- # totp plugin, even when not set via CLI so we
- # need to check this first and drop those keys
- if dict_search('server.mfa.totp', tmp) == None:
- del openvpn['server']['mfa']
-
- # OpenVPN Data-Channel-Offload (DCO) is a Kernel module. If loaded it applies to all
- # OpenVPN interfaces. Check if DCO is used by any other interface instance.
- tmp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
- for interface, interface_config in tmp.items():
- # If one interface has DCO configured, enable it. No need to further check
- # all other OpenVPN interfaces. We must use a dedicated key to indicate
- # the Kernel module must be loaded or not. The per interface "offload.dco"
- # key is required per OpenVPN interface instance.
- if dict_search('offload.dco', interface_config) != None:
- openvpn['module_load_dco'] = {}
- break
-
- return openvpn
-
-def is_ec_private_key(pki, cert_name):
- if not pki or 'certificate' not in pki:
- return False
- if cert_name not in pki['certificate']:
- return False
-
- pki_cert = pki['certificate'][cert_name]
- if 'private' not in pki_cert or 'key' not in pki_cert['private']:
- return False
-
- key = load_private_key(pki_cert['private']['key'])
- return isinstance(key, ec.EllipticCurvePrivateKey)
-
-def verify_pki(openvpn):
- pki = openvpn['pki']
- interface = openvpn['ifname']
- mode = openvpn['mode']
- shared_secret_key = dict_search_args(openvpn, 'shared_secret_key')
- tls = dict_search_args(openvpn, 'tls')
-
- if not bool(shared_secret_key) ^ bool(tls): # xor check if only one is set
- raise ConfigError('Must specify only one of "shared-secret-key" and "tls"')
-
- if mode in ['server', 'client'] and not tls:
- raise ConfigError('Must specify "tls" for server and client modes')
-
- if not pki:
- raise ConfigError('PKI is not configured')
-
- if shared_secret_key:
- if not dict_search_args(pki, 'openvpn', 'shared_secret'):
- raise ConfigError('There are no openvpn shared-secrets in PKI configuration')
-
- if shared_secret_key not in pki['openvpn']['shared_secret']:
- raise ConfigError(f'Invalid shared-secret on openvpn interface {interface}')
-
- # If PSK settings are correct, warn about its deprecation
- DeprecationWarning("OpenVPN shared-secret support will be removed in future VyOS versions.\n\
- Please migrate your site-to-site tunnels to TLS.\n\
- You can use self-signed certificates with peer fingerprint verification, consult the documentation for details.")
-
- if tls:
- if (mode in ['server', 'client']) and ('ca_certificate' not in tls):
- raise ConfigError(f'Must specify "tls ca-certificate" on openvpn interface {interface},\
- it is required in server and client modes')
- else:
- if ('ca_certificate' not in tls) and ('peer_fingerprint' not in tls):
- raise ConfigError('Either "tls ca-certificate" or "tls peer-fingerprint" is required\
- on openvpn interface {interface} in site-to-site mode')
-
- if 'ca_certificate' in tls:
- for ca_name in tls['ca_certificate']:
- if ca_name not in pki['ca']:
- raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}')
-
- if len(tls['ca_certificate']) > 1:
- sorted_chain = sort_ca_chain(tls['ca_certificate'], pki['ca'])
- if not verify_ca_chain(sorted_chain, pki['ca']):
- raise ConfigError(f'CA certificates are not a valid chain')
-
- if mode != 'client' and 'auth_key' not in tls:
- if 'certificate' not in tls:
- raise ConfigError(f'Missing "tls certificate" on openvpn interface {interface}')
-
- if 'certificate' in tls:
- if tls['certificate'] not in pki['certificate']:
- raise ConfigError(f'Invalid certificate on openvpn interface {interface}')
-
- if dict_search_args(pki, 'certificate', tls['certificate'], 'private', 'password_protected') is not None:
- raise ConfigError(f'Cannot use encrypted private key on openvpn interface {interface}')
-
- if 'dh_params' in tls:
- pki_dh = pki['dh'][tls['dh_params']]
- dh_params = load_dh_parameters(pki_dh['parameters'])
- dh_numbers = dh_params.parameter_numbers()
- dh_bits = dh_numbers.p.bit_length()
-
- if dh_bits < 2048:
- raise ConfigError(f'Minimum DH key-size is 2048 bits')
-
-
- if 'auth_key' in tls or 'crypt_key' in tls:
- if not dict_search_args(pki, 'openvpn', 'shared_secret'):
- raise ConfigError('There are no openvpn shared-secrets in PKI configuration')
-
- if 'auth_key' in tls:
- if tls['auth_key'] not in pki['openvpn']['shared_secret']:
- raise ConfigError(f'Invalid auth-key on openvpn interface {interface}')
-
- if 'crypt_key' in tls:
- if tls['crypt_key'] not in pki['openvpn']['shared_secret']:
- raise ConfigError(f'Invalid crypt-key on openvpn interface {interface}')
-
-def verify(openvpn):
- if 'deleted' in openvpn:
- # remove totp secrets file if totp is not configured
- if os.path.isfile(otp_file.format(**openvpn)):
- os.remove(otp_file.format(**openvpn))
-
- verify_bridge_delete(openvpn)
- return None
-
- if 'mode' not in openvpn:
- raise ConfigError('Must specify OpenVPN operation mode!')
-
- #
- # OpenVPN client mode - VERIFY
- #
- if openvpn['mode'] == 'client':
- if 'local_port' in openvpn:
- raise ConfigError('Cannot specify "local-port" in client mode')
-
- if 'local_host' in openvpn:
- raise ConfigError('Cannot specify "local-host" in client mode')
-
- if 'remote_host' not in openvpn:
- raise ConfigError('Must specify "remote-host" in client mode')
-
- if openvpn['protocol'] == 'tcp-passive':
- raise ConfigError('Protocol "tcp-passive" is not valid in client mode')
-
- if dict_search('tls.dh_params', openvpn):
- raise ConfigError('Cannot specify "tls dh-params" in client mode')
-
- #
- # OpenVPN site-to-site - VERIFY
- #
- elif openvpn['mode'] == 'site-to-site':
- if 'local_address' not in openvpn and 'is_bridge_member' not in openvpn:
- raise ConfigError('Must specify "local-address" or add interface to bridge')
-
- if 'local_address' in openvpn:
- if len([addr for addr in openvpn['local_address'] if is_ipv4(addr)]) > 1:
- raise ConfigError('Only one IPv4 local-address can be specified')
-
- if len([addr for addr in openvpn['local_address'] if is_ipv6(addr)]) > 1:
- raise ConfigError('Only one IPv6 local-address can be specified')
-
- if openvpn['device_type'] == 'tun':
- if 'remote_address' not in openvpn:
- raise ConfigError('Must specify "remote-address"')
-
- if 'remote_address' in openvpn:
- if len([addr for addr in openvpn['remote_address'] if is_ipv4(addr)]) > 1:
- raise ConfigError('Only one IPv4 remote-address can be specified')
-
- if len([addr for addr in openvpn['remote_address'] if is_ipv6(addr)]) > 1:
- raise ConfigError('Only one IPv6 remote-address can be specified')
-
- if not 'local_address' in openvpn:
- raise ConfigError('"remote-address" requires "local-address"')
-
- v4loAddr = [addr for addr in openvpn['local_address'] if is_ipv4(addr)]
- v4remAddr = [addr for addr in openvpn['remote_address'] if is_ipv4(addr)]
- if v4loAddr and not v4remAddr:
- raise ConfigError('IPv4 "local-address" requires IPv4 "remote-address"')
- elif v4remAddr and not v4loAddr:
- raise ConfigError('IPv4 "remote-address" requires IPv4 "local-address"')
-
- v6remAddr = [addr for addr in openvpn['remote_address'] if is_ipv6(addr)]
- v6loAddr = [addr for addr in openvpn['local_address'] if is_ipv6(addr)]
- if v6loAddr and not v6remAddr:
- raise ConfigError('IPv6 "local-address" requires IPv6 "remote-address"')
- elif v6remAddr and not v6loAddr:
- raise ConfigError('IPv6 "remote-address" requires IPv6 "local-address"')
-
- if is_list_equal(v4loAddr, v4remAddr) or is_list_equal(v6loAddr, v6remAddr):
- raise ConfigError('"local-address" and "remote-address" cannot be the same')
-
- if dict_search('local_host', openvpn) in dict_search('local_address', openvpn):
- raise ConfigError('"local-address" cannot be the same as "local-host"')
-
- if dict_search('remote_host', openvpn) in dict_search('remote_address', openvpn):
- raise ConfigError('"remote-address" and "remote-host" can not be the same')
-
- if openvpn['device_type'] == 'tap' and 'local_address' in openvpn:
- # we can only have one local_address, this is ensured above
- v4addr = None
- for laddr in openvpn['local_address']:
- if is_ipv4(laddr):
- v4addr = laddr
- break
-
- if v4addr in openvpn['local_address'] and 'subnet_mask' not in openvpn['local_address'][v4addr]:
- raise ConfigError('Must specify IPv4 "subnet-mask" for local-address')
-
- if dict_search('encryption.ncp_ciphers', openvpn):
- raise ConfigError('NCP ciphers can only be used in client or server mode')
-
- else:
- # checks for client-server or site-to-site bridged
- if 'local_address' in openvpn or 'remote_address' in openvpn:
- raise ConfigError('Cannot specify "local-address" or "remote-address" ' \
- 'in client/server or bridge mode')
-
- #
- # OpenVPN server mode - VERIFY
- #
- if openvpn['mode'] == 'server':
- if openvpn['protocol'] == 'tcp-active':
- raise ConfigError('Protocol "tcp-active" is not valid in server mode')
-
- if dict_search('authentication.username', openvpn) or dict_search('authentication.password', openvpn):
- raise ConfigError('Cannot specify "authentication" in server mode')
-
- if 'remote_port' in openvpn:
- raise ConfigError('Cannot specify "remote-port" in server mode')
-
- if 'remote_host' in openvpn:
- raise ConfigError('Cannot specify "remote-host" in server mode')
-
- tmp = dict_search('server.subnet', openvpn)
- if tmp:
- v4_subnets = len([subnet for subnet in tmp if is_ipv4(subnet)])
- v6_subnets = len([subnet for subnet in tmp if is_ipv6(subnet)])
- if v4_subnets > 1:
- raise ConfigError('Cannot specify more than 1 IPv4 server subnet')
- if v6_subnets > 1:
- raise ConfigError('Cannot specify more than 1 IPv6 server subnet')
-
- for subnet in tmp:
- if is_ipv4(subnet):
- subnet = IPv4Network(subnet)
-
- if openvpn['device_type'] == 'tun' and subnet.prefixlen > 29:
- raise ConfigError('Server subnets smaller than /29 with device type "tun" are not supported')
- elif openvpn['device_type'] == 'tap' and subnet.prefixlen > 30:
- raise ConfigError('Server subnets smaller than /30 with device type "tap" are not supported')
-
- for client in (dict_search('client', openvpn) or []):
- if client['ip'] and not IPv4Address(client['ip'][0]) in subnet:
- raise ConfigError(f'Client "{client["name"]}" IP {client["ip"][0]} not in server subnet {subnet}')
-
- else:
- if 'is_bridge_member' not in openvpn:
- raise ConfigError('Must specify "server subnet" or add interface to bridge in server mode')
-
- if hasattr(dict_search('server.client', openvpn), '__iter__'):
- for client_k, client_v in dict_search('server.client', openvpn).items():
- if (client_v.get('ip') and len(client_v['ip']) > 1) or (client_v.get('ipv6_ip') and len(client_v['ipv6_ip']) > 1):
- raise ConfigError(f'Server client "{client_k}": cannot specify more than 1 IPv4 and 1 IPv6 IP')
-
- if dict_search('server.client_ip_pool', openvpn):
- if not (dict_search('server.client_ip_pool.start', openvpn) and dict_search('server.client_ip_pool.stop', openvpn)):
- raise ConfigError('Server client-ip-pool requires both start and stop addresses')
- else:
- v4PoolStart = IPv4Address(dict_search('server.client_ip_pool.start', openvpn))
- v4PoolStop = IPv4Address(dict_search('server.client_ip_pool.stop', openvpn))
- if v4PoolStart > v4PoolStop:
- raise ConfigError(f'Server client-ip-pool start address {v4PoolStart} is larger than stop address {v4PoolStop}')
-
- v4PoolSize = int(v4PoolStop) - int(v4PoolStart)
- if v4PoolSize >= 65536:
- raise ConfigError(f'Server client-ip-pool is too large [{v4PoolStart} -> {v4PoolStop} = {v4PoolSize}], maximum is 65536 addresses.')
-
- v4PoolNets = list(summarize_address_range(v4PoolStart, v4PoolStop))
- for client in (dict_search('client', openvpn) or []):
- if client['ip']:
- for v4PoolNet in v4PoolNets:
- if IPv4Address(client['ip'][0]) in v4PoolNet:
- print(f'Warning: Client "{client["name"]}" IP {client["ip"][0]} is in server IP pool, it is not reserved for this client.')
- # configuring a client_ip_pool will set 'server ... nopool' which is currently incompatible with 'server-ipv6' (probably to be fixed upstream)
- for subnet in (dict_search('server.subnet', openvpn) or []):
- if is_ipv6(subnet):
- raise ConfigError(f'Setting client-ip-pool is incompatible having an IPv6 server subnet.')
-
- for subnet in (dict_search('server.subnet', openvpn) or []):
- if is_ipv6(subnet):
- tmp = dict_search('client_ipv6_pool.base', openvpn)
- if tmp:
- if not dict_search('server.client_ip_pool', openvpn):
- raise ConfigError('IPv6 server pool requires an IPv4 server pool')
-
- if int(tmp.split('/')[1]) >= 112:
- raise ConfigError('IPv6 server pool must be larger than /112')
-
- #
- # todo - weird logic
- #
- v6PoolStart = IPv6Address(tmp)
- v6PoolStop = IPv6Network((v6PoolStart, openvpn['server_ipv6_pool_prefixlen']), strict=False)[-1] # don't remove the parentheses, it's a 2-tuple
- v6PoolSize = int(v6PoolStop) - int(v6PoolStart) if int(openvpn['server_ipv6_pool_prefixlen']) > 96 else 65536
- if v6PoolSize < v4PoolSize:
- raise ConfigError(f'IPv6 server pool must be at least as large as the IPv4 pool (current sizes: IPv6={v6PoolSize} IPv4={v4PoolSize})')
-
- v6PoolNets = list(summarize_address_range(v6PoolStart, v6PoolStop))
- for client in (dict_search('client', openvpn) or []):
- if client['ipv6_ip']:
- for v6PoolNet in v6PoolNets:
- if IPv6Address(client['ipv6_ip'][0]) in v6PoolNet:
- print(f'Warning: Client "{client["name"]}" IP {client["ipv6_ip"][0]} is in server IP pool, it is not reserved for this client.')
-
- # add mfa users to the file the mfa plugin uses
- if dict_search('server.mfa.totp', openvpn):
- user_data = ''
- if not os.path.isfile(otp_file.format(**openvpn)):
- write_file(otp_file.format(**openvpn), user_data,
- user=user, group=group, mode=0o644)
-
- ovpn_users = read_file(otp_file.format(**openvpn))
- for client in (dict_search('server.client', openvpn) or []):
- exists = None
- for ovpn_user in ovpn_users.split('\n'):
- if re.search('^' + client + ' ', ovpn_user):
- user_data += f'{ovpn_user}\n'
- exists = 'true'
-
- if not exists:
- random = SystemRandom()
- totp_secret = ''.join(random.choice(secret_chars) for _ in range(16))
- user_data += f'{client} otp totp:sha1:base32:{totp_secret}::xxx *\n'
-
- write_file(otp_file.format(**openvpn), user_data,
- user=user, group=group, mode=0o644)
-
- else:
- # checks for both client and site-to-site go here
- if dict_search('server.reject_unconfigured_clients', openvpn):
- raise ConfigError('Option reject-unconfigured-clients only supported in server mode')
-
- if 'replace_default_route' in openvpn and 'remote_host' not in openvpn:
- raise ConfigError('Cannot set "replace-default-route" without "remote-host"')
-
- #
- # OpenVPN common verification section
- # not depending on any operation mode
- #
-
- # verify specified IP address is present on any interface on this system
- if 'local_host' in openvpn:
- if not is_addr_assigned(openvpn['local_host']):
- print('local-host IP address "{local_host}" not assigned' \
- ' to any interface'.format(**openvpn))
-
- # TCP active
- if openvpn['protocol'] == 'tcp-active':
- if 'local_port' in openvpn:
- raise ConfigError('Cannot specify "local-port" with "tcp-active"')
-
- if 'remote_host' not in openvpn:
- raise ConfigError('Must specify "remote-host" with "tcp-active"')
-
- #
- # TLS/encryption
- #
- if 'shared_secret_key' in openvpn:
- if dict_search('encryption.cipher', openvpn) in ['aes128gcm', 'aes192gcm', 'aes256gcm']:
- raise ConfigError('GCM encryption with shared-secret-key not supported')
-
- if 'tls' in openvpn:
- if {'auth_key', 'crypt_key'} <= set(openvpn['tls']):
- raise ConfigError('TLS auth and crypt keys are mutually exclusive')
-
- tmp = dict_search('tls.role', openvpn)
- if tmp:
- if openvpn['mode'] in ['client', 'server']:
- if not dict_search('tls.auth_key', openvpn):
- raise ConfigError('Cannot specify "tls role" in client-server mode')
-
- if tmp == 'active':
- if openvpn['protocol'] == 'tcp-passive':
- raise ConfigError('Cannot specify "tcp-passive" when "tls role" is "active"')
-
- if dict_search('tls.dh_params', openvpn):
- raise ConfigError('Cannot specify "tls dh-params" when "tls role" is "active"')
-
- elif tmp == 'passive':
- if openvpn['protocol'] == 'tcp-active':
- raise ConfigError('Cannot specify "tcp-active" when "tls role" is "passive"')
-
- if 'certificate' in openvpn['tls'] and is_ec_private_key(openvpn['pki'], openvpn['tls']['certificate']):
- if 'dh_params' in openvpn['tls']:
- print('Warning: using dh-params and EC keys simultaneously will ' \
- 'lead to DH ciphers being used instead of ECDH')
-
- if dict_search('encryption.cipher', openvpn) == 'none':
- print('Warning: "encryption none" was specified!')
- print('No encryption will be performed and data is transmitted in ' \
- 'plain text over the network!')
-
- verify_pki(openvpn)
-
- #
- # Auth user/pass
- #
- if (dict_search('authentication.username', openvpn) and not
- dict_search('authentication.password', openvpn)):
- raise ConfigError('Password for authentication is missing')
-
- if (dict_search('authentication.password', openvpn) and not
- dict_search('authentication.username', openvpn)):
- raise ConfigError('Username for authentication is missing')
-
- verify_vrf(openvpn)
- verify_bond_bridge_member(openvpn)
- verify_mirror_redirect(openvpn)
-
- return None
-
-def generate_pki_files(openvpn):
- pki = openvpn['pki']
- if not pki:
- return None
-
- interface = openvpn['ifname']
- shared_secret_key = dict_search_args(openvpn, 'shared_secret_key')
- tls = dict_search_args(openvpn, 'tls')
-
- if shared_secret_key:
- pki_key = pki['openvpn']['shared_secret'][shared_secret_key]
- key_path = os.path.join(cfg_dir, f'{interface}_shared.key')
- write_file(key_path, wrap_openvpn_key(pki_key['key']),
- user=user, group=group)
-
- if tls:
- if 'ca_certificate' in tls:
- cert_path = os.path.join(cfg_dir, f'{interface}_ca.pem')
- crl_path = os.path.join(cfg_dir, f'{interface}_crl.pem')
-
- if os.path.exists(cert_path):
- os.unlink(cert_path)
-
- if os.path.exists(crl_path):
- os.unlink(crl_path)
-
- for cert_name in sort_ca_chain(tls['ca_certificate'], pki['ca']):
- pki_ca = pki['ca'][cert_name]
-
- if 'certificate' in pki_ca:
- write_file(cert_path, wrap_certificate(pki_ca['certificate']) + "\n",
- user=user, group=group, mode=0o600, append=True)
-
- if 'crl' in pki_ca:
- for crl in pki_ca['crl']:
- write_file(crl_path, wrap_crl(crl) + "\n", user=user, group=group,
- mode=0o600, append=True)
-
- openvpn['tls']['crl'] = True
-
- if 'certificate' in tls:
- cert_name = tls['certificate']
- pki_cert = pki['certificate'][cert_name]
-
- if 'certificate' in pki_cert:
- cert_path = os.path.join(cfg_dir, f'{interface}_cert.pem')
- write_file(cert_path, wrap_certificate(pki_cert['certificate']),
- user=user, group=group, mode=0o600)
-
- if 'private' in pki_cert and 'key' in pki_cert['private']:
- key_path = os.path.join(cfg_dir, f'{interface}_cert.key')
- write_file(key_path, wrap_private_key(pki_cert['private']['key']),
- user=user, group=group, mode=0o600)
-
- openvpn['tls']['private_key'] = True
-
- if 'dh_params' in tls:
- dh_name = tls['dh_params']
- pki_dh = pki['dh'][dh_name]
-
- if 'parameters' in pki_dh:
- dh_path = os.path.join(cfg_dir, f'{interface}_dh.pem')
- write_file(dh_path, wrap_dh_parameters(pki_dh['parameters']),
- user=user, group=group, mode=0o600)
-
- if 'auth_key' in tls:
- key_name = tls['auth_key']
- pki_key = pki['openvpn']['shared_secret'][key_name]
-
- if 'key' in pki_key:
- key_path = os.path.join(cfg_dir, f'{interface}_auth.key')
- write_file(key_path, wrap_openvpn_key(pki_key['key']),
- user=user, group=group, mode=0o600)
-
- if 'crypt_key' in tls:
- key_name = tls['crypt_key']
- pki_key = pki['openvpn']['shared_secret'][key_name]
-
- if 'key' in pki_key:
- key_path = os.path.join(cfg_dir, f'{interface}_crypt.key')
- write_file(key_path, wrap_openvpn_key(pki_key['key']),
- user=user, group=group, mode=0o600)
-
-
-def generate(openvpn):
- interface = openvpn['ifname']
- directory = os.path.dirname(cfg_file.format(**openvpn))
- openvpn['plugin_dir'] = '/usr/lib/openvpn'
- # create base config directory on demand
- makedir(directory, user, group)
- # enforce proper permissions on /run/openvpn
- chown(directory, user, group)
-
- # we can't know in advance which clients have been removed,
- # thus all client configs will be removed and re-added on demand
- ccd_dir = os.path.join(directory, 'ccd', interface)
- if os.path.isdir(ccd_dir):
- rmtree(ccd_dir, ignore_errors=True)
-
- # Remove systemd directories with overrides
- service_dir = os.path.dirname(service_file.format(**openvpn))
- if os.path.isdir(service_dir):
- rmtree(service_dir, ignore_errors=True)
-
- if 'deleted' in openvpn or 'disable' in openvpn:
- return None
-
- # create client config directory on demand
- makedir(ccd_dir, user, group)
-
- # Fix file permissons for keys
- generate_pki_files(openvpn)
-
- # Generate User/Password authentication file
- if 'authentication' in openvpn:
- render(openvpn['auth_user_pass_file'], 'openvpn/auth.pw.j2', openvpn,
- user=user, group=group, permission=0o600)
- else:
- # delete old auth file if present
- if os.path.isfile(openvpn['auth_user_pass_file']):
- os.remove(openvpn['auth_user_pass_file'])
-
- # Generate client specific configuration
- server_client = dict_search_args(openvpn, 'server', 'client')
- if server_client:
- for client, client_config in server_client.items():
- client_file = os.path.join(ccd_dir, client)
-
- # Our client need's to know its subnet mask ...
- client_config['server_subnet'] = dict_search('server.subnet', openvpn)
-
- render(client_file, 'openvpn/client.conf.j2', client_config,
- user=user, group=group)
-
- # we need to support quoting of raw parameters from OpenVPN CLI
- # see https://vyos.dev/T1632
- render(cfg_file.format(**openvpn), 'openvpn/server.conf.j2', openvpn,
- formater=lambda _: _.replace("&quot;", '"'), user=user, group=group)
-
- # Render 20-override.conf for OpenVPN service
- render(service_file.format(**openvpn), 'openvpn/service-override.conf.j2', openvpn,
- formater=lambda _: _.replace("&quot;", '"'), user=user, group=group)
- # Reload systemd services config to apply an override
- call(f'systemctl daemon-reload')
-
- return None
-
-def apply(openvpn):
- interface = openvpn['ifname']
-
- # Do some cleanup when OpenVPN is disabled/deleted
- if 'deleted' in openvpn or 'disable' in openvpn:
- call(f'systemctl stop openvpn@{interface}.service')
- for cleanup_file in glob(f'/run/openvpn/{interface}.*'):
- if os.path.isfile(cleanup_file):
- os.unlink(cleanup_file)
-
- if interface in interfaces():
- VTunIf(interface).remove()
-
- # dynamically load/unload DCO Kernel extension if requested
- dco_module = 'ovpn_dco_v2'
- if 'module_load_dco' in openvpn:
- check_kmod(dco_module)
- else:
- unload_kmod(dco_module)
-
- # Now bail out early if interface is disabled or got deleted
- if 'deleted' in openvpn or 'disable' in openvpn:
- return None
-
- # verify specified IP address is present on any interface on this system
- # Allow to bind service to nonlocal address, if it virtaual-vrrp address
- # or if address will be assign later
- if 'local_host' in openvpn:
- if not is_addr_assigned(openvpn['local_host']):
- cmd('sysctl -w net.ipv4.ip_nonlocal_bind=1')
-
- # No matching OpenVPN process running - maybe it got killed or none
- # existed - nevertheless, spawn new OpenVPN process
- action = 'reload-or-restart'
- if 'restart_required' in openvpn:
- action = 'restart'
- call(f'systemctl {action} openvpn@{interface}.service')
-
- o = VTunIf(**openvpn)
- o.update(openvpn)
-
- return None
-
-
-if __name__ == '__main__':
- try:
- c = get_config()
- verify(c)
- generate(c)
- apply(c)
- except ConfigError as e:
- print(e)
- exit(1)
-