From 0b93fce06526a2826c19adcbb25874e51cccf68e Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Mon, 5 Jul 2021 16:22:54 +0200 Subject: 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. --- data/templates/ipsec/charon/dhcp.conf.tmpl | 23 +++++++ data/templates/ipsec/swanctl.conf.tmpl | 14 ++++- data/templates/ipsec/swanctl/remote_access.tmpl | 16 +++-- interface-definitions/vpn_ipsec.xml.in | 82 +++++++++++++++++++++++++ src/conf_mode/vpn_ipsec.py | 50 ++++++++++++++- 5 files changed, 176 insertions(+), 9 deletions(-) create mode 100644 data/templates/ipsec/charon/dhcp.conf.tmpl 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 @@ + + + remote-access global options + + + + + DHCP pool options for remote-access + + + + + Interface with DHCP server to use + + + + + + + + DHCP server address + + ipv4 + IPv4 address of the DHCP server + + + + + + + @@ -720,6 +751,26 @@ #include #include + + + Client authentication mode + + eap-tls eap-mschapv2 + + + eap-tls + EAP-TLS + + + eap-mschapv2 + EAP-MSCHAPv2 + + + ^(eap-tls|eap-mschapv2)$ + + + eap-mschapv2 + Local user authentication for PPPoE server @@ -740,6 +791,31 @@ + + + Server authentication mode + + pre-shared-secret x509 + + + pre-shared-secret + pre-shared-secret_description + + + x509 + x509_description + + + ^(pre-shared-secret|x509)$ + + + x509 + + + + Pre-shared-secret used for server authentication + + #include @@ -753,6 +829,12 @@ IP address pool for remote-access users + + + Enable DHCP pool for clients on this connection + + + Local IPv4 or IPv6 pool prefix exclusions 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) -- cgit v1.2.3