From a1abb118c9eb413f3c78cfb2077f9c0d4b443c3a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 3 Jul 2021 15:31:38 +0200 Subject: ipsec: T2816: rework IKE and ESP key assignment Commit 2d79a500 ("ipsec: T2816: add Jinja2 converter for ESP/IKE groups to string") added a Jinja2 helper function which can be used to transform VyOS CLI ESP and IKE key proposals into a strongSwan compatible string cipher. This commit changes the IPSec implementation to make use of this new Jinja2 filter fubction/Python helper. This is required base work for better automated tests (smoketests) but also for an IKEv2 road-warrior setup. --- src/conf_mode/vpn_ipsec.py | 167 +++++++++++++-------------------------------- 1 file changed, 47 insertions(+), 120 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index ce72ee094..e95a3e82d 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -39,46 +39,11 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -authby_translate = { - 'pre-shared-secret': 'psk', - 'rsa': 'pubkey', - 'x509': 'pubkey' -} - -default_pfs = 'dh-group2' -pfs_translate = { - 'dh-group1' : 'modp768', - 'dh-group2' : 'modp1024', - 'dh-group5' : 'modp1536', - 'dh-group14' : 'modp2048', - 'dh-group15' : 'modp3072', - 'dh-group16' : 'modp4096', - 'dh-group17' : 'modp6144', - 'dh-group18' : 'modp8192', - 'dh-group19' : 'ecp256', - 'dh-group20' : 'ecp384', - 'dh-group21' : 'ecp512', - 'dh-group22' : 'modp1024s160', - 'dh-group23' : 'modp2048s224', - 'dh-group24' : 'modp2048s256', - 'dh-group25' : 'ecp192', - 'dh-group26' : 'ecp224', - 'dh-group27' : 'ecp224bp', - 'dh-group28' : 'ecp256bp', - 'dh-group29' : 'ecp384bp', - 'dh-group30' : 'ecp512bp', - 'dh-group31' : 'curve25519', - 'dh-group32' : 'curve448' -} - any_log_modes = [ 'dmn', 'mgr', 'ike', 'chd','job', 'cfg', 'knl', 'net', 'asn', 'enc', 'lib', 'esp', 'tls', 'tnc', 'imc', 'imv', 'pts' ] -ike_ciphers = {} -esp_ciphers = {} - dhcp_wait_attempts = 2 dhcp_wait_sleep = 1 @@ -113,52 +78,18 @@ def get_config(config=None): get_first_key=True, no_tag_node_value_mangle=True) ipsec['dhcp_no_address'] = {} - ipsec['interface_change'] = leaf_node_changed(conf, base + ['ipsec-interfaces', 'interface']) - ipsec['l2tp_exists'] = conf.exists(['vpn', 'l2tp', 'remote-access', 'ipsec-settings']) + ipsec['interface_change'] = leaf_node_changed(conf, base + ['ipsec-interfaces', + 'interface']) + ipsec['l2tp_exists'] = conf.exists(['vpn', 'l2tp', 'remote-access', + 'ipsec-settings']) ipsec['nhrp_exists'] = conf.exists(['protocols', 'nhrp', 'tunnel']) ipsec['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - ipsec['rsa_keys'] = conf.get_config_dict(['vpn', 'rsa-keys'], key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - - default_ike_pfs = None - - if 'ike_group' in ipsec: - for group, ike_conf in ipsec['ike_group'].items(): - if 'proposal' in ike_conf: - ciphers = [] - for i in ike_conf['proposal']: - proposal = ike_conf['proposal'][i] - enc = proposal['encryption'] if 'encryption' in proposal else None - hash = proposal['hash'] if 'hash' in proposal else None - pfs = ('dh-group' + proposal['dh_group']) if 'dh_group' in proposal else default_pfs - - if not default_ike_pfs: - default_ike_pfs = pfs - - if enc and hash: - ciphers.append(f"{enc}-{hash}-{pfs_translate[pfs]}" if pfs else f"{enc}-{hash}") - ike_ciphers[group] = ','.join(ciphers) - - if 'esp_group' in ipsec: - for group, esp_conf in ipsec['esp_group'].items(): - pfs = esp_conf['pfs'] if 'pfs' in esp_conf else 'enable' - - if pfs == 'disable': - pfs = None - - if pfs == 'enable': - pfs = default_ike_pfs - - if 'proposal' in esp_conf: - ciphers = [] - for i in esp_conf['proposal']: - proposal = esp_conf['proposal'][i] - enc = proposal['encryption'] if 'encryption' in proposal else None - hash = proposal['hash'] if 'hash' in proposal else None - if enc and hash: - ciphers.append(f"{enc}-{hash}-{pfs_translate[pfs]}" if pfs else f"{enc}-{hash}") - esp_ciphers[group] = ','.join(ciphers) + get_first_key=True, + no_tag_node_value_mangle=True) + ipsec['rsa_keys'] = conf.get_config_dict(['vpn', 'rsa-keys'], + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) return ipsec @@ -361,58 +292,54 @@ def generate(ipsec): os.unlink(config_file) return - data = {} - if ipsec: - if ipsec['dhcp_no_address']: - with open(DHCP_HOOK_IFLIST, 'w') as f: - f.write(" ".join(ipsec['dhcp_no_address'].values())) + if ipsec['dhcp_no_address']: + with open(DHCP_HOOK_IFLIST, 'w') as f: + f.write(" ".join(ipsec['dhcp_no_address'].values())) - data = ipsec - data['authby'] = authby_translate - data['ciphers'] = {'ike': ike_ciphers, 'esp': esp_ciphers} - data['rsa_local_key'] = verify_rsa_local_key(ipsec) + data = ipsec + data['rsa_local_key'] = verify_rsa_local_key(ipsec) - for path in [swanctl_dir, CERT_PATH, CA_PATH, CRL_PATH]: - if not os.path.exists(path): - os.mkdir(path, mode=0o755) + for path in [swanctl_dir, CERT_PATH, CA_PATH, CRL_PATH]: + if not os.path.exists(path): + os.mkdir(path, mode=0o755) - if not os.path.exists(KEY_PATH): - os.mkdir(KEY_PATH, mode=0o700) + if not os.path.exists(KEY_PATH): + os.mkdir(KEY_PATH, mode=0o700) - if 'site_to_site' in data and 'peer' in data['site_to_site']: - for peer, peer_conf in ipsec['site_to_site']['peer'].items(): - if peer in ipsec['dhcp_no_address']: - continue + if 'site_to_site' in data and 'peer' in data['site_to_site']: + for peer, peer_conf in ipsec['site_to_site']['peer'].items(): + if peer in ipsec['dhcp_no_address']: + continue - if peer_conf['authentication']['mode'] == 'x509': - generate_pki_files(ipsec['pki'], peer_conf['authentication']['x509']) + if peer_conf['authentication']['mode'] == 'x509': + generate_pki_files(ipsec['pki'], peer_conf['authentication']['x509']) - local_ip = '' - if 'local_address' in peer_conf: - local_ip = peer_conf['local_address'] - elif 'dhcp_interface' in peer_conf: - local_ip = get_dhcp_address(peer_conf['dhcp_interface']) + local_ip = '' + if 'local_address' in peer_conf: + local_ip = peer_conf['local_address'] + elif 'dhcp_interface' in peer_conf: + local_ip = get_dhcp_address(peer_conf['dhcp_interface']) - data['site_to_site']['peer'][peer]['local_address'] = local_ip + data['site_to_site']['peer'][peer]['local_address'] = local_ip - if 'tunnel' in peer_conf: - for tunnel, tunnel_conf in peer_conf['tunnel'].items(): - local_prefixes = dict_search('local.prefix', tunnel_conf) - remote_prefixes = dict_search('remote.prefix', tunnel_conf) + if 'tunnel' in peer_conf: + for tunnel, tunnel_conf in peer_conf['tunnel'].items(): + local_prefixes = dict_search('local.prefix', tunnel_conf) + remote_prefixes = dict_search('remote.prefix', tunnel_conf) - if not local_prefixes or not remote_prefixes: - continue + if not local_prefixes or not remote_prefixes: + continue - passthrough = [] + passthrough = [] - for local_prefix in local_prefixes: - for remote_prefix in remote_prefixes: - local_net = ipaddress.ip_network(local_prefix) - remote_net = ipaddress.ip_network(remote_prefix) - if local_net.overlaps(remote_net): - passthrough.append(local_prefix) + for local_prefix in local_prefixes: + for remote_prefix in remote_prefixes: + local_net = ipaddress.ip_network(local_prefix) + remote_net = ipaddress.ip_network(remote_prefix) + if local_net.overlaps(remote_net): + passthrough.append(local_prefix) - data['site_to_site']['peer'][peer]['tunnel'][tunnel]['passthrough'] = passthrough + data['site_to_site']['peer'][peer]['tunnel'][tunnel]['passthrough'] = passthrough if 'logging' in ipsec and 'log_modes' in ipsec['logging']: modes = ipsec['logging']['log_modes'] @@ -448,7 +375,7 @@ def apply(ipsec): if not ipsec: call('sudo /usr/sbin/ipsec stop') else: - should_start = ('profile' in ipsec or dict_search('site_to_site.peer', ipsec)) + should_start = 'profile' in ipsec or dict_search('site_to_site.peer', ipsec) if not process_named_running('charon') and should_start: args = f'--auto-update {ipsec["auto_update"]}' if 'auto_update' in ipsec else '' -- cgit v1.2.3 From 1e74c0df2179c60036e440e15ed9036163039b2a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 3 Jul 2021 15:39:17 +0200 Subject: ipsec: T2816: remove default values from Jinja2 template and place them in XML VyOS has a known to work mechanism in supplying CLI default values into the Python configuration scripts. This commit removes hardcoded default values from the Jinja2 template and places them into the appropriate XML definitions. The big advantage is that the default value itself and the corresponding help string are located in the exact same file. --- data/templates/ipsec/swanctl/peer.tmpl | 4 ++-- data/templates/ipsec/swanctl/profile.tmpl | 6 +++--- interface-definitions/vpn_ipsec.xml.in | 4 ++++ src/conf_mode/vpn_ipsec.py | 15 +++++++++++++++ 4 files changed, 24 insertions(+), 5 deletions(-) (limited to 'src/conf_mode') diff --git a/data/templates/ipsec/swanctl/peer.tmpl b/data/templates/ipsec/swanctl/peer.tmpl index 0559d1dac..b35cd4b60 100644 --- a/data/templates/ipsec/swanctl/peer.tmpl +++ b/data/templates/ipsec/swanctl/peer.tmpl @@ -63,7 +63,7 @@ if_id_in = {{ peer_conf.vti.bind | replace('vti', '') }} if_id_out = {{ peer_conf.vti.bind | replace('vti', '') }} ipcomp = {{ 'yes' if vti_esp.compression is defined and vti_esp.compression == 'enable' else 'no' }} - mode = {{ vti_esp.mode if vti_esp.mode is defined else "tunnel" }} + mode = {{ vti_esp.mode }} {% if peer[0:1] == '@' %} start_action = none {% elif peer_conf.connection_type is not defined or peer_conf.connection_type == 'initiate' %} @@ -101,7 +101,7 @@ remote_ts = {{ peer }}{{ remote_suffix }} {% endif %} ipcomp = {{ 'yes' if tunnel_esp.compression is defined and tunnel_esp.compression == 'enable' else 'no' }} - mode = {{ tunnel_esp.mode if tunnel_esp.mode is defined else "tunnel" }} + mode = {{ tunnel_esp.mode }} {% if peer[0:1] == '@' %} start_action = none {% elif peer_conf.connection_type is not defined or peer_conf.connection_type == 'initiate' %} diff --git a/data/templates/ipsec/swanctl/profile.tmpl b/data/templates/ipsec/swanctl/profile.tmpl index 0360972f6..0a7268405 100644 --- a/data/templates/ipsec/swanctl/profile.tmpl +++ b/data/templates/ipsec/swanctl/profile.tmpl @@ -7,7 +7,7 @@ dmvpn-{{ name }}-{{ interface }} { proposals = {{ ike_group[profile_conf.ike_group] | get_esp_ike_cipher | join(',') }} version = {{ ike.key_exchange[4:] if ike is defined and ike.key_exchange is defined else "0" }} - rekey_time = {{ ike.lifetime if ike.lifetime is defined else '28800' }}s + rekey_time = {{ ike.lifetime }}s keyingtries = 0 {% if profile_conf.authentication is defined and profile_conf.authentication.mode is defined and profile_conf.authentication.mode == 'pre-shared-secret' %} local { @@ -20,11 +20,11 @@ children { dmvpn { esp_proposals = {{ esp | get_esp_ike_cipher | join(',') }} - rekey_time = {{ esp.lifetime if esp.lifetime is defined else '3600' }}s + rekey_time = {{ esp.lifetime }}s rand_time = 540s local_ts = dynamic[gre] remote_ts = dynamic[gre] - mode = {{ esp.mode if esp.mode is defined else 'transport' }} + mode = {{ esp.mode }} {% if ike.dead_peer_detection is defined and ike.dead_peer_detection.action is defined %} dpd_action = {{ ike.dead_peer_detection.action }} {% endif %} diff --git a/interface-definitions/vpn_ipsec.xml.in b/interface-definitions/vpn_ipsec.xml.in index 6aff7bef5..a2e9a7a5a 100644 --- a/interface-definitions/vpn_ipsec.xml.in +++ b/interface-definitions/vpn_ipsec.xml.in @@ -64,6 +64,7 @@ + 3600 @@ -83,6 +84,7 @@ ^(tunnel|transport)$ + tunnel @@ -190,6 +192,7 @@ ^(enable|dh-group1|dh-group2|dh-group5|dh-group14|dh-group15|dh-group16|dh-group17|dh-group18|dh-group19|dh-group20|dh-group21|dh-group22|dh-group23|dh-group24|dh-group25|dh-group26|dh-group27|dh-group28|dh-group29|dh-group30|dh-group31|dh-group32|disable)$ + enable @@ -341,6 +344,7 @@ + 28800 diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index e95a3e82d..6d5d24e52 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -23,6 +23,7 @@ from time import sleep from vyos.config import Config from vyos.configdict import leaf_node_changed from vyos.configverify import verify_interface_exists +from vyos.configdict import dict_merge from vyos.ifconfig import Interface from vyos.pki import wrap_certificate from vyos.pki import wrap_crl @@ -35,6 +36,7 @@ from vyos.util import call from vyos.util import dict_search from vyos.util import process_named_running from vyos.util import run +from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -77,6 +79,19 @@ def get_config(config=None): ipsec = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) + if 'esp_group' in ipsec: + default_values = defaults(base + ['esp-group']) + for group in ipsec['esp_group']: + ipsec['esp_group'][group] = dict_merge(default_values, + ipsec['esp_group'][group]) + + if 'ike_group' in ipsec: + default_values = defaults(base + ['ike-group']) + for group in ipsec['ike_group']: + ipsec['ike_group'][group] = dict_merge(default_values, + ipsec['ike_group'][group]) + + ipsec['dhcp_no_address'] = {} ipsec['interface_change'] = leaf_node_changed(conf, base + ['ipsec-interfaces', 'interface']) -- cgit v1.2.3