diff options
author | sarthurdev <965089+sarthurdev@users.noreply.github.com> | 2021-07-05 16:22:54 +0200 |
---|---|---|
committer | sarthurdev <965089+sarthurdev@users.noreply.github.com> | 2021-07-05 22:01:10 +0200 |
commit | 0b93fce06526a2826c19adcbb25874e51cccf68e (patch) | |
tree | 5ca97537d047a2f543144ac8a93c8f04fe69a796 | |
parent | da02980779821862eed8966fd9e9258b807eb03d (diff) | |
download | vyos-1x-0b93fce06526a2826c19adcbb25874e51cccf68e.tar.gz vyos-1x-0b93fce06526a2826c19adcbb25874e51cccf68e.zip |
ipsec: T1210: T1251: Add more features to remote-access connections
- Adds client/server authentication methods.
- Adds basic verification to remote-access.
- Adds DHCP pool and options to remote-access.
- Cleanup unused PKI files.
-rw-r--r-- | data/templates/ipsec/charon/dhcp.conf.tmpl | 23 | ||||
-rw-r--r-- | data/templates/ipsec/swanctl.conf.tmpl | 14 | ||||
-rw-r--r-- | data/templates/ipsec/swanctl/remote_access.tmpl | 16 | ||||
-rw-r--r-- | interface-definitions/vpn_ipsec.xml.in | 82 | ||||
-rwxr-xr-x | src/conf_mode/vpn_ipsec.py | 50 |
5 files changed, 176 insertions, 9 deletions
diff --git a/data/templates/ipsec/charon/dhcp.conf.tmpl b/data/templates/ipsec/charon/dhcp.conf.tmpl new file mode 100644 index 000000000..2879550a8 --- /dev/null +++ b/data/templates/ipsec/charon/dhcp.conf.tmpl @@ -0,0 +1,23 @@ +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 }} +{% endif %} +{% if options.remote_access.dhcp_pool.server is defined %} + server = {{ options.remote_access.dhcp_pool.server }} +{% endif %} +{% endif %} + + # Always use the configured server address. + # force_server_address = no + + # Derive user-defined MAC address from hash of IKE identity and send client + # identity DHCP option. + # identity_lease = no + + # Use the DHCP server port (67) as source port when a unicast server address + # is configured. + # use_server_port = no +} diff --git a/data/templates/ipsec/swanctl.conf.tmpl b/data/templates/ipsec/swanctl.conf.tmpl index 0eda8479a..00251d44d 100644 --- a/data/templates/ipsec/swanctl.conf.tmpl +++ b/data/templates/ipsec/swanctl.conf.tmpl @@ -23,7 +23,7 @@ connections { pools { {% if remote_access is defined %} -{% for ra, ra_conf in remote_access.items() if remote_access is defined %} +{% for ra, ra_conf in remote_access.items() if ra_conf.pool.dhcp_enable is not defined %} ra-{{ ra }} { addrs = {{ ra_conf.pool.prefix }} dns = {{ ra_conf.pool.name_server | join(",") }} @@ -82,7 +82,17 @@ secrets { {% 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 %} +{% if ra_conf.authentication.server_mode == 'pre-shared-secret' %} + ike_{{ ra }} { +{% if ra_conf.authentication.id is defined %} + id = "{{ ra_conf.authentication.id }}" +{% elif ra_conf.local_address is defined %} + id = "{{ ra_conf.local_address }}" +{% endif %} + secret = "{{ ra_conf.authentication.pre_shared_secret }}" + } +{% endif %} +{% if ra_conf.authentication.client_mode == 'eap-mschapv2' 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 }}" diff --git a/data/templates/ipsec/swanctl/remote_access.tmpl b/data/templates/ipsec/swanctl/remote_access.tmpl index a3a1cf0b2..95f2108fb 100644 --- a/data/templates/ipsec/swanctl/remote_access.tmpl +++ b/data/templates/ipsec/swanctl/remote_access.tmpl @@ -10,19 +10,27 @@ send_certreq = no rekey_time = {{ ike.lifetime }}s keyingtries = 0 +{% if rw_conf.pool.dhcp_enable is defined %} + pools = dhcp +{% else %} pools = ra-{{ name }} +{% endif %} 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 %} +{% if 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 %} +{% if rw_conf.authentication.server_mode == 'x509' %} + auth = pubkey certs = {{ rw_conf.authentication.x509.certificate }}.pem +{% elif rw_conf.authentication.server_mode == 'pre-shared-secret' %} + auth = psk {% endif %} } remote { - auth = eap-mschapv2 + auth = {{ rw_conf.authentication.client_mode }} +{% if rw_conf.authentication.client_mode.startswith("eap") %} eap_id = %any +{% endif %} } children { ikev2-vpn { diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in index f6b18d1d5..4425ab02a 100644 --- a/interface-definitions/vpn_ipsec.xml.in +++ b/interface-definitions/vpn_ipsec.xml.in @@ -648,6 +648,37 @@ <valueless/> </properties> </leafNode> + <node name="remote-access"> + <properties> + <help>remote-access global options</help> + </properties> + <children> + <node name="dhcp-pool"> + <properties> + <help>DHCP pool options for remote-access</help> + </properties> + <children> + <leafNode name="interface"> + <properties> + <help>Interface with DHCP server to use</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py</script> + </completionHelp> + </properties> + </leafNode> + <leafNode name="server"> + <properties> + <help>DHCP server address</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address of the DHCP server</description> + </valueHelp> + </properties> + </leafNode> + </children> + </node> + </children> + </node> </children> </node> <tagNode name="profile"> @@ -720,6 +751,26 @@ <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"> <properties> <help>Local user authentication for PPPoE server</help> @@ -740,6 +791,31 @@ </tagNode> </children> </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> + <leafNode name="pre-shared-secret"> + <properties> + <help>Pre-shared-secret used for server authentication</help> + </properties> + </leafNode> </children> </node> #include <include/generic-description.xml.i> @@ -753,6 +829,12 @@ <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> diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index 76ee64a20..cf23a89c6 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -46,6 +46,7 @@ dhcp_wait_sleep = 1 swanctl_dir = '/etc/swanctl' ipsec_conf = '/etc/ipsec.conf' ipsec_secrets = '/etc/ipsec.secrets' +charon_dhcp_conf = '/etc/strongswan.d/charon/dhcp.conf' interface_conf = '/etc/strongswan.d/interfaces_use.conf' swanctl_conf = f'{swanctl_dir}/swanctl.conf' @@ -150,13 +151,13 @@ def verify_pki(pki, x509_conf): ca_cert_name = x509_conf['ca_certificate'] cert_name = x509_conf['certificate'] - if not dict_search_args(ipsec['pki'], 'ca', ca_cert_name, 'certificate'): + if not dict_search_args(pki, 'ca', ca_cert_name, 'certificate'): raise ConfigError(f'Missing CA certificate on specified PKI CA certificate "{ca_cert_name}"') - if not dict_search_args(ipsec['pki'], 'certificate', cert_name, 'certificate'): + if not dict_search_args(pki, 'certificate', cert_name, 'certificate'): raise ConfigError(f'Missing certificate on specified PKI certificate "{cert_name}"') - if not dict_search_args(ipsec['pki'], 'certificate', cert_name, 'private', 'key'): + if not dict_search_args(pki, 'certificate', cert_name, 'private', 'key'): raise ConfigError(f'Missing private key on specified PKI certificate "{cert_name}"') return True @@ -190,6 +191,37 @@ def verify(ipsec): if 'authentication' not in profile_conf: 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 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(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 'site_to_site' in ipsec and 'peer' in ipsec['site_to_site']: for peer, peer_conf in ipsec['site_to_site']['peer'].items(): has_default_esp = False @@ -282,6 +314,15 @@ def verify(ipsec): if ('local' in tunnel_conf and 'prefix' in tunnel_conf['local']) or ('remote' in tunnel_conf and 'prefix' in tunnel_conf['remote']): raise ConfigError(f"Local/remote prefix cannot be used with ESP transport mode on tunnel {tunnel} for site-to-site peer {peer}") +def cleanup_pki_files(): + for path in [CERT_PATH, CA_PATH, CRL_PATH, KEY_PATH]: + if not os.path.exists(path): + continue + for file in os.listdir(path): + file_path = os.path.join(path, file) + if os.path.isfile(file_path): + os.unlink(file_path) + def generate_pki_files(pki, x509_conf): ca_cert_name = x509_conf['ca_certificate'] ca_cert_data = dict_search_args(pki, 'ca', ca_cert_name, 'certificate') @@ -308,6 +349,8 @@ def generate_pki_files(pki, x509_conf): f.write(wrap_private_key(key_data, protected)) def generate(ipsec): + cleanup_pki_files() + if not ipsec: for config_file in [ipsec_conf, ipsec_secrets, interface_conf, swanctl_conf]: if os.path.isfile(config_file): @@ -371,6 +414,7 @@ def generate(ipsec): render(ipsec_conf, 'ipsec/ipsec.conf.tmpl', data) render(ipsec_secrets, 'ipsec/ipsec.secrets.tmpl', data) + render(charon_dhcp_conf, 'ipsec/charon/dhcp.conf.tmpl', data) render(interface_conf, 'ipsec/interfaces_use.conf.tmpl', data) render(swanctl_conf, 'ipsec/swanctl.conf.tmpl', data) |