From dcf89afba457fd4e4d4f764484f329c19d8ed554 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Thu, 4 Aug 2022 13:10:03 +0000 Subject: ipsec: T4594: Rewrite op-mode show vpn ipsec sa Rewrite op-mode "show vpn ipsec sa" to new format Use vyos.opmode format Ability to get raw and formatted output --- src/op_mode/ipsec.py | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) (limited to 'src') diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py index 432856585..caa1eef51 100755 --- a/src/op_mode/ipsec.py +++ b/src/op_mode/ipsec.py @@ -16,13 +16,122 @@ import re import sys + +from collections import OrderedDict +from hurry import filesize +from re import split as re_split +from tabulate import tabulate + from vyos.util import call +from vyos.util import convert_data +from vyos.util import seconds_to_human + import vyos.opmode SWANCTL_CONF = '/etc/swanctl/swanctl.conf' +def _convert(text): + return int(text) if text.isdigit() else text.lower() + + +def _alphanum_key(key): + return [_convert(c) for c in re_split('([0-9]+)', str(key))] + + +def _get_vici_sas(): + from vici import Session as vici_session + + session = vici_session() + sas = list(session.list_sas()) + return sas + + +def _get_raw_data_sas(): + get_sas = _get_vici_sas() + sas = convert_data(get_sas) + return sas + + +def _get_formatted_output_sas(sas): + sa_data = [] + for sa in sas: + for parent_sa in sa.values(): + # create an item for each child-sa + for child_sa in parent_sa.get('child-sas', {}).values(): + # prepare a list for output data + sa_out_name = sa_out_state = sa_out_uptime = sa_out_bytes = sa_out_packets = sa_out_remote_addr = sa_out_remote_id = sa_out_proposal = 'N/A' + + # collect raw data + sa_name = child_sa.get('name') + sa_state = child_sa.get('state') + sa_uptime = child_sa.get('install-time') + sa_bytes_in = child_sa.get('bytes-in') + sa_bytes_out = child_sa.get('bytes-out') + sa_packets_in = child_sa.get('packets-in') + sa_packets_out = child_sa.get('packets-out') + sa_remote_addr = parent_sa.get('remote-host') + sa_remote_id = parent_sa.get('remote-id') + sa_proposal_encr_alg = child_sa.get('encr-alg') + sa_proposal_integ_alg = child_sa.get('integ-alg') + sa_proposal_encr_keysize = child_sa.get('encr-keysize') + sa_proposal_dh_group = child_sa.get('dh-group') + + # format data to display + if sa_name: + sa_out_name = sa_name + if sa_state: + if sa_state == 'INSTALLED': + sa_out_state = 'up' + else: + sa_out_state = 'down' + if sa_uptime: + sa_out_uptime = seconds_to_human(sa_uptime) + if sa_bytes_in and sa_bytes_out: + bytes_in = filesize.size(int(sa_bytes_in)) + bytes_out = filesize.size(int(sa_bytes_out)) + sa_out_bytes = f'{bytes_in}/{bytes_out}' + if sa_packets_in and sa_packets_out: + packets_in = filesize.size(int(sa_packets_in), + system=filesize.si) + packets_out = filesize.size(int(sa_packets_out), + system=filesize.si) + packets_str = f'{packets_in}/{packets_out}' + sa_out_packets = re.sub(r'B', r'', packets_str) + if sa_remote_addr: + sa_out_remote_addr = sa_remote_addr + if sa_remote_id: + sa_out_remote_id = sa_remote_id + # format proposal + if sa_proposal_encr_alg: + sa_out_proposal = sa_proposal_encr_alg + if sa_proposal_encr_keysize: + sa_proposal_encr_keysize_str = sa_proposal_encr_keysize + sa_out_proposal = f'{sa_out_proposal}_{sa_proposal_encr_keysize_str}' + if sa_proposal_integ_alg: + sa_proposal_integ_alg_str = sa_proposal_integ_alg + sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_integ_alg_str}' + if sa_proposal_dh_group: + sa_proposal_dh_group_str = sa_proposal_dh_group + sa_out_proposal = f'{sa_out_proposal}/{sa_proposal_dh_group_str}' + + # add a new item to output data + sa_data.append([ + sa_out_name, sa_out_state, sa_out_uptime, sa_out_bytes, + sa_out_packets, sa_out_remote_addr, sa_out_remote_id, + sa_out_proposal + ]) + + headers = [ + "Connection", "State", "Uptime", "Bytes In/Out", "Packets In/Out", + "Remote address", "Remote ID", "Proposal" + ] + sa_data = sorted(sa_data, key=_alphanum_key) + output = tabulate(sa_data, headers) + return output + + def get_peer_connections(peer, tunnel, return_all = False): peer = peer.replace(':', '-') search = rf'^[\s]*(peer_{peer}_(tunnel_[\d]+|vti)).*' @@ -61,6 +170,13 @@ def reset_peer(peer: str, tunnel:str): print('Peer reset result: ' + ('success' if result else 'failed')) +def show_sa(raw: bool): + sa_data = _get_raw_data_sas() + if raw: + return sa_data + return _get_formatted_output_sas(sa_data) + + if __name__ == '__main__': try: res = vyos.opmode.run(sys.modules[__name__]) -- cgit v1.2.3 From 1bd3a9635a5ff703f5623743a487e0effb846c41 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Sat, 6 Aug 2022 10:09:09 +0000 Subject: ocserv: T4596: Rewrite show openconnect sessions op-mode Rewrite "show openconnect-server sessions" to vyos.opmode format Ability to get raw and formatted output Ability to get data via API --- data/op-mode-standardized.json | 1 + op-mode-definitions/openconnect.xml.in | 2 +- src/op_mode/openconnect-control.py | 5 --- src/op_mode/openconnect.py | 81 ++++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 6 deletions(-) create mode 100755 src/op_mode/openconnect.py (limited to 'src') diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json index b5e9308c5..5f49be781 100644 --- a/data/op-mode-standardized.json +++ b/data/op-mode-standardized.json @@ -6,6 +6,7 @@ "memory.py", "nat.py", "neighbor.py", +"openconnect.py", "route.py", "version.py", "vrf.py" diff --git a/op-mode-definitions/openconnect.xml.in b/op-mode-definitions/openconnect.xml.in index 9343637c0..88e1f9f15 100644 --- a/op-mode-definitions/openconnect.xml.in +++ b/op-mode-definitions/openconnect.xml.in @@ -11,7 +11,7 @@ Show active OpenConnect server sessions - ${vyos_op_scripts_dir}/openconnect-control.py --action="show_sessions" + ${vyos_op_scripts_dir}/openconnect.py show_sessions diff --git a/src/op_mode/openconnect-control.py b/src/op_mode/openconnect-control.py index a128cc011..20c50e779 100755 --- a/src/op_mode/openconnect-control.py +++ b/src/op_mode/openconnect-control.py @@ -19,7 +19,6 @@ import argparse import json from vyos.config import Config -from vyos.util import commit_in_progress from vyos.util import popen from vyos.util import run from vyos.util import DEVNULL @@ -60,10 +59,6 @@ def main(): # Check is Openconnect server configured is_ocserv_configured() - if commit_in_progress(): - print('Cannot restart openconnect while a commit is in progress') - exit(1) - if args.action == "restart": run("sudo systemctl restart ocserv.service") sys.exit(0) diff --git a/src/op_mode/openconnect.py b/src/op_mode/openconnect.py new file mode 100755 index 000000000..00992c66a --- /dev/null +++ b/src/op_mode/openconnect.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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 sys +import json + +from tabulate import tabulate +from vyos.configquery import ConfigTreeQuery +from vyos.util import rc_cmd + +import vyos.opmode + + +occtl = '/usr/bin/occtl' +occtl_socket = '/run/ocserv/occtl.socket' + + +def _get_raw_data_sessions(): + rc, out = rc_cmd(f'sudo {occtl} --json --socket-file {occtl_socket} show users') + if rc != 0: + output = {'openconnect': + { + 'configured': False, + 'return_code': rc, + 'reason': out + } + } + return output + + sessions = json.loads(out) + return sessions + + +def _get_formatted_sessions(data): + headers = ["Interface", "Username", "IP", "Remote IP", "RX", "TX", "State", "Uptime"] + ses_list = [] + for ses in data: + ses_list.append([ + ses["Device"], ses["Username"], ses["IPv4"], ses["Remote IP"], + ses["_RX"], ses["_TX"], ses["State"], ses["_Connected at"] + ]) + if len(ses_list) > 0: + output = tabulate(ses_list, headers) + else: + output = 'No active openconnect sessions' + return output + + +def show_sessions(raw: bool): + config = ConfigTreeQuery() + if not config.exists('vpn openconnect') and not raw: + print('Openconnect is not configured') + exit(0) + + openconnect_data = _get_raw_data_sessions() + if raw: + return openconnect_data + return _get_formatted_sessions(openconnect_data) + + +if __name__ == '__main__': + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) -- cgit v1.2.3 From 160262edaf1b6b6405baa5c65f69573225cbe208 Mon Sep 17 00:00:00 2001 From: mkorobeinikov <92354771+mkorobeinikov@users.noreply.github.com> Date: Tue, 16 Aug 2022 10:02:46 +1000 Subject: dhcp-relay: T4601: restart dhcp relay-agent The command "restart dhcp relay-agent" doesn't restart "isc-dhcp-relay" service. --- src/op_mode/restart_dhcp_relay.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/op_mode/restart_dhcp_relay.py b/src/op_mode/restart_dhcp_relay.py index db5a48970..9203c009f 100755 --- a/src/op_mode/restart_dhcp_relay.py +++ b/src/op_mode/restart_dhcp_relay.py @@ -43,7 +43,7 @@ if __name__ == '__main__': if commit_in_progress(): print('Cannot restart DHCP relay while a commit is in progress') exit(1) - call('systemctl restart isc-dhcp-server.service') + call('systemctl restart isc-dhcp-relay.service') sys.exit(0) elif args.ipv6: @@ -54,7 +54,7 @@ if __name__ == '__main__': if commit_in_progress(): print('Cannot restart DHCPv6 relay while commit is in progress') exit(1) - call('systemctl restart isc-dhcp-server6.service') + call('systemctl restart isc-dhcp-relay6.service') sys.exit(0) else: -- cgit v1.2.3 From 9c9e7618cdc5c293c41cad1a3e4666f98298870f Mon Sep 17 00:00:00 2001 From: DaniilHarun Date: Tue, 16 Aug 2022 15:38:57 +0300 Subject: T4619: Replacing instead of adding a static arp entry --- src/conf_mode/arp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/conf_mode/arp.py b/src/conf_mode/arp.py index 1cd8f5451..7dc5206e0 100755 --- a/src/conf_mode/arp.py +++ b/src/conf_mode/arp.py @@ -61,7 +61,7 @@ def apply(arp): continue for address, address_config in interface_config['address'].items(): mac = address_config['mac'] - call(f'ip neigh add {address} lladdr {mac} dev {interface}') + call(f'ip neigh replace {address} lladdr {mac} dev {interface}') if __name__ == '__main__': try: -- cgit v1.2.3 From d69b7989620da1348fe187975dc5a1c467400354 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Tue, 16 Aug 2022 14:55:10 +0000 Subject: upnp: T4613: Verify listen key in dictionary There is no check if 'listen' is exist in the dictionary, fix it Fix odd ValueHelp format --- interface-definitions/service-upnp.xml.in | 12 ++++++------ src/conf_mode/service_upnp.py | 19 +++++++++++++------ 2 files changed, 19 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/interface-definitions/service-upnp.xml.in b/interface-definitions/service-upnp.xml.in index a129b7260..b1e6f170a 100644 --- a/interface-definitions/service-upnp.xml.in +++ b/interface-definitions/service-upnp.xml.in @@ -103,19 +103,19 @@ ipv4 - IP address to listen for incoming connections + IPv4 address to listen for incoming connections - ipv4-prefix - IP prefix to listen for incoming connections + ipv4net + IPv4 prefix to listen for incoming connections ipv6 - IP address to listen for incoming connections + IPv6 address to listen for incoming connections - ipv6-prefix - IP prefix to listen for incoming connections + ipv6net + IPv6 prefix to listen for incoming connections diff --git a/src/conf_mode/service_upnp.py b/src/conf_mode/service_upnp.py index 36f3e18a7..c798fd515 100755 --- a/src/conf_mode/service_upnp.py +++ b/src/conf_mode/service_upnp.py @@ -24,8 +24,6 @@ from ipaddress import IPv6Network from vyos.config import Config from vyos.configdict import dict_merge -from vyos.configdict import get_interface_dict -from vyos.configverify import verify_vrf from vyos.util import call from vyos.template import render from vyos.template import is_ipv4 @@ -113,19 +111,28 @@ def verify(upnpd): listen_dev = [] system_addrs_cidr = get_all_interface_addr(True, [], [netifaces.AF_INET, netifaces.AF_INET6]) system_addrs = get_all_interface_addr(False, [], [netifaces.AF_INET, netifaces.AF_INET6]) + if 'listen' not in upnpd: + raise ConfigError(f'Listen address or interface is required!') for listen_if_or_addr in upnpd['listen']: if listen_if_or_addr not in netifaces.interfaces(): listen_dev.append(listen_if_or_addr) - if (listen_if_or_addr not in system_addrs) and (listen_if_or_addr not in system_addrs_cidr) and (listen_if_or_addr not in netifaces.interfaces()): + if (listen_if_or_addr not in system_addrs) and (listen_if_or_addr not in system_addrs_cidr) and \ + (listen_if_or_addr not in netifaces.interfaces()): if is_ipv4(listen_if_or_addr) and IPv4Network(listen_if_or_addr).is_multicast: - raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed to listen on. It is not an interface address nor a multicast address!') + raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed' + f'to listen on. It is not an interface address nor a multicast address!') if is_ipv6(listen_if_or_addr) and IPv6Network(listen_if_or_addr).is_multicast: - raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed to listen on. It is not an interface address nor a multicast address!') + raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed' + f'to listen on. It is not an interface address nor a multicast address!') system_listening_dev_addrs_cidr = get_all_interface_addr(True, listen_dev, [netifaces.AF_INET6]) system_listening_dev_addrs = get_all_interface_addr(False, listen_dev, [netifaces.AF_INET6]) for listen_if_or_addr in upnpd['listen']: - if listen_if_or_addr not in netifaces.interfaces() and (listen_if_or_addr not in system_listening_dev_addrs_cidr) and (listen_if_or_addr not in system_listening_dev_addrs) and is_ipv6(listen_if_or_addr) and (not IPv6Network(listen_if_or_addr).is_multicast): + if listen_if_or_addr not in netifaces.interfaces() and \ + (listen_if_or_addr not in system_listening_dev_addrs_cidr) and \ + (listen_if_or_addr not in system_listening_dev_addrs) and \ + is_ipv6(listen_if_or_addr) and \ + (not IPv6Network(listen_if_or_addr).is_multicast): raise ConfigError(f'{listen_if_or_addr} must listen on the interface of the network card') def generate(upnpd): -- cgit v1.2.3 From f92a23ef9ab8be59681e5b7ba627e399d89bce53 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 19 Aug 2022 18:55:24 +0200 Subject: ethernet: T4538: fix wrong systemd unit used for EAPoL When MACsec was bound to an ethernet interface and the underlaying source-interface got changed (even description only) this terminated the MACsec session running on top of it. The root cause is when EAPoL was implemented in commit d59354e52a8a7f we re-used the same systemd unit which is responsible for MACsec. That indeed lead to the fact that wpa_supplicant was always stopped when anything happened on the underlaying source-interface that was not related to EAPoL. --- src/conf_mode/interfaces-ethernet.py | 23 +++++++++++++--------- .../wpa_supplicant-wired@.service.d/override.conf | 11 +++++++++++ 2 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 src/etc/systemd/system/wpa_supplicant-wired@.service.d/override.conf (limited to 'src') diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 30e7a2af7..e02841831 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -153,11 +153,20 @@ def verify(ethernet): return None def generate(ethernet): - if 'eapol' in ethernet: - render(wpa_suppl_conf.format(**ethernet), - 'ethernet/wpa_supplicant.conf.j2', ethernet) + # render real configuration file once + wpa_supplicant_conf = wpa_suppl_conf.format(**ethernet) + + if 'deleted' in ethernet: + # delete configuration on interface removal + if os.path.isfile(wpa_supplicant_conf): + os.unlink(wpa_supplicant_conf) + return None + if 'eapol' in ethernet: ifname = ethernet['ifname'] + + render(wpa_supplicant_conf, 'ethernet/wpa_supplicant.conf.j2', ethernet) + cert_file_path = os.path.join(cfg_dir, f'{ifname}_cert.pem') cert_key_path = os.path.join(cfg_dir, f'{ifname}_cert.key') @@ -184,10 +193,6 @@ def generate(ethernet): write_file(ca_cert_file_path, '\n'.join(encode_certificate(c) for c in ca_full_chain)) - else: - # delete configuration on interface removal - if os.path.isfile(wpa_suppl_conf.format(**ethernet)): - os.unlink(wpa_suppl_conf.format(**ethernet)) return None @@ -203,9 +208,9 @@ def apply(ethernet): else: e.update(ethernet) if 'eapol' in ethernet: - eapol_action='restart' + eapol_action='reload-or-restart' - call(f'systemctl {eapol_action} wpa_supplicant-macsec@{ifname}') + call(f'systemctl {eapol_action} wpa_supplicant-wired@{ifname}') if __name__ == '__main__': try: diff --git a/src/etc/systemd/system/wpa_supplicant-wired@.service.d/override.conf b/src/etc/systemd/system/wpa_supplicant-wired@.service.d/override.conf new file mode 100644 index 000000000..030b89a2b --- /dev/null +++ b/src/etc/systemd/system/wpa_supplicant-wired@.service.d/override.conf @@ -0,0 +1,11 @@ +[Unit] +After= +After=vyos-router.service + +[Service] +WorkingDirectory= +WorkingDirectory=/run/wpa_supplicant +PIDFile=/run/wpa_supplicant/%I.pid +ExecStart= +ExecStart=/sbin/wpa_supplicant -c/run/wpa_supplicant/%I.conf -Dwired -P/run/wpa_supplicant/%I.pid -i%I +ExecReload=/bin/kill -HUP $MAINPID -- cgit v1.2.3 From c0f5d00d92667f2a45896180cd05747c3ba82782 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Sat, 20 Aug 2022 13:48:30 +0000 Subject: ocserv: T4597: Fix check bounded port by service itself We check listen port before commit service if is port available and not bounded, but when we start openconnect our own port starts be bounded by "ocserv-main" process and next commit will be fail as port is already bound To fix it, extend check if port already bonded and it is not our self process "ocserv-main" --- python/vyos/util.py | 23 +++++++++++++++++++++++ src/conf_mode/vpn_openconnect.py | 5 ++++- 2 files changed, 27 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/python/vyos/util.py b/python/vyos/util.py index b86b1949c..c1459f02a 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -471,6 +471,29 @@ def process_named_running(name): return p.pid return None +def is_listen_port_bind_service(port: int, service: str) -> bool: + """Check if listen port bound to expected program name + :param port: Bind port + :param service: Program name + :return: bool + + Example: + % is_listen_port_bind_service(443, 'nginx') + True + % is_listen_port_bind_service(443, 'ocservr-main') + False + """ + from psutil import net_connections as connections + from psutil import Process as process + for connection in connections(): + addr = connection.laddr + pid = connection.pid + pid_name = process(pid).name() + pid_port = addr.port + if service == pid_name and port == pid_port: + return True + return False + def seconds_to_human(s, separator=""): """ Converts number of seconds passed to a human-readable interval such as 1w4d18h35m59s diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py index a3e774678..240546817 100755 --- a/src/conf_mode/vpn_openconnect.py +++ b/src/conf_mode/vpn_openconnect.py @@ -25,6 +25,7 @@ from vyos.template import render from vyos.util import call from vyos.util import check_port_availability from vyos.util import is_systemd_service_running +from vyos.util import is_listen_port_bind_service from vyos.util import dict_search from vyos.xml import defaults from vyos import ConfigError @@ -77,8 +78,10 @@ def verify(ocserv): if ocserv is None: return None # Check if listen-ports not binded other services + # It can be only listen by 'ocserv-main' for proto, port in ocserv.get('listen_ports').items(): - if check_port_availability('0.0.0.0', int(port), proto) is not True: + if check_port_availability('0.0.0.0', int(port), proto) is not True and \ + not is_listen_port_bind_service(int(port), 'ocserv-main'): raise ConfigError(f'"{proto}" port "{port}" is used by another service') # Check authentication if "authentication" in ocserv: -- cgit v1.2.3 From 8eede91cd25242370543c8af139b716e46fcbd41 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 22 Aug 2022 10:34:31 -0500 Subject: graphql: T3993: add missing sys.exit() --- src/services/vyos-http-api-server | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index af8837e1e..190f3409d 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -678,6 +678,7 @@ if __name__ == '__main__': server_config = load_server_config() except Exception as err: logger.critical(f"Failed to load the HTTP API server config: {err}") + sys.exit(1) config_session = ConfigSession(os.getpid()) -- cgit v1.2.3 From bf178babd96ee5b898f0dfa1f6e7d5a74fe34afd Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 22 Aug 2022 10:35:52 -0500 Subject: graphql: T4544: fix for directly running on system for testing --- src/services/api/graphql/utils/schema_from_op_mode.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/services/api/graphql/utils/schema_from_op_mode.py b/src/services/api/graphql/utils/schema_from_op_mode.py index d27586747..f990aae52 100755 --- a/src/services/api/graphql/utils/schema_from_op_mode.py +++ b/src/services/api/graphql/utils/schema_from_op_mode.py @@ -25,7 +25,10 @@ from inspect import signature, getmembers, isfunction from jinja2 import Template from vyos.defaults import directories -from . util import load_as_module, is_op_mode_function_name, is_show_function_name +if __package__ is None or __package__ == '': + from util import load_as_module, is_op_mode_function_name, is_show_function_name +else: + from . util import load_as_module, is_op_mode_function_name, is_show_function_name OP_MODE_PATH = directories['op_mode'] SCHEMA_PATH = directories['api_schema'] -- cgit v1.2.3 From ed5fb0645367e4bd099fe12decbb515af1e6dcc6 Mon Sep 17 00:00:00 2001 From: Sander Klein Date: Mon, 22 Aug 2022 21:47:30 +0200 Subject: keepalived: T4526: keepalived-fifo.py unable to load config keepalived-fifo.py cannot load the VyOS config because the script is started before the commit is completely finished. This change makes sure the script waits for the commit to be completed. It retries every 0.5 seconds. If the commit is still not completed it will continue as did the original implementation. --- src/system/keepalived-fifo.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'src') diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py index a8df232ae..a0fccd1d0 100755 --- a/src/system/keepalived-fifo.py +++ b/src/system/keepalived-fifo.py @@ -30,6 +30,7 @@ from vyos.ifconfig.vrrp import VRRP from vyos.configquery import ConfigTreeQuery from vyos.util import cmd from vyos.util import dict_search +from vyos.util import commit_in_progress # configure logging logger = logging.getLogger(__name__) @@ -63,6 +64,17 @@ class KeepalivedFifo: # load configuration def _config_load(self): + # For VRRP configuration to be read, the commit must be finished + count = 1 + while commit_in_progress(): + if ( count <= 40 ): + logger.debug(f'commit in progress try: {count}') + else: + logger.error(f'commit still in progress after {count} continuing anyway') + break + count += 1 + time.sleep(0.5) + try: base = ['high-availability', 'vrrp'] conf = ConfigTreeQuery() -- cgit v1.2.3 From ecaafaa26f85ba4ae3f34b5382fe0ebbe38bf13b Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Tue, 23 Aug 2022 09:21:29 +0000 Subject: https: T4597: Verify bind port before apply HTTPS API service If Nginx address/port is already binded to another service (for exampmle openconnect default port 443) https api cannot start and we don't see any error in the output. Add this check before applying service/commit --- src/conf_mode/https.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index 3057357fc..7cd7ea42e 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2021 VyOS maintainers and contributors +# Copyright (C) 2019-2022 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 @@ -29,6 +29,8 @@ from vyos.pki import wrap_certificate from vyos.pki import wrap_private_key from vyos.template import render from vyos.util import call +from vyos.util import check_port_availability +from vyos.util import is_listen_port_bind_service from vyos.util import write_file from vyos import airbag @@ -107,6 +109,31 @@ def verify(https): raise ConfigError("At least one 'virtual-host server-name' " "matching the 'certbot domain-name' is required.") + server_block_list = [] + + # organize by vhosts + vhost_dict = https.get('virtual-host', {}) + + if not vhost_dict: + # no specified virtual hosts (server blocks); use default + server_block_list.append(default_server_block) + else: + for vhost in list(vhost_dict): + server_block = deepcopy(default_server_block) + data = vhost_dict.get(vhost, {}) + server_block['address'] = data.get('listen-address', '*') + server_block['port'] = data.get('listen-port', '443') + server_block_list.append(server_block) + + for entry in server_block_list: + _address = entry.get('address') + _address = '0.0.0.0' if _address == '*' else _address + _port = entry.get('port') + proto = 'tcp' + if check_port_availability(_address, int(_port), proto) is not True and \ + not is_listen_port_bind_service(int(_port), 'nginx'): + raise ConfigError(f'"{proto}" port "{_port}" is used by another service') + verify_vrf(https) return None -- cgit v1.2.3 From 9b3cdfb96af98d479cb804c69baa68c1ad50b48f Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Tue, 23 Aug 2022 11:33:20 +0000 Subject: conntrack: T4623: Add conntrack statistics for op-mode --- op-mode-definitions/show-conntrack.xml.in | 6 ++++++ src/op_mode/conntrack.py | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) (limited to 'src') diff --git a/op-mode-definitions/show-conntrack.xml.in b/op-mode-definitions/show-conntrack.xml.in index 8d921e6a5..4cdcffcdb 100644 --- a/op-mode-definitions/show-conntrack.xml.in +++ b/op-mode-definitions/show-conntrack.xml.in @@ -7,6 +7,12 @@ Show conntrack tables entries + + + Show conntrack statistics + + sudo ${vyos_op_scripts_dir}/conntrack.py show_statistics + Show conntrack entries for table diff --git a/src/op_mode/conntrack.py b/src/op_mode/conntrack.py index 036226418..b27aa6060 100755 --- a/src/op_mode/conntrack.py +++ b/src/op_mode/conntrack.py @@ -51,6 +51,21 @@ def _get_raw_data(family): return _xml_to_dict(xml) +def _get_raw_statistics(): + entries = [] + data = cmd('sudo conntrack -S') + data = data.replace(' \t', '').split('\n') + for entry in data: + entries.append(entry.split()) + return entries + + +def get_formatted_statistics(entries): + headers = ["CPU", "Found", "Invalid", "Insert", "Insert fail", "Drop", "Early drop", "Errors", "Search restart"] + output = tabulate(entries, headers, numalign="left") + return output + + def get_formatted_output(dict_data): """ :param xml: @@ -111,6 +126,14 @@ def show(raw: bool, family: str): return get_formatted_output(conntrack_data) +def show_statistics(raw: bool): + conntrack_statistics = _get_raw_statistics() + if raw: + return conntrack_statistics + else: + return get_formatted_statistics(conntrack_statistics) + + if __name__ == '__main__': try: res = vyos.opmode.run(sys.modules[__name__]) -- cgit v1.2.3 From f66ad001e153ee42bc46edbe7df55145b7971544 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 22 Aug 2022 12:03:56 -0500 Subject: graphql: T3993: reorganize/rename directory structure --- python/vyos/defaults.py | 2 +- src/services/api/graphql/graphql/mutations.py | 4 +- src/services/api/graphql/graphql/queries.py | 4 +- src/services/api/graphql/recipes/__init__.py | 0 .../api/graphql/recipes/queries/system_status.py | 38 ---- .../remove_firewall_address_group_members.py | 35 ---- src/services/api/graphql/recipes/session.py | 207 --------------------- .../recipes/templates/create_dhcp_server.tmpl | 9 - .../templates/create_firewall_address_group.tmpl | 4 - .../create_firewall_address_ipv_6_group.tmpl | 4 - .../templates/create_interface_ethernet.tmpl | 5 - .../remove_firewall_address_group_members.tmpl | 3 - ...emove_firewall_address_ipv_6_group_members.tmpl | 3 - .../update_firewall_address_group_members.tmpl | 3 - ...pdate_firewall_address_ipv_6_group_members.tmpl | 3 - src/services/api/graphql/session/__init__.py | 0 .../api/graphql/session/composite/system_status.py | 38 ++++ .../remove_firewall_address_group_members.py | 35 ++++ src/services/api/graphql/session/session.py | 207 +++++++++++++++++++++ .../session/templates/create_dhcp_server.tmpl | 9 + .../templates/create_firewall_address_group.tmpl | 4 + .../create_firewall_address_ipv_6_group.tmpl | 4 + .../templates/create_interface_ethernet.tmpl | 5 + .../remove_firewall_address_group_members.tmpl | 3 + ...emove_firewall_address_ipv_6_group_members.tmpl | 3 + .../update_firewall_address_group_members.tmpl | 3 + ...pdate_firewall_address_ipv_6_group_members.tmpl | 3 + 27 files changed, 319 insertions(+), 319 deletions(-) delete mode 100644 src/services/api/graphql/recipes/__init__.py delete mode 100755 src/services/api/graphql/recipes/queries/system_status.py delete mode 100644 src/services/api/graphql/recipes/remove_firewall_address_group_members.py delete mode 100644 src/services/api/graphql/recipes/session.py delete mode 100644 src/services/api/graphql/recipes/templates/create_dhcp_server.tmpl delete mode 100644 src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl delete mode 100644 src/services/api/graphql/recipes/templates/create_firewall_address_ipv_6_group.tmpl delete mode 100644 src/services/api/graphql/recipes/templates/create_interface_ethernet.tmpl delete mode 100644 src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl delete mode 100644 src/services/api/graphql/recipes/templates/remove_firewall_address_ipv_6_group_members.tmpl delete mode 100644 src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl delete mode 100644 src/services/api/graphql/recipes/templates/update_firewall_address_ipv_6_group_members.tmpl create mode 100644 src/services/api/graphql/session/__init__.py create mode 100755 src/services/api/graphql/session/composite/system_status.py create mode 100644 src/services/api/graphql/session/override/remove_firewall_address_group_members.py create mode 100644 src/services/api/graphql/session/session.py create mode 100644 src/services/api/graphql/session/templates/create_dhcp_server.tmpl create mode 100644 src/services/api/graphql/session/templates/create_firewall_address_group.tmpl create mode 100644 src/services/api/graphql/session/templates/create_firewall_address_ipv_6_group.tmpl create mode 100644 src/services/api/graphql/session/templates/create_interface_ethernet.tmpl create mode 100644 src/services/api/graphql/session/templates/remove_firewall_address_group_members.tmpl create mode 100644 src/services/api/graphql/session/templates/remove_firewall_address_ipv_6_group_members.tmpl create mode 100644 src/services/api/graphql/session/templates/update_firewall_address_group_members.tmpl create mode 100644 src/services/api/graphql/session/templates/update_firewall_address_ipv_6_group_members.tmpl (limited to 'src') diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index 09ae73eac..6894fc4da 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -26,7 +26,7 @@ directories = { "templates": "/usr/share/vyos/templates/", "certbot": "/config/auth/letsencrypt", "api_schema": "/usr/libexec/vyos/services/api/graphql/graphql/schema/", - "api_templates": "/usr/libexec/vyos/services/api/graphql/recipes/templates/", + "api_templates": "/usr/libexec/vyos/services/api/graphql/session/templates/", "vyos_udev_dir": "/run/udev/vyos" } diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py index 3e89fb239..c8ae0f516 100644 --- a/src/services/api/graphql/graphql/mutations.py +++ b/src/services/api/graphql/graphql/mutations.py @@ -21,7 +21,7 @@ from makefun import with_signature from .. import state from .. import key_auth -from api.graphql.recipes.session import Session +from api.graphql.session.session import Session mutation = ObjectType("Mutation") @@ -71,7 +71,7 @@ def make_mutation_resolver(mutation_name, class_name, session_func): # one may override the session functions with a local subclass try: - mod = import_module(f'api.graphql.recipes.{func_base_name}') + mod = import_module(f'api.graphql.session.override.{func_base_name}') klass = getattr(mod, class_name) except ImportError: # otherwise, dynamically generate subclass to invoke subclass diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py index f6544709e..921a66274 100644 --- a/src/services/api/graphql/graphql/queries.py +++ b/src/services/api/graphql/graphql/queries.py @@ -21,7 +21,7 @@ from makefun import with_signature from .. import state from .. import key_auth -from api.graphql.recipes.session import Session +from api.graphql.session.session import Session query = ObjectType("Query") @@ -71,7 +71,7 @@ def make_query_resolver(query_name, class_name, session_func): # one may override the session functions with a local subclass try: - mod = import_module(f'api.graphql.recipes.{func_base_name}') + mod = import_module(f'api.graphql.session.override.{func_base_name}') klass = getattr(mod, class_name) except ImportError: # otherwise, dynamically generate subclass to invoke subclass diff --git a/src/services/api/graphql/recipes/__init__.py b/src/services/api/graphql/recipes/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/services/api/graphql/recipes/queries/system_status.py b/src/services/api/graphql/recipes/queries/system_status.py deleted file mode 100755 index 8dadcc9f3..000000000 --- a/src/services/api/graphql/recipes/queries/system_status.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2022 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 json -import importlib.util - -from vyos.defaults import directories - -from api.graphql.utils.util import load_op_mode_as_module - -def get_system_version() -> dict: - show_version = load_op_mode_as_module('version.py') - return show_version.show(raw=True, funny=False) - -def get_system_uptime() -> dict: - show_uptime = load_op_mode_as_module('show_uptime.py') - return show_uptime.get_raw_data() - -def get_system_ram_usage() -> dict: - show_ram = load_op_mode_as_module('memory.py') - return show_ram.show(raw=True) diff --git a/src/services/api/graphql/recipes/remove_firewall_address_group_members.py b/src/services/api/graphql/recipes/remove_firewall_address_group_members.py deleted file mode 100644 index b91932e14..000000000 --- a/src/services/api/graphql/recipes/remove_firewall_address_group_members.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2021 VyOS maintainers and contributors -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library 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 -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this library. If not, see . - -from . session import Session - -class RemoveFirewallAddressGroupMembers(Session): - def __init__(self, session, data): - super().__init__(session, data) - - # Define any custom processing of parameters here by overriding - # configure: - # - # def configure(self): - # self._data = transform_data(self._data) - # super().configure() - # self.clean_up() - - def configure(self): - super().configure() - - group_name = self._data['name'] - path = ['firewall', 'group', 'address-group', group_name] - self.delete_path_if_childless(path) diff --git a/src/services/api/graphql/recipes/session.py b/src/services/api/graphql/recipes/session.py deleted file mode 100644 index ac185beb7..000000000 --- a/src/services/api/graphql/recipes/session.py +++ /dev/null @@ -1,207 +0,0 @@ -# Copyright 2021-2022 VyOS maintainers and contributors -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library 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 -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this library. If not, see . - -import os -import json - -from ariadne import convert_camel_case_to_snake - -from vyos.config import Config -from vyos.configtree import ConfigTree -from vyos.defaults import directories -from vyos.template import render - -from api.graphql.utils.util import load_op_mode_as_module, split_compound_op_mode_name - -op_mode_include_file = os.path.join(directories['data'], 'op-mode-standardized.json') - -class Session: - """ - Wrapper for calling configsession functions based on GraphQL requests. - Non-nullable fields in the respective schema allow avoiding a key check - in 'data'. - """ - def __init__(self, session, data): - self._session = session - self._data = data - self._name = convert_camel_case_to_snake(type(self).__name__) - - try: - with open(op_mode_include_file) as f: - self._op_mode_list = json.loads(f.read()) - except Exception: - self._op_mode_list = None - - def configure(self): - session = self._session - data = self._data - func_base_name = self._name - - tmpl_file = f'{func_base_name}.tmpl' - cmd_file = f'/tmp/{func_base_name}.cmds' - tmpl_dir = directories['api_templates'] - - try: - render(cmd_file, tmpl_file, data, location=tmpl_dir) - commands = [] - with open(cmd_file) as f: - lines = f.readlines() - for line in lines: - commands.append(line.split()) - for cmd in commands: - if cmd[0] == 'set': - session.set(cmd[1:]) - elif cmd[0] == 'delete': - session.delete(cmd[1:]) - else: - raise ValueError('Operation must be "set" or "delete"') - session.commit() - except Exception as error: - raise error - - def delete_path_if_childless(self, path): - session = self._session - config = Config(session.get_session_env()) - if not config.list_nodes(path): - session.delete(path) - session.commit() - - def show_config(self): - session = self._session - data = self._data - out = '' - - try: - out = session.show_config(data['path']) - if data.get('config_format', '') == 'json': - config_tree = vyos.configtree.ConfigTree(out) - out = json.loads(config_tree.to_json()) - except Exception as error: - raise error - - return out - - def save(self): - session = self._session - data = self._data - if 'file_name' not in data or not data['file_name']: - data['file_name'] = '/config/config.boot' - - try: - session.save_config(data['file_name']) - except Exception as error: - raise error - - def load(self): - session = self._session - data = self._data - - try: - session.load_config(data['file_name']) - session.commit() - except Exception as error: - raise error - - def show(self): - session = self._session - data = self._data - out = '' - - try: - out = session.show(data['path']) - except Exception as error: - raise error - - return out - - def add(self): - session = self._session - data = self._data - - try: - res = session.install_image(data['location']) - except Exception as error: - raise error - - return res - - def delete(self): - session = self._session - data = self._data - - try: - res = session.remove_image(data['name']) - except Exception as error: - raise error - - return res - - def system_status(self): - import api.graphql.recipes.queries.system_status as system_status - - session = self._session - data = self._data - - status = {} - status['host_name'] = session.show(['host', 'name']).strip() - status['version'] = system_status.get_system_version() - status['uptime'] = system_status.get_system_uptime() - status['ram'] = system_status.get_system_ram_usage() - - return status - - def gen_op_query(self): - session = self._session - data = self._data - name = self._name - op_mode_list = self._op_mode_list - - # handle the case that the op-mode file contains underscores: - if op_mode_list is None: - raise FileNotFoundError(f"No op-mode file list at '{op_mode_include_file}'") - (func_name, scriptname) = split_compound_op_mode_name(name, op_mode_list) - if scriptname == '': - raise FileNotFoundError(f"No op-mode file named in string '{name}'") - - mod = load_op_mode_as_module(f'{scriptname}') - func = getattr(mod, func_name) - if len(list(data)) > 0: - res = func(True, **data) - else: - res = func(True) - - return res - - def gen_op_mutation(self): - session = self._session - data = self._data - name = self._name - op_mode_list = self._op_mode_list - - # handle the case that the op-mode file name contains underscores: - if op_mode_list is None: - raise FileNotFoundError(f"No op-mode file list at '{op_mode_include_file}'") - (func_name, scriptname) = split_compound_op_mode_name(name, op_mode_list) - if scriptname == '': - raise FileNotFoundError(f"No op-mode file named in string '{name}'") - - mod = load_op_mode_as_module(f'{scriptname}') - func = getattr(mod, func_name) - if len(list(data)) > 0: - res = func(**data) - else: - res = func() - - return res diff --git a/src/services/api/graphql/recipes/templates/create_dhcp_server.tmpl b/src/services/api/graphql/recipes/templates/create_dhcp_server.tmpl deleted file mode 100644 index 70de43183..000000000 --- a/src/services/api/graphql/recipes/templates/create_dhcp_server.tmpl +++ /dev/null @@ -1,9 +0,0 @@ -set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} default-router {{ default_router }} -set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} name-server {{ name_server }} -set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} domain-name {{ domain_name }} -set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} lease {{ lease }} -set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} range {{ range }} start {{ start }} -set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} range {{ range }} stop {{ stop }} -set service dns forwarding allow-from {{ dns_forwarding_allow_from }} -set service dns forwarding cache-size {{ dns_forwarding_cache_size }} -set service dns forwarding listen-address {{ dns_forwarding_listen_address }} diff --git a/src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl b/src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl deleted file mode 100644 index a890d0086..000000000 --- a/src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl +++ /dev/null @@ -1,4 +0,0 @@ -set firewall group address-group {{ name }} -{% for add in address %} -set firewall group address-group {{ name }} address {{ add }} -{% endfor %} diff --git a/src/services/api/graphql/recipes/templates/create_firewall_address_ipv_6_group.tmpl b/src/services/api/graphql/recipes/templates/create_firewall_address_ipv_6_group.tmpl deleted file mode 100644 index e9b660722..000000000 --- a/src/services/api/graphql/recipes/templates/create_firewall_address_ipv_6_group.tmpl +++ /dev/null @@ -1,4 +0,0 @@ -set firewall group ipv6-address-group {{ name }} -{% for add in address %} -set firewall group ipv6-address-group {{ name }} address {{ add }} -{% endfor %} diff --git a/src/services/api/graphql/recipes/templates/create_interface_ethernet.tmpl b/src/services/api/graphql/recipes/templates/create_interface_ethernet.tmpl deleted file mode 100644 index d9d7ed691..000000000 --- a/src/services/api/graphql/recipes/templates/create_interface_ethernet.tmpl +++ /dev/null @@ -1,5 +0,0 @@ -{% if replace %} -delete interfaces ethernet {{ interface }} address -{% endif %} -set interfaces ethernet {{ interface }} address {{ address }} -set interfaces ethernet {{ interface }} description {{ description }} diff --git a/src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl b/src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl deleted file mode 100644 index 458f3e5fc..000000000 --- a/src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl +++ /dev/null @@ -1,3 +0,0 @@ -{% for add in address %} -delete firewall group address-group {{ name }} address {{ add }} -{% endfor %} diff --git a/src/services/api/graphql/recipes/templates/remove_firewall_address_ipv_6_group_members.tmpl b/src/services/api/graphql/recipes/templates/remove_firewall_address_ipv_6_group_members.tmpl deleted file mode 100644 index 0efa0b226..000000000 --- a/src/services/api/graphql/recipes/templates/remove_firewall_address_ipv_6_group_members.tmpl +++ /dev/null @@ -1,3 +0,0 @@ -{% for add in address %} -delete firewall group ipv6-address-group {{ name }} address {{ add }} -{% endfor %} diff --git a/src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl b/src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl deleted file mode 100644 index f56c61231..000000000 --- a/src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl +++ /dev/null @@ -1,3 +0,0 @@ -{% for add in address %} -set firewall group address-group {{ name }} address {{ add }} -{% endfor %} diff --git a/src/services/api/graphql/recipes/templates/update_firewall_address_ipv_6_group_members.tmpl b/src/services/api/graphql/recipes/templates/update_firewall_address_ipv_6_group_members.tmpl deleted file mode 100644 index f98a5517c..000000000 --- a/src/services/api/graphql/recipes/templates/update_firewall_address_ipv_6_group_members.tmpl +++ /dev/null @@ -1,3 +0,0 @@ -{% for add in address %} -set firewall group ipv6-address-group {{ name }} address {{ add }} -{% endfor %} diff --git a/src/services/api/graphql/session/__init__.py b/src/services/api/graphql/session/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/services/api/graphql/session/composite/system_status.py b/src/services/api/graphql/session/composite/system_status.py new file mode 100755 index 000000000..8dadcc9f3 --- /dev/null +++ b/src/services/api/graphql/session/composite/system_status.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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 json +import importlib.util + +from vyos.defaults import directories + +from api.graphql.utils.util import load_op_mode_as_module + +def get_system_version() -> dict: + show_version = load_op_mode_as_module('version.py') + return show_version.show(raw=True, funny=False) + +def get_system_uptime() -> dict: + show_uptime = load_op_mode_as_module('show_uptime.py') + return show_uptime.get_raw_data() + +def get_system_ram_usage() -> dict: + show_ram = load_op_mode_as_module('memory.py') + return show_ram.show(raw=True) diff --git a/src/services/api/graphql/session/override/remove_firewall_address_group_members.py b/src/services/api/graphql/session/override/remove_firewall_address_group_members.py new file mode 100644 index 000000000..b91932e14 --- /dev/null +++ b/src/services/api/graphql/session/override/remove_firewall_address_group_members.py @@ -0,0 +1,35 @@ +# Copyright 2021 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +from . session import Session + +class RemoveFirewallAddressGroupMembers(Session): + def __init__(self, session, data): + super().__init__(session, data) + + # Define any custom processing of parameters here by overriding + # configure: + # + # def configure(self): + # self._data = transform_data(self._data) + # super().configure() + # self.clean_up() + + def configure(self): + super().configure() + + group_name = self._data['name'] + path = ['firewall', 'group', 'address-group', group_name] + self.delete_path_if_childless(path) diff --git a/src/services/api/graphql/session/session.py b/src/services/api/graphql/session/session.py new file mode 100644 index 000000000..23bc7154c --- /dev/null +++ b/src/services/api/graphql/session/session.py @@ -0,0 +1,207 @@ +# Copyright 2021-2022 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +import os +import json + +from ariadne import convert_camel_case_to_snake + +from vyos.config import Config +from vyos.configtree import ConfigTree +from vyos.defaults import directories +from vyos.template import render + +from api.graphql.utils.util import load_op_mode_as_module, split_compound_op_mode_name + +op_mode_include_file = os.path.join(directories['data'], 'op-mode-standardized.json') + +class Session: + """ + Wrapper for calling configsession functions based on GraphQL requests. + Non-nullable fields in the respective schema allow avoiding a key check + in 'data'. + """ + def __init__(self, session, data): + self._session = session + self._data = data + self._name = convert_camel_case_to_snake(type(self).__name__) + + try: + with open(op_mode_include_file) as f: + self._op_mode_list = json.loads(f.read()) + except Exception: + self._op_mode_list = None + + def configure(self): + session = self._session + data = self._data + func_base_name = self._name + + tmpl_file = f'{func_base_name}.tmpl' + cmd_file = f'/tmp/{func_base_name}.cmds' + tmpl_dir = directories['api_templates'] + + try: + render(cmd_file, tmpl_file, data, location=tmpl_dir) + commands = [] + with open(cmd_file) as f: + lines = f.readlines() + for line in lines: + commands.append(line.split()) + for cmd in commands: + if cmd[0] == 'set': + session.set(cmd[1:]) + elif cmd[0] == 'delete': + session.delete(cmd[1:]) + else: + raise ValueError('Operation must be "set" or "delete"') + session.commit() + except Exception as error: + raise error + + def delete_path_if_childless(self, path): + session = self._session + config = Config(session.get_session_env()) + if not config.list_nodes(path): + session.delete(path) + session.commit() + + def show_config(self): + session = self._session + data = self._data + out = '' + + try: + out = session.show_config(data['path']) + if data.get('config_format', '') == 'json': + config_tree = vyos.configtree.ConfigTree(out) + out = json.loads(config_tree.to_json()) + except Exception as error: + raise error + + return out + + def save(self): + session = self._session + data = self._data + if 'file_name' not in data or not data['file_name']: + data['file_name'] = '/config/config.boot' + + try: + session.save_config(data['file_name']) + except Exception as error: + raise error + + def load(self): + session = self._session + data = self._data + + try: + session.load_config(data['file_name']) + session.commit() + except Exception as error: + raise error + + def show(self): + session = self._session + data = self._data + out = '' + + try: + out = session.show(data['path']) + except Exception as error: + raise error + + return out + + def add(self): + session = self._session + data = self._data + + try: + res = session.install_image(data['location']) + except Exception as error: + raise error + + return res + + def delete(self): + session = self._session + data = self._data + + try: + res = session.remove_image(data['name']) + except Exception as error: + raise error + + return res + + def system_status(self): + import api.graphql.session.composite.system_status as system_status + + session = self._session + data = self._data + + status = {} + status['host_name'] = session.show(['host', 'name']).strip() + status['version'] = system_status.get_system_version() + status['uptime'] = system_status.get_system_uptime() + status['ram'] = system_status.get_system_ram_usage() + + return status + + def gen_op_query(self): + session = self._session + data = self._data + name = self._name + op_mode_list = self._op_mode_list + + # handle the case that the op-mode file contains underscores: + if op_mode_list is None: + raise FileNotFoundError(f"No op-mode file list at '{op_mode_include_file}'") + (func_name, scriptname) = split_compound_op_mode_name(name, op_mode_list) + if scriptname == '': + raise FileNotFoundError(f"No op-mode file named in string '{name}'") + + mod = load_op_mode_as_module(f'{scriptname}') + func = getattr(mod, func_name) + if len(list(data)) > 0: + res = func(True, **data) + else: + res = func(True) + + return res + + def gen_op_mutation(self): + session = self._session + data = self._data + name = self._name + op_mode_list = self._op_mode_list + + # handle the case that the op-mode file name contains underscores: + if op_mode_list is None: + raise FileNotFoundError(f"No op-mode file list at '{op_mode_include_file}'") + (func_name, scriptname) = split_compound_op_mode_name(name, op_mode_list) + if scriptname == '': + raise FileNotFoundError(f"No op-mode file named in string '{name}'") + + mod = load_op_mode_as_module(f'{scriptname}') + func = getattr(mod, func_name) + if len(list(data)) > 0: + res = func(**data) + else: + res = func() + + return res diff --git a/src/services/api/graphql/session/templates/create_dhcp_server.tmpl b/src/services/api/graphql/session/templates/create_dhcp_server.tmpl new file mode 100644 index 000000000..70de43183 --- /dev/null +++ b/src/services/api/graphql/session/templates/create_dhcp_server.tmpl @@ -0,0 +1,9 @@ +set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} default-router {{ default_router }} +set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} name-server {{ name_server }} +set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} domain-name {{ domain_name }} +set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} lease {{ lease }} +set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} range {{ range }} start {{ start }} +set service dhcp-server shared-network-name {{ shared_network_name }} subnet {{ subnet }} range {{ range }} stop {{ stop }} +set service dns forwarding allow-from {{ dns_forwarding_allow_from }} +set service dns forwarding cache-size {{ dns_forwarding_cache_size }} +set service dns forwarding listen-address {{ dns_forwarding_listen_address }} diff --git a/src/services/api/graphql/session/templates/create_firewall_address_group.tmpl b/src/services/api/graphql/session/templates/create_firewall_address_group.tmpl new file mode 100644 index 000000000..a890d0086 --- /dev/null +++ b/src/services/api/graphql/session/templates/create_firewall_address_group.tmpl @@ -0,0 +1,4 @@ +set firewall group address-group {{ name }} +{% for add in address %} +set firewall group address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/session/templates/create_firewall_address_ipv_6_group.tmpl b/src/services/api/graphql/session/templates/create_firewall_address_ipv_6_group.tmpl new file mode 100644 index 000000000..e9b660722 --- /dev/null +++ b/src/services/api/graphql/session/templates/create_firewall_address_ipv_6_group.tmpl @@ -0,0 +1,4 @@ +set firewall group ipv6-address-group {{ name }} +{% for add in address %} +set firewall group ipv6-address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/session/templates/create_interface_ethernet.tmpl b/src/services/api/graphql/session/templates/create_interface_ethernet.tmpl new file mode 100644 index 000000000..d9d7ed691 --- /dev/null +++ b/src/services/api/graphql/session/templates/create_interface_ethernet.tmpl @@ -0,0 +1,5 @@ +{% if replace %} +delete interfaces ethernet {{ interface }} address +{% endif %} +set interfaces ethernet {{ interface }} address {{ address }} +set interfaces ethernet {{ interface }} description {{ description }} diff --git a/src/services/api/graphql/session/templates/remove_firewall_address_group_members.tmpl b/src/services/api/graphql/session/templates/remove_firewall_address_group_members.tmpl new file mode 100644 index 000000000..458f3e5fc --- /dev/null +++ b/src/services/api/graphql/session/templates/remove_firewall_address_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +delete firewall group address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/session/templates/remove_firewall_address_ipv_6_group_members.tmpl b/src/services/api/graphql/session/templates/remove_firewall_address_ipv_6_group_members.tmpl new file mode 100644 index 000000000..0efa0b226 --- /dev/null +++ b/src/services/api/graphql/session/templates/remove_firewall_address_ipv_6_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +delete firewall group ipv6-address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/session/templates/update_firewall_address_group_members.tmpl b/src/services/api/graphql/session/templates/update_firewall_address_group_members.tmpl new file mode 100644 index 000000000..f56c61231 --- /dev/null +++ b/src/services/api/graphql/session/templates/update_firewall_address_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +set firewall group address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/session/templates/update_firewall_address_ipv_6_group_members.tmpl b/src/services/api/graphql/session/templates/update_firewall_address_ipv_6_group_members.tmpl new file mode 100644 index 000000000..f98a5517c --- /dev/null +++ b/src/services/api/graphql/session/templates/update_firewall_address_ipv_6_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +set firewall group ipv6-address-group {{ name }} address {{ add }} +{% endfor %} -- cgit v1.2.3 From 38ab693dc9755f249283a6ded00c2e4d966b3380 Mon Sep 17 00:00:00 2001 From: aapostoliuk Date: Tue, 9 Aug 2022 17:21:12 +0300 Subject: opennhrp: T1070: Fixed removal all SAs in script Fixed removal all dmvpn SAs. Changed vici terminate by child-sa name on terminate by ike-id --- src/etc/opennhrp/opennhrp-script.py | 315 +++++++++++++++++++++++++++++------- 1 file changed, 253 insertions(+), 62 deletions(-) (limited to 'src') diff --git a/src/etc/opennhrp/opennhrp-script.py b/src/etc/opennhrp/opennhrp-script.py index 8274e6564..a5293c97e 100755 --- a/src/etc/opennhrp/opennhrp-script.py +++ b/src/etc/opennhrp/opennhrp-script.py @@ -18,44 +18,120 @@ import os import re import sys import vici + from json import loads +from pathlib import Path +from vyos.logger import getLogger from vyos.util import cmd from vyos.util import process_named_running -NHRP_CONFIG = "/run/opennhrp/opennhrp.conf" +NHRP_CONFIG: str = '/run/opennhrp/opennhrp.conf' + + +def vici_get_ipsec_uniqueid(conn: str, src_nbma: str, + dst_nbma: str) -> list[str]: + """ Find and return IKE SAs by src nbma and dst nbma + + Args: + conn (str): a connection name + src_nbma (str): an IP address of NBMA source + dst_nbma (str): an IP address of NBMA destination + + Returns: + list: a list of IKE connections that match a criteria + """ + if not conn or not src_nbma or not dst_nbma: + logger.error( + f'Incomplete input data for resolving IKE unique ids: ' + f'conn: {conn}, src_nbma: {src_nbma}, dst_nbma: {dst_nbma}') + return [] + + try: + logger.info( + f'Resolving IKE unique ids for: conn: {conn}, ' + f'src_nbma: {src_nbma}, dst_nbma: {dst_nbma}') + session: vici.Session = vici.Session() + list_ikeid: list[str] = [] + list_sa = session.list_sas({'ike': conn}) + for sa in list_sa: + if sa[conn]['local-host'].decode('ascii') == src_nbma \ + and sa[conn]['remote-host'].decode('ascii') == dst_nbma: + list_ikeid.append(sa[conn]['uniqueid'].decode('ascii')) + return list_ikeid + except Exception as err: + logger.error(f'Unable to find unique ids for IKE: {err}') + return [] + + +def vici_ike_terminate(list_ikeid: list[str]) -> bool: + """Terminating IKE SAs by list of IKE IDs + + Args: + list_ikeid (list[str]): a list of IKE ids to terminate + + Returns: + bool: result of termination action + """ + if not list: + logger.warning('An empty list for termination was provided') + return False + + try: + session = vici.Session() + for ikeid in list_ikeid: + logger.info(f'Terminating IKE SA with id {ikeid}') + session.terminate({'ike-id': ikeid, 'timeout': '-1'}) + return True + except Exception as err: + logger.error(f'Failed to terminate SA for IKE ids {list_ikeid}: {err}') + return False + +def parse_type_ipsec(interface: str) -> tuple[str, str]: + """Get DMVPN Type and NHRP Profile from the configuration -def parse_type_ipsec(interface): - with open(NHRP_CONFIG, 'r') as f: - lines = f.readlines() - match = rf'^interface {interface} #(hub|spoke)(?:\s([\w-]+))?$' - for line in lines: - m = re.match(match, line) - if m: - return m[1], m[2] - return None, None + Args: + interface (str): a name of interface + + Returns: + tuple[str, str]: `peer_type` and `profile_name` + """ + if not interface: + logger.error('Cannot find peer type - no input provided') + return '', '' + + config_file: str = Path(NHRP_CONFIG).read_text() + regex: str = rf'^interface {interface} #(?Phub|spoke) ?(?P[^\n]*)$' + match = re.search(regex, config_file, re.M) + if match: + return match.groupdict()['peer_type'], match.groupdict()[ + 'profile_name'] + return '', '' def add_peer_route(nbma_src: str, nbma_dst: str, mtu: str) -> None: """Add a route to a NBMA peer Args: - nmba_src (str): a local IP address + nbma_src (str): a local IP address nbma_dst (str): a remote IP address mtu (str): a MTU for a route """ + logger.info(f'Adding route from {nbma_src} to {nbma_dst} with MTU {mtu}') # Find routes to a peer - route_get_cmd = f'sudo ip -j route get {nbma_dst} from {nbma_src}' + route_get_cmd: str = f'sudo ip --json route get {nbma_dst} from {nbma_src}' try: route_info_data = loads(cmd(route_get_cmd)) except Exception as err: - print(f'Unable to find a route to {nbma_dst}: {err}') + logger.error(f'Unable to find a route to {nbma_dst}: {err}') + return # Check if an output has an expected format if not isinstance(route_info_data, list): - print(f'Garbage returned from the "{route_get_cmd}" command: \ - {route_info_data}') + logger.error( + f'Garbage returned from the "{route_get_cmd}" ' + f'command: {route_info_data}') return # Add static routes to a peer @@ -76,104 +152,217 @@ def add_peer_route(nbma_src: str, nbma_dst: str, mtu: str) -> None: try: cmd(route_add_cmd) except Exception as err: - print(f'Unable to add a route using command "{route_add_cmd}": \ - {err}') + logger.error( + f'Unable to add a route using command "{route_add_cmd}": ' + f'{err}') -def vici_initiate(conn, child_sa, src_addr, dest_addr): - try: - session = vici.Session() - logs = session.initiate({ - 'ike': conn, - 'child': child_sa, - 'timeout': '-1', - 'my-host': src_addr, - 'other-host': dest_addr - }) - for log in logs: - message = log['msg'].decode('ascii') - print('INIT LOG:', message) - return True - except: - return None +def vici_initiate(conn: str, child_sa: str, src_addr: str, + dest_addr: str) -> bool: + """Initiate IKE SA connection with specific peer + Args: + conn (str): an IKE connection name + child_sa (str): a child SA profile name + src_addr (str): NBMA local address + dest_addr (str): NBMA address of a peer -def vici_terminate(conn, child_sa, src_addr, dest_addr): + Returns: + bool: a result of initiation command + """ + logger.info( + f'Trying to initiate connection. Name: {conn}, child sa: {child_sa}, ' + f'src_addr: {src_addr}, dst_addr: {dest_addr}') try: session = vici.Session() - logs = session.terminate({ + session.initiate({ 'ike': conn, 'child': child_sa, 'timeout': '-1', 'my-host': src_addr, 'other-host': dest_addr }) - for log in logs: - message = log['msg'].decode('ascii') - print('TERM LOG:', message) return True - except: - return None + except Exception as err: + logger.error(f'Unable to initiate connection {err}') + return False + + +def vici_terminate(conn: str, src_addr: str, dest_addr: str) -> None: + """Find and terminate IKE SAs by local NBMA and remote NBMA addresses + + Args: + conn (str): IKE connection name + src_addr (str): NBMA local address + dest_addr (str): NBMA address of a peer + """ + logger.info( + f'Terminating IKE connection {conn} between {src_addr} ' + f'and {dest_addr}') + ikeid_list: list[str] = vici_get_ipsec_uniqueid(conn, src_addr, dest_addr) -def iface_up(interface): - cmd(f'sudo ip route flush proto 42 dev {interface}') - cmd(f'sudo ip neigh flush dev {interface}') + if not ikeid_list: + logger.warning( + f'No active sessions found for IKE profile {conn}, ' + f'local NBMA {src_addr}, remote NBMA {dest_addr}') + else: + vici_ike_terminate(ikeid_list) -def peer_up(dmvpn_type, conn): - # src_addr = os.getenv('NHRP_SRCADDR') +def iface_up(interface: str) -> None: + """Proceed tunnel interface UP event + + Args: + interface (str): an interface name + """ + if not interface: + logger.warning('No interface name provided for UP event') + + logger.info(f'Turning up interface {interface}') + try: + cmd(f'sudo ip route flush proto 42 dev {interface}') + cmd(f'sudo ip neigh flush dev {interface}') + except Exception as err: + logger.error( + f'Unable to flush route on interface "{interface}": {err}') + + +def peer_up(dmvpn_type: str, conn: str) -> None: + """Proceed NHRP peer UP event + + Args: + dmvpn_type (str): a type of peer + conn (str): an IKE profile name + """ + logger.info(f'Peer UP event for {dmvpn_type} using IKE profile {conn}') src_nbma = os.getenv('NHRP_SRCNBMA') - # dest_addr = os.getenv('NHRP_DESTADDR') dest_nbma = os.getenv('NHRP_DESTNBMA') dest_mtu = os.getenv('NHRP_DESTMTU') + if not src_nbma or not dest_nbma: + logger.error( + f'Can not get NHRP NBMA addresses: local {src_nbma}, ' + f'remote {dest_nbma}') + return + + logger.info(f'NBMA addresses: local {src_nbma}, remote {dest_nbma}') if dest_mtu: add_peer_route(src_nbma, dest_nbma, dest_mtu) - if conn and dmvpn_type == 'spoke' and process_named_running('charon'): - vici_terminate(conn, 'dmvpn', src_nbma, dest_nbma) + vici_terminate(conn, src_nbma, dest_nbma) vici_initiate(conn, 'dmvpn', src_nbma, dest_nbma) -def peer_down(dmvpn_type, conn): +def peer_down(dmvpn_type: str, conn: str) -> None: + """Proceed NHRP peer DOWN event + + Args: + dmvpn_type (str): a type of peer + conn (str): an IKE profile name + """ + logger.info(f'Peer DOWN event for {dmvpn_type} using IKE profile {conn}') + src_nbma = os.getenv('NHRP_SRCNBMA') dest_nbma = os.getenv('NHRP_DESTNBMA') + if not src_nbma or not dest_nbma: + logger.error( + f'Can not get NHRP NBMA addresses: local {src_nbma}, ' + f'remote {dest_nbma}') + return + + logger.info(f'NBMA addresses: local {src_nbma}, remote {dest_nbma}') if conn and dmvpn_type == 'spoke' and process_named_running('charon'): - vici_terminate(conn, 'dmvpn', src_nbma, dest_nbma) + vici_terminate(conn, src_nbma, dest_nbma) + try: + cmd(f'sudo ip route del {dest_nbma} src {src_nbma} proto 42') + except Exception as err: + logger.error( + f'Unable to del route from {src_nbma} to {dest_nbma}: {err}') - cmd(f'sudo ip route del {dest_nbma} src {src_nbma} proto 42') +def route_up(interface: str) -> None: + """Proceed NHRP route UP event + + Args: + interface (str): an interface name + """ + logger.info(f'Route UP event for interface {interface}') -def route_up(interface): dest_addr = os.getenv('NHRP_DESTADDR') dest_prefix = os.getenv('NHRP_DESTPREFIX') next_hop = os.getenv('NHRP_NEXTHOP') - cmd(f'sudo ip route replace {dest_addr}/{dest_prefix} proto 42 \ - via {next_hop} dev {interface}') - cmd('sudo ip route flush cache') + if not dest_addr or not dest_prefix or not next_hop: + logger.error( + f'Can not get route details: dest_addr {dest_addr}, ' + f'dest_prefix {dest_prefix}, next_hop {next_hop}') + return + + logger.info( + f'Route details: dest_addr {dest_addr}, dest_prefix {dest_prefix}, ' + f'next_hop {next_hop}') + try: + cmd(f'sudo ip route replace {dest_addr}/{dest_prefix} proto 42 \ + via {next_hop} dev {interface}') + cmd('sudo ip route flush cache') + except Exception as err: + logger.error( + f'Unable replace or flush route to {dest_addr}/{dest_prefix} ' + f'via {next_hop} dev {interface}: {err}') + + +def route_down(interface: str) -> None: + """Proceed NHRP route DOWN event + + Args: + interface (str): an interface name + """ + logger.info(f'Route DOWN event for interface {interface}') -def route_down(interface): dest_addr = os.getenv('NHRP_DESTADDR') dest_prefix = os.getenv('NHRP_DESTPREFIX') - cmd(f'sudo ip route del {dest_addr}/{dest_prefix} proto 42') - cmd('sudo ip route flush cache') + if not dest_addr or not dest_prefix: + logger.error( + f'Can not get route details: dest_addr {dest_addr}, ' + f'dest_prefix {dest_prefix}') + return + + logger.info( + f'Route details: dest_addr {dest_addr}, dest_prefix {dest_prefix}') + try: + cmd(f'sudo ip route del {dest_addr}/{dest_prefix} proto 42') + cmd('sudo ip route flush cache') + except Exception as err: + logger.error( + f'Unable delete or flush route to {dest_addr}/{dest_prefix}: ' + f'{err}') if __name__ == '__main__': + logger = getLogger('opennhrp-script', syslog=True) + logger.debug( + f'Running script with arguments: {sys.argv}, ' + f'environment: {os.environ}') + action = sys.argv[1] interface = os.getenv('NHRP_INTERFACE') - dmvpn_type, profile_name = parse_type_ipsec(interface) - dmvpn_conn = None + if not interface: + logger.error('Can not get NHRP interface name') + sys.exit(1) - if profile_name: - dmvpn_conn = f'dmvpn-{profile_name}-{interface}' + dmvpn_type, profile_name = parse_type_ipsec(interface) + if not dmvpn_type: + logger.info(f'Interface {interface} is not NHRP tunnel') + sys.exit() + dmvpn_conn: str = '' + if profile_name: + dmvpn_conn: str = f'dmvpn-{profile_name}-{interface}' if action == 'interface-up': iface_up(interface) elif action == 'peer-register': @@ -186,3 +375,5 @@ if __name__ == '__main__': route_up(interface) elif action == 'route-down': route_down(interface) + + sys.exit() -- cgit v1.2.3 From 8d4205a99a9f0475da2b286d51febb36c7350e6e Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Wed, 24 Aug 2022 11:34:38 +0000 Subject: nat66: T4626: Rewrite op-mode show nat66 rules Rewrite op-mode "show nat66 source|destination rules" to the new format use "show_rules --direction --family " Delete old script show_nat66_rules.py --- op-mode-definitions/nat.xml.in | 4 +- op-mode-definitions/nat66.xml.in | 4 +- src/op_mode/nat.py | 31 +++++++----- src/op_mode/show_nat66_rules.py | 102 --------------------------------------- 4 files changed, 22 insertions(+), 119 deletions(-) delete mode 100755 src/op_mode/show_nat66_rules.py (limited to 'src') diff --git a/op-mode-definitions/nat.xml.in b/op-mode-definitions/nat.xml.in index 7148c1128..e89c3801f 100644 --- a/op-mode-definitions/nat.xml.in +++ b/op-mode-definitions/nat.xml.in @@ -16,7 +16,7 @@ Show configured source NAT rules - ${vyos_op_scripts_dir}/nat.py show_rules --direction source + ${vyos_op_scripts_dir}/nat.py show_rules --direction source --family inet @@ -58,7 +58,7 @@ Show configured destination NAT rules - ${vyos_op_scripts_dir}/nat.py show_rules --direction destination + ${vyos_op_scripts_dir}/nat.py show_rules --direction destination --family inet diff --git a/op-mode-definitions/nat66.xml.in b/op-mode-definitions/nat66.xml.in index 1ec46eb11..aba2d6add 100644 --- a/op-mode-definitions/nat66.xml.in +++ b/op-mode-definitions/nat66.xml.in @@ -16,7 +16,7 @@ Show configured source NAT66 rules - ${vyos_op_scripts_dir}/show_nat66_rules.py --source + ${vyos_op_scripts_dir}/nat.py show_rules --direction source --family inet6 @@ -58,7 +58,7 @@ Show configured destination NAT66 rules - ${vyos_op_scripts_dir}/show_nat66_rules.py --destination + ${vyos_op_scripts_dir}/nat.py show_rules --direction destination --family inet6 diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py index 12fc4c782..2dffc378b 100755 --- a/src/op_mode/nat.py +++ b/src/op_mode/nat.py @@ -27,7 +27,7 @@ from vyos.util import dict_search import vyos.opmode -def _get_json_data(direction): +def _get_json_data(direction, family): """ Get NAT format JSON """ @@ -35,14 +35,15 @@ def _get_json_data(direction): chain = 'POSTROUTING' if direction == 'destination': chain = 'PREROUTING' - return cmd(f'sudo nft --json list chain ip nat {chain}') + family = 'ip6' if family == 'inet6' else 'ip' + return cmd(f'sudo nft --json list chain {family} nat {chain}') -def _get_raw_data_rules(direction): +def _get_raw_data_rules(direction, family): """Get interested rules :returns dict """ - data = _get_json_data(direction) + data = _get_json_data(direction, family) data_dict = json.loads(data) rules = [] for rule in data_dict['nftables']: @@ -51,10 +52,12 @@ def _get_raw_data_rules(direction): return rules -def _get_formatted_output_rules(data, direction): +def _get_formatted_output_rules(data, direction, family): # Add default values before loop sport, dport, proto = 'any', 'any', 'any' - saddr, daddr = '0.0.0.0/0', '0.0.0.0/0' + saddr = '::/0' if family == 'inet6' else '0.0.0.0/0' + daddr = '::/0' if family == 'inet6' else '0.0.0.0/0' + data_entries = [] for rule in data: if 'comment' in rule['rule']: @@ -69,11 +72,13 @@ def _get_formatted_output_rules(data, direction): if 'prefix' in match['right'] or 'set' in match['right']: # Merge dict src/dst l3_l4 parameters my_dict = {**match['left']['payload'], **match['right']} + my_dict['op'] = match['op'] + op = '!' if my_dict.get('op') == '!=' else '' proto = my_dict.get('protocol').upper() if my_dict['field'] == 'saddr': - saddr = f'{my_dict["prefix"]["addr"]}/{my_dict["prefix"]["len"]}' + saddr = f'{op}{my_dict["prefix"]["addr"]}/{my_dict["prefix"]["len"]}' elif my_dict['field'] == 'daddr': - daddr = f'{my_dict["prefix"]["addr"]}/{my_dict["prefix"]["len"]}' + daddr = f'{op}{my_dict["prefix"]["addr"]}/{my_dict["prefix"]["len"]}' elif my_dict['field'] == 'sport': # Port range or single port if jmespath.search('set[*].range', my_dict): @@ -96,8 +101,8 @@ def _get_formatted_output_rules(data, direction): if jmespath.search('left.payload.field', match) == 'daddr': daddr = match.get('right') else: - saddr = '0.0.0.0/0' - daddr = '0.0.0.0/0' + saddr = '::/0' if family == 'inet6' else '0.0.0.0/0' + daddr = '::/0' if family == 'inet6' else '0.0.0.0/0' sport = 'any' dport = 'any' proto = 'any' @@ -175,12 +180,12 @@ def _get_formatted_output_statistics(data, direction): return output -def show_rules(raw: bool, direction: str): - nat_rules = _get_raw_data_rules(direction) +def show_rules(raw: bool, direction: str, family: str): + nat_rules = _get_raw_data_rules(direction, family) if raw: return nat_rules else: - return _get_formatted_output_rules(nat_rules, direction) + return _get_formatted_output_rules(nat_rules, direction, family) def show_statistics(raw: bool, direction: str): diff --git a/src/op_mode/show_nat66_rules.py b/src/op_mode/show_nat66_rules.py deleted file mode 100755 index 967ec9d37..000000000 --- a/src/op_mode/show_nat66_rules.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/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 . - -import jmespath -import json - -from argparse import ArgumentParser -from jinja2 import Template -from sys import exit -from vyos.util import cmd -from vyos.util import dict_search - -parser = ArgumentParser() -group = parser.add_mutually_exclusive_group() -group.add_argument("--source", help="Show statistics for configured source NAT rules", action="store_true") -group.add_argument("--destination", help="Show statistics for configured destination NAT rules", action="store_true") -args = parser.parse_args() - -if args.source or args.destination: - tmp = cmd('sudo nft -j list table ip6 nat') - tmp = json.loads(tmp) - - format_nat66_rule = '{0: <10} {1: <50} {2: <50} {3: <10}' - print(format_nat66_rule.format("Rule", "Source" if args.source else "Destination", "Translation", "Outbound Interface" if args.source else "Inbound Interface")) - print(format_nat66_rule.format("----", "------" if args.source else "-----------", "-----------", "------------------" if args.source else "-----------------")) - - data_json = jmespath.search('nftables[?rule].rule[?chain]', tmp) - for idx in range(0, len(data_json)): - data = data_json[idx] - - # The following key values must exist - # When the rule JSON does not have some keys, this is not a rule we can work with - continue_rule = False - for key in ['comment', 'chain', 'expr']: - if key not in data: - continue_rule = True - continue - if continue_rule: - continue - - comment = data['comment'] - - # Check the annotation to see if the annotation format is created by VYOS - continue_rule = True - for comment_prefix in ['SRC-NAT66-', 'DST-NAT66-']: - if comment_prefix in comment: - continue_rule = False - if continue_rule: - continue - - # When log is detected from the second index of expr, then this rule should be ignored - if 'log' in data['expr'][2]: - continue - - rule = comment.replace('SRC-NAT66-','') - rule = rule.replace('DST-NAT66-','') - chain = data['chain'] - if not ((args.source and chain == 'POSTROUTING') or (not args.source and chain == 'PREROUTING')): - continue - interface = dict_search('match.right', data['expr'][0]) - srcdest = dict_search('match.right.prefix.addr', data['expr'][2]) - if srcdest: - addr_tmp = dict_search('match.right.prefix.len', data['expr'][2]) - if addr_tmp: - srcdest = srcdest + '/' + str(addr_tmp) - else: - srcdest = dict_search('match.right', data['expr'][2]) - - tran_addr_json = dict_search('snat.addr' if args.source else 'dnat.addr', data['expr'][3]) - if tran_addr_json: - if isinstance(srcdest_json,str): - tran_addr = tran_addr_json - - if 'prefix' in tran_addr_json: - addr_tmp = dict_search('snat.addr.prefix.addr' if args.source else 'dnat.addr.prefix.addr', data['expr'][3]) - len_tmp = dict_search('snat.addr.prefix.len' if args.source else 'dnat.addr.prefix.len', data['expr'][3]) - if addr_tmp: - tran_addr = addr_tmp + '/' + str(len_tmp) - else: - if 'masquerade' in data['expr'][3]: - tran_addr = 'masquerade' - - print(format_nat66_rule.format(rule, srcdest, tran_addr, interface)) - - exit(0) -else: - parser.print_help() - exit(1) - -- cgit v1.2.3 From f5360b98703e0a954066c099b48119daecd1c12b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 24 Aug 2022 19:30:53 +0200 Subject: ipsec: T2185: use systemd to start/stop service --- op-mode-definitions/monitor-log.xml.in | 13 +++++++++++++ op-mode-definitions/show-log.xml.in | 2 +- src/conf_mode/vpn_ipsec.py | 8 +++----- 3 files changed, 17 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in index 8a02e1f08..774acaa5c 100644 --- a/op-mode-definitions/monitor-log.xml.in +++ b/op-mode-definitions/monitor-log.xml.in @@ -224,6 +224,19 @@ journalctl --no-hostname --boot --follow --unit ssh.service + + + Show log for Virtual Private Network (VPN) + + + + + Monitor last lines of IPSec + + journalctl --no-hostname --boot --follow --unit strongswan-starter.service + + + diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index 24a1b5f3e..455bd7c64 100644 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -386,7 +386,7 @@ Show log for IPSec - cat $(printf "%s\n" /var/log/messages* | sort -nr) | grep -e charon + journalctl --no-hostname --boot --unit strongswan-starter.service diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py index bad9cfbd8..5ca32d23e 100755 --- a/src/conf_mode/vpn_ipsec.py +++ b/src/conf_mode/vpn_ipsec.py @@ -595,13 +595,11 @@ def wait_for_vici_socket(timeout=5, sleep_interval=0.1): sleep(sleep_interval) def apply(ipsec): + systemd_service = 'strongswan-starter.service' if not ipsec: - call('sudo ipsec stop') + call(f'systemctl stop {systemd_service}') else: - call('sudo ipsec restart') - call('sudo ipsec rereadall') - call('sudo ipsec reload') - + call(f'systemctl reload-or-restart {systemd_service}') if wait_for_vici_socket(): call('sudo swanctl -q') -- cgit v1.2.3 From eb4a7ee3afc0765671ce0fa379ab5e3518e9e49e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 24 Aug 2022 21:43:10 +0200 Subject: T4630: can not use same source-interface for macsec and pseudo-ethernet A macsec interface requires a dedicated source interface, it can not be shared with another macsec or a pseudo-ethernet interface. set interfaces macsec macsec10 address '192.168.2.1/30' set interfaces macsec macsec10 security cipher 'gcm-aes-256' set interfaces macsec macsec10 security encrypt set interfaces macsec macsec10 security mka cak '232e44b7fda6f8e2d88a07bf78a7aff4232e44b7fda6f8e2d88a07bf78a7aff4' set interfaces macsec macsec10 security mka ckn '09924585a6f3010208cf5222ef24c821405b0e34f4b4f63b1f0ced474b9bb6e6' set interfaces macsec macsec10 source-interface 'eth1' commit set interfaces pseudo-ethernet peth0 source-interface eth1 commit Reuslts in FileNotFoundError: [Errno 2] failed to run command: ip link add peth0 link eth1 type macvlan mode private returned: exit code: 2 noteworthy: cmd 'ip link add peth0 link eth1 type macvlan mode private' returned (out): returned (err): RTNETLINK answers: Device or resource busy [[interfaces pseudo-ethernet peth0]] failed Commit failed --- python/vyos/configdict.py | 11 +++++++++-- python/vyos/configverify.py | 6 ++++++ src/conf_mode/interfaces-macsec.py | 8 +------- src/conf_mode/interfaces-pseudo-ethernet.py | 7 ++++++- 4 files changed, 22 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 8f822a97d..912bc94f2 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -295,11 +295,18 @@ def is_source_interface(conf, interface, intftype=None): """ ret_val = None intftypes = ['macsec', 'pppoe', 'pseudo-ethernet', 'tunnel', 'vxlan'] - if intftype not in intftypes + [None]: + if not intftype: + intftype = intftypes + + if isinstance(intftype, str): + intftype = [intftype] + elif not isinstance(intftype, list): + raise ValueError(f'Interface type "{type(intftype)}" must be either str or list!') + + if not all(x in intftypes for x in intftype): raise ValueError(f'unknown interface type "{intftype}" or it can not ' 'have a source-interface') - intftype = intftypes if intftype == None else [intftype] for it in intftype: base = ['interfaces', it] for intf in conf.list_nodes(base): diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 2ab3cb408..447ec795c 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -295,6 +295,12 @@ def verify_source_interface(config): raise ConfigError(f'Invalid source-interface "{src_ifname}". Interface ' f'is already a member of bond "{bond_name}"!') + if 'is_source_interface' in config: + tmp = config['is_source_interface'] + src_ifname = config['source_interface'] + raise ConfigError(f'Can not use source-interface "{src_ifname}", it already ' \ + f'belongs to interface "{tmp}"!') + def verify_dhcpv6(config): """ Common helper function used by interface implementations to perform diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index 870049a88..649ea8d50 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -67,7 +67,7 @@ def get_config(config=None): macsec.update({'shutdown_required': {}}) if 'source_interface' in macsec: - tmp = is_source_interface(conf, macsec['source_interface'], 'macsec') + tmp = is_source_interface(conf, macsec['source_interface'], ['macsec', 'pseudo-ethernet']) if tmp and tmp != ifname: macsec.update({'is_source_interface' : tmp}) return macsec @@ -102,12 +102,6 @@ def verify(macsec): # gcm-aes-128 requires a 128bit long key - 64 characters (string) = 32byte = 256bit raise ConfigError('gcm-aes-128 requires a 256bit long key!') - if 'is_source_interface' in macsec: - tmp = macsec['is_source_interface'] - src_ifname = macsec['source_interface'] - raise ConfigError(f'Can not use source-interface "{src_ifname}", it already ' \ - f'belongs to interface "{tmp}"!') - if 'source_interface' in macsec: # MACsec adds a 40 byte overhead (32 byte MACsec + 8 bytes VLAN 802.1ad # and 802.1q) - we need to check the underlaying MTU if our configured diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index f26a50a0e..20f2b1975 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-2022 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 @@ -19,6 +19,7 @@ from sys import exit from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configdict import is_node_changed +from vyos.configdict import is_source_interface from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete @@ -51,6 +52,10 @@ def get_config(config=None): if 'source_interface' in peth: _, peth['parent'] = get_interface_dict(conf, ['interfaces', 'ethernet'], peth['source_interface']) + # test if source-interface is maybe already used by another interface + tmp = is_source_interface(conf, peth['source_interface'], ['macsec']) + if tmp and tmp != ifname: peth.update({'is_source_interface' : tmp}) + return peth def verify(peth): -- cgit v1.2.3 From ac885f3e0912acebf0e3bc62582cc767dc9d5a6d Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Thu, 25 Aug 2022 10:18:08 +0000 Subject: sstp: T4644: Check SSTP bind port before commit By default SSTP bind port '443' and this port can be used by another service like 'service https' or 'vpn openconnect' Check if port bound to another service --- src/conf_mode/vpn_sstp.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src') diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py index 23e5162ba..2949ab290 100755 --- a/src/conf_mode/vpn_sstp.py +++ b/src/conf_mode/vpn_sstp.py @@ -26,7 +26,9 @@ from vyos.pki import wrap_certificate from vyos.pki import wrap_private_key from vyos.template import render from vyos.util import call +from vyos.util import check_port_availability from vyos.util import dict_search +from vyos.util import is_listen_port_bind_service from vyos.util import write_file from vyos import ConfigError from vyos import airbag @@ -62,6 +64,12 @@ def verify(sstp): if not sstp: return None + port = sstp.get('port') + proto = 'tcp' + if check_port_availability('0.0.0.0', int(port), proto) is not True and \ + not is_listen_port_bind_service(int(port), 'accel-pppd'): + raise ConfigError(f'"{proto}" port "{port}" is used by another service') + verify_accel_ppp_base_service(sstp) if 'client_ip_pool' not in sstp and 'client_ipv6_pool' not in sstp: -- cgit v1.2.3 From 53bc8022e3beb1f817aec0d263c4d3bb379dcaf7 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Thu, 25 Aug 2022 12:26:15 +0000 Subject: op-mode: T4645: Show nat source stat missing argument --family As we use in commit 8d4205a9 argument '--family' for the function '_get_raw_data_rules(direction, family)' we must use it and for 'nat.py show_statistics' as it get raw data from the same function --- op-mode-definitions/nat.xml.in | 2 +- src/op_mode/nat.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/op-mode-definitions/nat.xml.in b/op-mode-definitions/nat.xml.in index e89c3801f..dbc06b930 100644 --- a/op-mode-definitions/nat.xml.in +++ b/op-mode-definitions/nat.xml.in @@ -22,7 +22,7 @@ Show statistics for configured source NAT rules - ${vyos_op_scripts_dir}/nat.py show_statistics --direction source + ${vyos_op_scripts_dir}/nat.py show_statistics --direction source --family inet diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py index 2dffc378b..dec04aa48 100755 --- a/src/op_mode/nat.py +++ b/src/op_mode/nat.py @@ -188,8 +188,8 @@ def show_rules(raw: bool, direction: str, family: str): return _get_formatted_output_rules(nat_rules, direction, family) -def show_statistics(raw: bool, direction: str): - nat_statistics = _get_raw_data_rules(direction) +def show_statistics(raw: bool, direction: str, family: str): + nat_statistics = _get_raw_data_rules(direction, family) if raw: return nat_statistics else: -- cgit v1.2.3 From 1b5b6d8b9d3e57ac2f2db3402b35b183972302e7 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 25 Aug 2022 18:51:15 +0200 Subject: telegraf: T4617: add VRF support --- data/templates/monitoring/override.conf.j2 | 7 -- data/templates/monitoring/syslog_telegraf.j2 | 5 - .../monitoring/systemd_vyos_telegraf_service.j2 | 16 --- data/templates/monitoring/telegraf.j2 | 122 --------------------- data/templates/telegraf/override.conf.j2 | 15 +++ data/templates/telegraf/syslog_telegraf.j2 | 5 + data/templates/telegraf/telegraf.j2 | 122 +++++++++++++++++++++ .../service-monitoring-telegraf.xml.in | 1 + .../cli/test_service_monitoring_telegraf.py | 2 +- src/conf_mode/service_monitoring_telegraf.py | 60 +++++----- src/systemd/telegraf.service | 15 +++ 11 files changed, 189 insertions(+), 181 deletions(-) delete mode 100644 data/templates/monitoring/override.conf.j2 delete mode 100644 data/templates/monitoring/syslog_telegraf.j2 delete mode 100644 data/templates/monitoring/systemd_vyos_telegraf_service.j2 delete mode 100644 data/templates/monitoring/telegraf.j2 create mode 100644 data/templates/telegraf/override.conf.j2 create mode 100644 data/templates/telegraf/syslog_telegraf.j2 create mode 100644 data/templates/telegraf/telegraf.j2 create mode 100644 src/systemd/telegraf.service (limited to 'src') diff --git a/data/templates/monitoring/override.conf.j2 b/data/templates/monitoring/override.conf.j2 deleted file mode 100644 index 9f1b4ebec..000000000 --- a/data/templates/monitoring/override.conf.j2 +++ /dev/null @@ -1,7 +0,0 @@ -[Unit] -After=vyos-router.service -ConditionPathExists=/run/telegraf/vyos-telegraf.conf -[Service] -Environment=INFLUX_TOKEN={{ influxdb.authentication.token }} -CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN CAP_SYS_ADMIN -AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN diff --git a/data/templates/monitoring/syslog_telegraf.j2 b/data/templates/monitoring/syslog_telegraf.j2 deleted file mode 100644 index cdcbd92a4..000000000 --- a/data/templates/monitoring/syslog_telegraf.j2 +++ /dev/null @@ -1,5 +0,0 @@ -# Generated by /usr/libexec/vyos/conf_mode/service_monitoring_telegraf.py - -$ModLoad omuxsock -$OMUxSockSocket /run/telegraf/telegraf_syslog.sock -*.notice :omuxsock: diff --git a/data/templates/monitoring/systemd_vyos_telegraf_service.j2 b/data/templates/monitoring/systemd_vyos_telegraf_service.j2 deleted file mode 100644 index 234ef5586..000000000 --- a/data/templates/monitoring/systemd_vyos_telegraf_service.j2 +++ /dev/null @@ -1,16 +0,0 @@ -[Unit] -Description=The plugin-driven server agent for reporting metrics into InfluxDB -Documentation=https://github.com/influxdata/telegraf -After=network.target - -[Service] -EnvironmentFile=-/etc/default/telegraf -User=telegraf -ExecStart=/usr/bin/telegraf -config /run/telegraf/vyos-telegraf.conf -config-directory /etc/telegraf/telegraf.d $TELEGRAF_OPTS -ExecReload=/bin/kill -HUP $MAINPID -Restart=on-failure -RestartForceExitStatus=SIGPIPE -KillMode=control-group - -[Install] -WantedBy=multi-user.target diff --git a/data/templates/monitoring/telegraf.j2 b/data/templates/monitoring/telegraf.j2 deleted file mode 100644 index 6b395692b..000000000 --- a/data/templates/monitoring/telegraf.j2 +++ /dev/null @@ -1,122 +0,0 @@ -# Generated by /usr/libexec/vyos/conf_mode/service_monitoring_telegraf.py - -[agent] - interval = "15s" - round_interval = true - metric_batch_size = 1000 - metric_buffer_limit = 10000 - collection_jitter = "5s" - flush_interval = "15s" - flush_jitter = "0s" - precision = "" - debug = false - quiet = false - logfile = "" - hostname = "" - omit_hostname = false -{% if azure_data_explorer is vyos_defined %} -### Azure Data Explorer ### -[[outputs.azure_data_explorer]] - ## The URI property of the Azure Data Explorer resource on Azure - endpoint_url = "{{ azure_data_explorer.url }}" - - ## The Azure Data Explorer database that the metrics will be ingested into. - ## The plugin will NOT generate this database automatically, it's expected that this database already exists before ingestion. - database = "{{ azure_data_explorer.database }}" - metrics_grouping_type = "{{ azure_data_explorer.group_metrics }}" - - ## Name of the single table to store all the metrics (Only needed if metrics_grouping_type is "SingleTable"). -{% if azure_data_explorer.table is vyos_defined and azure_data_explorer.group_metrics == 'SingleTable' %} - table_name = "{{ azure_data_explorer.table }}" -{% endif %} -### End Azure Data Explorer ### -{% endif %} -{% if influxdb is vyos_defined %} -### InfluxDB2 ### -[[outputs.influxdb_v2]] - urls = ["{{ influxdb.url }}:{{ influxdb.port }}"] - insecure_skip_verify = true - token = "$INFLUX_TOKEN" - organization = "{{ influxdb.authentication.organization }}" - bucket = "{{ influxdb.bucket }}" -### End InfluxDB2 ### -{% endif %} -{% if prometheus_client is vyos_defined %} -### Prometheus ### -[[outputs.prometheus_client]] - ## Address to listen on - listen = "{{ prometheus_client.listen_address if prometheus_client.listen_address is vyos_defined else '' }}:{{ prometheus_client.port }}" - metric_version = {{ prometheus_client.metric_version }} -{% if prometheus_client.authentication.username is vyos_defined and prometheus_client.authentication.password is vyos_defined %} - ## Use HTTP Basic Authentication - basic_username = "{{ prometheus_client.authentication.username }}" - basic_password = "{{ prometheus_client.authentication.password }}" -{% endif %} -{% if prometheus_client.allow_from is vyos_defined %} - ip_range = {{ prometheus_client.allow_from }} -{% endif %} -### End Prometheus ### -{% endif %} -{% if splunk is vyos_defined %} -### Splunk ### -[[outputs.http]] - ## URL is the address to send metrics to - url = "{{ splunk.url }}" - ## Timeout for HTTP message - # timeout = "5s" - ## Use TLS but skip chain & host verification -{% if splunk.authentication.insecure is vyos_defined %} - insecure_skip_verify = true -{% endif %} - ## Data format to output - data_format = "splunkmetric" - ## Provides time, index, source overrides for the HEC - splunkmetric_hec_routing = true - ## Additional HTTP headers - [outputs.http.headers] - # Should be set manually to "application/json" for json data_format - Content-Type = "application/json" - Authorization = "Splunk {{ splunk.authentication.token }}" - X-Splunk-Request-Channel = "{{ splunk.authentication.token }}" -### End Splunk ### -{% endif %} -[[inputs.cpu]] - percpu = true - totalcpu = true - collect_cpu_time = false - report_active = false -[[inputs.disk]] - ignore_fs = ["devtmpfs", "devfs"] -[[inputs.diskio]] -[[inputs.mem]] -[[inputs.net]] -[[inputs.system]] -[[inputs.netstat]] -[[inputs.processes]] -[[inputs.kernel]] -[[inputs.interrupts]] -[[inputs.linux_sysctl_fs]] -[[inputs.systemd_units]] -[[inputs.conntrack]] - files = ["ip_conntrack_count","ip_conntrack_max","nf_conntrack_count","nf_conntrack_max"] - dirs = ["/proc/sys/net/ipv4/netfilter","/proc/sys/net/netfilter"] -[[inputs.ethtool]] - interface_include = {{ interfaces_ethernet }} -[[inputs.ntpq]] - dns_lookup = true -[[inputs.internal]] -[[inputs.nstat]] -[[inputs.syslog]] - server = "unixgram:///run/telegraf/telegraf_syslog.sock" - best_effort = true - syslog_standard = "RFC3164" -{% if influxdb_configured is vyos_defined %} -[[inputs.exec]] - commands = [ - "{{ custom_scripts_dir }}/show_firewall_input_filter.py", - "{{ custom_scripts_dir }}/show_interfaces_input_filter.py", - "{{ custom_scripts_dir }}/vyos_services_input_filter.py" - ] - timeout = "10s" - data_format = "influx" -{% endif %} diff --git a/data/templates/telegraf/override.conf.j2 b/data/templates/telegraf/override.conf.j2 new file mode 100644 index 000000000..d30bb19de --- /dev/null +++ b/data/templates/telegraf/override.conf.j2 @@ -0,0 +1,15 @@ +{% set vrf_command = 'ip vrf exec ' ~ vrf ~ ' ' if vrf is vyos_defined else '' %} +[Unit] +After= +After=vyos-router.service +ConditionPathExists=/run/telegraf/telegraf.conf + +[Service] +ExecStart= +ExecStart={{ vrf_command }}/usr/bin/telegraf --config /run/telegraf/telegraf.conf --config-directory /etc/telegraf/telegraf.d --pidfile /run/telegraf/telegraf.pid +PIDFile=/run/telegraf/telegraf.pid +EnvironmentFile= +Environment=INFLUX_TOKEN={{ influxdb.authentication.token }} +CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN CAP_SYS_ADMIN CAP_BPF CAP_DAC_OVERRIDE +AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN + diff --git a/data/templates/telegraf/syslog_telegraf.j2 b/data/templates/telegraf/syslog_telegraf.j2 new file mode 100644 index 000000000..cdcbd92a4 --- /dev/null +++ b/data/templates/telegraf/syslog_telegraf.j2 @@ -0,0 +1,5 @@ +# Generated by /usr/libexec/vyos/conf_mode/service_monitoring_telegraf.py + +$ModLoad omuxsock +$OMUxSockSocket /run/telegraf/telegraf_syslog.sock +*.notice :omuxsock: diff --git a/data/templates/telegraf/telegraf.j2 b/data/templates/telegraf/telegraf.j2 new file mode 100644 index 000000000..6b395692b --- /dev/null +++ b/data/templates/telegraf/telegraf.j2 @@ -0,0 +1,122 @@ +# Generated by /usr/libexec/vyos/conf_mode/service_monitoring_telegraf.py + +[agent] + interval = "15s" + round_interval = true + metric_batch_size = 1000 + metric_buffer_limit = 10000 + collection_jitter = "5s" + flush_interval = "15s" + flush_jitter = "0s" + precision = "" + debug = false + quiet = false + logfile = "" + hostname = "" + omit_hostname = false +{% if azure_data_explorer is vyos_defined %} +### Azure Data Explorer ### +[[outputs.azure_data_explorer]] + ## The URI property of the Azure Data Explorer resource on Azure + endpoint_url = "{{ azure_data_explorer.url }}" + + ## The Azure Data Explorer database that the metrics will be ingested into. + ## The plugin will NOT generate this database automatically, it's expected that this database already exists before ingestion. + database = "{{ azure_data_explorer.database }}" + metrics_grouping_type = "{{ azure_data_explorer.group_metrics }}" + + ## Name of the single table to store all the metrics (Only needed if metrics_grouping_type is "SingleTable"). +{% if azure_data_explorer.table is vyos_defined and azure_data_explorer.group_metrics == 'SingleTable' %} + table_name = "{{ azure_data_explorer.table }}" +{% endif %} +### End Azure Data Explorer ### +{% endif %} +{% if influxdb is vyos_defined %} +### InfluxDB2 ### +[[outputs.influxdb_v2]] + urls = ["{{ influxdb.url }}:{{ influxdb.port }}"] + insecure_skip_verify = true + token = "$INFLUX_TOKEN" + organization = "{{ influxdb.authentication.organization }}" + bucket = "{{ influxdb.bucket }}" +### End InfluxDB2 ### +{% endif %} +{% if prometheus_client is vyos_defined %} +### Prometheus ### +[[outputs.prometheus_client]] + ## Address to listen on + listen = "{{ prometheus_client.listen_address if prometheus_client.listen_address is vyos_defined else '' }}:{{ prometheus_client.port }}" + metric_version = {{ prometheus_client.metric_version }} +{% if prometheus_client.authentication.username is vyos_defined and prometheus_client.authentication.password is vyos_defined %} + ## Use HTTP Basic Authentication + basic_username = "{{ prometheus_client.authentication.username }}" + basic_password = "{{ prometheus_client.authentication.password }}" +{% endif %} +{% if prometheus_client.allow_from is vyos_defined %} + ip_range = {{ prometheus_client.allow_from }} +{% endif %} +### End Prometheus ### +{% endif %} +{% if splunk is vyos_defined %} +### Splunk ### +[[outputs.http]] + ## URL is the address to send metrics to + url = "{{ splunk.url }}" + ## Timeout for HTTP message + # timeout = "5s" + ## Use TLS but skip chain & host verification +{% if splunk.authentication.insecure is vyos_defined %} + insecure_skip_verify = true +{% endif %} + ## Data format to output + data_format = "splunkmetric" + ## Provides time, index, source overrides for the HEC + splunkmetric_hec_routing = true + ## Additional HTTP headers + [outputs.http.headers] + # Should be set manually to "application/json" for json data_format + Content-Type = "application/json" + Authorization = "Splunk {{ splunk.authentication.token }}" + X-Splunk-Request-Channel = "{{ splunk.authentication.token }}" +### End Splunk ### +{% endif %} +[[inputs.cpu]] + percpu = true + totalcpu = true + collect_cpu_time = false + report_active = false +[[inputs.disk]] + ignore_fs = ["devtmpfs", "devfs"] +[[inputs.diskio]] +[[inputs.mem]] +[[inputs.net]] +[[inputs.system]] +[[inputs.netstat]] +[[inputs.processes]] +[[inputs.kernel]] +[[inputs.interrupts]] +[[inputs.linux_sysctl_fs]] +[[inputs.systemd_units]] +[[inputs.conntrack]] + files = ["ip_conntrack_count","ip_conntrack_max","nf_conntrack_count","nf_conntrack_max"] + dirs = ["/proc/sys/net/ipv4/netfilter","/proc/sys/net/netfilter"] +[[inputs.ethtool]] + interface_include = {{ interfaces_ethernet }} +[[inputs.ntpq]] + dns_lookup = true +[[inputs.internal]] +[[inputs.nstat]] +[[inputs.syslog]] + server = "unixgram:///run/telegraf/telegraf_syslog.sock" + best_effort = true + syslog_standard = "RFC3164" +{% if influxdb_configured is vyos_defined %} +[[inputs.exec]] + commands = [ + "{{ custom_scripts_dir }}/show_firewall_input_filter.py", + "{{ custom_scripts_dir }}/show_interfaces_input_filter.py", + "{{ custom_scripts_dir }}/vyos_services_input_filter.py" + ] + timeout = "10s" + data_format = "influx" +{% endif %} diff --git a/interface-definitions/service-monitoring-telegraf.xml.in b/interface-definitions/service-monitoring-telegraf.xml.in index 36f40a539..dc014ee16 100644 --- a/interface-definitions/service-monitoring-telegraf.xml.in +++ b/interface-definitions/service-monitoring-telegraf.xml.in @@ -306,6 +306,7 @@ + #include diff --git a/smoketest/scripts/cli/test_service_monitoring_telegraf.py b/smoketest/scripts/cli/test_service_monitoring_telegraf.py index 1c8cc9759..c1c4044e6 100755 --- a/smoketest/scripts/cli/test_service_monitoring_telegraf.py +++ b/smoketest/scripts/cli/test_service_monitoring_telegraf.py @@ -24,7 +24,7 @@ from vyos.util import process_named_running from vyos.util import read_file PROCESS_NAME = 'telegraf' -TELEGRAF_CONF = '/run/telegraf/vyos-telegraf.conf' +TELEGRAF_CONF = '/run/telegraf/telegraf.conf' base_path = ['service', 'monitoring', 'telegraf'] org = 'log@in.local' token = 'GuRJc12tIzfjnYdKRAIYbxdWd2aTpOT9PVYNddzDnFV4HkAcD7u7-kndTFXjGuXzJN6TTxmrvPODB4mnFcseDV==' diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py index 62f5e1ddf..18b32edab 100755 --- a/src/conf_mode/service_monitoring_telegraf.py +++ b/src/conf_mode/service_monitoring_telegraf.py @@ -22,6 +22,8 @@ from shutil import rmtree from vyos.config import Config from vyos.configdict import dict_merge +from vyos.configdict import is_node_changed +from vyos.configverify import verify_vrf from vyos.ifconfig import Section from vyos.template import render from vyos.util import call @@ -32,20 +34,14 @@ from vyos import ConfigError from vyos import airbag airbag.enable() - -base_dir = '/run/telegraf' cache_dir = f'/etc/telegraf/.cache' -config_telegraf = f'{base_dir}/vyos-telegraf.conf' +config_telegraf = f'/run/telegraf/telegraf.conf' custom_scripts_dir = '/etc/telegraf/custom_scripts' syslog_telegraf = '/etc/rsyslog.d/50-telegraf.conf' -systemd_telegraf_service = '/etc/systemd/system/vyos-telegraf.service' -systemd_telegraf_override_dir = '/etc/systemd/system/vyos-telegraf.service.d' -systemd_override = f'{systemd_telegraf_override_dir}/10-override.conf' - +systemd_override = '/etc/systemd/system/telegraf.service.d/10-override.conf' def get_interfaces(type='', vlan=True): """ - Get interfaces get_interfaces() ['dum0', 'eth0', 'eth1', 'eth1.5', 'lo', 'tun0'] @@ -62,9 +58,7 @@ def get_interfaces(type='', vlan=True): return interfaces def get_nft_filter_chains(): - """ - Get nft chains for table filter - """ + """ Get nft chains for table filter """ nft = cmd('nft --json list table ip filter') nft = json.loads(nft) chain_list = [] @@ -78,7 +72,6 @@ def get_nft_filter_chains(): def get_config(config=None): - if config: conf = config else: @@ -87,8 +80,12 @@ def get_config(config=None): if not conf.exists(base): return None - monitoring = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) + monitoring = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + tmp = is_node_changed(conf, base + ['vrf']) + if tmp: monitoring.update({'restart_required': {}}) # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. @@ -131,6 +128,8 @@ def verify(monitoring): if not monitoring: return None + verify_vrf(monitoring) + # Verify influxdb if 'influxdb' in monitoring: if 'authentication' not in monitoring['influxdb'] or \ @@ -173,7 +172,7 @@ def verify(monitoring): def generate(monitoring): if not monitoring: # Delete config and systemd files - config_files = [config_telegraf, systemd_telegraf_service, systemd_override, syslog_telegraf] + config_files = [config_telegraf, systemd_override, syslog_telegraf] for file in config_files: if os.path.isfile(file): os.unlink(file) @@ -190,33 +189,34 @@ def generate(monitoring): chown(cache_dir, 'telegraf', 'telegraf') - # Create systemd override dir - if not os.path.exists(systemd_telegraf_override_dir): - os.mkdir(systemd_telegraf_override_dir) - # Create custome scripts dir if not os.path.exists(custom_scripts_dir): os.mkdir(custom_scripts_dir) # Render telegraf configuration and systemd override - render(config_telegraf, 'monitoring/telegraf.j2', monitoring) - render(systemd_telegraf_service, 'monitoring/systemd_vyos_telegraf_service.j2', monitoring) - render(systemd_override, 'monitoring/override.conf.j2', monitoring, permission=0o640) - render(syslog_telegraf, 'monitoring/syslog_telegraf.j2', monitoring) - - chown(base_dir, 'telegraf', 'telegraf') + render(config_telegraf, 'telegraf/telegraf.j2', monitoring, user='telegraf', group='telegraf') + render(systemd_override, 'telegraf/override.conf.j2', monitoring) + render(syslog_telegraf, 'telegraf/syslog_telegraf.j2', monitoring) return None def apply(monitoring): # Reload systemd manager configuration + systemd_service = 'telegraf.service' call('systemctl daemon-reload') - if monitoring: - call('systemctl restart vyos-telegraf.service') - else: - call('systemctl stop vyos-telegraf.service') + if not monitoring: + call(f'systemctl stop {systemd_service}') + return + + # we need to restart the service if e.g. the VRF name changed + systemd_action = 'reload-or-restart' + if 'restart_required' in monitoring: + systemd_action = 'restart' + + call(f'systemctl {systemd_action} {systemd_service}') + # Telegraf include custom rsyslog config changes - call('systemctl restart rsyslog') + call('systemctl reload-or-restart rsyslog') if __name__ == '__main__': try: diff --git a/src/systemd/telegraf.service b/src/systemd/telegraf.service new file mode 100644 index 000000000..553942ac6 --- /dev/null +++ b/src/systemd/telegraf.service @@ -0,0 +1,15 @@ +[Unit] +Description=The plugin-driven server agent for reporting metrics into InfluxDB +Documentation=https://github.com/influxdata/telegraf +After=network.target + +[Service] +EnvironmentFile=-/etc/default/telegraf +ExecStart=/usr/bin/telegraf --config /run/telegraf/vyos-telegraf.conf --config-directory /etc/telegraf/telegraf.d +ExecReload=/bin/kill -HUP $MAINPID +Restart=on-failure +RestartForceExitStatus=SIGPIPE +KillMode=control-group + +[Install] +WantedBy=multi-user.target -- cgit v1.2.3 From 7d83077102b56d984fe2ea73ab3cd45f60a27c41 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 25 Aug 2022 18:55:36 +0200 Subject: ntp: T2185: use reload-or-restart on configuration changes --- src/conf_mode/ntp.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py index 5490a794d..0ecb4d736 100755 --- a/src/conf_mode/ntp.py +++ b/src/conf_mode/ntp.py @@ -17,6 +17,7 @@ import os from vyos.config import Config +from vyos.configdict import is_node_changed from vyos.configverify import verify_vrf from vyos.configverify import verify_interface_exists from vyos.util import call @@ -40,6 +41,10 @@ def get_config(config=None): ntp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) ntp['config_file'] = config_file + + tmp = is_node_changed(conf, base + ['vrf']) + if tmp: ntp.update({'restart_required': {}}) + return ntp def verify(ntp): @@ -78,19 +83,25 @@ def generate(ntp): return None def apply(ntp): + systemd_service = 'ntp.service' + # Reload systemd manager configuration + call('systemctl daemon-reload') + if not ntp: # NTP support is removed in the commit - call('systemctl stop ntp.service') + call(f'systemctl stop {systemd_service}') if os.path.exists(config_file): os.unlink(config_file) if os.path.isfile(systemd_override): os.unlink(systemd_override) + return - # Reload systemd manager configuration - call('systemctl daemon-reload') - if ntp: - call('systemctl restart ntp.service') + # we need to restart the service if e.g. the VRF name changed + systemd_action = 'reload-or-restart' + if 'restart_required' in ntp: + systemd_action = 'restart' + call(f'systemctl {systemd_action} {systemd_service}') return None if __name__ == '__main__': -- cgit v1.2.3 From 02e3dbbe53ac15309eb3b809c78ce9f64da1205f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 25 Aug 2022 18:55:44 +0200 Subject: ssh: T2185: use reload-or-restart on configuration changes --- src/conf_mode/ssh.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py index 28669694b..2bbd7142a 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -22,6 +22,7 @@ from syslog import LOG_INFO from vyos.config import Config from vyos.configdict import dict_merge +from vyos.configdict import is_node_changed from vyos.configverify import verify_vrf from vyos.util import call from vyos.template import render @@ -50,6 +51,10 @@ def get_config(config=None): return None ssh = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + tmp = is_node_changed(conf, base + ['vrf']) + if tmp: ssh.update({'restart_required': {}}) + # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. default_values = defaults(base) @@ -104,17 +109,25 @@ def generate(ssh): return None def apply(ssh): + systemd_service_ssh = 'ssh.service' + systemd_service_sshguard = 'sshguard.service' if not ssh: # SSH access is removed in the commit - call('systemctl stop ssh.service') - call('systemctl stop sshguard.service') + call(f'systemctl stop {systemd_service_ssh}') + call(f'systemctl stop {systemd_service_sshguard}') return None + if 'dynamic_protection' not in ssh: - call('systemctl stop sshguard.service') + call(f'systemctl stop {systemd_service_sshguard}') else: - call('systemctl restart sshguard.service') + call(f'systemctl reload-or-restart {systemd_service_sshguard}') + + # we need to restart the service if e.g. the VRF name changed + systemd_action = 'reload-or-restart' + if 'restart_required' in ssh: + systemd_action = 'restart' - call('systemctl restart ssh.service') + call(f'systemctl {systemd_action} {systemd_service_ssh}') return None if __name__ == '__main__': -- cgit v1.2.3 From b4646149b4993578707833df96e972f76a298ef7 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 25 Aug 2022 13:36:43 -0500 Subject: graphql: T4640: add schema defs and resolver support for op-mode errors --- src/services/api/graphql/bindings.py | 3 +- src/services/api/graphql/graphql/errors.py | 8 ++++ src/services/api/graphql/graphql/mutations.py | 13 ++++++- src/services/api/graphql/graphql/queries.py | 13 ++++++- .../api/graphql/session/errors/op_mode_errors.py | 13 +++++++ src/services/api/graphql/session/session.py | 13 ++++--- .../api/graphql/utils/schema_from_op_mode.py | 43 +++++++++++++++++++++- 7 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 src/services/api/graphql/graphql/errors.py create mode 100644 src/services/api/graphql/session/errors/op_mode_errors.py (limited to 'src') diff --git a/src/services/api/graphql/bindings.py b/src/services/api/graphql/bindings.py index 049d59de7..0b1260912 100644 --- a/src/services/api/graphql/bindings.py +++ b/src/services/api/graphql/bindings.py @@ -17,6 +17,7 @@ import vyos.defaults from . graphql.queries import query from . graphql.mutations import mutation from . graphql.directives import directives_dict +from . graphql.errors import op_mode_error from . utils.schema_from_op_mode import generate_op_mode_definitions from ariadne import make_executable_schema, load_schema_from_path, snake_case_fallback_resolvers @@ -27,6 +28,6 @@ def generate_schema(): type_defs = load_schema_from_path(api_schema_dir) - schema = make_executable_schema(type_defs, query, mutation, snake_case_fallback_resolvers, directives=directives_dict) + schema = make_executable_schema(type_defs, query, op_mode_error, mutation, snake_case_fallback_resolvers, directives=directives_dict) return schema diff --git a/src/services/api/graphql/graphql/errors.py b/src/services/api/graphql/graphql/errors.py new file mode 100644 index 000000000..1066300e0 --- /dev/null +++ b/src/services/api/graphql/graphql/errors.py @@ -0,0 +1,8 @@ + +from ariadne import InterfaceType + +op_mode_error = InterfaceType("OpModeError") + +@op_mode_error.type_resolver +def resolve_op_mode_error(obj, *_): + return obj['name'] diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py index c8ae0f516..1b77cff87 100644 --- a/src/services/api/graphql/graphql/mutations.py +++ b/src/services/api/graphql/graphql/mutations.py @@ -22,6 +22,8 @@ from makefun import with_signature from .. import state from .. import key_auth from api.graphql.session.session import Session +from api.graphql.session.errors.op_mode_errors import op_mode_err_msg, op_mode_err_code +from vyos.opmode import Error as OpModeError mutation = ObjectType("Mutation") @@ -86,10 +88,19 @@ def make_mutation_resolver(mutation_name, class_name, session_func): "success": True, "data": data } + except OpModeError as e: + typename = type(e).__name__ + return { + "success": False, + "errore": ['op_mode_error'], + "op_mode_error": {"name": f"{typename}", + "message": op_mode_err_msg.get(typename, "Unknown"), + "vyos_code": op_mode_err_code.get(typename, 9999)} + } except Exception as error: return { "success": False, - "errors": [str(error)] + "errors": [repr(error)] } return func_impl diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py index 921a66274..8ae61b704 100644 --- a/src/services/api/graphql/graphql/queries.py +++ b/src/services/api/graphql/graphql/queries.py @@ -22,6 +22,8 @@ from makefun import with_signature from .. import state from .. import key_auth from api.graphql.session.session import Session +from api.graphql.session.errors.op_mode_errors import op_mode_err_msg, op_mode_err_code +from vyos.opmode import Error as OpModeError query = ObjectType("Query") @@ -86,10 +88,19 @@ def make_query_resolver(query_name, class_name, session_func): "success": True, "data": data } + except OpModeError as e: + typename = type(e).__name__ + return { + "success": False, + "errors": ['op_mode_error'], + "op_mode_error": {"name": f"{typename}", + "message": op_mode_err_msg.get(typename, "Unknown"), + "vyos_code": op_mode_err_code.get(typename, 9999)} + } except Exception as error: return { "success": False, - "errors": [str(error)] + "errors": [repr(error)] } return func_impl diff --git a/src/services/api/graphql/session/errors/op_mode_errors.py b/src/services/api/graphql/session/errors/op_mode_errors.py new file mode 100644 index 000000000..7ba75455d --- /dev/null +++ b/src/services/api/graphql/session/errors/op_mode_errors.py @@ -0,0 +1,13 @@ + + +op_mode_err_msg = { + "UnconfiguredSubsystem": "subsystem is not configured or not running", + "DataUnavailable": "data currently unavailable", + "PermissionDenied": "client does not have permission" +} + +op_mode_err_code = { + "UnconfiguredSubsystem": 2000, + "DataUnavailable": 2001, + "PermissionDenied": 1003 +} diff --git a/src/services/api/graphql/session/session.py b/src/services/api/graphql/session/session.py index 23bc7154c..93e1c328e 100644 --- a/src/services/api/graphql/session/session.py +++ b/src/services/api/graphql/session/session.py @@ -22,6 +22,7 @@ from vyos.config import Config from vyos.configtree import ConfigTree from vyos.defaults import directories from vyos.template import render +from vyos.opmode import Error as OpModeError from api.graphql.utils.util import load_op_mode_as_module, split_compound_op_mode_name @@ -177,10 +178,10 @@ class Session: mod = load_op_mode_as_module(f'{scriptname}') func = getattr(mod, func_name) - if len(list(data)) > 0: + try: res = func(True, **data) - else: - res = func(True) + except OpModeError as e: + raise e return res @@ -199,9 +200,9 @@ class Session: mod = load_op_mode_as_module(f'{scriptname}') func = getattr(mod, func_name) - if len(list(data)) > 0: + try: res = func(**data) - else: - res = func() + except OpModeError as e: + raise e return res diff --git a/src/services/api/graphql/utils/schema_from_op_mode.py b/src/services/api/graphql/utils/schema_from_op_mode.py index f990aae52..379d15250 100755 --- a/src/services/api/graphql/utils/schema_from_op_mode.py +++ b/src/services/api/graphql/utils/schema_from_op_mode.py @@ -21,7 +21,7 @@ import os import json import typing -from inspect import signature, getmembers, isfunction +from inspect import signature, getmembers, isfunction, isclass, getmro from jinja2 import Template from vyos.defaults import directories @@ -35,6 +35,7 @@ SCHEMA_PATH = directories['api_schema'] DATA_DIR = directories['data'] op_mode_include_file = os.path.join(DATA_DIR, 'op-mode-standardized.json') +op_mode_error_schema = 'op_mode_error.graphql' schema_data: dict = {'schema_name': '', 'schema_fields': []} @@ -53,6 +54,7 @@ type {{ schema_name }} { type {{ schema_name }}Result { data: {{ schema_name }} + op_mode_error: OpModeError success: Boolean! errors: [String] } @@ -76,6 +78,7 @@ type {{ schema_name }} { type {{ schema_name }}Result { data: {{ schema_name }} + op_mode_error: OpModeError success: Boolean! errors: [String] } @@ -85,6 +88,21 @@ extend type Mutation { } """ +error_template = """ +interface OpModeError { + name: String! + message: String! + vyos_code: Int! +} +{% for name in error_names %} +type {{ name }} implements OpModeError { + name: String! + message: String! + vyos_code: Int! +} +{%- endfor %} +""" + def _snake_to_pascal_case(name: str) -> str: res = ''.join(map(str.title, name.split('_'))) return res @@ -136,7 +154,30 @@ def create_schema(func_name: str, base_name: str, func: callable) -> str: return res +def create_error_schema(): + from vyos import opmode + + e = Exception + err_types = getmembers(opmode, isclass) + err_types = [k for k in err_types if issubclass(k[1], e)] + # drop base class, to be replaced by interface type. Find the class + # programmatically, in case the base class name changes. + for i in range(len(err_types)): + if err_types[i][1] in getmro(err_types[i-1][1]): + del err_types[i] + break + err_names = [k[0] for k in err_types] + error_data = {'error_names': err_names} + j2_template = Template(error_template) + res = j2_template.render(error_data) + + return res + def generate_op_mode_definitions(): + out = create_error_schema() + with open(f'{SCHEMA_PATH}/{op_mode_error_schema}', 'w') as f: + f.write(out) + with open(op_mode_include_file) as f: op_mode_files = json.load(f) -- cgit v1.2.3 From 141bf8d437b6c0c76fd0fc21659d10d4477c92a0 Mon Sep 17 00:00:00 2001 From: aapostoliuk Date: Thu, 25 Aug 2022 18:59:10 +0300 Subject: opennhrp: T1070: Fixed creating IPSEC tunnel to Hub Fixed creating IPSEC tunnel to Hub. Added continues of execution generator functions. --- src/etc/opennhrp/opennhrp-script.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/etc/opennhrp/opennhrp-script.py b/src/etc/opennhrp/opennhrp-script.py index a5293c97e..bf25a7331 100755 --- a/src/etc/opennhrp/opennhrp-script.py +++ b/src/etc/opennhrp/opennhrp-script.py @@ -81,7 +81,13 @@ def vici_ike_terminate(list_ikeid: list[str]) -> bool: session = vici.Session() for ikeid in list_ikeid: logger.info(f'Terminating IKE SA with id {ikeid}') - session.terminate({'ike-id': ikeid, 'timeout': '-1'}) + session_generator = session.terminate( + {'ike-id': ikeid, 'timeout': '-1'}) + # 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 as err: logger.error(f'Failed to terminate SA for IKE ids {list_ikeid}: {err}') @@ -175,13 +181,18 @@ def vici_initiate(conn: str, child_sa: str, src_addr: str, f'src_addr: {src_addr}, dst_addr: {dest_addr}') try: session = vici.Session() - session.initiate({ + session_generator = session.initiate({ 'ike': conn, 'child': child_sa, 'timeout': '-1', 'my-host': src_addr, 'other-host': dest_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 as err: logger.error(f'Unable to initiate connection {err}') -- cgit v1.2.3 From 5d7a5d433a97c2a51b9cad79b99938f58a24f788 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Fri, 26 Aug 2022 15:52:04 +0000 Subject: nat: nat66: T4650: Rewrite op-mode nat translation Rewrite op-moe "show nat|nat66 translation" to vyos.opmode format Ability to get machine-readable format "raw" --- op-mode-definitions/nat.xml.in | 4 +- op-mode-definitions/nat66.xml.in | 4 +- src/op_mode/nat.py | 101 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/op-mode-definitions/nat.xml.in b/op-mode-definitions/nat.xml.in index dbc06b930..ce0544390 100644 --- a/op-mode-definitions/nat.xml.in +++ b/op-mode-definitions/nat.xml.in @@ -45,7 +45,7 @@ ${vyos_op_scripts_dir}/show_nat_translations.py --type=source --verbose - ${vyos_op_scripts_dir}/show_nat_translations.py --type=source + ${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet @@ -87,7 +87,7 @@ ${vyos_op_scripts_dir}/show_nat_translations.py --type=destination --verbose - ${vyos_op_scripts_dir}/show_nat_translations.py --type=destination + ${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet diff --git a/op-mode-definitions/nat66.xml.in b/op-mode-definitions/nat66.xml.in index aba2d6add..25aa04d59 100644 --- a/op-mode-definitions/nat66.xml.in +++ b/op-mode-definitions/nat66.xml.in @@ -45,7 +45,7 @@ ${vyos_op_scripts_dir}/show_nat66_translations.py --type=source --verbose - ${vyos_op_scripts_dir}/show_nat66_translations.py --type=source + ${vyos_op_scripts_dir}/nat.py show_translations --direction source --family inet6 @@ -87,7 +87,7 @@ ${vyos_op_scripts_dir}/show_nat66_translations.py --type=destination --verbose - ${vyos_op_scripts_dir}/show_nat66_translations.py --type=destination + ${vyos_op_scripts_dir}/nat.py show_translations --direction destination --family inet6 diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py index dec04aa48..1339d5b92 100755 --- a/src/op_mode/nat.py +++ b/src/op_mode/nat.py @@ -17,6 +17,7 @@ import jmespath import json import sys +import xmltodict from sys import exit from tabulate import tabulate @@ -27,6 +28,29 @@ from vyos.util import dict_search import vyos.opmode +def _get_xml_translation(direction, family): + """ + Get conntrack XML output --src-nat|--dst-nat + """ + if direction == 'source': + opt = '--src-nat' + if direction == 'destination': + opt = '--dst-nat' + return cmd(f'sudo conntrack --dump --family {family} {opt} --output xml') + + +def _xml_to_dict(xml): + """ + Convert XML to dictionary + Return: dictionary + """ + parse = xmltodict.parse(xml, attr_prefix='') + # If only one conntrack entry we must change dict + if 'meta' in parse['conntrack']['flow']: + return dict(conntrack={'flow': [parse['conntrack']['flow']]}) + return parse + + def _get_json_data(direction, family): """ Get NAT format JSON @@ -52,6 +76,22 @@ def _get_raw_data_rules(direction, family): return rules +def _get_raw_translation(direction, family): + """ + Return: dictionary + """ + xml = _get_xml_translation(direction, family) + if len(xml) == 0: + output = {'conntrack': + { + 'error': True, + 'reason': 'entries not found' + } + } + return output + return _xml_to_dict(xml) + + def _get_formatted_output_rules(data, direction, family): # Add default values before loop sport, dport, proto = 'any', 'any', 'any' @@ -180,6 +220,58 @@ def _get_formatted_output_statistics(data, direction): return output +def _get_formatted_translation(dict_data, nat_direction, family): + data_entries = [] + if 'error' in dict_data['conntrack']: + return 'Entries not found' + for entry in dict_data['conntrack']['flow']: + orig_src, orig_dst, orig_sport, orig_dport = {}, {}, {}, {} + reply_src, reply_dst, reply_sport, reply_dport = {}, {}, {}, {} + proto = {} + for meta in entry['meta']: + direction = meta['direction'] + if direction in ['original']: + if 'layer3' in meta: + orig_src = meta['layer3']['src'] + orig_dst = meta['layer3']['dst'] + if 'layer4' in meta: + if meta.get('layer4').get('sport'): + orig_sport = meta['layer4']['sport'] + if meta.get('layer4').get('dport'): + orig_dport = meta['layer4']['dport'] + proto = meta['layer4']['protoname'] + if direction in ['reply']: + if 'layer3' in meta: + reply_src = meta['layer3']['src'] + reply_dst = meta['layer3']['dst'] + if 'layer4' in meta: + if meta.get('layer4').get('sport'): + reply_sport = meta['layer4']['sport'] + if meta.get('layer4').get('dport'): + reply_dport = meta['layer4']['dport'] + proto = meta['layer4']['protoname'] + if direction == 'independent': + conn_id = meta['id'] + timeout = meta['timeout'] + orig_src = f'{orig_src}:{orig_sport}' if orig_sport else orig_src + orig_dst = f'{orig_dst}:{orig_dport}' if orig_dport else orig_dst + reply_src = f'{reply_src}:{reply_sport}' if reply_sport else reply_src + reply_dst = f'{reply_dst}:{reply_dport}' if reply_dport else reply_dst + state = meta['state'] if 'state' in meta else '' + mark = meta['mark'] + zone = meta['zone'] if 'zone' in meta else '' + if nat_direction == 'source': + data_entries.append( + [orig_src, reply_dst, proto, timeout, mark, zone]) + elif nat_direction == 'destination': + data_entries.append( + [orig_dst, reply_src, proto, timeout, mark, zone]) + + headers = ["Pre-NAT", "Post-NAT", "Proto", "Timeout", "Mark", "Zone"] + output = tabulate(data_entries, headers, numalign="left") + return output + + def show_rules(raw: bool, direction: str, family: str): nat_rules = _get_raw_data_rules(direction, family) if raw: @@ -196,6 +288,15 @@ def show_statistics(raw: bool, direction: str, family: str): return _get_formatted_output_statistics(nat_statistics, direction) +def show_translations(raw: bool, direction: str, family: str): + family = 'ipv6' if family == 'inet6' else 'ipv4' + nat_translation = _get_raw_translation(direction, family) + if raw: + return nat_translation + else: + return _get_formatted_translation(nat_translation, direction, family) + + if __name__ == '__main__': try: res = vyos.opmode.run(sys.modules[__name__]) -- cgit v1.2.3 From 8a3bc80ca7a3209b045f3e3defc47efe4dc8d1e0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 25 Aug 2022 19:01:21 +0200 Subject: telegraf: T3872: replace local get_interfaces() function with Section.interface() Commit cfde4b49 ("ifconfig: T2223: add vlan switch for Section.interfaces()") added the functionality of the local get_interfaces() function to the base class so all other parts in the system can query for interface names of a given type including or excluding their vlan sub-interfaces. --- src/conf_mode/service_monitoring_telegraf.py | 20 +------------------- .../custom_scripts/show_interfaces_input_filter.py | 16 +--------------- 2 files changed, 2 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/conf_mode/service_monitoring_telegraf.py b/src/conf_mode/service_monitoring_telegraf.py index 18b32edab..53df006a4 100755 --- a/src/conf_mode/service_monitoring_telegraf.py +++ b/src/conf_mode/service_monitoring_telegraf.py @@ -40,23 +40,6 @@ custom_scripts_dir = '/etc/telegraf/custom_scripts' syslog_telegraf = '/etc/rsyslog.d/50-telegraf.conf' systemd_override = '/etc/systemd/system/telegraf.service.d/10-override.conf' -def get_interfaces(type='', vlan=True): - """ - get_interfaces() - ['dum0', 'eth0', 'eth1', 'eth1.5', 'lo', 'tun0'] - - get_interfaces("dummy") - ['dum0'] - """ - interfaces = [] - ifaces = Section.interfaces(type) - for iface in ifaces: - if vlan == False and '.' in iface: - continue - interfaces.append(iface) - - return interfaces - def get_nft_filter_chains(): """ Get nft chains for table filter """ nft = cmd('nft --json list table ip filter') @@ -70,7 +53,6 @@ def get_nft_filter_chains(): return chain_list - def get_config(config=None): if config: conf = config @@ -93,7 +75,7 @@ def get_config(config=None): monitoring = dict_merge(default_values, monitoring) monitoring['custom_scripts_dir'] = custom_scripts_dir - monitoring['interfaces_ethernet'] = get_interfaces('ethernet', vlan=False) + monitoring['interfaces_ethernet'] = Section.interfaces('ethernet', vlan=False) monitoring['nft_chains'] = get_nft_filter_chains() # Redefine azure group-metrics 'single-table' and 'table-per-metric' diff --git a/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py b/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py index 0c7474156..6f14d6a8e 100755 --- a/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py +++ b/src/etc/telegraf/custom_scripts/show_interfaces_input_filter.py @@ -5,20 +5,6 @@ from vyos.ifconfig import Interface import time -def get_interfaces(type='', vlan=True): - """ - Get interfaces: - ['dum0', 'eth0', 'eth1', 'eth1.5', 'lo', 'tun0'] - """ - interfaces = [] - ifaces = Section.interfaces(type) - for iface in ifaces: - if vlan == False and '.' in iface: - continue - interfaces.append(iface) - - return interfaces - def get_interface_addresses(iface, link_local_v6=False): """ Get IP and IPv6 addresses from interface in one string @@ -77,7 +63,7 @@ def get_interface_oper_state(iface): return oper_state -interfaces = get_interfaces() +interfaces = Section.interfaces('') for iface in interfaces: print(f'show_interfaces,interface={iface} ' -- cgit v1.2.3 From aa9633b4358c571e58710dba5330f72f7f893304 Mon Sep 17 00:00:00 2001 From: Viacheslav Hletenko Date: Mon, 29 Aug 2022 11:36:16 +0000 Subject: nat: T4367: Move nat rules from /tmp to /run/nftables_nat.conf Move nftables nat configuration from /tmp to /run As we have for other services like firewall, conntrack Don't remove the config file '/run/nftables_nat.conf' after commit --- src/conf_mode/nat.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'src') diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 85819a77e..a72e82a83 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -44,7 +44,7 @@ if LooseVersion(kernel_version()) > LooseVersion('5.1'): else: k_mod = ['nft_nat', 'nft_chain_nat_ipv4'] -nftables_nat_config = '/tmp/vyos-nat-rules.nft' +nftables_nat_config = '/run/nftables_nat.conf' def get_handler(json, chain, target): """ Get nftable rule handler number of given chain/target combination. @@ -186,16 +186,12 @@ def generate(nat): # dry-run newly generated configuration tmp = run(f'nft -c -f {nftables_nat_config}') if tmp > 0: - if os.path.exists(nftables_nat_config): - os.unlink(nftables_nat_config) raise ConfigError('Configuration file errors encountered!') return None def apply(nat): cmd(f'nft -f {nftables_nat_config}') - if os.path.isfile(nftables_nat_config): - os.unlink(nftables_nat_config) return None -- cgit v1.2.3 From ad1236e8d72ff29e0e2215df175b6f032fba75eb Mon Sep 17 00:00:00 2001 From: zsdc Date: Tue, 30 Aug 2022 12:36:01 +0300 Subject: console: T4646: Fixed USB console issues * fixed the `systemctl restart` command that used a value from config instead converted to `ttyUSBX` * moved systemd units from `/etc/` to `/run/` --- src/conf_mode/system_console.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/conf_mode/system_console.py b/src/conf_mode/system_console.py index 86985d765..e922edc4e 100755 --- a/src/conf_mode/system_console.py +++ b/src/conf_mode/system_console.py @@ -16,6 +16,7 @@ import os import re +from pathlib import Path from vyos.config import Config from vyos.configdict import dict_merge @@ -68,18 +69,15 @@ def verify(console): # amount of connected devices. We will resolve the fixed device name # to its dynamic device file - and create a new dict entry for it. by_bus_device = f'{by_bus_dir}/{device}' - if os.path.isdir(by_bus_dir) and os.path.exists(by_bus_device): - device = os.path.basename(os.readlink(by_bus_device)) - - # If the device name still starts with usbXXX no matching tty was found - # and it can not be used as a serial interface - if device.startswith('usb'): - raise ConfigError(f'Device {device} does not support beeing used as tty') + # If the device name still starts with usbXXX no matching tty was found + # and it can not be used as a serial interface + if not os.path.isdir(by_bus_dir) or not os.path.exists(by_bus_device): + raise ConfigError(f'Device {device} does not support beeing used as tty') return None def generate(console): - base_dir = '/etc/systemd/system' + base_dir = '/run/systemd/system' # Remove all serial-getty configuration files in advance for root, dirs, files in os.walk(base_dir): for basename in files: @@ -90,7 +88,8 @@ def generate(console): if not console or 'device' not in console: return None - for device, device_config in console['device'].items(): + # replace keys in the config for ttyUSB items to use them in `apply()` later + for device in console['device'].copy(): if device.startswith('usb'): # It is much easiert to work with the native ttyUSBn name when using # getty, but that name may change across reboots - depending on the @@ -98,9 +97,17 @@ def generate(console): # to its dynamic device file - and create a new dict entry for it. by_bus_device = f'{by_bus_dir}/{device}' if os.path.isdir(by_bus_dir) and os.path.exists(by_bus_device): - device = os.path.basename(os.readlink(by_bus_device)) + device_updated = os.path.basename(os.readlink(by_bus_device)) + + # replace keys in the config to use them in `apply()` later + console['device'][device_updated] = console['device'][device] + del console['device'][device] + else: + raise ConfigError(f'Device {device} does not support beeing used as tty') + for device, device_config in console['device'].items(): config_file = base_dir + f'/serial-getty@{device}.service' + Path(f'{base_dir}/getty.target.wants').mkdir(exist_ok=True) getty_wants_symlink = base_dir + f'/getty.target.wants/serial-getty@{device}.service' render(config_file, 'getty/serial-getty.service.j2', device_config) -- cgit v1.2.3 From 69f79beee2070906b68f2b910296c362e7216278 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 30 Aug 2022 17:36:19 +0200 Subject: firewall: T4655: implement XML defaultValue for name and ipv6-name This extends the implementation of commit 0cc7e0a49094 ("firewall: T4655: Fix default action 'drop' for the firewall") in a way that we can now also use the XML node under "firewall name" and "firewall ipv6-name". This is a much cleaner approach which also adds the default value automatically to the CLIs completion helper ("?"). --- .../include/firewall/default-action.xml.i | 1 + python/vyos/template.py | 2 +- src/conf_mode/firewall.py | 30 +++++++++++++++++++--- 3 files changed, 28 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/interface-definitions/include/firewall/default-action.xml.i b/interface-definitions/include/firewall/default-action.xml.i index b11dfd2e8..92a2fcaaf 100644 --- a/interface-definitions/include/firewall/default-action.xml.i +++ b/interface-definitions/include/firewall/default-action.xml.i @@ -21,5 +21,6 @@ (drop|reject|accept) + drop diff --git a/python/vyos/template.py b/python/vyos/template.py index 62303bd55..9804308c1 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -550,7 +550,7 @@ def nft_rule(rule_conf, fw_name, rule_id, ip_name='ip'): @register_filter('nft_default_rule') def nft_default_rule(fw_conf, fw_name): output = ['counter'] - default_action = fw_conf.get('default_action', 'drop') + default_action = fw_conf['default_action'] if 'enable_default_log' in fw_conf: action_suffix = default_action[:1].upper() diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 07eca722f..f0ea1a1e5 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -206,9 +206,31 @@ def get_config(config=None): firewall = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) + # We have gathered the dict representation of the CLI, but there are + # default options which we need to update into the dictionary retrived. + # XXX: T2665: we currently have no nice way for defaults under tag + # nodes, thus we load the defaults "by hand" default_values = defaults(base) + for tmp in ['name', 'ipv6_name']: + if tmp in default_values: + del default_values[tmp] + firewall = dict_merge(default_values, firewall) + # Merge in defaults for IPv4 ruleset + if 'name' in firewall: + default_values = defaults(base + ['name']) + for name in firewall['name']: + firewall['name'][name] = dict_merge(default_values, + firewall['name'][name]) + + # Merge in defaults for IPv6 ruleset + if 'ipv6_name' in firewall: + default_values = defaults(base + ['ipv6-name']) + for ipv6_name in firewall['ipv6_name']: + firewall['ipv6_name'][ipv6_name] = dict_merge(default_values, + firewall['ipv6_name'][ipv6_name]) + firewall['policy_resync'] = bool('group' in firewall or node_changed(conf, base + ['group'])) firewall['interfaces'] = get_firewall_interfaces(conf) firewall['zone_policy'] = get_firewall_zones(conf) @@ -315,7 +337,7 @@ def verify_nested_group(group_name, group, groups, seen): if g in seen: raise ConfigError(f'Group "{group_name}" has a circular reference') - + seen.append(g) if 'include' in groups[g]: @@ -378,7 +400,7 @@ def cleanup_commands(firewall): if firewall['geoip_updated']: geoip_key = 'deleted_ipv6_name' if table == 'ip6 filter' else 'deleted_name' geoip_list = dict_search_args(firewall, 'geoip_updated', geoip_key) or [] - + json_str = cmd(f'nft -t -j list table {table}') obj = loads(json_str) @@ -420,7 +442,7 @@ def cleanup_commands(firewall): if set_name.startswith('GEOIP_CC_') and set_name in geoip_list: commands_sets.append(f'delete set {table} {set_name}') continue - + if set_name.startswith("RECENT_"): commands_sets.append(f'delete set {table} {set_name}') continue @@ -520,7 +542,7 @@ def apply(firewall): if install_result == 1: raise ConfigError('Failed to apply firewall') - # set fireall group domain-group xxx + # set firewall group domain-group xxx if 'group' in firewall: if 'domain_group' in firewall['group']: # T970 Enable a resolver (systemd daemon) that checks -- cgit v1.2.3