summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/ipsec/swanctl.conf.tmpl32
-rw-r--r--data/templates/ipsec/swanctl/remote_access.tmpl40
-rw-r--r--debian/control1
-rw-r--r--interface-definitions/include/ipsec/local-address.xml.i27
-rw-r--r--interface-definitions/include/ipsec/local-traffic-selector.xml.i28
-rw-r--r--interface-definitions/vpn_ipsec.xml.in188
-rw-r--r--python/vyos/util.py33
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py42
8 files changed, 292 insertions, 99 deletions
diff --git a/data/templates/ipsec/swanctl.conf.tmpl b/data/templates/ipsec/swanctl.conf.tmpl
index cafe52e78..0eda8479a 100644
--- a/data/templates/ipsec/swanctl.conf.tmpl
+++ b/data/templates/ipsec/swanctl.conf.tmpl
@@ -1,6 +1,7 @@
### Autogenerated by vpn_ipsec.py ###
{% import 'ipsec/swanctl/profile.tmpl' as profile_tmpl %}
{% import 'ipsec/swanctl/peer.tmpl' as peer_tmpl %}
+{% import 'ipsec/swanctl/remote_access.tmpl' as remote_access_tmpl %}
connections {
{% if profile is defined %}
@@ -13,6 +14,25 @@ connections {
{{ peer_tmpl.conn(peer, peer_conf, ike_group, esp_group) }}
{% endfor %}
{% endif %}
+{% if remote_access is defined and remote_access is not none %}
+{% for rw, rw_conf in remote_access.items() if rw_conf.disable is not defined %}
+{{ remote_access_tmpl.conn(rw, rw_conf, ike_group, esp_group) }}
+{% endfor %}
+{% endif %}
+}
+
+pools {
+{% if remote_access is defined %}
+{% for ra, ra_conf in remote_access.items() if remote_access is defined %}
+ ra-{{ ra }} {
+ addrs = {{ ra_conf.pool.prefix }}
+ dns = {{ ra_conf.pool.name_server | join(",") }}
+{% if ra_conf.pool.exclude is defined %}
+ split_exclude = {{ ra_conf.pool.exclude | join(",") }}
+{% endif %}
+ }
+{% endfor %}
+{% endif %}
}
secrets {
@@ -60,5 +80,17 @@ secrets {
{% endif %}
{% endfor %}
{% endif %}
+{% if remote_access is defined %}
+{% for ra, ra_conf in remote_access.items() if remote_access is defined %}
+{% if ra_conf.authentication is defined and ra_conf.authentication.local_users is defined and ra_conf.authentication.local_users.username is defined %}
+{% for user, user_conf in ra_conf.authentication.local_users.username.items() if user_conf.disable is not defined %}
+ eap-{{ ra }}-{{ user }} {
+ secret = "{{ user_conf.password }}"
+ id-{{ ra }}-{{ user }} = "{{ user }}"
+ }
+{% endfor %}
+{% endif %}
+{% endfor %}
+{% endif %}
}
diff --git a/data/templates/ipsec/swanctl/remote_access.tmpl b/data/templates/ipsec/swanctl/remote_access.tmpl
new file mode 100644
index 000000000..a3a1cf0b2
--- /dev/null
+++ b/data/templates/ipsec/swanctl/remote_access.tmpl
@@ -0,0 +1,40 @@
+{% macro conn(name, rw_conf, ike_group, esp_group) %}
+{# peer needs to reference the global IKE configuration for certain values #}
+{% set ike = ike_group[rw_conf.ike_group] %}
+{% set esp = esp_group[rw_conf.esp_group] %}
+ ra-{{ name }} {
+ remote_addrs = %any
+ local_addrs = {{ rw_conf.local_address if rw_conf.local_address is defined else '%any' }}
+ proposals = {{ ike_group[rw_conf.ike_group] | get_esp_ike_cipher | join(',') }}
+ version = {{ ike.key_exchange[4:] if ike is defined and ike.key_exchange is defined else "0" }}
+ send_certreq = no
+ rekey_time = {{ ike.lifetime }}s
+ keyingtries = 0
+ pools = ra-{{ name }}
+ local {
+ auth = pubkey
+{% if rw_conf.authentication is defined and rw_conf.authentication.id is defined and rw_conf.authentication.use_x509_id is not defined %}
+ id = "{{ rw_conf.authentication.id }}"
+{% endif %}
+{% if rw_conf.authentication is defined and rw_conf.authentication.x509 is defined and rw_conf.authentication.x509.certificate is defined %}
+ certs = {{ rw_conf.authentication.x509.certificate }}.pem
+{% endif %}
+ }
+ remote {
+ auth = eap-mschapv2
+ eap_id = %any
+ }
+ children {
+ ikev2-vpn {
+ esp_proposals = {{ esp | get_esp_ike_cipher | join(',') }}
+ rekey_time = {{ esp.lifetime }}s
+ rand_time = 540s
+ dpd_action = clear
+{% set local_prefix = rw_conf.local.prefix if rw_conf.local is defined and rw_conf.local.prefix is defined else ['0.0.0.0/0', '::/0'] %}
+{% set local_port = rw_conf.local.port if rw_conf.local is defined and rw_conf.local.port is defined else '' %}
+{% set local_suffix = '[%any/{1}]'.format(local_port) if local_port else '' %}
+ local_ts = {{ local_prefix | join(local_suffix + ",") }}{{ local_suffix }}
+ }
+ }
+ }
+{% endmacro %}
diff --git a/debian/control b/debian/control
index aed1a3b2d..5a434bfc6 100644
--- a/debian/control
+++ b/debian/control
@@ -24,6 +24,7 @@ Build-Depends:
python3-setuptools,
python3-sphinx,
python3-xmltodict,
+ python3-inotify,
quilt,
whois
Standards-Version: 3.9.6
diff --git a/interface-definitions/include/ipsec/local-address.xml.i b/interface-definitions/include/ipsec/local-address.xml.i
new file mode 100644
index 000000000..2de6ecb1f
--- /dev/null
+++ b/interface-definitions/include/ipsec/local-address.xml.i
@@ -0,0 +1,27 @@
+<!-- include start from ipsec/local-address.xml.i -->
+<leafNode name="local-address">
+ <properties>
+ <help>IPv4 or IPv6 address of a local interface to use for VPN</help>
+ <completionHelp>
+ <list>any</list>
+ </completionHelp>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address of a local interface for VPN</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address of a local interface for VPN</description>
+ </valueHelp>
+ <valueHelp>
+ <format>any</format>
+ <description>Allow any IPv4 address present on the system to be used for VPN</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
+ <regex>^(any)$</regex>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/ipsec/local-traffic-selector.xml.i b/interface-definitions/include/ipsec/local-traffic-selector.xml.i
new file mode 100644
index 000000000..d30a6d11a
--- /dev/null
+++ b/interface-definitions/include/ipsec/local-traffic-selector.xml.i
@@ -0,0 +1,28 @@
+<!-- include start from ipsec/local-traffic-selector.xml.i -->
+<node name="local">
+ <properties>
+ <help>Local parameters for interesting traffic</help>
+ </properties>
+ <children>
+ #include <include/port-number.xml.i>
+ <leafNode name="prefix">
+ <properties>
+ <help>Local IPv4 or IPv6 prefix</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Local IPv4 prefix</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Local IPv6 prefix</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-prefix"/>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in
index ff60bb82f..f6b18d1d5 100644
--- a/interface-definitions/vpn_ipsec.xml.in
+++ b/interface-definitions/vpn_ipsec.xml.in
@@ -692,9 +692,12 @@
<leafNode name="tunnel">
<properties>
<help>Tunnel interface associated with this configuration profile</help>
+ <completionHelp>
+ <path>interfaces tunnel</path>
+ </completionHelp>
<valueHelp>
<format>txt</format>
- <description>Tunnel interface associated with this configuration profile</description>
+ <description>Associated interface to this configuration profile</description>
</valueHelp>
<multi/>
</properties>
@@ -705,6 +708,105 @@
#include <include/ipsec/ike-group.xml.i>
</children>
</tagNode>
+ <tagNode name="remote-access">
+ <properties>
+ <help>Remote access IKEv2 VPN </help>
+ </properties>
+ <children>
+ <node name="authentication">
+ <properties>
+ <help>Authentication for remote access</help>
+ </properties>
+ <children>
+ #include <include/ipsec/authentication-id.xml.i>
+ #include <include/ipsec/authentication-x509.xml.i>
+ <node name="local-users">
+ <properties>
+ <help>Local user authentication for PPPoE server</help>
+ </properties>
+ <children>
+ <tagNode name="username">
+ <properties>
+ <help>User name for authentication</help>
+ </properties>
+ <children>
+ #include <include/generic-disable-node.xml.i>
+ <leafNode name="password">
+ <properties>
+ <help>Password for authentication</help>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ #include <include/generic-description.xml.i>
+ #include <include/generic-disable-node.xml.i>
+ #include <include/ipsec/esp-group.xml.i>
+ #include <include/ipsec/ike-group.xml.i>
+ #include <include/ipsec/local-address.xml.i>
+ #include <include/ipsec/local-traffic-selector.xml.i>
+ <node name="pool">
+ <properties>
+ <help>IP address pool for remote-access users</help>
+ </properties>
+ <children>
+ <leafNode name="exclude">
+ <properties>
+ <help>Local IPv4 or IPv6 pool prefix exclusions</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Local IPv4 pool prefix exclusion</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Local IPv6 pool prefix exclusion</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-prefix"/>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="prefix">
+ <properties>
+ <help>Local IPv4 or IPv6 pool prefix</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Local IPv4 pool prefix</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>Local IPv6 pool prefix</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-prefix"/>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <!-- Include Accel-PPP definition here, maybe time for a rename? -->
+ #include <include/accel-ppp/name-server.xml.i>
+ </children>
+ </node>
+ <leafNode name="timeout">
+ <properties>
+ <help>Timeout to close connection if no data is transmitted</help>
+ <valueHelp>
+ <format>u32:10-86400</format>
+ <description>Timeout in seconds (default 28800)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 10-86400"/>
+ </constraint>
+ </properties>
+ <defaultValue>28800</defaultValue>
+ </leafNode>
+ </children>
+ </tagNode>
<node name="site-to-site">
<properties>
<help>Site-to-site VPN</help>
@@ -872,31 +974,7 @@
</constraint>
</properties>
</leafNode>
- <leafNode name="local-address">
- <properties>
- <help>IPv4 or IPv6 address of a local interface to use for VPN</help>
- <completionHelp>
- <list>any</list>
- </completionHelp>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 address of a local interface for VPN</description>
- </valueHelp>
- <valueHelp>
- <format>ipv6</format>
- <description>IPv6 address of a local interface for VPN</description>
- </valueHelp>
- <valueHelp>
- <format>any</format>
- <description>Allow any IPv4 address present on the system to be used for VPN</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
- <regex>^(any)$</regex>
- </constraint>
- </properties>
- </leafNode>
+ #include <include/ipsec/local-address.xml.i>
<tagNode name="tunnel">
<properties>
<help>Peer tunnel [REQUIRED]</help>
@@ -908,63 +986,14 @@
<children>
#include <include/generic-disable-node.xml.i>
#include <include/ipsec/esp-group.xml.i>
- <node name="local">
- <properties>
- <help>Local parameters for interesting traffic</help>
- </properties>
- <children>
- <leafNode name="port">
- <properties>
- <help>Any TCP or UDP port</help>
- <valueHelp>
- <format>port name</format>
- <description>Named port (any name in /etc/services, e.g., http)</description>
- </valueHelp>
- <valueHelp>
- <format>u32:1-65535</format>
- <description>Numbered port</description>
- </valueHelp>
- </properties>
- </leafNode>
- <leafNode name="prefix">
- <properties>
- <help>Local IPv4 or IPv6 prefix</help>
- <valueHelp>
- <format>ipv4</format>
- <description>Local IPv4 prefix</description>
- </valueHelp>
- <valueHelp>
- <format>ipv6</format>
- <description>Local IPv6 prefix</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-prefix"/>
- <validator name="ipv6-prefix"/>
- </constraint>
- <multi/>
- </properties>
- </leafNode>
- </children>
- </node>
+ #include <include/ipsec/local-traffic-selector.xml.i>
#include <include/ip-protocol.xml.i>
<node name="remote">
<properties>
<help>Remote parameters for interesting traffic</help>
</properties>
<children>
- <leafNode name="port">
- <properties>
- <help>Any TCP or UDP port</help>
- <valueHelp>
- <format>port name</format>
- <description>Named port (any name in /etc/services, e.g., http)</description>
- </valueHelp>
- <valueHelp>
- <format>u32:1-65535</format>
- <description>Numbered port</description>
- </valueHelp>
- </properties>
- </leafNode>
+ #include <include/port-number.xml.i>
<leafNode name="prefix">
<properties>
<help>Remote IPv4 or IPv6 prefix</help>
@@ -994,7 +1023,10 @@
<children>
<leafNode name="bind">
<properties>
- <help>VTI tunnel interface associated with this configuration [REQUIRED]</help>
+ <help>VTI tunnel interface associated with this configuration</help>
+ <completionHelp>
+ <path>interfaces vti</path>
+ </completionHelp>
</properties>
</leafNode>
#include <include/ipsec/esp-group.xml.i>
diff --git a/python/vyos/util.py b/python/vyos/util.py
index cf90dc74f..8247ccb2d 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -506,6 +506,39 @@ def file_is_persistent(path):
absolute = os.path.abspath(os.path.dirname(path))
return re.match(location,absolute)
+def wait_for_inotify(file_path, event_type=None, timeout=None, sleep_interval=0.1):
+ """ Waits for an inotify event to occur """
+ if not os.path.dirname(file_path):
+ raise ValueError(
+ "File path {} does not have a directory part (required for inotify watching)".format(file_path))
+ if not os.path.basename(file_path):
+ raise ValueError(
+ "File path {} does not have a file part, do not know what to watch for".format(file_path))
+
+ from inotify.adapters import Inotify
+ from time import time
+
+ time_start = time()
+
+ i = Inotify()
+ i.add_watch(os.path.dirname(file_path))
+
+ for event in i.event_gen(yield_nones=True):
+ if (timeout is not None) and ((time.time() - time_start) > timeout):
+ # If the function didn't return until this point,
+ # the file failed to have been written to and closed within the timeout
+ raise OSError("Waiting for file {} to be written has failed".format(file_path))
+
+ if event is not None:
+ (_, type_names, path, filename) = event
+ if filename == os.path.basename(file_path):
+ if event_type in type_names:
+ return
+
+def wait_for_file_write_complete(file_path, timeout=None, sleep_interval=0.1):
+ """ Waits for a process to close a file after opening it in write mode. """
+ wait_for_inotify(file_path,
+ event_type='IN_CLOSE_WRITE', timeout=timeout, sleep_interval=sleep_interval)
def commit_in_progress():
""" Not to be used in normal op mode scripts! """
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index d1b29ee9a..50223320d 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -34,7 +34,6 @@ from vyos.template import render
from vyos.validate import is_ipv6_link_local
from vyos.util import call
from vyos.util import dict_search
-from vyos.util import process_named_running
from vyos.util import run
from vyos.xml import defaults
from vyos import ConfigError
@@ -81,6 +80,7 @@ def get_config(config=None):
# added in a more fine grained way later on
del default_values['esp_group']
del default_values['ike_group']
+ del default_values['remote_access']
ipsec = dict_merge(default_values, ipsec)
if 'esp_group' in ipsec:
@@ -88,12 +88,16 @@ def get_config(config=None):
for group in ipsec['esp_group']:
ipsec['esp_group'][group] = dict_merge(default_values,
ipsec['esp_group'][group])
-
if 'ike_group' in ipsec:
default_values = defaults(base + ['ike-group'])
for group in ipsec['ike_group']:
ipsec['ike_group'][group] = dict_merge(default_values,
ipsec['ike_group'][group])
+ if 'remote_access' in ipsec:
+ default_values = defaults(base + ['remote-access'])
+ for rw in ipsec['remote_access']:
+ ipsec['remote_access'][rw] = dict_merge(default_values,
+ ipsec['remote_access'][rw])
ipsec['dhcp_no_address'] = {}
ipsec['interface_change'] = leaf_node_changed(conf, base + ['ipsec-interfaces',
@@ -109,8 +113,6 @@ def get_config(config=None):
get_first_key=True,
no_tag_node_value_mangle=True)
- import pprint
- pprint.pprint(ipsec)
return ipsec
def get_rsa_local_key(ipsec):
@@ -326,6 +328,11 @@ def generate(ipsec):
if not os.path.exists(KEY_PATH):
os.mkdir(KEY_PATH, mode=0o700)
+ if 'remote_access' in ipsec:
+ for rw, rw_conf in ipsec['remote_access'].items():
+ if 'authentication' in rw_conf and 'x509' in rw_conf['authentication']:
+ generate_pki_files(ipsec['pki'], rw_conf['authentication']['x509'])
+
if 'site_to_site' in data and 'peer' in data['site_to_site']:
for peer, peer_conf in ipsec['site_to_site']['peer'].items():
if peer in ipsec['dhcp_no_address']:
@@ -385,24 +392,17 @@ def resync_nhrp(ipsec):
def apply(ipsec):
if not ipsec:
- call('sudo /usr/sbin/ipsec stop')
+ call('sudo ipsec stop')
else:
- should_start = 'profile' in ipsec or dict_search('site_to_site.peer', ipsec)
-
- if not process_named_running('charon') and should_start:
- args = f'--auto-update {ipsec["auto_update"]}' if 'auto_update' in ipsec else ''
- call(f'sudo /usr/sbin/ipsec start {args}')
- elif not should_start:
- call('sudo /usr/sbin/ipsec stop')
- elif ipsec['interface_change']:
- call('sudo /usr/sbin/ipsec restart')
- else:
- call('sudo /usr/sbin/ipsec rereadall')
- call('sudo /usr/sbin/ipsec reload')
-
- if should_start:
- sleep(2) # Give charon enough time to start
- call('sudo /usr/sbin/swanctl -q')
+ args = ''
+ if 'auto_update' in ipsec:
+ args = '--auto-update ' + ipsec['auto_update']
+ call(f'sudo ipsec restart {args}')
+ call('sudo ipsec rereadall')
+ call('sudo ipsec reload')
+
+ sleep(5) # Give charon enough time to start
+ call('sudo swanctl -q')
resync_l2tp(ipsec)
resync_nhrp(ipsec)