summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsarthurdev <965089+sarthurdev@users.noreply.github.com>2021-07-05 16:22:54 +0200
committersarthurdev <965089+sarthurdev@users.noreply.github.com>2021-07-05 22:01:10 +0200
commit0b93fce06526a2826c19adcbb25874e51cccf68e (patch)
tree5ca97537d047a2f543144ac8a93c8f04fe69a796
parentda02980779821862eed8966fd9e9258b807eb03d (diff)
downloadvyos-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.tmpl23
-rw-r--r--data/templates/ipsec/swanctl.conf.tmpl14
-rw-r--r--data/templates/ipsec/swanctl/remote_access.tmpl16
-rw-r--r--interface-definitions/vpn_ipsec.xml.in82
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py50
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)