diff options
author | Christian Poessinger <christian@poessinger.com> | 2021-07-19 19:01:43 +0200 |
---|---|---|
committer | Christian Poessinger <christian@poessinger.com> | 2021-07-19 19:01:43 +0200 |
commit | 9556d78b1d54c7320a0154990c61d23c6197c38f (patch) | |
tree | b04a677d46bc5e4055b96452f73ca44de392cb2f | |
parent | 48c768abbf53b752a55db2adea1f998cb28da55c (diff) | |
download | vyos-1x-9556d78b1d54c7320a0154990c61d23c6197c38f.tar.gz vyos-1x-9556d78b1d54c7320a0154990c61d23c6197c38f.zip |
ipsec: T1210: split out pool from remote-access configuration
Remote access IP pools can now be defined at a global level and referenced
in IPSec remote-access connections. To defined a pool use:
set vpn ipsec remote-access pool global-ipv4 name-server '172.16.1.1'
set vpn ipsec remote-access pool global-ipv4 prefix '192.168.0.0/24'
set vpn ipsec remote-access pool global-ipv6 name-server '2001:db8::1'
set vpn ipsec remote-access pool global-ipv6 prefix '2001:db8:1000::/64'
A connection can then reference the pool:
set vpn ipsec remote-access connection foo pool 'global-ipv4'
set vpn ipsec remote-access connection foo pool 'global-ipv6'
-rw-r--r-- | data/templates/ipsec/charon/dhcp.conf.tmpl | 11 | ||||
-rw-r--r-- | data/templates/ipsec/swanctl.conf.tmpl | 46 | ||||
-rw-r--r-- | data/templates/ipsec/swanctl/remote_access.tmpl | 17 | ||||
-rw-r--r-- | interface-definitions/vpn_ipsec.xml.in | 179 | ||||
-rwxr-xr-x | src/conf_mode/vpn_ipsec.py | 153 |
5 files changed, 197 insertions, 209 deletions
diff --git a/data/templates/ipsec/charon/dhcp.conf.tmpl b/data/templates/ipsec/charon/dhcp.conf.tmpl index 2879550a8..96dfd7633 100644 --- a/data/templates/ipsec/charon/dhcp.conf.tmpl +++ b/data/templates/ipsec/charon/dhcp.conf.tmpl @@ -1,12 +1,11 @@ dhcp { load = yes - -{% if options is defined and options.remote_access is defined and options.remote_access.dhcp_pool is defined %} -{% if options.remote_access.dhcp_pool.interface is defined %} - interface = {{ options.remote_access.dhcp_pool.interface }} +{% if options is defined and options.remote_access is defined and options.remote_access.dhcp is defined %} +{% if options.remote_access.dhcp.interface is defined %} + interface = {{ options.remote_access.dhcp.interface }} {% endif %} -{% if options.remote_access.dhcp_pool.server is defined %} - server = {{ options.remote_access.dhcp_pool.server }} +{% if options.remote_access.dhcp.server is defined %} + server = {{ options.remote_access.dhcp.server }} {% endif %} {% endif %} diff --git a/data/templates/ipsec/swanctl.conf.tmpl b/data/templates/ipsec/swanctl.conf.tmpl index b85fe7d41..161f19f95 100644 --- a/data/templates/ipsec/swanctl.conf.tmpl +++ b/data/templates/ipsec/swanctl.conf.tmpl @@ -15,8 +15,8 @@ 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 %} +{% if remote_access is defined and remote_access.connection is defined and remote_access.connection is not none %} +{% for rw, rw_conf in remote_access.connection.items() if rw_conf.disable is not defined %} {{ remote_access_tmpl.conn(rw, rw_conf, ike_group, esp_group) }} {% endfor %} {% endif %} @@ -26,33 +26,19 @@ connections { } pools { -{% if remote_access is defined %} -{% for ra, ra_conf in remote_access.items() if ra_conf.pool.dhcp_enable is not defined %} -{% if ra_conf.pool is defined and ra_conf.pool.prefix is defined %} -{% for prefix in ra_conf.pool.prefix %} -{% if prefix | is_ipv4 %} - ra-{{ ra }}-ipv4 { - addrs = {{ prefix }} -{% if ra_conf.pool.name_server_v4 is defined and ra_conf.pool.name_server_v4 is not none %} - dns = {{ ra_conf.pool.name_server_v4 | join(',') }} -{% endif %} -{% if ra_conf.pool.exclude_v4 is defined and ra_conf.pool.exclude_v4 is not none %} - split_exclude = {{ ra_conf.pool.exclude_v4 | join(',') }} -{% endif %} - } -{% elif prefix | is_ipv6 %} - ra-{{ ra }}-ipv6 { - addrs = {{ prefix }} -{% if ra_conf.pool.name_server_v6 is defined and ra_conf.pool.name_server_v6 is not none %} - dns = {{ ra_conf.pool.name_server_v6 | join(',') }} -{% endif %} -{% if ra_conf.pool.exclude_v6 is defined and ra_conf.pool.exclude_v6 is not none %} - split_exclude = {{ ra_conf.pool.exclude_v6 | join(',') }} -{% endif %} - } -{% endif %} -{% endfor %} +{% if remote_access is defined and remote_access.pool is defined and remote_access.pool is not none %} +{% for pool, pool_config in remote_access.pool.items() %} + {{ pool }} { +{% if pool_config.prefix is defined and pool_config.prefix is not none %} + addrs = {{ pool_config.prefix }} {% endif %} +{% if pool_config.name_server is defined and pool_config.name_server is not none %} + dns = {{ pool_config.name_server | join(',') }} +{% endif %} +{% if pool_config.exclude is defined and pool_config.exclude is not none %} + split_exclude = {{ pool_config.exclude | join(',') }} +{% endif %} + } {% endfor %} {% endif %} } @@ -103,8 +89,8 @@ secrets { {% endif %} {% endfor %} {% endif %} -{% if remote_access is defined %} -{% for ra, ra_conf in remote_access.items() if remote_access is defined %} +{% if remote_access is defined and remote_access.connection is defined and remote_access.connection is not none %} +{% for ra, ra_conf in remote_access.connection.items() if ra_conf.disable is not defined %} {% if ra_conf.authentication.server_mode == 'pre-shared-secret' %} ike_{{ ra }} { {% if ra_conf.authentication.id is defined %} diff --git a/data/templates/ipsec/swanctl/remote_access.tmpl b/data/templates/ipsec/swanctl/remote_access.tmpl index ea79a6d6b..66ac94b13 100644 --- a/data/templates/ipsec/swanctl/remote_access.tmpl +++ b/data/templates/ipsec/swanctl/remote_access.tmpl @@ -10,18 +10,9 @@ send_certreq = no rekey_time = {{ ike.lifetime }}s keyingtries = 0 -{% if rw_conf.pool is defined and rw_conf.pool.dhcp_enable is defined %} - pools = dhcp -{% elif rw_conf.pool is defined and rw_conf.pool.prefix is defined and rw_conf.pool.prefix is not none %} -{% set pool = namespace(name='') %} -{% for prefix in rw_conf.pool.prefix %} -{% if not loop.first %} -{% set pool.name = pool.name ~ ',' %} -{% endif %} -{% set afi = '-ipv4' if prefix | is_ipv4 else '-ipv6' %} -{% set pool.name = pool.name + 'ra-' + name + afi %} -{% endfor %} - pools = {{ pool.name }} + unique = never +{% if rw_conf.pool is defined and rw_conf.pool is not none %} + pools = {{ rw_conf.pool | join(',') }} {% endif %} local { {% if rw_conf.authentication.id is defined and rw_conf.authentication.use_x509_id is not defined %} @@ -42,7 +33,7 @@ } children { ikev2-vpn { - esp_proposals = {{ esp | get_esp_ike_cipher | join(',') }} + esp_proposals = {{ esp | get_esp_ike_cipher | join(',') }} rekey_time = {{ esp.lifetime }}s rand_time = 540s dpd_action = clear diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in index 14063091d..5272b57cc 100644 --- a/interface-definitions/vpn_ipsec.xml.in +++ b/interface-definitions/vpn_ipsec.xml.in @@ -647,7 +647,7 @@ <help>remote-access global options</help> </properties> <children> - <node name="dhcp-pool"> + <node name="dhcp"> <properties> <help>DHCP pool options for remote-access</help> </properties> @@ -665,8 +665,11 @@ <help>DHCP server address</help> <valueHelp> <format>ipv4</format> - <description>IPv4 address of the DHCP server</description> + <description>DHCP server IPv4 address</description> </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> </properties> </leafNode> </children> @@ -725,98 +728,126 @@ #include <include/ipsec/ike-group.xml.i> </children> </tagNode> - <tagNode name="remote-access"> + <node name="remote-access"> <properties> - <help>Remote access IKEv2 VPN </help> + <help>IKEv2 remote access VPN</help> </properties> <children> - <node name="authentication"> + <tagNode name="connection"> <properties> - <help>Authentication for remote access</help> + <help>IKEv2 VPN connection name</help> </properties> <children> - #include <include/ipsec/authentication-id.xml.i> - #include <include/ipsec/authentication-x509.xml.i> - <leafNode name="client-mode"> - <properties> - <help>Client authentication mode</help> - <completionHelp> - <list>eap-tls eap-mschapv2</list> - </completionHelp> - <valueHelp> - <format>eap-tls</format> - <description>EAP-TLS</description> - </valueHelp> - <valueHelp> - <format>eap-mschapv2</format> - <description>EAP-MSCHAPv2</description> - </valueHelp> - <constraint> - <regex>^(eap-tls|eap-mschapv2)$</regex> - </constraint> - </properties> - <defaultValue>eap-mschapv2</defaultValue> - </leafNode> - <node name="local-users"> + <node name="authentication"> <properties> - <help>Local user authentication for PPPoE server</help> + <help>Authentication for remote access</help> </properties> <children> - <tagNode name="username"> + #include <include/ipsec/authentication-id.xml.i> + #include <include/ipsec/authentication-x509.xml.i> + <leafNode name="client-mode"> + <properties> + <help>Client authentication mode</help> + <completionHelp> + <list>eap-tls eap-mschapv2</list> + </completionHelp> + <valueHelp> + <format>eap-tls</format> + <description>EAP-TLS</description> + </valueHelp> + <valueHelp> + <format>eap-mschapv2</format> + <description>EAP-MSCHAPv2</description> + </valueHelp> + <constraint> + <regex>^(eap-tls|eap-mschapv2)$</regex> + </constraint> + </properties> + <defaultValue>eap-mschapv2</defaultValue> + </leafNode> + <node name="local-users"> <properties> - <help>User name for authentication</help> + <help>Local user authentication for PPPoE server</help> </properties> <children> - #include <include/generic-disable-node.xml.i> - <leafNode name="password"> + <tagNode name="username"> <properties> - <help>Password for authentication</help> + <help>User name for authentication</help> </properties> - </leafNode> + <children> + #include <include/generic-disable-node.xml.i> + <leafNode name="password"> + <properties> + <help>Password for authentication</help> + </properties> + </leafNode> + </children> + </tagNode> </children> - </tagNode> + </node> + <leafNode name="server-mode"> + <properties> + <help>Server authentication mode</help> + <completionHelp> + <list>pre-shared-secret x509</list> + </completionHelp> + <valueHelp> + <format>pre-shared-secret</format> + <description>pre-shared-secret_description</description> + </valueHelp> + <valueHelp> + <format>x509</format> + <description>x509_description</description> + </valueHelp> + <constraint> + <regex>^(pre-shared-secret|x509)$</regex> + </constraint> + </properties> + <defaultValue>x509</defaultValue> + </leafNode> + #include <include/ipsec/authentication-pre-shared-secret.xml.i> </children> </node> - <leafNode name="server-mode"> + #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> + <leafNode name="timeout"> <properties> - <help>Server authentication mode</help> - <completionHelp> - <list>pre-shared-secret x509</list> - </completionHelp> - <valueHelp> - <format>pre-shared-secret</format> - <description>pre-shared-secret_description</description> - </valueHelp> + <help>Timeout to close connection if no data is transmitted</help> <valueHelp> - <format>x509</format> - <description>x509_description</description> + <format>u32:10-86400</format> + <description>Timeout in seconds (default 28800)</description> </valueHelp> <constraint> - <regex>^(pre-shared-secret|x509)$</regex> + <validator name="numeric" argument="--range 10-86400"/> </constraint> </properties> - <defaultValue>x509</defaultValue> + <defaultValue>28800</defaultValue> + </leafNode> + <leafNode name="pool"> + <properties> + <help>Pool name used for IP address assignments</help> + <completionHelp> + <path>vpn ipsec remote-access pool</path> + <list>dhcp</list> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Pool name</description> + </valueHelp> + <multi/> + </properties> </leafNode> - #include <include/ipsec/authentication-pre-shared-secret.xml.i> </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"> + </tagNode> + <tagNode name="pool"> <properties> <help>IP address pool for remote-access users</help> </properties> <children> - <leafNode name="dhcp-enable"> - <properties> - <help>Enable DHCP pool for clients on this connection</help> - <valueless/> - </properties> - </leafNode> <leafNode name="exclude"> <properties> <help>Local IPv4 or IPv6 pool prefix exclusions</help> @@ -850,28 +881,14 @@ <validator name="ipv4-prefix"/> <validator name="ipv6-prefix"/> </constraint> - <multi/> </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> + </tagNode> </children> - </tagNode> + </node> <node name="site-to-site"> <properties> <help>Site-to-site VPN</help> diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index 49c6c6a2d..c50724592 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -34,9 +34,11 @@ from vyos.pki import wrap_public_key from vyos.pki import wrap_private_key from vyos.template import ip_from_cidr from vyos.template import is_ipv4 +from vyos.template import is_ipv6 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 dict_search_args from vyos.util import run from vyos.xml import defaults @@ -102,11 +104,11 @@ def get_config(config=None): 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]) + if 'remote_access' in ipsec and 'connection' in ipsec['remote_access']: + default_values = defaults(base + ['remote-access', 'connection']) + for rw in ipsec['remote_access']['connection']: + ipsec['remote_access']['connection'][rw] = dict_merge(default_values, + ipsec['remote_access']['connection'][rw]) ipsec['dhcp_no_address'] = {} ipsec['install_routes'] = 'no' if conf.exists(base + ["options", "disable-route-autoinstall"]) else default_install_routes @@ -127,33 +129,6 @@ def get_config(config=None): ipsec['l2tp_ike_default'] = 'aes256-sha1-modp1024,3des-sha1-modp1024,3des-sha1-modp1024' ipsec['l2tp_esp_default'] = 'aes256-sha1,3des-sha1' - if 'remote_access' in ipsec: - for name, ra_conf in ipsec['remote_access'].items(): - if 'pool' in ra_conf: - if 'name_server' in ra_conf['pool']: - ns_v4 = [] - ns_v6 = [] - for ns in ra_conf['pool']['name_server']: - if is_ipv4(ns): ns_v4.append(ns) - else: ns_v6.append(ns) - - # Only update nameserver keys if there are address-family specific name-servers - if ns_v4: - ipsec['remote_access'][name]['pool'].update({'name_server_v4' : ns_v4}) - if ns_v6: - ipsec['remote_access'][name]['pool'].update({'name_server_v6' : ns_v6}) - del ipsec['remote_access'][name]['pool']['name_server'] - - if 'exclude' in ra_conf['pool']: - exclude_v4 = [] - exclude_v6 = [] - for exclude in ra_conf['pool']['exclude']: - if is_ipv4(exclude): exclude_v4.append(exclude) - else: exclude_v6.append(exclude) - - ipsec['remote_access'][name]['pool'].update({'exclude_v4' : ns_v4, 'exclude_v6' : ns_v6}) - del ipsec['remote_access'][name]['pool']['exclude'] - return ipsec def get_dhcp_address(iface): @@ -257,52 +232,71 @@ def verify(ipsec): raise ConfigError(f"Missing authentication on {profile} profile") if 'remote_access' in ipsec: - for name, ra_conf in ipsec['remote_access'].items(): - if 'esp_group' in ra_conf: - if 'esp_group' not in ipsec or ra_conf['esp_group'] not in ipsec['esp_group']: - raise ConfigError(f"Invalid esp-group on {name} remote-access config") - else: - raise ConfigError(f"Missing esp-group on {name} remote-access config") - - if 'ike_group' in ra_conf: - if 'ike_group' not in ipsec or ra_conf['ike_group'] not in ipsec['ike_group']: - raise ConfigError(f"Invalid ike-group on {name} remote-access config") - else: - raise ConfigError(f"Missing ike-group on {name} remote-access config") - - if 'authentication' not in ra_conf: - raise ConfigError(f"Missing authentication on {name} remote-access config") + if 'connection' in ipsec['remote_access']: + for name, ra_conf in ipsec['remote_access']['connection'].items(): + if 'esp_group' in ra_conf: + if 'esp_group' not in ipsec or ra_conf['esp_group'] not in ipsec['esp_group']: + raise ConfigError(f"Invalid esp-group on {name} remote-access config") + else: + raise ConfigError(f"Missing esp-group on {name} remote-access config") + + if 'ike_group' in ra_conf: + if 'ike_group' not in ipsec or ra_conf['ike_group'] not in ipsec['ike_group']: + raise ConfigError(f"Invalid ike-group on {name} remote-access config") + else: + raise ConfigError(f"Missing ike-group on {name} remote-access config") + + if 'authentication' not in ra_conf: + raise ConfigError(f"Missing authentication on {name} remote-access config") + + if ra_conf['authentication']['server_mode'] == 'x509': + if 'x509' not in ra_conf['authentication']: + raise ConfigError(f"Missing x509 settings on {name} remote-access config") + + x509 = ra_conf['authentication']['x509'] + + if 'ca_certificate' not in x509 or 'certificate' not in x509: + raise ConfigError(f"Missing x509 certificates on {name} remote-access config") + + verify_pki_x509(ipsec['pki'], x509) + elif ra_conf['authentication']['server_mode'] == 'pre-shared-secret': + if 'pre_shared_secret' not in ra_conf['authentication']: + raise ConfigError(f"Missing pre-shared-key on {name} remote-access config") + + if 'pool' in ra_conf: + if 'dhcp' in ra_conf['pool'] and len(ra_conf['pool']) > 1: + raise ConfigError(f'Can not use both DHCP and a predefined address pool for "{name}"!') + + for pool in ra_conf['pool']: + if pool == 'dhcp': + if dict_search('options.remote_access.dhcp.server', ipsec) == None: + raise ConfigError('IPSec DHCP server is not configured!') + + elif 'pool' not in ipsec['remote_access'] or pool not in ipsec['remote_access']['pool']: + raise ConfigError(f'Requested pool "{pool}" does not exist!') + + if 'pool' in ipsec['remote_access']: + for pool, pool_config in ipsec['remote_access']['pool'].items(): + if 'prefix' not in pool_config: + raise ConfigError(f'Missing madatory prefix option for pool "{pool}"!') + + if 'name_server' in pool_config: + if len(pool_config['name_server']) > 2: + raise ConfigError(f'Only two name-servers are supported for remote-access pool "{pool}"!') + + for ns in pool_config['name_server']: + v4_addr_and_ns = is_ipv4(ns) and not is_ipv4(pool_config['prefix']) + v6_addr_and_ns = is_ipv6(ns) and not is_ipv6(pool_config['prefix']) + if v4_addr_and_ns or v6_addr_and_ns: + raise ConfigError('Must use both IPv4 or IPv6 addresses for pool prefix and name-server adresses!') + + if 'exclude' in pool_config: + for exclude in pool_config['exclude']: + v4_addr_and_exclude = is_ipv4(exclude) and not is_ipv4(pool_config['prefix']) + v6_addr_and_exclude = is_ipv6(exclude) and not is_ipv6(pool_config['prefix']) + if v4_addr_and_exclude or v6_addr_and_exclude: + raise ConfigError('Must use both IPv4 or IPv6 addresses for pool prefix and exclude prefixes!') - if ra_conf['authentication']['server_mode'] == 'x509': - if 'x509' not in ra_conf['authentication']: - raise ConfigError(f"Missing x509 settings on {name} remote-access config") - - x509 = ra_conf['authentication']['x509'] - - if 'ca_certificate' not in x509 or 'certificate' not in x509: - raise ConfigError(f"Missing x509 certificates on {name} remote-access config") - - verify_pki_x509(ipsec['pki'], x509) - elif ra_conf['authentication']['server_mode'] == 'pre-shared-secret': - if 'pre_shared_secret' not in ra_conf['authentication']: - raise ConfigError(f"Missing pre-shared-key on {name} remote-access config") - - if 'pool' in ra_conf: - if 'name_server_ipv4' in ra_conf['pool'] and len(ra_conf['pool']['name_server_ipv4']) > 2: - raise ConfigError(f'IPSec remote-access "{name}" supports only two IPv4 name-servers!') - if 'name_server_ipv6' in ra_conf['pool'] and len(ra_conf['pool']['name_server_ipv6']) > 2: - raise ConfigError(f'IPSec remote-access "{name}" supports only two IPv6 name-servers!') - - if 'prefix' in ra_conf['pool']: - prefix_v4 = [] - prefix_v6 = [] - for prefix in ra_conf['pool']['prefix']: - if is_ipv4(prefix): prefix_v4.append(prefix) - else: prefix_v6.append(prefix) - if len(prefix_v4) > 1: - raise ConfigError(f'IPSec remote-access "{name}" supports only one IPv4 prefix!') - if len(prefix_v6) > 1: - raise ConfigError(f'IPSec remote-access "{name}" supports only one IPv6 prefix!') if 'site_to_site' in ipsec and 'peer' in ipsec['site_to_site']: for peer, peer_conf in ipsec['site_to_site']['peer'].items(): @@ -476,8 +470,9 @@ def generate(ipsec): if 'authentication' in ipsec['l2tp'] and 'x509' in ipsec['l2tp']['authentication']: generate_pki_files_x509(ipsec['pki'], ipsec['l2tp']['authentication']['x509']) - if 'remote_access' in ipsec: - for rw, rw_conf in ipsec['remote_access'].items(): + if 'remote_access' in ipsec and 'connection' in ipsec['remote_access']: + for rw, rw_conf in ipsec['remote_access']['connection'].items(): + if 'authentication' in rw_conf and 'x509' in rw_conf['authentication']: generate_pki_files_x509(ipsec['pki'], rw_conf['authentication']['x509']) |