From ecdc9b1f0bf47b762669d8600aaddc5cfa5ed206 Mon Sep 17 00:00:00 2001 From: aapostoliuk Date: Thu, 16 Mar 2023 12:33:18 +0200 Subject: ipsec: T5043: Rewritten and fixed 'reset vpn' commands 1. Rewritten CLI of 'reset vpn' commands. 2. Created 'reset vpn ipsec remote-access' commands to reset RA IKEv2 session. 3. Created 'reset vpn ipsec site-to-site all' command to reset all configured IPSec site-to-site peers sessions. 4. Rewritten 'reset vpn l2t|pptp|sstp' commands to new opmode style. --- src/op_mode/ipsec.py | 81 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 10 deletions(-) (limited to 'src/op_mode/ipsec.py') diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py index 8e76f4cc0..6acde08ea 100755 --- a/src/op_mode/ipsec.py +++ b/src/op_mode/ipsec.py @@ -13,7 +13,6 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . - import re import sys import typing @@ -24,6 +23,7 @@ from tabulate import tabulate from vyos.util import convert_data from vyos.util import seconds_to_human +from vyos.configquery import ConfigTreeQuery import vyos.opmode import vyos.ipsec @@ -401,30 +401,91 @@ def _get_childsa_id_list(ike_sas: list) -> list: return list_childsa_id +def _get_all_sitetosite_peers_name_list() -> list: + """ + Return site-to-site peers configuration + :return: site-to-site peers configuration + :rtype: list + """ + conf: ConfigTreeQuery = ConfigTreeQuery() + config_path = ['vpn', 'ipsec', 'site-to-site', 'peer'] + peers_config = conf.get_config_dict(config_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + peers_list: list = [] + for name in peers_config: + peers_list.append(name) + return peers_list + + def reset_peer(peer: str, tunnel: typing.Optional[str] = None): # Convert tunnel to Strongwan format of CHILD_SA + tunnel_sw = None if tunnel: if tunnel.isnumeric(): - tunnel = f'{peer}-tunnel-{tunnel}' + tunnel_sw = f'{peer}-tunnel-{tunnel}' elif tunnel == 'vti': - tunnel = f'{peer}-vti' + tunnel_sw = f'{peer}-vti' try: - sa_list: list = vyos.ipsec.get_vici_sas_by_name(peer, tunnel) - + sa_list: list = vyos.ipsec.get_vici_sas_by_name(peer, tunnel_sw) if not sa_list: - raise vyos.opmode.IncorrectValue('Peer not found, aborting') + raise vyos.opmode.IncorrectValue( + f'Peer\'s {peer} SA(s) not found, aborting') if tunnel and sa_list: childsa_id_list: list = _get_childsa_id_list(sa_list) if not childsa_id_list: raise vyos.opmode.IncorrectValue( - 'Peer or tunnel(s) not found, aborting') - vyos.ipsec.terminate_vici_by_name(peer, tunnel) - print('Peer reset result: success') + f'Peer {peer} tunnel {tunnel} SA(s) not found, aborting') + vyos.ipsec.terminate_vici_by_name(peer, tunnel_sw) + print(f'Peer {peer} reset result: success') except (vyos.ipsec.ViciInitiateError) as err: raise vyos.opmode.UnconfiguredSubsystem(err) - except (vyos.ipsec.ViciInitiateError) as err: + except (vyos.ipsec.ViciCommandError) as err: raise vyos.opmode.IncorrectValue(err) +def reset_all_peers(): + sitetosite_list = _get_all_sitetosite_peers_name_list() + if sitetosite_list: + for peer_name in sitetosite_list: + try: + reset_peer(peer_name) + except (vyos.opmode.IncorrectValue) as err: + print(err) + print('Peers reset result: success') + else: + raise vyos.opmode.UnconfiguredSubsystem( + 'VPN IPSec site-to-site is not configured, aborting') + +def _get_ra_session_list_by_username(username: typing.Optional[str] = None): + """ + Return list of remote-access IKE_SAs uniqueids + :param username: + :type username: + :return: + :rtype: + """ + list_sa_id = [] + sa_list = vyos.ipsec.get_vici_sas() + for sa_val in sa_list: + for sa in sa_val.values(): + if 'remote-eap-id' in sa: + if username: + if username == sa['remote-eap-id'].decode(): + list_sa_id.append(sa['uniqueid'].decode()) + else: + list_sa_id.append(sa['uniqueid'].decode()) + return list_sa_id + + +def reset_ra(username: typing.Optional[str] = None): + #Reset remote-access ipsec sessions + if username: + list_sa_id = _get_ra_session_list_by_username(username) + else: + list_sa_id = _get_ra_session_list_by_username() + if list_sa_id: + vyos.ipsec.terminate_vici_ikeid_list(list_sa_id) + def show_sa(raw: bool): sa_data = _get_raw_data_sas() -- cgit v1.2.3 From a78982625a8a18069bd5a13744734873698fd0f9 Mon Sep 17 00:00:00 2001 From: aapostoliuk Date: Thu, 30 Mar 2023 18:28:56 +0300 Subject: ipsec: T5093: Fixed 'reset vpn ipsec profile' command Fixed 'reset vpn ipsec profile' command using vici library and new op-mode style. Added ability to use 'reset vpn ipsec profile' command with 'remote-host' option. --- op-mode-definitions/vpn-ipsec.xml.in | 19 +++++++-- python/vyos/ipsec.py | 38 +++++++++++++++++ src/completion/list_ipsec_profile_tunnels.py | 48 +++++++++++++++++++++ src/op_mode/ipsec.py | 62 ++++++++++++++++++++++++++++ src/op_mode/vpn_ipsec.py | 61 ++------------------------- 5 files changed, 168 insertions(+), 60 deletions(-) create mode 100644 src/completion/list_ipsec_profile_tunnels.py (limited to 'src/op_mode/ipsec.py') diff --git a/op-mode-definitions/vpn-ipsec.xml.in b/op-mode-definitions/vpn-ipsec.xml.in index 5baaec7ce..5a7e6dd63 100644 --- a/op-mode-definitions/vpn-ipsec.xml.in +++ b/op-mode-definitions/vpn-ipsec.xml.in @@ -14,7 +14,7 @@ - Reset all tunnels for given DMVPN profile + Reset a specific tunnel for given DMVPN profile vpn ipsec profile @@ -23,11 +23,24 @@ Reset a specific tunnel for given DMVPN profile + + + - sudo ${vyos_op_scripts_dir}/vpn_ipsec.py --action="reset-profile" --name="$6" --tunnel="$8" + + + + Reset a specific tunnel for given DMVPN NBMA + + <x.x.x.x> <h:h:h:h:h:h:h:h> + + + sudo ${vyos_op_scripts_dir}/ipsec.py reset_profile_dst --profile="$5" --tunnel="$7" --nbma_dst="$9" + + + sudo ${vyos_op_scripts_dir}/ipsec.py reset_profile_all --profile="$5" --tunnel="$7" - sudo ${vyos_op_scripts_dir}/vpn_ipsec.py --action="reset-profile" --name="$6" --tunnel="all" diff --git a/python/vyos/ipsec.py b/python/vyos/ipsec.py index cb7c39ff6..bb5611025 100644 --- a/python/vyos/ipsec.py +++ b/python/vyos/ipsec.py @@ -139,3 +139,41 @@ def terminate_vici_by_name(ike_name: str, child_name: str) -> None: else: raise ViciCommandError( f'Failed to terminate SA for IKE {ike_name}') + + +def vici_initiate(ike_sa_name: str, child_sa_name: str, src_addr: str, + dst_addr: str) -> bool: + """Initiate IKE SA connection with specific peer + + Args: + ike_sa_name (str): an IKE SA connection name + child_sa_name (str): a child SA profile name + src_addr (str): source address + dst_addr (str): remote address + + Returns: + bool: a result of initiation command + """ + from vici import Session as vici_session + + try: + session = vici_session() + except Exception: + raise ViciInitiateError("IPsec not initialized") + + try: + session_generator = session.initiate({ + 'ike': ike_sa_name, + 'child': child_sa_name, + 'timeout': '-1', + 'my-host': src_addr, + 'other-host': dst_addr + }) + # a dummy `for` loop is required because of requirements + # from vici. Without a full iteration on the output, the + # command to vici may not be executed completely + for _ in session_generator: + pass + return True + except Exception: + raise ViciCommandError(f'Failed to initiate SA for IKE {ike_sa_name}') \ No newline at end of file diff --git a/src/completion/list_ipsec_profile_tunnels.py b/src/completion/list_ipsec_profile_tunnels.py new file mode 100644 index 000000000..df6c52f6d --- /dev/null +++ b/src/completion/list_ipsec_profile_tunnels.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2023 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 . + +import os +import sys +import argparse + +from vyos.config import Config +from vyos.util import dict_search + +def get_tunnels_from_ipsecprofile(profile): + config = Config() + base = ['vpn', 'ipsec', 'profile', profile, 'bind'] + profile_conf = config.get_config_dict(base, effective=True, key_mangling=('-', '_')) + tunnels = [] + + try: + for tunnel in (dict_search('bind.tunnel', profile_conf) or []): + tunnels.append(tunnel) + except: + pass + + return tunnels + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("-p", "--profile", type=str, help="List tunnels per profile") + args = parser.parse_args() + + tunnels = [] + + tunnels = get_tunnels_from_ipsecprofile(args.profile) + + print(" ".join(tunnels)) + diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py index 6acde08ea..7f4fb72e5 100755 --- a/src/op_mode/ipsec.py +++ b/src/op_mode/ipsec.py @@ -13,6 +13,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + import re import sys import typing @@ -487,6 +488,67 @@ def reset_ra(username: typing.Optional[str] = None): vyos.ipsec.terminate_vici_ikeid_list(list_sa_id) +def reset_profile_dst(profile: str, tunnel: str, nbma_dst: str): + if profile and tunnel and nbma_dst: + ike_sa_name = f'dmvpn-{profile}-{tunnel}' + try: + # Get IKE SAs + sa_list = convert_data( + vyos.ipsec.get_vici_sas_by_name(ike_sa_name, None)) + if not sa_list: + raise vyos.opmode.IncorrectValue( + f'SA(s) for profile {profile} tunnel {tunnel} not found, aborting') + sa_nbma_list = list([x for x in sa_list if + ike_sa_name in x and x[ike_sa_name][ + 'remote-host'] == nbma_dst]) + if not sa_nbma_list: + raise vyos.opmode.IncorrectValue( + f'SA(s) for profile {profile} tunnel {tunnel} remote-host {nbma_dst} not found, aborting') + # terminate IKE SAs + vyos.ipsec.terminate_vici_ikeid_list(list( + [x[ike_sa_name]['uniqueid'] for x in sa_nbma_list if + ike_sa_name in x])) + # initiate IKE SAs + for ike in sa_nbma_list: + if ike_sa_name in ike: + vyos.ipsec.vici_initiate(ike_sa_name, 'dmvpn', + ike[ike_sa_name]['local-host'], + ike[ike_sa_name]['remote-host']) + print( + f'Profile {profile} tunnel {tunnel} remote-host {nbma_dst} reset result: success') + except (vyos.ipsec.ViciInitiateError) as err: + raise vyos.opmode.UnconfiguredSubsystem(err) + except (vyos.ipsec.ViciCommandError) as err: + raise vyos.opmode.IncorrectValue(err) + + +def reset_profile_all(profile: str, tunnel: str): + if profile and tunnel: + ike_sa_name = f'dmvpn-{profile}-{tunnel}' + try: + # Get IKE SAs + sa_list: list = convert_data( + vyos.ipsec.get_vici_sas_by_name(ike_sa_name, None)) + if not sa_list: + raise vyos.opmode.IncorrectValue( + f'SA(s) for profile {profile} tunnel {tunnel} not found, aborting') + # terminate IKE SAs + vyos.ipsec.terminate_vici_by_name(ike_sa_name, None) + # initiate IKE SAs + for ike in sa_list: + if ike_sa_name in ike: + vyos.ipsec.vici_initiate(ike_sa_name, 'dmvpn', + ike[ike_sa_name]['local-host'], + ike[ike_sa_name]['remote-host']) + print( + f'Profile {profile} tunnel {tunnel} remote-host {ike[ike_sa_name]["remote-host"]} reset result: success') + print(f'Profile {profile} tunnel {tunnel} reset result: success') + except (vyos.ipsec.ViciInitiateError) as err: + raise vyos.opmode.UnconfiguredSubsystem(err) + except (vyos.ipsec.ViciCommandError) as err: + raise vyos.opmode.IncorrectValue(err) + + def show_sa(raw: bool): sa_data = _get_raw_data_sas() if raw: diff --git a/src/op_mode/vpn_ipsec.py b/src/op_mode/vpn_ipsec.py index 2392cfe92..b81d1693e 100755 --- a/src/op_mode/vpn_ipsec.py +++ b/src/op_mode/vpn_ipsec.py @@ -16,12 +16,12 @@ import re import argparse -from subprocess import TimeoutExpired from vyos.util import call SWANCTL_CONF = '/etc/swanctl/swanctl.conf' + def get_peer_connections(peer, tunnel, return_all = False): search = rf'^[\s]*(peer_{peer}_(tunnel_[\d]+|vti)).*' matches = [] @@ -34,57 +34,6 @@ def get_peer_connections(peer, tunnel, return_all = False): matches.append(result[1]) return matches -def reset_peer(peer, tunnel): - if not peer: - print('Invalid peer, aborting') - return - - conns = get_peer_connections(peer, tunnel, return_all = (not tunnel or tunnel == 'all')) - - if not conns: - print('Tunnel(s) not found, aborting') - return - - result = True - for conn in conns: - try: - call(f'/usr/sbin/ipsec down {conn}{{*}}', timeout = 10) - call(f'/usr/sbin/ipsec up {conn}', timeout = 10) - except TimeoutExpired as e: - print(f'Timed out while resetting {conn}') - result = False - - - print('Peer reset result: ' + ('success' if result else 'failed')) - -def get_profile_connection(profile, tunnel = None): - search = rf'(dmvpn-{profile}-[\w]+)' if tunnel == 'all' else rf'(dmvpn-{profile}-{tunnel})' - with open(SWANCTL_CONF, 'r') as f: - for line in f.readlines(): - result = re.search(search, line) - if result: - return result[1] - return None - -def reset_profile(profile, tunnel): - if not profile: - print('Invalid profile, aborting') - return - - if not tunnel: - print('Invalid tunnel, aborting') - return - - conn = get_profile_connection(profile) - - if not conn: - print('Profile not found, aborting') - return - - call(f'/usr/sbin/ipsec down {conn}') - result = call(f'/usr/sbin/ipsec up {conn}') - - print('Profile reset result: ' + ('success' if result == 0 else 'failed')) def debug_peer(peer, tunnel): peer = peer.replace(':', '-') @@ -119,6 +68,7 @@ def debug_peer(peer, tunnel): for conn in conns: call(f'/usr/sbin/ipsec statusall | grep {conn}') + if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--action', help='Control action', required=True) @@ -127,9 +77,6 @@ if __name__ == '__main__': args = parser.parse_args() - if args.action == 'reset-peer': - reset_peer(args.name, args.tunnel) - elif args.action == "reset-profile": - reset_profile(args.name, args.tunnel) - elif args.action == "vpn-debug": + + if args.action == "vpn-debug": debug_peer(args.name, args.tunnel) -- cgit v1.2.3