Page Menu
Home
VyOS Platform
Search
Configure Global Search
Log In
Files
F117520640
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
55 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/debian/control b/debian/control
index c8946e991..a65d0158e 100644
--- a/debian/control
+++ b/debian/control
@@ -1,64 +1,67 @@
Source: vyos-1x
Section: contrib/net
Priority: extra
Maintainer: VyOS Package Maintainers <maintainers@vyos.net>
Build-Depends: debhelper (>= 9),
quilt,
python3,
python3-setuptools,
quilt,
python3-lxml,
python3-nose,
python3-coverage,
whois,
libvyosconfig0 (>= 0.0.7)
Standards-Version: 3.9.6
Package: vyos-1x
Architecture: all
Depends: python3,
${python3:Depends},
python3-netifaces,
python3-jinja2,
python3-pystache,
python3-psutil,
python3-tabulate,
python3-six,
python3-isc-dhcp-leases,
python3-hurry.filesize,
python3-vici (>= 5.7.2),
python3-bottle,
ipaddrcheck,
tcpdump,
tshark,
bmon,
hvinfo,
file,
lsscsi,
pciutils,
usbutils,
procps,
snmp, snmpd,
openssh-server,
ntp,
ntpdate,
iputils-arping,
libvyosconfig0,
beep,
isc-dhcp-server,
isc-dhcp-relay,
keepalived (>=2.0.5),
wireguard,
tftpd-hpa,
igmpproxy,
vyos-accel-ppp,
vyos-accel-ppp-ipoe-kmod,
mdns-repeater,
udp-broadcast-relay,
pdns-recursor,
lcdproc,
lcdproc-extra-drivers,
+ openvpn,
+ openvpn-auth-ldap,
+ openvpn-auth-radius,
${shlibs:Depends},
${misc:Depends}
Description: VyOS configuration scripts and data
VyOS configuration scripts, interface definitions, and everything
diff --git a/interface-definitions/interfaces-openvpn.xml b/interface-definitions/interfaces-openvpn.xml
new file mode 100644
index 000000000..f2eb1ebab
--- /dev/null
+++ b/interface-definitions/interfaces-openvpn.xml
@@ -0,0 +1,624 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="interfaces">
+ <children>
+ <tagNode name="openvpn" owner="${vyos_conf_scripts_dir}/interface-openvpn.py">
+ <properties>
+ <help>OpenVPN tunnel interface name</help>
+ <priority>460</priority>
+ <constraint>
+ <regex>^vtun[0-9]+$</regex>
+ </constraint>
+ <constraintErrorMessage>OpenVPN tunnel interface must be named vtunN</constraintErrorMessage>
+ <valueHelp>
+ <format>vtunN</format>
+ <description>OpenVPN interface name</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <node name="authentication">
+ <properties>
+ <help>Authentication options</help>
+ </properties>
+ <children>
+ <leafNode name="password">
+ <properties>
+ <help>OpenVPN password used for authentication</help>
+ </properties>
+ </leafNode>
+ <leafNode name="username">
+ <properties>
+ <help>OpenVPN username used for authentication</help>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="bridge-group">
+ <properties>
+ <help>Interface to be added to a bridge group</help>
+ </properties>
+ <children>
+ <leafNode name="bridge">
+ <properties>
+ <help>Interface to a bridge-group</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py -t bridge</script>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="cost">
+ <properties>
+ <help>Path cost for this port</help>
+ <valueHelp>
+ <format>0-2147483647</format>
+ <description>Path cost for this port</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-2147483647"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="cost">
+ <properties>
+ <help>Path priority for this port</help>
+ <valueHelp>
+ <format>0-255</format>
+ <description>Path priority for this port</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="description">
+ <properties>
+ <help>Description</help>
+ </properties>
+ </leafNode>
+ <leafNode name="device-type">
+ <properties>
+ <help>OpenVPN interface device-type</help>
+ <completionHelp>
+ <list>tun tap</list>
+ </completionHelp>
+ <valueHelp>
+ <format>tun</format>
+ <description>TUN device, required for OSI layer 3</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tap</format>
+ <description>TAP device, required for OSI layer 2</description>
+ </valueHelp>
+ <constraint>
+ <regex>(tun|tap)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="disable">
+ <properties>
+ <help>Disable interface</help>
+ </properties>
+ </leafNode>
+ <leafNode name="encryption">
+ <properties>
+ <help>Data Encryption Algorithm</help>
+ <completionHelp>
+ <list>des 3des bf128 bf256 aes128 aes192 aes256</list>
+ </completionHelp>
+ <valueHelp>
+ <format>des</format>
+ <description>DES algorithm</description>
+ </valueHelp>
+ <valueHelp>
+ <format>3des</format>
+ <description>DES algorithm with triple encryption</description>
+ </valueHelp>
+ <valueHelp>
+ <format>bf128</format>
+ <description>Blowfish algorithm with 128-bit key</description>
+ </valueHelp>
+ <valueHelp>
+ <format>bf256</format>
+ <description>Blowfish algorithm with 256-bit key</description>
+ </valueHelp>
+ <valueHelp>
+ <format>aes128</format>
+ <description>AES algorithm with 128-bit key</description>
+ </valueHelp>
+ <valueHelp>
+ <format>aes192</format>
+ <description>AES algorithm with 192-bit key</description>
+ </valueHelp>
+ <valueHelp>
+ <format>aes256</format>
+ <description>AES algorithm with 256-bit key</description>
+ </valueHelp>
+ <constraint>
+ <regex>(des|3des|bf128|bf256|aes128|aes192|aes256)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="hash">
+ <properties>
+ <help>Hashing Algorithm</help>
+ <completionHelp>
+ <list>md5 sha1 sha256 sha384 sha512</list>
+ </completionHelp>
+ <valueHelp>
+ <format>md5</format>
+ <description>MD5 algorithm</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sha1</format>
+ <description>SHA-1 algorithm</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sha256</format>
+ <description>SHA-256 algorithm</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sha384</format>
+ <description>SHA-384 algorithm</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sha512</format>
+ <description>SHA-512 algorithm</description>
+ </valueHelp>
+ <constraint>
+ <regex>(md5|sha1|sha256|sha384|sha512)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <node name="keep-alive">
+ <properties>
+ <help>Keepalive helper options</help>
+ </properties>
+ <children>
+ <leafNode name="failure-count">
+ <properties>
+ <help>Maximum number of keepalive packet failures [default 6]</help>
+ <valueHelp>
+ <format>0-1000</format>
+ <description>Maximum number of keepalive packet failures</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-1000"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="interval">
+ <properties>
+ <help>Keepalive packet interval (seconds) [default 10]</help>
+ <valueHelp>
+ <format>0-600</format>
+ <description>Keepalive packet interval (seconds)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-600"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <tagNode name="local-address">
+ <properties>
+ <help>Local IP address of tunnel</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="subnet-mask">
+ <properties>
+ <help>Subnet-mask for local IP address of tunnel</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="local-host">
+ <properties>
+ <help>Local IP address to accept connections (all if not set)</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Local IPv4 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="local-port">
+ <properties>
+ <help>Local port number to accept connections</help>
+ <valueHelp>
+ <format>1-65535</format>
+ <description>Numeric IP port</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="mode">
+ <properties>
+ <help>OpenVPN mode of operation</help>
+ <completionHelp>
+ <list>site-to-site client server</list>
+ </completionHelp>
+ <valueHelp>
+ <format>site-to-site</format>
+ <description>Site-to-site mode</description>
+ </valueHelp>
+ <valueHelp>
+ <format>client</format>
+ <description>Client in client-server mode</description>
+ </valueHelp>
+ <valueHelp>
+ <format>server</format>
+ <description>Server in client-server mode</description>
+ </valueHelp>
+ <constraint>
+ <regex>(site-to-site|client|server)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="openvpn-option">
+ <properties>
+ <help>Additional OpenVPN options. You must
+ use the syntax of openvpn.conf in this text-field. Using this
+ without proper knowledge may result in a crashed OpenVPN server.
+ Check system log to look for errors.</help>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="persistent-tunnel">
+ <properties>
+ <help>Do not close and reopen interface (TUN/TAP device) on client restarts</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="protocol">
+ <properties>
+ <help>OpenVPN communication protocol</help>
+ <completionHelp>
+ <list>udp tcp-passive tcp-active</list>
+ </completionHelp>
+ <valueHelp>
+ <format>udp</format>
+ <description>Site-to-site mode</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tcp-passive</format>
+ <description>TCP and accepts connections passively</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tcp-active</format>
+ <description>TCP and initiates connections actively</description>
+ </valueHelp>
+ <constraint>
+ <regex>(udp|tcp-passive|tcp-active)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="remote-address">
+ <properties>
+ <help>IP address of remote end of tunnel</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Remote end IPv4 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="remote-host">
+ <properties>
+ <help>Remote host to connect to (dynamic if not set)</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IP address of remote host</description>
+ </valueHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Hostname of remote host</description>
+ </valueHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="remote-port">
+ <properties>
+ <help>Remote port number to connect to</help>
+ <valueHelp>
+ <format>1-65535</format>
+ <description>Numeric IP port</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <node name="replace-default-route">
+ <properties>
+ <help>OpenVPN tunnel to be used as the default route</help>
+ </properties>
+ <children>
+ <leafNode name="local">
+ <properties>
+ <help>Tunnel endpoints are on the same subnet</help>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="server">
+ <properties>
+ <help>Server-mode options</help>
+ </properties>
+ <children>
+ <node name="2-factor-authentication">
+ <properties>
+ <help>Two Factor Authentication providers</help>
+ </properties>
+ <children>
+ <node name="authy">
+ <properties>
+ <help>Authy Two Factor Authentication providers</help>
+ </properties>
+ <children>
+ <leafNode name="api-key">
+ <properties>
+ <help>Authy api key</help>
+ </properties>
+ </leafNode>
+ <tagNode name="user">
+ <properties>
+ <help>Authy users (must be email address)</help>
+ <constraint>
+ <regex>[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$</regex>
+ </constraint>
+ <constraintErrorMessage>Invalid email address</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="country-calling-code">
+ <properties>
+ <help>Country calling codes</help>
+ <constraint>
+ <regex>[0-9]+$</regex>
+ </constraint>
+ <constraintErrorMessage>Invalid Country Calling Code</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="phone-number">
+ <properties>
+ <help>Mobile phone number</help>
+ <constraint>
+ <regex>[0-9]+$</regex>
+ </constraint>
+ <constraintErrorMessage>Invalid Phone Number</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ <tagNode name="client">
+ <properties>
+ <help>Client-specific settings</help>
+ <valueHelp>
+ <format>name</format>
+ <description>Client common-name in the certificate</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <leafNode name="disable">
+ <properties>
+ <help>Option to disable client connection</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ip">
+ <properties>
+ <help>IP address of the client</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Client IPv4 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="push-route">
+ <properties>
+ <help>Route to be pushed to the client</help>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>IPv4 network and prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="subnet">
+ <properties>
+ <help>Subnet belonging to the client</help>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>IPv4 network and prefix length belonging to the client</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="domain-name">
+ <properties>
+ <help>DNS suffix to be pushed to all clients</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Domain Name Server suffix</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="max-connections">
+ <properties>
+ <help>Number of maximum client connections</help>
+ <valueHelp>
+ <format>1-4096</format>
+ <description>Number of concurrent clients</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-4096"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="name-server">
+ <properties>
+ <help>Domain Name Server (DNS)</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>DNS server IPv4 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="push-route">
+ <properties>
+ <help>Route to be pushed to all clients</help>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>IPv4 network and prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="reject-unconfigured-clients">
+ <properties>
+ <help>Reject connections from clients that are not explicitly configured</help>
+ </properties>
+ </leafNode>
+ <leafNode name="subnet">
+ <properties>
+ <help>Server-mode subnet (from which client IPs are allocated)</help>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>IPv4 address and prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-prefix"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="topology">
+ <properties>
+ <help>Topology for clients</help>
+ <completionHelp>
+ <list>point-to-point subnet</list>
+ </completionHelp>
+ <valueHelp>
+ <format>point-to-point</format>
+ <description>Point-to-point topology</description>
+ </valueHelp>
+ <valueHelp>
+ <format>subnet</format>
+ <description>Subnet topology</description>
+ </valueHelp>
+ <constraint>
+ <regex>(subnet|point-to-point)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="shared-secret-key-file">
+ <properties>
+ <help>File containing the secret key shared with remote end of tunnel</help>
+ <valueHelp>
+ <format>file</format>
+ <description>File in /config/auth directory</description>
+ </valueHelp>
+ <constraint>
+ <validator name="file-exists" argument="--directory /config/auth"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <node name="tls">
+ <properties>
+ <help>Transport Layer Security (TLS) options</help>
+ </properties>
+ <children>
+ <leafNode name="ca-cert-file">
+ <properties>
+ <help>File containing certificate for Certificate Authority (CA)</help>
+ </properties>
+ </leafNode>
+ <leafNode name="cert-file">
+ <properties>
+ <help>File containing certificate for this host</help>
+ </properties>
+ </leafNode>
+ <leafNode name="crl-file">
+ <properties>
+ <help>File containing certificate revocation list (CRL) for this host</help>
+ </properties>
+ </leafNode>
+ <leafNode name="dh-file">
+ <properties>
+ <help>File containing Diffie Hellman parameters (server only)</help>
+ </properties>
+ </leafNode>
+ <leafNode name="key-file">
+ <properties>
+ <help>File containing this host's private key</help>
+ </properties>
+ </leafNode>
+ <leafNode name="role">
+ <properties>
+ <help>File containing this host's private key</help>
+ <completionHelp>
+ <list>active passive</list>
+ </completionHelp>
+ <valueHelp>
+ <format>active</format>
+ <description>Initiate TLS negotiation actively</description>
+ </valueHelp>
+ <valueHelp>
+ <format>passive</format>
+ <description>Waiting for TLS connections passively</description>
+ </valueHelp>
+ <constraint>
+ <regex>(active|passive)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="use-lzo-compression">
+ <properties>
+ <help>Use fast LZO compression on this TUN/TAP interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/src/conf_mode/interface-openvpn.py b/src/conf_mode/interface-openvpn.py
new file mode 100755
index 000000000..1420cabe9
--- /dev/null
+++ b/src/conf_mode/interface-openvpn.py
@@ -0,0 +1,903 @@
+#!/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 pwd
+import grp
+import sys
+import stat
+import copy
+import jinja2
+import psutil
+from ipaddress import ip_address,ip_network,IPv4Interface
+
+from signal import SIGUSR1
+from subprocess import Popen, PIPE
+
+from vyos.config import Config
+from vyos import ConfigError
+from vyos.validate import is_addr_assigned
+
+# 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
+
+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
+
+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 'site-to-site' in server_topology %}p2p{% else %}{{ server_topology }}{% 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 'tap' in type %}
+ifconfig {{ local_address }} {{ local_address_subnet }}
+{% else %}
+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_dh %}
+dh {{ tls_dh }}
+{% 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 'aes128' in encryption %}
+cipher aes-128-cbc
+{%- elif 'aes192' in encryption %}
+cipher aes-192-cbc
+{%- 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 %}
+
+{% for option in options -%}
+{{ option }}
+{% endfor -%}
+
+{%- if server_2fa_authy_key %}
+plugin /usr/lib/authy/authy-openvpn.so https://api.authy.com/protected/json {{ server_2fa_authy_key }} nopam
+{% endif %}
+"""
+
+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 disabled %}
+disable
+{% endif %}
+"""
+
+default_config_data = {
+ 'address': [],
+ 'auth_user': '',
+ 'auth_pass': '',
+ 'auth': False,
+ 'bridge_member': [],
+ 'compress_lzo': False,
+ 'deleted': False,
+ 'description': '',
+ 'disabled': 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': '',
+ 'server_2fa_authy_key': '',
+ 'server_2fa_authy': [],
+ '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_ca_cert': '',
+ 'tls_cert': '',
+ 'tls_crl': '',
+ 'tls_dh': '',
+ 'tls_key': '',
+ 'tls_role': '',
+ 'type': 'tun',
+ 'uid': 'nobody',
+ 'gid': 'nogroup',
+}
+
+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 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 = pwd.getpwnam('root').pw_uid
+ gid = grp.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 True
+
+ with open(filename, 'r') as f:
+ for line in f:
+ if re.match(header, line):
+ return True
+
+ return False
+
+def get_config():
+ openvpn = copy.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")
+
+ print('Executing for interface ' + openvpn['intf'])
+
+ # 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')
+
+ # interface disabled
+ if conf.exists('disabled'):
+ openvpn['disabled'] = 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'
+
+ # Two Factor Authentication providers
+ # currently limited to authy
+ if conf.exists('2-factor-authentication authy api-key'):
+ openvpn['server_2fa_authy_key'] = conf.return_value('2-factor-authentication authy api-key')
+
+ # Authy users (must be email address)
+ for user in conf.list_nodes('server 2-factor-authentication authy user'):
+ # set configuration level
+ conf.set_level('interfaces openvpn ' + openvpn['intf'] + ' 2-factor-authentication authy user ' + user)
+ data = {
+ 'user': user,
+ 'country_code': '',
+ 'mobile_number': ''
+ }
+
+ # Country calling codes
+ if conf.exists('country-calling-code'):
+ data['country_code'] = conf.return_value('country-calling-code')
+
+ # Mobile phone number
+ if conf.exists('phone-number'):
+ data['mobile_number'] = conf.return_value('phone-number')
+
+ openvpn['server_2fa_authy'].append(data)
+
+ # 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,
+ 'disabled': 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'][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['disabled'] = 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'):
+ network = conf.return_value('server push-route')
+ tmp = IPv4Interface(network).with_netmask
+ openvpn['server_push_route'] = 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 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
+
+ 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"')
+
+ if not openvpn['remote_address']:
+ raise ConfigError('Must specify "remote-address"')
+
+ if openvpn['local_address'] == openvpn['local_host']:
+ raise ConfigError('"local-address" cannot be the same as "local-host"')
+
+ for host in openvpn['remote_host']:
+ if host == openvpn['remote_address']:
+ raise ConfigError('"remote-address" cannot be the same as "remote-host"')
+
+ if openvpn['local_address'] == openvpn['remote_address']:
+ raise ConfigError('"local-address" and "remote-address" cannot be the same')
+
+ if openvpn['type'] == 'tap' and openvpn['local_address_subnet'] == '':
+ raise ConfigError('Must specify "subnet-mask" for local-address')
+
+ 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 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 not checkCertHeader('-----BEGIN CERTIFICATE-----', openvpn['tls_cert']):
+ raise ConfigError('Specified cert-file "{}" is invalid'.format(openvpn['tls_cert']))
+
+ if not checkCertHeader('-----BEGIN (?:RSA )?PRIVATE KEY-----', openvpn['tls_key']):
+ raise ConfigError('Specified key-file "{}" is not valid'.format(openvpn['tls_key']))
+
+ if not checkCertHeader('-----BEGIN X509 CRL-----', openvpn['tls_crl']):
+ raise ConfigError('Specified crl-file "{} not valid'.format(openvpn['tls_crl']))
+
+ 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']:
+ 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']:
+ return None
+
+ interface = openvpn['intf']
+ # create config directory on demand
+ directory = os.path.dirname(get_config_name(interface))
+ if not os.path.exists(directory):
+ os.mkdir(directory)
+
+ # create status directory on demand
+ if not os.path.exists(directory + '/status'):
+ os.mkdir(directory + '/status')
+
+ # fix permission on status directory
+ os.chmod(directory + '/status', stat.S_IRWXU|stat.S_IRWXG|stat.S_IROTH)
+ uid = pwd.getpwnam(openvpn['uid']).pw_uid
+ gid = grp.getgrnam(openvpn['gid']).gr_gid
+ os.chown(directory + '/status', uid, gid)
+
+ # create client config dir on demand
+ if not os.path.exists(directory + '/ccd/'):
+ os.mkdir(directory + '/ccd/')
+
+ # crete client config dir per interface on demand
+ if not os.path.exists(directory + '/ccd/' + interface):
+ os.mkdir(directory + '/ccd/' + interface)
+
+ os.chmod(directory + '/ccd/' + interface, stat.S_IRWXU|stat.S_IRWXG|stat.S_IROTH)
+ os.chown(directory + '/ccd/' + interface, uid, gid)
+
+ # 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)
+
+ # 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)
+
+ tmpl = jinja2.Template(config_tmpl)
+ config_text = tmpl.render(openvpn)
+ with open(get_config_name(interface), 'w') as f:
+ f.write(config_text)
+
+ return None
+
+
+def apply(openvpn):
+ interface = openvpn['intf']
+
+ pid = 0
+ pidfile = '/var/run/openvpn/{}.pid'.format(interface)
+ if os.path.isfile(pidfile):
+ pid = 0
+ with open(pidfile, 'r') as f:
+ pid = int(f.read())
+
+ # If tunnel interface has been deleted - stop service
+ if openvpn['deleted']:
+ directory = os.path.dirname(get_config_name(interface))
+
+ # we only need to stop the demon if it's running
+ # daemon could have died or killed by someone
+ if psutil.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)
+
+ # cleanup old configuration file
+ if os.path.isfile(get_config_name(interface)):
+ os.remove(get_config_name(interface))
+
+ # cleanup client config dir
+ if os.path.isdir(directory + '/ccd/' + interface):
+ os.remove(directory + '/ccd/' + interface + '/*')
+
+ return None
+
+ # Send SIGUSR1 to the process instead of creating a new process
+ if psutil.pid_exists(pid):
+ os.kill(pid, SIGUSR1)
+ return None
+
+ # 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(interface)
+
+ # execute assembled command
+ subprocess_cmd(cmd)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Sep 26, 11:28 AM (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4285044
Default Alt Text
(55 KB)
Attached To
Mode
rVYOSONEX vyos-1x
Attached
Detach File
Event Timeline
Log In to Comment