diff options
author | Daniil Baturin <daniil@baturin.org> | 2025-04-14 11:56:36 +0100 |
---|---|---|
committer | Daniil Baturin <daniil@baturin.org> | 2025-04-14 16:49:52 +0100 |
commit | 5797e2c0f489272f2ff32b3884ea756f306b50e4 (patch) | |
tree | 2f3d13abe9e2dfb737941e3edc3ee2ffacb82e56 | |
parent | 29cc3d790fa690356016a7df21ec75b12b84cb3c (diff) | |
download | vyos-1x-5797e2c0f489272f2ff32b3884ea756f306b50e4.tar.gz vyos-1x-5797e2c0f489272f2ff32b3884ea756f306b50e4.zip |
ids: T7241: remove Fastnetmon from the base system
It will eventually be moved to an addon
-rw-r--r-- | data/templates/ids/fastnetmon.j2 | 121 | ||||
-rw-r--r-- | data/templates/ids/fastnetmon_excluded_networks_list.j2 | 5 | ||||
-rw-r--r-- | data/templates/ids/fastnetmon_networks_list.j2 | 5 | ||||
-rw-r--r-- | debian/control | 1 | ||||
-rw-r--r-- | debian/vyos-1x.postinst | 2 | ||||
-rw-r--r-- | interface-definitions/include/version/ids-version.xml.i | 2 | ||||
-rw-r--r-- | interface-definitions/service_ids_ddos-protection.xml.in | 167 | ||||
-rw-r--r-- | op-mode-definitions/monitor-log.xml.in | 13 | ||||
-rwxr-xr-x | op-mode-definitions/show-log.xml.in | 13 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_service_ids_ddos-protection.py | 116 | ||||
-rwxr-xr-x | src/conf_mode/service_ids_ddos-protection.py | 104 | ||||
-rw-r--r-- | src/etc/systemd/system/fastnetmon.service.d/override.conf | 12 | ||||
-rw-r--r-- | src/migration-scripts/ids/1-to-2 | 30 |
13 files changed, 31 insertions, 560 deletions
diff --git a/data/templates/ids/fastnetmon.j2 b/data/templates/ids/fastnetmon.j2 deleted file mode 100644 index f6f03d0db..000000000 --- a/data/templates/ids/fastnetmon.j2 +++ /dev/null @@ -1,121 +0,0 @@ -# enable this option if you want to send logs to local syslog facility -logging:logging_level = debug -logging:local_syslog_logging = on - -# list of all your networks in CIDR format -networks_list_path = /run/fastnetmon/networks_list - -# list networks in CIDR format which will be not monitored for attacks -white_list_path = /run/fastnetmon/excluded_networks_list - -# Enable/Disable any actions in case of attack -enable_ban = on -enable_ban_ipv6 = on - -## How many packets will be collected from attack traffic -ban_details_records_count = 500 - -## How long (in seconds) we should keep an IP in blocked state -## If you set 0 here it completely disables unban capability -{% if ban_time is vyos_defined %} -ban_time = {{ ban_time }} -{% endif %} - -# Check if the attack is still active, before triggering an unban callback with this option -# If the attack is still active, check each run of the unban watchdog -unban_only_if_attack_finished = on - -# enable per subnet speed meters -# For each subnet, list track speed in bps and pps for both directions -enable_subnet_counters = off - -{% if mode is vyos_defined('mirror') %} -mirror_afpacket = on -{% elif mode is vyos_defined('sflow') %} -sflow = on -{% if sflow.port is vyos_defined %} -sflow_port = {{ sflow.port }} -{% endif %} -{% if sflow.listen_address is vyos_defined %} -sflow_host = {{ sflow.listen_address }} -{% endif %} -{% endif %} - - -process_incoming_traffic = {{ 'on' if direction is vyos_defined and 'in' in direction else 'off' }} -process_outgoing_traffic = {{ 'on' if direction is vyos_defined and 'out' in direction else 'off' }} - -{% if threshold is vyos_defined %} -{% if threshold.general is vyos_defined %} -# General threshold -{% for thr, thr_value in threshold.general.items() %} -{% if thr is vyos_defined('fps') %} -ban_for_flows = on -threshold_flows = {{ thr_value }} -{% elif thr is vyos_defined('mbps') %} -ban_for_bandwidth = on -threshold_mbps = {{ thr_value }} -{% elif thr is vyos_defined('pps') %} -ban_for_pps = on -threshold_pps = {{ thr_value }} -{% endif %} -{% endfor %} -{% endif %} - -{% if threshold.tcp is vyos_defined %} -# TCP threshold -{% for thr, thr_value in threshold.tcp.items() %} -{% if thr is vyos_defined('fps') %} -ban_for_tcp_flows = on -threshold_tcp_flows = {{ thr_value }} -{% elif thr is vyos_defined('mbps') %} -ban_for_tcp_bandwidth = on -threshold_tcp_mbps = {{ thr_value }} -{% elif thr is vyos_defined('pps') %} -ban_for_tcp_pps = on -threshold_tcp_pps = {{ thr_value }} -{% endif %} -{% endfor %} -{% endif %} - -{% if threshold.udp is vyos_defined %} -# UDP threshold -{% for thr, thr_value in threshold.udp.items() %} -{% if thr is vyos_defined('fps') %} -ban_for_udp_flows = on -threshold_udp_flows = {{ thr_value }} -{% elif thr is vyos_defined('mbps') %} -ban_for_udp_bandwidth = on -threshold_udp_mbps = {{ thr_value }} -{% elif thr is vyos_defined('pps') %} -ban_for_udp_pps = on -threshold_udp_pps = {{ thr_value }} -{% endif %} -{% endfor %} -{% endif %} - -{% if threshold.icmp is vyos_defined %} -# ICMP threshold -{% for thr, thr_value in threshold.icmp.items() %} -{% if thr is vyos_defined('fps') %} -ban_for_icmp_flows = on -threshold_icmp_flows = {{ thr_value }} -{% elif thr is vyos_defined('mbps') %} -ban_for_icmp_bandwidth = on -threshold_icmp_mbps = {{ thr_value }} -{% elif thr is vyos_defined('pps') %} -ban_for_icmp_pps = on -threshold_icmp_pps = {{ thr_value }} -{% endif %} -{% endfor %} -{% endif %} - -{% endif %} - -{% if listen_interface is vyos_defined %} -interfaces = {{ listen_interface | join(',') }} -{% endif %} - -{% if alert_script is vyos_defined %} -notify_script_path = {{ alert_script }} -{% endif %} diff --git a/data/templates/ids/fastnetmon_excluded_networks_list.j2 b/data/templates/ids/fastnetmon_excluded_networks_list.j2 deleted file mode 100644 index c88a1c527..000000000 --- a/data/templates/ids/fastnetmon_excluded_networks_list.j2 +++ /dev/null @@ -1,5 +0,0 @@ -{% if excluded_network is vyos_defined %} -{% for net in excluded_network %} -{{ net }} -{% endfor %} -{% endif %} diff --git a/data/templates/ids/fastnetmon_networks_list.j2 b/data/templates/ids/fastnetmon_networks_list.j2 deleted file mode 100644 index 0a0576d2a..000000000 --- a/data/templates/ids/fastnetmon_networks_list.j2 +++ /dev/null @@ -1,5 +0,0 @@ -{% if network is vyos_defined %} -{% for net in network %} -{{ net }} -{% endfor %} -{% endif %} diff --git a/debian/control b/debian/control index a19461412..c1d2b2f96 100644 --- a/debian/control +++ b/debian/control @@ -192,7 +192,6 @@ Depends: ddclient (>= 3.11.1), # End "service dns dynamic" # # For "service ids" - fastnetmon [amd64], suricata, suricata-update, # End "service ids" diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index d83634cfc..ee2748969 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -221,11 +221,9 @@ fi # Remove unwanted daemon files from /etc # conntackd # pmacct -# fastnetmon # ntp DELETE="/etc/logrotate.d/conntrackd.distrib /etc/init.d/conntrackd /etc/default/conntrackd /etc/default/pmacctd /etc/pmacct - /etc/networks_list /etc/networks_whitelist /etc/fastnetmon.conf /etc/ntp.conf /etc/default/ssh /etc/avahi/avahi-daemon.conf /etc/avahi/hosts /etc/powerdns /etc/default/pdns-recursor /etc/ppp/ip-up.d/0000usepeerdns /etc/ppp/ip-down.d/0000usepeerdns" diff --git a/interface-definitions/include/version/ids-version.xml.i b/interface-definitions/include/version/ids-version.xml.i index 9133be02b..6d4e92c21 100644 --- a/interface-definitions/include/version/ids-version.xml.i +++ b/interface-definitions/include/version/ids-version.xml.i @@ -1,3 +1,3 @@ <!-- include start from include/version/ids-version.xml.i --> -<syntaxVersion component='ids' version='1'></syntaxVersion> +<syntaxVersion component='ids' version='2'></syntaxVersion> <!-- include end --> diff --git a/interface-definitions/service_ids_ddos-protection.xml.in b/interface-definitions/service_ids_ddos-protection.xml.in deleted file mode 100644 index 3ef2640b3..000000000 --- a/interface-definitions/service_ids_ddos-protection.xml.in +++ /dev/null @@ -1,167 +0,0 @@ -<?xml version="1.0"?> -<interfaceDefinition> - <node name="service"> - <children> - <node name="ids"> - <properties> - <help>Intrusion Detection System</help> - </properties> - <children> - <node name="ddos-protection" owner="${vyos_conf_scripts_dir}/service_ids_ddos-protection.py"> - <properties> - <help>FastNetMon detection and protection parameters</help> - <priority>731</priority> - </properties> - <children> - <leafNode name="alert-script"> - <properties> - <help>Path to fastnetmon alert script</help> - </properties> - </leafNode> - <leafNode name="ban-time"> - <properties> - <help>How long we should keep an IP in blocked state</help> - <valueHelp> - <format>u32:1-4294967294</format> - <description>Time in seconds</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-4294967294"/> - </constraint> - </properties> - <defaultValue>1900</defaultValue> - </leafNode> - <leafNode name="direction"> - <properties> - <help>Direction for processing traffic</help> - <completionHelp> - <list>in out</list> - </completionHelp> - <constraint> - <regex>(in|out)</regex> - </constraint> - <multi/> - </properties> - </leafNode> - <leafNode name="excluded-network"> - <properties> - <help>Specify IPv4 and IPv6 networks which are going to be excluded from protection</help> - <valueHelp> - <format>ipv4net</format> - <description>IPv4 prefix(es) to exclude</description> - </valueHelp> - <valueHelp> - <format>ipv6net</format> - <description>IPv6 prefix(es) to exclude</description> - </valueHelp> - <constraint> - <validator name="ipv4-prefix"/> - <validator name="ipv6-prefix"/> - </constraint> - <multi/> - </properties> - </leafNode> - <leafNode name="listen-interface"> - <properties> - <help>Listen interface for mirroring traffic</help> - <completionHelp> - <script>${vyos_completion_dir}/list_interfaces</script> - </completionHelp> - <multi/> - </properties> - </leafNode> - <leafNode name="mode"> - <properties> - <help>Traffic capture mode</help> - <completionHelp> - <list>mirror sflow</list> - </completionHelp> - <valueHelp> - <format>mirror</format> - <description>Listen to mirrored traffic</description> - </valueHelp> - <valueHelp> - <format>sflow</format> - <description>Capture sFlow flows</description> - </valueHelp> - <constraint> - <regex>(mirror|sflow)</regex> - </constraint> - </properties> - </leafNode> - <node name="sflow"> - <properties> - <help>Sflow settings</help> - </properties> - <children> - #include <include/listen-address-ipv4-single.xml.i> - #include <include/port-number.xml.i> - <leafNode name="port"> - <defaultValue>6343</defaultValue> - </leafNode> - </children> - </node> - <leafNode name="network"> - <properties> - <help>Specify IPv4 and IPv6 networks which belong to you</help> - <valueHelp> - <format>ipv4net</format> - <description>Your IPv4 prefix(es)</description> - </valueHelp> - <valueHelp> - <format>ipv6net</format> - <description>Your IPv6 prefix(es)</description> - </valueHelp> - <constraint> - <validator name="ipv4-prefix"/> - <validator name="ipv6-prefix"/> - </constraint> - <multi/> - </properties> - </leafNode> - <node name="threshold"> - <properties> - <help>Attack limits thresholds</help> - </properties> - <children> - <node name="general"> - <properties> - <help>General threshold</help> - </properties> - <children> - #include <include/ids/threshold.xml.i> - </children> - </node> - <node name="tcp"> - <properties> - <help>TCP threshold</help> - </properties> - <children> - #include <include/ids/threshold.xml.i> - </children> - </node> - <node name="udp"> - <properties> - <help>UDP threshold</help> - </properties> - <children> - #include <include/ids/threshold.xml.i> - </children> - </node> - <node name="icmp"> - <properties> - <help>ICMP threshold</help> - </properties> - <children> - #include <include/ids/threshold.xml.i> - </children> - </node> - </children> - </node> - </children> - </node> - </children> - </node> - </children> - </node> -</interfaceDefinition> diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in index 6a2b7e53b..cde28f284 100644 --- a/op-mode-definitions/monitor-log.xml.in +++ b/op-mode-definitions/monitor-log.xml.in @@ -17,19 +17,6 @@ </properties> <command>SYSTEMD_LOG_COLOR=false grc journalctl --no-hostname --follow --boot</command> </node> - <node name="ids"> - <properties> - <help>Monitor Intrusion Detection System log</help> - </properties> - <children> - <leafNode name="ddos-protection"> - <properties> - <help>Monitor last lines of DDOS protection</help> - </properties> - <command>journalctl --no-hostname --follow --boot --unit fastnetmon.service</command> - </leafNode> - </children> - </node> <leafNode name="certbot"> <properties> <help>Monitor last lines of certbot log</help> diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index c2504686d..e792ace0e 100755 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -62,19 +62,6 @@ </properties> <command>journalctl --no-hostname --boot --unit conserver-server.service</command> </leafNode> - <node name="ids"> - <properties> - <help>Show log for for Intrusion Detection System</help> - </properties> - <children> - <leafNode name="ddos-protection"> - <properties> - <help>Show log for DDOS protection</help> - </properties> - <command>journalctl --no-hostname --boot --unit fastnetmon.service</command> - </leafNode> - </children> - </node> <node name="dhcp"> <properties> <help>Show log for Dynamic Host Control Protocol (DHCP)</help> diff --git a/smoketest/scripts/cli/test_service_ids_ddos-protection.py b/smoketest/scripts/cli/test_service_ids_ddos-protection.py deleted file mode 100755 index 91b056eea..000000000 --- a/smoketest/scripts/cli/test_service_ids_ddos-protection.py +++ /dev/null @@ -1,116 +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 <http://www.gnu.org/licenses/>. - -import os -import unittest - -from base_vyostest_shim import VyOSUnitTestSHIM - -from vyos.configsession import ConfigSessionError -from vyos.utils.process import process_named_running -from vyos.utils.file import read_file - -PROCESS_NAME = 'fastnetmon' -FASTNETMON_CONF = '/run/fastnetmon/fastnetmon.conf' -NETWORKS_CONF = '/run/fastnetmon/networks_list' -EXCLUDED_NETWORKS_CONF = '/run/fastnetmon/excluded_networks_list' -base_path = ['service', 'ids', 'ddos-protection'] - -class TestServiceIDS(VyOSUnitTestSHIM.TestCase): - @classmethod - def setUpClass(cls): - super(TestServiceIDS, cls).setUpClass() - - # ensure we can also run this test on a live system - so lets clean - # out the current configuration :) - cls.cli_delete(cls, base_path) - - def tearDown(self): - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - - # delete test config - self.cli_delete(base_path) - self.cli_commit() - - self.assertFalse(os.path.exists(FASTNETMON_CONF)) - self.assertFalse(process_named_running(PROCESS_NAME)) - - def test_fastnetmon(self): - networks = ['10.0.0.0/24', '10.5.5.0/24', '2001:db8:10::/64', '2001:db8:20::/64'] - excluded_networks = ['10.0.0.1/32', '2001:db8:10::1/128'] - interfaces = ['eth0', 'eth1'] - fps = '3500' - mbps = '300' - pps = '60000' - - self.cli_set(base_path + ['mode', 'mirror']) - # Required network! - with self.assertRaises(ConfigSessionError): - self.cli_commit() - for tmp in networks: - self.cli_set(base_path + ['network', tmp]) - - # optional excluded-network! - with self.assertRaises(ConfigSessionError): - self.cli_commit() - for tmp in excluded_networks: - self.cli_set(base_path + ['excluded-network', tmp]) - - # Required interface(s)! - with self.assertRaises(ConfigSessionError): - self.cli_commit() - for tmp in interfaces: - self.cli_set(base_path + ['listen-interface', tmp]) - - self.cli_set(base_path + ['direction', 'in']) - self.cli_set(base_path + ['threshold', 'general', 'fps', fps]) - self.cli_set(base_path + ['threshold', 'general', 'pps', pps]) - self.cli_set(base_path + ['threshold', 'general', 'mbps', mbps]) - - # commit changes - self.cli_commit() - - # Check configured port - config = read_file(FASTNETMON_CONF) - self.assertIn(f'mirror_afpacket = on', config) - self.assertIn(f'process_incoming_traffic = on', config) - self.assertIn(f'process_outgoing_traffic = off', config) - self.assertIn(f'ban_for_flows = on', config) - self.assertIn(f'threshold_flows = {fps}', config) - self.assertIn(f'ban_for_bandwidth = on', config) - self.assertIn(f'threshold_mbps = {mbps}', config) - self.assertIn(f'ban_for_pps = on', config) - self.assertIn(f'threshold_pps = {pps}', config) - # default - self.assertIn(f'enable_ban = on', config) - self.assertIn(f'enable_ban_ipv6 = on', config) - self.assertIn(f'ban_time = 1900', config) - - tmp = ','.join(interfaces) - self.assertIn(f'interfaces = {tmp}', config) - - - network_config = read_file(NETWORKS_CONF) - for tmp in networks: - self.assertIn(f'{tmp}', network_config) - - excluded_network_config = read_file(EXCLUDED_NETWORKS_CONF) - for tmp in excluded_networks: - self.assertIn(f'{tmp}', excluded_network_config) - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/src/conf_mode/service_ids_ddos-protection.py b/src/conf_mode/service_ids_ddos-protection.py deleted file mode 100755 index 276a71fcb..000000000 --- a/src/conf_mode/service_ids_ddos-protection.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-2023 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import os - -from sys import exit - -from vyos.config import Config -from vyos.template import render -from vyos.utils.process import call -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -config_file = r'/run/fastnetmon/fastnetmon.conf' -networks_list = r'/run/fastnetmon/networks_list' -excluded_networks_list = r'/run/fastnetmon/excluded_networks_list' -attack_dir = '/var/log/fastnetmon_attacks' - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - base = ['service', 'ids', 'ddos-protection'] - if not conf.exists(base): - return None - - fastnetmon = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, - with_recursive_defaults=True) - - return fastnetmon - -def verify(fastnetmon): - if not fastnetmon: - return None - - if 'mode' not in fastnetmon: - raise ConfigError('Specify operating mode!') - - if fastnetmon.get('mode') == 'mirror' and 'listen_interface' not in fastnetmon: - raise ConfigError("Incorrect settings for 'mode mirror': must specify interface(s) for traffic mirroring") - - if fastnetmon.get('mode') == 'sflow' and 'listen_address' not in fastnetmon.get('sflow', {}): - raise ConfigError("Incorrect settings for 'mode sflow': must specify sFlow 'listen-address'") - - if 'alert_script' in fastnetmon: - if os.path.isfile(fastnetmon['alert_script']): - # Check script permissions - if not os.access(fastnetmon['alert_script'], os.X_OK): - raise ConfigError('Script "{alert_script}" is not executable!'.format(fastnetmon['alert_script'])) - else: - raise ConfigError('File "{alert_script}" does not exists!'.format(fastnetmon)) - -def generate(fastnetmon): - if not fastnetmon: - for file in [config_file, networks_list]: - if os.path.isfile(file): - os.unlink(file) - - return None - - # Create dir for log attack details - if not os.path.exists(attack_dir): - os.mkdir(attack_dir) - - render(config_file, 'ids/fastnetmon.j2', fastnetmon) - render(networks_list, 'ids/fastnetmon_networks_list.j2', fastnetmon) - render(excluded_networks_list, 'ids/fastnetmon_excluded_networks_list.j2', fastnetmon) - return None - -def apply(fastnetmon): - systemd_service = 'fastnetmon.service' - if not fastnetmon: - # Stop fastnetmon service if removed - call(f'systemctl stop {systemd_service}') - else: - call(f'systemctl reload-or-restart {systemd_service}') - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/etc/systemd/system/fastnetmon.service.d/override.conf b/src/etc/systemd/system/fastnetmon.service.d/override.conf deleted file mode 100644 index 841666070..000000000 --- a/src/etc/systemd/system/fastnetmon.service.d/override.conf +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -RequiresMountsFor=/run -ConditionPathExists=/run/fastnetmon/fastnetmon.conf -After= -After=vyos-router.service - -[Service] -Type=simple -WorkingDirectory=/run/fastnetmon -PIDFile=/run/fastnetmon.pid -ExecStart= -ExecStart=/usr/sbin/fastnetmon --configuration_file /run/fastnetmon/fastnetmon.conf diff --git a/src/migration-scripts/ids/1-to-2 b/src/migration-scripts/ids/1-to-2 new file mode 100644 index 000000000..4c0333c88 --- /dev/null +++ b/src/migration-scripts/ids/1-to-2 @@ -0,0 +1,30 @@ +# Copyright 2025 VyOS maintainers and contributors <maintainers@vyos.io> +# +# 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 <http://www.gnu.org/licenses/>. + +# T: Migrate threshold and add new threshold types + +from vyos.configtree import ConfigTree + +# The old 'service ids' path was only used for FastNetMon +# Suricata is in 'service suricata', +# so this isn't an overreach +base = ['service', 'ids'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + else: + config.delete(base) |