summaryrefslogtreecommitdiff
path: root/src/conf_mode/interface-openvpn.py
diff options
context:
space:
mode:
authorChristian Poessinger <christian@poessinger.com>2019-10-13 12:40:54 +0200
committerChristian Poessinger <christian@poessinger.com>2019-10-13 12:41:31 +0200
commitfe7279454c86a2947b23fb0d769483b7fe2a3cc3 (patch)
treead9e398b576d27f1364a4e86d3a9057372823f06 /src/conf_mode/interface-openvpn.py
parent67ca26dc6f6e548dfd0a1bc787aa039d96450b97 (diff)
downloadvyos-1x-fe7279454c86a2947b23fb0d769483b7fe2a3cc3.tar.gz
vyos-1x-fe7279454c86a2947b23fb0d769483b7fe2a3cc3.zip
Sync XML interface description source file pattern and conf script name
renamed: interface-bonding.py -> interfaces-bonding.py renamed: interface-bridge.py -> interfaces-bridge.py renamed: interface-dummy.py -> interfaces-dummy.py renamed: interface-ethernet.py -> interfaces-ethernet.py renamed: interface-loopback.py -> interfaces-loopback.py renamed: interface-openvpn.py -> interfaces-openvpn.py renamed: interface-vxlan.py -> interfaces-vxlan.py renamed: interface-wireguard.py -> interfaces-wireguard.py
Diffstat (limited to 'src/conf_mode/interface-openvpn.py')
-rwxr-xr-xsrc/conf_mode/interface-openvpn.py960
1 files changed, 0 insertions, 960 deletions
diff --git a/src/conf_mode/interface-openvpn.py b/src/conf_mode/interface-openvpn.py
deleted file mode 100755
index 5345bf7a2..000000000
--- a/src/conf_mode/interface-openvpn.py
+++ /dev/null
@@ -1,960 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2019 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 sys
-import stat
-import jinja2
-
-from copy import deepcopy
-from grp import getgrnam
-from ipaddress import ip_address,ip_network,IPv4Interface
-from netifaces import interfaces
-from psutil import pid_exists
-from pwd import getpwnam
-from subprocess import Popen, PIPE
-from time import sleep
-
-from vyos import ConfigError
-from vyos.config import Config
-from vyos.ifconfig import Interface
-from vyos.validate import is_addr_assigned
-
-user = 'openvpn'
-group = 'openvpn'
-
-# Please be careful if you edit the template.
-config_tmpl = """
-### Autogenerated by interfaces-openvpn.py ###
-#
-# See https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage
-# for individual keyword definition
-
-{% if description %}
-# {{ description }}
-{% endif %}
-
-verb 3
-status /opt/vyatta/etc/openvpn/status/{{ intf }}.status 30
-writepid /var/run/openvpn/{{ intf }}.pid
-daemon openvpn-{{ intf }}
-
-dev-type {{ type }}
-dev {{ intf }}
-user {{ uid }}
-group {{ gid }}
-persist-key
-iproute /usr/libexec/vyos/system/unpriv-ip
-
-proto {% if 'tcp-active' in protocol -%}tcp-client{% elif 'tcp-passive' in protocol -%}tcp-server{% else %}udp{% endif %}
-
-{%- if local_host %}
-local {{ local_host }}
-{% endif %}
-
-{%- if local_port %}
-lport {{ local_port }}
-{% endif %}
-
-{%- if remote_port %}
-rport {{ remote_port }}
-{% endif %}
-
-{%- if remote_host %}
-{% for remote in remote_host -%}
-remote {{ remote }}
-{% endfor -%}
-{% endif %}
-
-{%- if shared_secret_file %}
-secret {{ shared_secret_file }}
-{% endif %}
-
-{%- if persistent_tunnel %}
-persist-tun
-{% endif %}
-
-{%- if mode %}
-{%- if 'client' in mode %}
-#
-# OpenVPN Client mode
-#
-client
-nobind
-{%- elif 'server' in mode %}
-#
-# OpenVPN Server mode
-#
-mode server
-tls-server
-keepalive {{ ping_interval }} {{ ping_restart }}
-management /tmp/openvpn-mgmt-intf unix
-
-{%- if server_topology %}
-topology {% if 'point-to-point' in server_topology %}p2p{% else %}subnet{% endif %}
-{% endif %}
-
-{% for ns in server_dns_nameserver -%}
-push "dhcp-option DNS {{ ns }}"
-{% endfor -%}
-
-{% for route in server_push_route -%}
-push "route {{ route }}"
-{% endfor -%}
-
-{%- if server_domain %}
-push "dhcp-option DOMAIN {{ server_domain }}"
-{% endif %}
-
-{%- if server_max_conn %}
-max-clients {{ server_max_conn }}
-{% endif %}
-
-{%- if bridge_member %}
-server-bridge nogw
-{%- else %}
-server {{ server_subnet }}
-{% endif %}
-
-{%- if server_reject_unconfigured %}
-ccd-exclusive
-{% endif %}
-
-{%- else %}
-#
-# OpenVPN site-2-site mode
-#
-ping {{ ping_interval }}
-ping-restart {{ ping_restart }}
-
-{%- if local_address_subnet %}
-ifconfig {{ local_address }} {{ local_address_subnet }}
-{% elif remote_address %}
-ifconfig {{ local_address }} {{ remote_address }}
-{% endif %}
-
-{% endif %}
-{% endif %}
-
-{%- if tls_ca_cert %}
-ca {{ tls_ca_cert }}
-{% endif %}
-
-{%- if tls_cert %}
-cert {{ tls_cert }}
-{% endif %}
-
-{%- if tls_key %}
-key {{ tls_key }}
-{% endif %}
-
-{%- if tls_crl %}
-crl-verify {{ tls_crl }}
-{% endif %}
-
-{%- if tls_version_min %}
-tls-version-min {{tls_version_min}}
-{% endif %}
-
-{%- if tls_dh %}
-dh {{ tls_dh }}
-{% endif %}
-
-{%- if tls_auth %}
-tls-auth {{tls_auth}}
-{% endif %}
-
-{%- if 'active' in tls_role %}
-tls-client
-{%- elif 'passive' in tls_role %}
-tls-server
-{% endif %}
-
-{%- if redirect_gateway %}
-push "redirect-gateway {{ redirect_gateway }}"
-{% endif %}
-
-{%- if compress_lzo %}
-compress lzo
-{% endif %}
-
-{%- if hash %}
-auth {{ hash }}
-{% endif %}
-
-{%- if encryption %}
-{%- if 'des' in encryption %}
-cipher des-cbc
-{%- elif '3des' in encryption %}
-cipher des-ede3-cbc
-{%- elif 'bf128' in encryption %}
-cipher bf-cbc
-keysize 128
-{%- elif 'bf256' in encryption %}
-cipher bf-cbc
-keysize 25
-{%- elif 'aes128gcm' in encryption %}
-cipher aes-128-gcm
-{%- elif 'aes128' in encryption %}
-cipher aes-128-cbc
-{%- elif 'aes192gcm' in encryption %}
-cipher aes-192-gcm
-{%- elif 'aes192' in encryption %}
-cipher aes-192-cbc
-{%- elif 'aes256gcm' in encryption %}
-cipher aes-256-gcm
-{%- elif 'aes256' in encryption %}
-cipher aes-256-cbc
-{% endif %}
-{% endif %}
-
-{%- if auth %}
-auth-user-pass /tmp/openvpn-{{ intf }}-pw
-auth-retry nointeract
-{% endif %}
-
-{%- if client %}
-client-config-dir /opt/vyatta/etc/openvpn/ccd/{{ intf }}
-{% endif %}
-
-# DEPRECATED This option will be removed in OpenVPN 2.5
-# Until OpenVPN v2.3 the format of the X.509 Subject fields was formatted like this:
-# /C=US/L=Somewhere/CN=John Doe/emailAddress=john@example.com In addition the old
-# behaviour was to remap any character other than alphanumeric, underscore ('_'),
-# dash ('-'), dot ('.'), and slash ('/') to underscore ('_'). The X.509 Subject
-# string as returned by the tls_id environmental variable, could additionally
-# contain colon (':') or equal ('='). When using the --compat-names option, this
-# old formatting and remapping will be re-enabled again. This is purely implemented
-# for compatibility reasons when using older plug-ins or scripts which does not
-# handle the new formatting or UTF-8 characters.
-#
-# See https://phabricator.vyos.net/T1512
-compat-names
-
-{% for option in options -%}
-{{ option }}
-{% endfor -%}
-"""
-
-client_tmpl = """
-### Autogenerated by interfaces-openvpn.py ###
-
-ifconfig-push {{ ip }} {{ remote_netmask }}
-{% for route in push_route -%}
-push "route {{ route }}"
-{% endfor -%}
-
-{% for net in subnet -%}
-iroute {{ net }}
-{% endfor -%}
-
-{%- if disable %}
-disable
-{% endif %}
-"""
-
-default_config_data = {
- 'address': [],
- 'auth_user': '',
- 'auth_pass': '',
- 'auth': False,
- 'bridge_member': [],
- 'compress_lzo': False,
- 'deleted': False,
- 'description': '',
- 'disable': False,
- 'encryption': '',
- 'hash': '',
- 'intf': '',
- 'ping_restart': '60',
- 'ping_interval': '10',
- 'local_address': '',
- 'local_address_subnet': '',
- 'local_host': '',
- 'local_port': '',
- 'mode': '',
- 'options': [],
- 'persistent_tunnel': False,
- 'protocol': '',
- 'redirect_gateway': '',
- 'remote_address': '',
- 'remote_host': [],
- 'remote_port': '',
- 'client': [],
- 'server_domain': '',
- 'server_max_conn': '',
- 'server_dns_nameserver': [],
- 'server_push_route': [],
- 'server_reject_unconfigured': False,
- 'server_subnet': '',
- 'server_topology': '',
- 'shared_secret_file': '',
- 'tls': False,
- 'tls_auth': '',
- 'tls_ca_cert': '',
- 'tls_cert': '',
- 'tls_crl': '',
- 'tls_dh': '',
- 'tls_key': '',
- 'tls_role': '',
- 'tls_version_min': '',
- 'type': 'tun',
- 'uid': user,
- 'gid': group,
-}
-
-def subprocess_cmd(command):
- p = Popen(command, stdout=PIPE, shell=True)
- p.communicate()
-
-def get_config_name(intf):
- cfg_file = r'/opt/vyatta/etc/openvpn/openvpn-{}.conf'.format(intf)
- return cfg_file
-
-def openvpn_mkdir(directory):
- # create directory on demand
- if not os.path.exists(directory):
- os.mkdir(directory)
-
- # fix permissions - corresponds to mode 755
- os.chmod(directory, stat.S_IRWXU|stat.S_IRGRP|stat.S_IXGRP|stat.S_IROTH|stat.S_IXOTH)
- uid = getpwnam(user).pw_uid
- gid = getgrnam(group).gr_gid
- os.chown(directory, uid, gid)
-
-def fixup_permission(filename, permission=stat.S_IRUSR):
- """
- Check if the given file exists and change ownershit to root/vyattacfg
- and appripriate file access permissions - default is user and group readable
- """
- if os.path.isfile(filename):
- os.chmod(filename, permission)
-
- # make file owned by root / vyattacfg
- uid = getpwnam('root').pw_uid
- gid = getgrnam('vyattacfg').gr_gid
- os.chown(filename, uid, gid)
-
-def checkCertHeader(header, filename):
- """
- Verify if filename contains specified header.
- Returns True on success or on file not found to not trigger the exceptions
- """
- if not os.path.isfile(filename):
- return False
-
- with open(filename, 'r') as f:
- for line in f:
- if re.match(header, line):
- return True
-
- return True
-
-def get_config():
- openvpn = deepcopy(default_config_data)
- conf = Config()
-
- # determine tagNode instance
- try:
- openvpn['intf'] = os.environ['VYOS_TAGNODE_VALUE']
- except KeyError as E:
- print("Interface not specified")
-
- # Check if interface instance has been removed
- if not conf.exists('interfaces openvpn ' + openvpn['intf']):
- openvpn['deleted'] = True
- return openvpn
-
- # Check if we belong to any bridge interface
- for bridge in conf.list_nodes('interfaces bridge'):
- for intf in conf.list_nodes('interfaces bridge {} member interface'.format(bridge)):
- if intf == openvpn['intf']:
- openvpn['bridge_member'].append(intf)
-
- # set configuration level
- conf.set_level('interfaces openvpn ' + openvpn['intf'])
-
- # retrieve authentication options - username
- if conf.exists('authentication username'):
- openvpn['auth_user'] = conf.return_value('authentication username')
- openvpn['auth'] = True
-
- # retrieve authentication options - username
- if conf.exists('authentication password'):
- openvpn['auth_pass'] = conf.return_value('authentication password')
- openvpn['auth'] = True
-
- # retrieve interface description
- if conf.exists('description'):
- openvpn['description'] = conf.return_value('description')
-
- # interface device-type
- if conf.exists('device-type'):
- openvpn['type'] = conf.return_value('device-type')
-
- # disable interface
- if conf.exists('disable'):
- openvpn['disable'] = True
-
- # data encryption algorithm
- if conf.exists('encryption'):
- openvpn['encryption'] = conf.return_value('encryption')
-
- # hash algorithm
- if conf.exists('hash'):
- openvpn['hash'] = conf.return_value('hash')
-
- # Maximum number of keepalive packet failures
- if conf.exists('keep-alive failure-count') and conf.exists('keep-alive interval'):
- fail_count = conf.return_value('keep-alive failure-count')
- interval = conf.return_value('keep-alive interval')
- openvpn['ping_interval' ] = interval
- openvpn['ping_restart' ] = int(interval) * int(fail_count)
-
- # Local IP address of tunnel - even as it is a tag node - we can only work
- # on the first address
- if conf.exists('local-address'):
- openvpn['local_address'] = conf.list_nodes('local-address')[0]
- if conf.exists('local-address {} subnet-mask'.format(openvpn['local_address'])):
- openvpn['local_address_subnet'] = conf.return_value('local-address {} subnet-mask'.format(openvpn['local_address']))
-
- # Local IP address to accept connections
- if conf.exists('local-host'):
- openvpn['local_host'] = conf.return_value('local-host')
-
- # Local port number to accept connections
- if conf.exists('local-port'):
- openvpn['local_port'] = conf.return_value('local-port')
-
- # OpenVPN operation mode
- if conf.exists('mode'):
- mode = conf.return_value('mode')
- openvpn['mode'] = mode
-
- # Additional OpenVPN options
- if conf.exists('openvpn-option'):
- openvpn['options'] = conf.return_values('openvpn-option')
-
- # Do not close and reopen interface
- if conf.exists('persistent-tunnel'):
- openvpn['persistent_tunnel'] = True
-
- # Communication protocol
- if conf.exists('protocol'):
- openvpn['protocol'] = conf.return_value('protocol')
-
- # IP address of remote end of tunnel
- if conf.exists('remote-address'):
- openvpn['remote_address'] = conf.return_value('remote-address')
-
- # Remote host to connect to (dynamic if not set)
- if conf.exists('remote-host'):
- openvpn['remote_host'] = conf.return_values('remote-host')
-
- # Remote port number to connect to
- if conf.exists('remote-port'):
- openvpn['remote_port'] = conf.return_value('remote-port')
-
- # OpenVPN tunnel to be used as the default route
- # see https://openvpn.net/community-resources/reference-manual-for-openvpn-2-4/
- # redirect-gateway flags
- if conf.exists('replace-default-route'):
- openvpn['redirect_gateway'] = 'def1'
-
- if conf.exists('replace-default-route local'):
- openvpn['redirect_gateway'] = 'local def1'
-
- # Topology for clients
- if conf.exists('server topology'):
- openvpn['server_topology'] = conf.return_value('server topology')
-
- # Server-mode subnet (from which client IPs are allocated)
- if conf.exists('server subnet'):
- network = conf.return_value('server subnet')
- tmp = IPv4Interface(network).with_netmask
- # convert the network in format: "192.0.2.0 255.255.255.0" for later use in template
- openvpn['server_subnet'] = tmp.replace(r'/', ' ')
-
- # Client-specific settings
- for client in conf.list_nodes('server client'):
- # set configuration level
- conf.set_level('interfaces openvpn ' + openvpn['intf'] + ' server client ' + client)
- data = {
- 'name': client,
- 'disable': False,
- 'ip': '',
- 'push_route': [],
- 'subnet': [],
- 'remote_netmask': ''
- }
-
- # note: with "topology subnet", this is "<ip> <netmask>".
- # with "topology p2p", this is "<ip> <our_ip>".
- if openvpn['server_topology'] == 'subnet':
- # we are only interested in the netmask portion of server_subnet
- data['remote_netmask'] = openvpn['server_subnet'].split(' ')[1]
- else:
- # we need the server subnet in format 192.0.2.0/255.255.255.0
- subnet = openvpn['server_subnet'].replace(' ', r'/')
- # get iterator over the usable hosts in the network
- tmp = ip_network(subnet).hosts()
- # OpenVPN always uses the subnets first available IP address
- data['remote_netmask'] = list(tmp)[0]
-
- # Option to disable client connection
- if conf.exists('disable'):
- data['disable'] = True
-
- # IP address of the client
- if conf.exists('ip'):
- data['ip'] = conf.return_value('ip')
-
- # Route to be pushed to the client
- for network in conf.return_values('push-route'):
- tmp = IPv4Interface(network).with_netmask
- data['push_route'].append(tmp.replace(r'/', ' '))
-
- # Subnet belonging to the client
- for network in conf.return_values('subnet'):
- tmp = IPv4Interface(network).with_netmask
- data['subnet'].append(tmp.replace(r'/', ' '))
-
- # Append to global client list
- openvpn['client'].append(data)
-
- # re-set configuration level
- conf.set_level('interfaces openvpn ' + openvpn['intf'])
-
- # DNS suffix to be pushed to all clients
- if conf.exists('server domain-name'):
- openvpn['server_domain'] = conf.return_value('server domain-name')
-
- # Number of maximum client connections
- if conf.exists('server max-connections'):
- openvpn['server_max_conn'] = conf.return_value('server max-connections')
-
- # Domain Name Server (DNS)
- if conf.exists('server name-server'):
- openvpn['server_dns_nameserver'] = conf.return_values('server name-server')
-
- # Route to be pushed to all clients
- if conf.exists('server push-route'):
- for network in conf.return_values('server push-route'):
- tmp = IPv4Interface(network).with_netmask
- openvpn['server_push_route'].append(tmp.replace(r'/', ' '))
-
- # Reject connections from clients that are not explicitly configured
- if conf.exists('server reject-unconfigured-clients'):
- openvpn['server_reject_unconfigured'] = True
-
- # File containing TLS auth static key
- if conf.exists('tls auth-file'):
- openvpn['tls_auth'] = conf.return_value('tls auth-file')
- openvpn['tls'] = True
-
- # File containing certificate for Certificate Authority (CA)
- if conf.exists('tls ca-cert-file'):
- openvpn['tls_ca_cert'] = conf.return_value('tls ca-cert-file')
- openvpn['tls'] = True
-
- # File containing certificate for this host
- if conf.exists('tls cert-file'):
- openvpn['tls_cert'] = conf.return_value('tls cert-file')
- openvpn['tls'] = True
-
- # File containing certificate revocation list (CRL) for this host
- if conf.exists('tls crl-file'):
- openvpn['tls_crl'] = conf.return_value('tls crl-file')
- openvpn['tls'] = True
-
- # File containing Diffie Hellman parameters (server only)
- if conf.exists('tls dh-file'):
- openvpn['tls_dh'] = conf.return_value('tls dh-file')
- openvpn['tls'] = True
-
- # File containing this host's private key
- if conf.exists('tls key-file'):
- openvpn['tls_key'] = conf.return_value('tls key-file')
- openvpn['tls'] = True
-
- # Role in TLS negotiation
- if conf.exists('tls role'):
- openvpn['tls_role'] = conf.return_value('tls role')
- openvpn['tls'] = True
-
- # Minimum required TLS version
- if conf.exists('tls tls-version-min'):
- openvpn['tls_version_min'] = conf.return_value('tls tls-version-min')
-
- if conf.exists('shared-secret-key-file'):
- openvpn['shared_secret_file'] = conf.return_value('shared-secret-key-file')
-
- if conf.exists('use-lzo-compression'):
- openvpn['compress_lzo'] = True
-
- return openvpn
-
-def verify(openvpn):
- if openvpn['deleted']:
- return None
-
- if not openvpn['mode']:
- raise ConfigError('Must specify OpenVPN operation mode')
-
- # Checks which need to be performed on interface rmeoval
- if openvpn['deleted']:
- # OpenVPN interface can not be deleted if it's still member of a bridge
- if openvpn['bridge_member']:
- raise ConfigError('Can not delete {} as it is a member interface of bridge {}!'.format(openvpn['intf'], bridge))
-
- #
- # OpenVPN client mode - VERIFY
- #
- if openvpn['mode'] == 'client':
- if openvpn['local_port']:
- raise ConfigError('Cannot specify "local-port" in client mode')
-
- if openvpn['local_host']:
- raise ConfigError('Cannot specify "local-host" in client mode')
-
- if openvpn['protocol'] == 'tcp-passive':
- raise ConfigError('Protocol "tcp-passive" is not valid in client mode')
-
- if not openvpn['remote_host']:
- raise ConfigError('Must specify "remote-host" in client mode')
-
- if openvpn['tls_dh']:
- raise ConfigError('Cannot specify "tls dh-file" in client mode')
-
- #
- # OpenVPN site-to-site - VERIFY
- #
- if openvpn['mode'] == 'site-to-site':
- if not (openvpn['local_address'] or openvpn['bridge_member']):
- raise ConfigError('Must specify "local-address" or "bridge member interface"')
-
- for host in openvpn['remote_host']:
- if host == openvpn['remote_address']:
- raise ConfigError('"remote-address" cannot be the same as "remote-host"')
-
- if openvpn['type'] == 'tun':
- if not openvpn['remote_address']:
- raise ConfigError('Must specify "remote-address"')
-
- if openvpn['local_address'] == openvpn['remote_address']:
- raise ConfigError('"local-address" and "remote-address" cannot be the same')
-
- if openvpn['local_address'] == openvpn['local_host']:
- raise ConfigError('"local-address" cannot be the same as "local-host"')
-
- else:
- if openvpn['local_address'] or openvpn['remote_address']:
- raise ConfigError('Cannot specify "local-address" or "remote-address" in client-server mode')
-
- elif openvpn['bridge_member']:
- raise ConfigError('Cannot specify "local-address" or "remote-address" in 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 openvpn['remote_port']:
- raise ConfigError('Cannot specify "remote-port" in server mode')
-
- if openvpn['remote_host']:
- raise ConfigError('Cannot specify "remote-host" in server mode')
-
- if openvpn['protocol'] == 'tcp-passive' and len(openvpn['remote_host']) > 1:
- raise ConfigError('Cannot specify more than 1 "remote-host" with "tcp-passive"')
-
- if not openvpn['tls_dh']:
- raise ConfigError('Must specify "tls dh-file" in server mode')
-
- if not openvpn['server_subnet']:
- if not openvpn['bridge_member']:
- raise ConfigError('Must specify "server subnet" option in server mode')
-
- else:
- # checks for both client and site-to-site go here
- if openvpn['server_reject_unconfigured']:
- raise ConfigError('reject-unconfigured-clients is only supported in OpenVPN server mode')
-
- if openvpn['server_topology']:
- raise ConfigError('The "topology" option is only valid in server mode')
-
- if (not openvpn['remote_host']) and openvpn['redirect_gateway']:
- 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 openvpn['local_host']:
- if not is_addr_assigned(openvpn['local_host']):
- raise ConfigError('No interface on system with specified local-host IP address: {}'.format(openvpn['local_host']))
-
- # TCP active
- if openvpn['protocol'] == 'tcp-active':
- if openvpn['local_port']:
- raise ConfigError('Cannot specify "local-port" with "tcp-active"')
-
- if not openvpn['remote_host']:
- raise ConfigError('Must specify "remote-host" with "tcp-active"')
-
- # shared secret and TLS
- if not (openvpn['shared_secret_file'] or openvpn['tls']):
- raise ConfigError('Must specify one of "shared-secret-key-file" and "tls"')
-
- if openvpn['shared_secret_file'] and openvpn['tls']:
- raise ConfigError('Can only specify one of "shared-secret-key-file" and "tls"')
-
- if openvpn['mode'] in ['client', 'server']:
- if not openvpn['tls']:
- raise ConfigError('Must specify "tls" in client-server mode')
-
- #
- # TLS/encryption
- #
- if openvpn['shared_secret_file']:
- if openvpn['encryption'] in ['aes128gcm', 'aes192gcm', 'aes256gcm']:
- raise ConfigError('GCM encryption with shared-secret-key-file is not supported')
-
- if not checkCertHeader('-----BEGIN OpenVPN Static key V1-----', openvpn['shared_secret_file']):
- raise ConfigError('Specified shared-secret-key-file "{}" is not valid'.format(openvpn['shared_secret_file']))
-
- if openvpn['tls']:
- if not openvpn['tls_ca_cert']:
- raise ConfigError('Must specify "tls ca-cert-file"')
-
- if not (openvpn['mode'] == 'client' and openvpn['auth']):
- if not openvpn['tls_cert']:
- raise ConfigError('Must specify "tls cert-file"')
-
- if not openvpn['tls_key']:
- raise ConfigError('Must specify "tls key-file"')
-
- if not checkCertHeader('-----BEGIN CERTIFICATE-----', openvpn['tls_ca_cert']):
- raise ConfigError('Specified ca-cert-file "{}" is invalid'.format(openvpn['tls_ca_cert']))
-
- if openvpn['tls_auth']:
- if not checkCertHeader('-----BEGIN OpenVPN Static key V1-----', openvpn['tls_auth']):
- raise ConfigError('Specified auth-file "{}" is invalid'.format(openvpn['tls_auth']))
-
- if openvpn['tls_cert']:
- if not checkCertHeader('-----BEGIN CERTIFICATE-----', openvpn['tls_cert']):
- raise ConfigError('Specified cert-file "{}" is invalid'.format(openvpn['tls_cert']))
-
- if openvpn['tls_key']:
- if not checkCertHeader('-----BEGIN (?:RSA )?PRIVATE KEY-----', openvpn['tls_key']):
- raise ConfigError('Specified key-file "{}" is not valid'.format(openvpn['tls_key']))
-
- if openvpn['tls_crl']:
- if not checkCertHeader('-----BEGIN X509 CRL-----', openvpn['tls_crl']):
- raise ConfigError('Specified crl-file "{} not valid'.format(openvpn['tls_crl']))
-
- if openvpn['tls_dh']:
- if not checkCertHeader('-----BEGIN DH PARAMETERS-----', openvpn['tls_dh']):
- raise ConfigError('Specified dh-file "{}" is not valid'.format(openvpn['tls_dh']))
-
- if openvpn['tls_role']:
- if openvpn['mode'] in ['client', 'server']:
- if not openvpn['tls_auth']:
- raise ConfigError('Cannot specify "tls role" in client-server mode')
-
- if openvpn['tls_role'] == 'active':
- if openvpn['protocol'] == 'tcp-passive':
- raise ConfigError('Cannot specify "tcp-passive" when "tls role" is "active"')
-
- if openvpn['tls_dh']:
- raise ConfigError('Cannot specify "tls dh-file" when "tls role" is "active"')
-
- elif openvpn['tls_role'] == 'passive':
- if openvpn['protocol'] == 'tcp-active':
- raise ConfigError('Cannot specify "tcp-active" when "tls role" is "passive"')
-
- if not openvpn['tls_dh']:
- raise ConfigError('Must specify "tls dh-file" when "tls role" is "passive"')
-
- #
- # Auth user/pass
- #
- if openvpn['auth']:
- if not openvpn['auth_user']:
- raise ConfigError('Username for authentication is missing')
-
- if not openvpn['auth_pass']:
- raise ConfigError('Password for authentication is missing')
-
- #
- # Client
- #
- subnet = openvpn['server_subnet'].replace(' ', '/')
- for client in openvpn['client']:
- if not ip_address(client['ip']) in ip_network(subnet):
- raise ConfigError('Client IP "{}" not in server subnet "{}'.format(client['ip'], subnet))
-
-
-
- return None
-
-def generate(openvpn):
- if openvpn['deleted'] or openvpn['disable']:
- return None
-
- interface = openvpn['intf']
- directory = os.path.dirname(get_config_name(interface))
-
- # create config directory on demand
- openvpn_mkdir(directory)
- # create status directory on demand
- openvpn_mkdir(directory + '/status')
- # create client config dir on demand
- openvpn_mkdir(directory + '/ccd')
- # crete client config dir per interface on demand
- openvpn_mkdir(directory + '/ccd/' + interface)
-
- # Fix file permissons for keys
- fixup_permission(openvpn['shared_secret_file'])
- fixup_permission(openvpn['tls_key'])
-
- # Generate User/Password authentication file
- if openvpn['auth']:
- auth_file = '/tmp/openvpn-{}-pw'.format(interface)
- with open(auth_file, 'w') as f:
- f.write('{}\n{}'.format(openvpn['auth_user'], openvpn['auth_pass']))
-
- fixup_permission(auth_file)
-
- # get numeric uid/gid
- uid = getpwnam(user).pw_uid
- gid = getgrnam(group).gr_gid
-
- # Generate client specific configuration
- for client in openvpn['client']:
- client_file = directory + '/ccd/' + interface + '/' + client['name']
- tmpl = jinja2.Template(client_tmpl)
- client_text = tmpl.render(client)
- with open(client_file, 'w') as f:
- f.write(client_text)
- os.chown(client_file, uid, gid)
-
- tmpl = jinja2.Template(config_tmpl)
- config_text = tmpl.render(openvpn)
-
- # we need to support quoting of raw parameters from OpenVPN CLI
- # see https://phabricator.vyos.net/T1632
- config_text = config_text.replace("&quot;",'"')
-
- with open(get_config_name(interface), 'w') as f:
- f.write(config_text)
- os.chown(get_config_name(interface), uid, gid)
-
- return None
-
-def apply(openvpn):
- pid = 0
- pidfile = '/var/run/openvpn/{}.pid'.format(openvpn['intf'])
- if os.path.isfile(pidfile):
- pid = 0
- with open(pidfile, 'r') as f:
- pid = int(f.read())
-
- # Always stop OpenVPN service. We can not send a SIGUSR1 for restart of the
- # service as the configuration is not re-read. Stop daemon only if it's
- # running - it could have died or killed by someone evil
- if pid_exists(pid):
- cmd = 'start-stop-daemon --stop --quiet'
- cmd += ' --pidfile ' + pidfile
- subprocess_cmd(cmd)
-
- # cleanup old PID file
- if os.path.isfile(pidfile):
- os.remove(pidfile)
-
- # Do some cleanup when OpenVPN is disabled/deleted
- if openvpn['deleted'] or openvpn['disable']:
- # cleanup old configuration file
- if os.path.isfile(get_config_name(openvpn['intf'])):
- os.remove(get_config_name(openvpn['intf']))
-
- # cleanup client config dir
- directory = os.path.dirname(get_config_name(openvpn['intf']))
- if os.path.isdir(directory + '/ccd/' + openvpn['intf']):
- try:
- os.remove(directory + '/ccd/' + openvpn['intf'] + '/*')
- except:
- pass
-
- return None
-
- # On configuration change we need to wait for the 'old' interface to
- # vanish from the Kernel, if it is not gone, OpenVPN will report:
- # ERROR: Cannot ioctl TUNSETIFF vtun10: Device or resource busy (errno=16)
- while openvpn['intf'] in interfaces():
- sleep(0.250) # 250ms
-
- # No matching OpenVPN process running - maybe it got killed or none
- # existed - nevertheless, spawn new OpenVPN process
- cmd = 'start-stop-daemon --start --quiet'
- cmd += ' --pidfile ' + pidfile
- cmd += ' --exec /usr/sbin/openvpn'
- # now pass arguments to openvpn binary
- cmd += ' --'
- cmd += ' --config ' + get_config_name(openvpn['intf'])
-
- # execute assembled command
- subprocess_cmd(cmd)
-
- # better late then sorry ... but we can only set interface alias after
- # OpenVPN has been launched and created the interface
- cnt = 0
- while openvpn['intf'] not in interfaces():
- # If VPN tunnel can't be established because the peer/server isn't
- # (temporarily) available, the vtun interface never becomes registered
- # with the kernel, and the commit would hang if there is no bail out
- # condition
- cnt += 1
- if cnt == 50:
- break
-
- # sleep 250ms
- sleep(0.250)
-
- try:
- # we need to catch the exception if the interface is not up due to
- # reason stated above
- Interface(openvpn['intf']).set_alias(openvpn['description'])
- except:
- pass
-
- return None
-
-
-if __name__ == '__main__':
- try:
- c = get_config()
- verify(c)
- generate(c)
- apply(c)
- except ConfigError as e:
- print(e)
- sys.exit(1)