From 11b5636519b360074eb2877006f2d8d63d9f6610 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Mon, 14 Jun 2021 13:04:04 +0200 Subject: ipsec: T2816: T645: T3613: Migrated IPsec to swanctl, includes multiple selectors, and selectors with VTI. --- src/conf_mode/vpn_ipsec.py | 41 ++++++++++++++-------- src/conf_mode/vpn_rsa-keys.py | 6 ++-- .../dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook | 30 +++++++++++----- src/etc/ipsec.d/vti-up-down | 14 ++++++++ src/op_mode/vpn_ike_sa.py | 4 +-- src/op_mode/vpn_ipsec.py | 13 +++---- 6 files changed, 75 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index f80a9455a..433c51e7e 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -28,7 +28,6 @@ from vyos.template import render from vyos.validate import is_ipv6_link_local from vyos.util import call from vyos.util import dict_search -from vyos.util import get_interface_address from vyos.util import process_named_running from vyos.util import run from vyos.util import cidr_fit @@ -37,9 +36,9 @@ from vyos import airbag airbag.enable() authby_translate = { - 'pre-shared-secret': 'secret', - 'rsa': 'rsasig', - 'x509': 'rsasig' + 'pre-shared-secret': 'psk', + 'rsa': 'pubkey', + 'x509': 'pubkey' } default_pfs = 'dh-group2' pfs_translate = { @@ -80,8 +79,10 @@ dhcp_wait_sleep = 1 mark_base = 0x900000 -CA_PATH = "/etc/ipsec.d/cacerts/" -CRL_PATH = "/etc/ipsec.d/crls/" +CERT_PATH="/etc/swanctl/x509/" +KEY_PATH="/etc/swanctl/private/" +CA_PATH = "/etc/swanctl/x509ca/" +CRL_PATH = "/etc/swanctl/x509crl/" DHCP_BASE = "/var/lib/dhcp/dhclient" DHCP_HOOK_IFLIST="/tmp/ipsec_dhcp_waiting" @@ -126,7 +127,7 @@ def get_config(config=None): if enc and hash: ciphers.append(f"{enc}-{hash}-{pfs_translate[pfs]}" if pfs else f"{enc}-{hash}") - ike_ciphers[group] = ','.join(ciphers) + '!' + ike_ciphers[group] = ','.join(ciphers) if 'esp_group' in ipsec: for group, esp_conf in ipsec['esp_group'].items(): @@ -146,7 +147,7 @@ def get_config(config=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) + '!' + esp_ciphers[group] = ','.join(ciphers) return ipsec @@ -324,7 +325,6 @@ def generate(ipsec): data['ciphers'] = {'ike': ike_ciphers, 'esp': esp_ciphers} data['marks'] = {} data['rsa_local_key'] = verify_rsa_local_key(ipsec) - data['x509_path'] = X509_PATH if 'site_to_site' in data and 'peer' in data['site_to_site']: for peer, peer_conf in ipsec['site_to_site']['peer'].items(): @@ -332,6 +332,12 @@ def generate(ipsec): continue if peer_conf['authentication']['mode'] == 'x509': + cert_file = os.path.join(X509_PATH, peer_conf['authentication']['x509']['cert_file']) + call(f'cp -f {cert_file} {CERT_PATH}') + + key_file = os.path.join(X509_PATH, peer_conf['authentication']['x509']['key']['file']) + call(f'cp -f {key_file} {KEY_PATH}') + ca_cert_file = os.path.join(X509_PATH, peer_conf['authentication']['x509']['ca_cert_file']) call(f'cp -f {ca_cert_file} {CA_PATH}') @@ -350,15 +356,22 @@ def generate(ipsec): if 'vti' in peer_conf and 'bind' in peer_conf['vti']: vti_interface = peer_conf['vti']['bind'] data['marks'][vti_interface] = get_mark(vti_interface) - else: + + if 'tunnel' in peer_conf: for tunnel, tunnel_conf in peer_conf['tunnel'].items(): - local_prefix = dict_search('local.prefix', tunnel_conf) - remote_prefix = dict_search('remote.prefix', tunnel_conf) + local_prefixes = dict_search('local.prefix', tunnel_conf) + remote_prefixes = dict_search('remote.prefix', tunnel_conf) - if not local_prefix or not remote_prefix: + if not local_prefixes or not remote_prefixes: continue - passthrough = cidr_fit(local_prefix, remote_prefix) + passthrough = [] + + for local_prefix in local_prefixes: + for remote_prefix in remote_prefixes: + if cidr_fit(local_prefix, remote_prefix): + passthrough.append(local_prefix) + data['site_to_site']['peer'][peer]['tunnel'][tunnel]['passthrough'] = passthrough if 'logging' in ipsec and 'log_modes' in ipsec['logging']: diff --git a/src/conf_mode/vpn_rsa-keys.py b/src/conf_mode/vpn_rsa-keys.py index 6cf7eba6e..c6ff369ad 100755 --- a/src/conf_mode/vpn_rsa-keys.py +++ b/src/conf_mode/vpn_rsa-keys.py @@ -29,7 +29,8 @@ from Crypto.PublicKey.RSA import construct airbag.enable() LOCAL_KEY_PATHS = ['/config/auth/', '/config/ipsec.d/rsa-keys/'] -LOCAL_OUTPUT = '/etc/ipsec.d/certs/localhost.pub' +LOCAL_OUTPUT = '/etc/swanctl/pubkey/localhost.pub' +LOCAL_KEY_OUTPUT = '/etc/swanctl/private/localhost.key' def get_config(config=None): if config: @@ -68,6 +69,7 @@ def generate(conf): if 'local_key' in conf and 'file' in conf['local_key']: local_key = conf['local_key']['file'] local_key_path = get_local_key(local_key) + call(f'sudo cp -f {local_key_path} {LOCAL_KEY_OUTPUT}') call(f'sudo /usr/bin/openssl rsa -in {local_key_path} -pubout -out {LOCAL_OUTPUT}') if 'rsa_key_name' in conf: @@ -82,7 +84,7 @@ def generate(conf): else: remote_key = bytes('-----BEGIN PUBLIC KEY-----\n' + remote_key + '\n-----END PUBLIC KEY-----\n', 'utf-8') - with open(f'/etc/ipsec.d/certs/{key_name}.pub', 'wb') as f: + with open(f'/etc/swanctl/pubkey/{key_name}.pub', 'wb') as f: f.write(remote_key) def migrate_from_vyatta_key(data): diff --git a/src/etc/dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook b/src/etc/dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook index e00e5fe6e..a7a9a2ce6 100755 --- a/src/etc/dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook +++ b/src/etc/dhcp/dhclient-exit-hooks.d/ipsec-dhclient-hook @@ -1,4 +1,18 @@ #!/bin/bash +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . if [ "$reason" == "REBOOT" ] || [ "$reason" == "EXPIRE" ]; then exit 0 @@ -24,8 +38,7 @@ import re from vyos.util import call from vyos.util import cmd -IPSEC_CONF="/etc/ipsec.conf" -IPSEC_SECRETS="/etc/ipsec.secrets" +SWANCTL_CONF="/etc/swanctl/swanctl.conf" def getlines(file): with open(file, 'r') as f: @@ -41,7 +54,7 @@ def ipsec_down(ip_address): connection_name = None for line in status.split("\n"): if line.find(ip_address) > 0: - regex_match = re.search(r'(peer-[^:\[]+)', line) + regex_match = re.search(r'(peer_[^:\[]+)', line) if regex_match: connection_name = regex_match[1] break @@ -53,8 +66,7 @@ if __name__ == '__main__': new_ip = os.getenv('new_ip_address') old_ip = os.getenv('old_ip_address') - conf_lines = getlines(IPSEC_CONF) - secrets_lines = getlines(IPSEC_SECRETS) + conf_lines = getlines(SWANCTL_CONF) found = False to_match = f'# dhcp:{interface}' @@ -68,9 +80,9 @@ if __name__ == '__main__': secrets_lines[i] = line.replace(old_ip, new_ip) if found: - writelines(IPSEC_CONF, conf_lines) - writelines(IPSEC_SECRETS, secrets_lines) + writelines(SWANCTL_CONF, conf_lines) ipsec_down(old_ip) - call('sudo /usr/sbin/ipsec rereadall') - call('sudo /usr/sbin/ipsec reload') + call('sudo ipsec rereadall') + call('sudo ipsec reload') + call('sudo swanctl -q') PYEND \ No newline at end of file diff --git a/src/etc/ipsec.d/vti-up-down b/src/etc/ipsec.d/vti-up-down index 0e1cd7753..2b66dd9e6 100755 --- a/src/etc/ipsec.d/vti-up-down +++ b/src/etc/ipsec.d/vti-up-down @@ -1,4 +1,18 @@ #!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . ## Script called up strongswan to bring the vti interface up/down based on the state of the IPSec tunnel. ## Called as vti_up_down vti_intf_name diff --git a/src/op_mode/vpn_ike_sa.py b/src/op_mode/vpn_ike_sa.py index 28da9f8dc..fe016da45 100755 --- a/src/op_mode/vpn_ike_sa.py +++ b/src/op_mode/vpn_ike_sa.py @@ -36,9 +36,9 @@ def ike_sa(peer, nat): peers = [] for conn in sas: for name, sa in conn.items(): - if peer and not name.startswith('peer-' + peer): + if peer and not name.startswith('peer_' + peer): continue - if name.startswith('peer-') and name in peers: + if name.startswith('peer_') and name in peers: continue if nat and 'nat-local' not in sa: continue diff --git a/src/op_mode/vpn_ipsec.py b/src/op_mode/vpn_ipsec.py index 434186abb..582b5ef95 100755 --- a/src/op_mode/vpn_ipsec.py +++ b/src/op_mode/vpn_ipsec.py @@ -100,13 +100,13 @@ def generate_x509_pair(name): print(f'Private key: {X509_PATH}{name}.key') def get_peer_connections(peer, tunnel, return_all = False): - search = rf'^conn (peer-{peer}-(tunnel-[\d]+|vti))$' + search = rf'^[\s]*(peer_{peer}_(tunnel_[\d]+|vti)).*' matches = [] - with open(IPSEC_CONF, 'r') as f: + with open(SWANCTL_CONF, 'r') as f: for line in f.readlines(): result = re.match(search, line) if result: - suffix = f'tunnel-{tunnel}' if tunnel.isnumeric() else tunnel + suffix = f'tunnel_{tunnel}' if tunnel.isnumeric() else tunnel if return_all or (result[2] == suffix): matches.append(result[1]) return matches @@ -171,13 +171,14 @@ def debug_peer(peer, tunnel): if not tunnel or tunnel == 'all': tunnel = '' - conn = get_peer_connection(peer, tunnel) + conn = get_peer_connections(peer, tunnel) - if not conn: + if not conns: print('Peer not found, aborting') return - call(f'sudo /usr/sbin/ipsec statusall | grep {conn}') + for conn in conns: + call(f'sudo /usr/sbin/ipsec statusall | grep {conn}') if __name__ == '__main__': parser = argparse.ArgumentParser() -- cgit v1.2.3