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