diff options
-rw-r--r-- | data/templates/ids/fastnetmon.j2 | 3 | ||||
-rw-r--r-- | data/templates/ids/fastnetmon_excluded_networks_list.j2 | 5 | ||||
-rw-r--r-- | interface-definitions/service-ids-ddos-protection.xml.in | 18 | ||||
-rw-r--r-- | op-mode-definitions/nat.xml.in | 2 | ||||
-rw-r--r-- | op-mode-definitions/show-vrf.xml.in | 2 | ||||
-rw-r--r-- | op-mode-definitions/vpn-ipsec.xml.in | 3 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_protocols_bgp.py | 26 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_service_ids.py | 12 | ||||
-rwxr-xr-x | src/conf_mode/flow_accounting_conf.py | 9 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-vxlan.py | 5 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-wwan.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/protocols_bgp.py | 6 | ||||
-rwxr-xr-x | src/conf_mode/service_ids_fastnetmon.py | 2 | ||||
-rwxr-xr-x | src/etc/opennhrp/opennhrp-script.py | 2 | ||||
-rw-r--r-- | src/etc/systemd/system/fastnetmon.service.d/override.conf | 2 | ||||
-rwxr-xr-x | src/op_mode/nat.py | 29 | ||||
-rwxr-xr-x | src/op_mode/show_vrf.py | 66 | ||||
-rwxr-xr-x | src/op_mode/vpn_ipsec.py | 5 | ||||
-rwxr-xr-x | src/op_mode/vrf.py | 20 |
19 files changed, 140 insertions, 79 deletions
diff --git a/data/templates/ids/fastnetmon.j2 b/data/templates/ids/fastnetmon.j2 index 005338836..b9f77a257 100644 --- a/data/templates/ids/fastnetmon.j2 +++ b/data/templates/ids/fastnetmon.j2 @@ -5,6 +5,9 @@ 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 diff --git a/data/templates/ids/fastnetmon_excluded_networks_list.j2 b/data/templates/ids/fastnetmon_excluded_networks_list.j2 new file mode 100644 index 000000000..c88a1c527 --- /dev/null +++ b/data/templates/ids/fastnetmon_excluded_networks_list.j2 @@ -0,0 +1,5 @@ +{% if excluded_network is vyos_defined %} +{% for net in excluded_network %} +{{ net }} +{% endfor %} +{% endif %} diff --git a/interface-definitions/service-ids-ddos-protection.xml.in b/interface-definitions/service-ids-ddos-protection.xml.in index a176d6fff..86fc4dffa 100644 --- a/interface-definitions/service-ids-ddos-protection.xml.in +++ b/interface-definitions/service-ids-ddos-protection.xml.in @@ -43,6 +43,24 @@ <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> diff --git a/op-mode-definitions/nat.xml.in b/op-mode-definitions/nat.xml.in index 84e999995..b0ec8989f 100644 --- a/op-mode-definitions/nat.xml.in +++ b/op-mode-definitions/nat.xml.in @@ -22,7 +22,7 @@ <properties> <help>Show statistics for configured source NAT rules</help> </properties> - <command>${vyos_op_scripts_dir}/show_nat_statistics.py --source</command> + <command>${vyos_op_scripts_dir}/nat.py show_statistics --direction source</command> </node> <node name="translations"> <properties> diff --git a/op-mode-definitions/show-vrf.xml.in b/op-mode-definitions/show-vrf.xml.in index d8d5284d7..0e0370445 100644 --- a/op-mode-definitions/show-vrf.xml.in +++ b/op-mode-definitions/show-vrf.xml.in @@ -15,7 +15,7 @@ <path>vrf name</path> </completionHelp> </properties> - <command>${vyos_op_scripts_dir}/show_vrf.py -e "$3"</command> + <command>${vyos_op_scripts_dir}/vrf.py show --name="$3"</command> <children> <leafNode name="processes"> <properties> diff --git a/op-mode-definitions/vpn-ipsec.xml.in b/op-mode-definitions/vpn-ipsec.xml.in index f1f43755b..a98cf8ff2 100644 --- a/op-mode-definitions/vpn-ipsec.xml.in +++ b/op-mode-definitions/vpn-ipsec.xml.in @@ -76,6 +76,9 @@ <tagNode name="peer"> <properties> <help>Show debugging information for a peer</help> + <completionHelp> + <path>vpn ipsec site-to-site peer</path> + </completionHelp> </properties> <children> <tagNode name="tunnel"> diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 9c0c93779..009dbc803 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -921,5 +921,31 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' neighbor {peer_group} peer-group', frrconfig) self.assertIn(f' neighbor {peer_group} remote-as {remote_asn}', frrconfig) + def test_bgp_15_local_as_ebgp(self): + # https://phabricator.vyos.net/T4560 + # local-as allowed only for ebgp peers + + neighbor = '192.0.2.99' + remote_asn = '500' + local_asn = '400' + + self.cli_set(base_path + ['local-as', ASN]) + self.cli_set(base_path + ['neighbor', neighbor, 'remote-as', ASN]) + self.cli_set(base_path + ['neighbor', neighbor, 'local-as', local_asn]) + + # check validate() - local-as allowed only for ebgp peers + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['neighbor', neighbor, 'remote-as', remote_asn]) + + self.cli_commit() + + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' neighbor {neighbor} remote-as {remote_asn}', frrconfig) + self.assertIn(f' neighbor {neighbor} local-as {local_asn}', frrconfig) + + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_ids.py b/smoketest/scripts/cli/test_service_ids.py index 8720362ba..d471eeaed 100755 --- a/smoketest/scripts/cli/test_service_ids.py +++ b/smoketest/scripts/cli/test_service_ids.py @@ -26,6 +26,7 @@ from vyos.util 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): @@ -50,6 +51,7 @@ class TestServiceIDS(VyOSUnitTestSHIM.TestCase): 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' @@ -62,6 +64,12 @@ class TestServiceIDS(VyOSUnitTestSHIM.TestCase): 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() @@ -100,5 +108,9 @@ class TestServiceIDS(VyOSUnitTestSHIM.TestCase): 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/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py index 7750c1247..7e16235c1 100755 --- a/src/conf_mode/flow_accounting_conf.py +++ b/src/conf_mode/flow_accounting_conf.py @@ -192,6 +192,11 @@ def verify(flow_config): raise ConfigError("All sFlow servers must use the same IP protocol") else: sflow_collector_ipver = ip_address(server).version + + # check if vrf is defined for Sflow + sflow_vrf = None + if 'vrf' in flow_config: + sflow_vrf = flow_config['vrf'] # check agent-id for sFlow: we should avoid mixing IPv4 agent-id with IPv6 collectors and vice-versa for server in flow_config['sflow']['server']: @@ -203,12 +208,12 @@ def verify(flow_config): if 'agent_address' in flow_config['sflow']: tmp = flow_config['sflow']['agent_address'] - if not is_addr_assigned(tmp): + if not is_addr_assigned(tmp, sflow_vrf): raise ConfigError(f'Configured "sflow agent-address {tmp}" does not exist in the system!') # Check if configured netflow source-address exist in the system if 'source_address' in flow_config['sflow']: - if not is_addr_assigned(flow_config['sflow']['source_address']): + if not is_addr_assigned(flow_config['sflow']['source_address'], sflow_vrf): tmp = flow_config['sflow']['source_address'] raise ConfigError(f'Configured "sflow source-address {tmp}" does not exist on the system!') diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index bf0f6840d..af2d0588d 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -118,6 +118,11 @@ def verify(vxlan): # in use. vxlan_overhead += 20 + # If source_address is not used - check IPv6 'remote' list + elif 'remote' in vxlan: + if any(is_ipv6(a) for a in vxlan['remote']): + vxlan_overhead += 20 + lower_mtu = Interface(vxlan['source_interface']).get_mtu() if lower_mtu < (int(vxlan['mtu']) + vxlan_overhead): raise ConfigError(f'Underlaying device MTU is to small ({lower_mtu} '\ diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index e275ace84..97b3a6396 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -76,7 +76,7 @@ def get_config(config=None): # We need to know the amount of other WWAN interfaces as ModemManager needs # to be started or stopped. conf.set_level(base) - _, wwan['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'), + wwan['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 5aa643476..7d3687094 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -213,6 +213,12 @@ def verify(bgp): if 'source_interface' in peer_config['interface']: raise ConfigError(f'"source-interface" option not allowed for neighbor "{peer}"') + # Local-AS allowed only for EBGP peers + if 'local_as' in peer_config: + remote_as = verify_remote_as(peer_config, bgp) + if remote_as == bgp['local_as']: + raise ConfigError(f'local-as configured for "{peer}", allowed only for eBGP peers!') + for afi in ['ipv4_unicast', 'ipv4_multicast', 'ipv4_labeled_unicast', 'ipv4_flowspec', 'ipv6_unicast', 'ipv6_multicast', 'ipv6_labeled_unicast', 'ipv6_flowspec', 'l2vpn_evpn']: diff --git a/src/conf_mode/service_ids_fastnetmon.py b/src/conf_mode/service_ids_fastnetmon.py index 615658c84..c58f8db9a 100755 --- a/src/conf_mode/service_ids_fastnetmon.py +++ b/src/conf_mode/service_ids_fastnetmon.py @@ -29,6 +29,7 @@ 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' def get_config(config=None): if config: @@ -75,6 +76,7 @@ def generate(fastnetmon): 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): diff --git a/src/etc/opennhrp/opennhrp-script.py b/src/etc/opennhrp/opennhrp-script.py index 5a64dade8..8274e6564 100755 --- a/src/etc/opennhrp/opennhrp-script.py +++ b/src/etc/opennhrp/opennhrp-script.py @@ -62,7 +62,7 @@ def add_peer_route(nbma_src: str, nbma_dst: str, mtu: str) -> None: for route_item in route_info_data: route_dev = route_item.get('dev') route_dst = route_item.get('dst') - route_gateway = route_item.get('route_gateway') + route_gateway = route_item.get('gateway') # Prepare a command to add a route route_add_cmd = 'sudo ip route add' if route_dst: diff --git a/src/etc/systemd/system/fastnetmon.service.d/override.conf b/src/etc/systemd/system/fastnetmon.service.d/override.conf index 8f7f3774f..841666070 100644 --- a/src/etc/systemd/system/fastnetmon.service.d/override.conf +++ b/src/etc/systemd/system/fastnetmon.service.d/override.conf @@ -7,6 +7,6 @@ After=vyos-router.service [Service] Type=simple WorkingDirectory=/run/fastnetmon -PIDFile=/run/fastnetmon/fastnetmon.pid +PIDFile=/run/fastnetmon.pid ExecStart= ExecStart=/usr/sbin/fastnetmon --configuration_file /run/fastnetmon/fastnetmon.conf diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py index f3f79f863..4b54ecf31 100755 --- a/src/op_mode/nat.py +++ b/src/op_mode/nat.py @@ -52,6 +52,9 @@ def _get_raw_data_rules(direction): def _get_formatted_output_rules(data, direction): + # Add default values before loop + sport, dport, proto = 'any', 'any', 'any' + saddr, daddr = '0.0.0.0/0', '0.0.0.0/0' data_entries = [] for rule in data: if 'comment' in rule['rule']: @@ -144,6 +147,24 @@ port {port}''' return output +def _get_formatted_output_statistics(data, direction): + data_entries = [] + for rule in data: + if 'comment' in rule['rule']: + comment = rule.get('rule').get('comment') + rule_number = comment.split('-')[-1] + rule_number = rule_number.split(' ')[0] + if 'expr' in rule['rule']: + interface = rule.get('rule').get('expr')[0].get('match').get('right') \ + if jmespath.search('rule.expr[*].match.left.meta', rule) else 'any' + packets = jmespath.search('rule.expr[*].counter.packets | [0]', rule) + _bytes = jmespath.search('rule.expr[*].counter.bytes | [0]', rule) + data_entries.append([rule_number, packets, _bytes, interface]) + headers = ["Rule", "Packets", "Bytes", "Interface"] + output = tabulate(data_entries, headers, numalign="left") + return output + + def show_rules(raw: bool, direction: str): nat_rules = _get_raw_data_rules(direction) if raw: @@ -152,6 +173,14 @@ def show_rules(raw: bool, direction: str): return _get_formatted_output_rules(nat_rules, direction) +def show_statistics(raw: bool, direction: str): + nat_statistics = _get_raw_data_rules(direction) + if raw: + return nat_statistics + else: + return _get_formatted_output_statistics(nat_statistics, direction) + + if __name__ == '__main__': try: res = vyos.opmode.run(sys.modules[__name__]) diff --git a/src/op_mode/show_vrf.py b/src/op_mode/show_vrf.py deleted file mode 100755 index 3c7a90205..000000000 --- a/src/op_mode/show_vrf.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2020 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 argparse -import jinja2 -from json import loads - -from vyos.util import cmd - -vrf_out_tmpl = """VRF name state mac address flags interfaces --------- ----- ----------- ----- ---------- -{%- for v in vrf %} -{{"%-16s"|format(v.ifname)}} {{ "%-8s"|format(v.operstate | lower())}} {{"%-17s"|format(v.address | lower())}} {{ v.flags|join(',')|lower()}} {{v.members|join(',')|lower()}} -{%- endfor %} - -""" - -def list_vrfs(): - command = 'ip -j -br link show type vrf' - answer = loads(cmd(command)) - return [_ for _ in answer if _] - -def list_vrf_members(vrf): - command = f'ip -j -br link show master {vrf}' - answer = loads(cmd(command)) - return [_ for _ in answer if _] - -parser = argparse.ArgumentParser() -group = parser.add_mutually_exclusive_group() -group.add_argument("-e", "--extensive", action="store_true", - help="provide detailed vrf informatio") -parser.add_argument('interface', metavar='I', type=str, nargs='?', - help='interface to display') - -args = parser.parse_args() - -if args.extensive: - data = { 'vrf': [] } - for vrf in list_vrfs(): - name = vrf['ifname'] - if args.interface and name != args.interface: - continue - - vrf['members'] = [] - for member in list_vrf_members(name): - vrf['members'].append(member['ifname']) - data['vrf'].append(vrf) - - tmpl = jinja2.Template(vrf_out_tmpl) - print(tmpl.render(data)) - -else: - print(" ".join([vrf['ifname'] for vrf in list_vrfs()])) diff --git a/src/op_mode/vpn_ipsec.py b/src/op_mode/vpn_ipsec.py index 8955e5a59..68dc5bc45 100755 --- a/src/op_mode/vpn_ipsec.py +++ b/src/op_mode/vpn_ipsec.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -87,6 +87,7 @@ def reset_profile(profile, tunnel): print('Profile reset result: ' + ('success' if result == 0 else 'failed')) def debug_peer(peer, tunnel): + peer = peer.replace(':', '-') if not peer or peer == "all": debug_commands = [ "sudo ipsec statusall", @@ -109,7 +110,7 @@ def debug_peer(peer, tunnel): if not tunnel or tunnel == 'all': tunnel = '' - conn = get_peer_connections(peer, tunnel) + conns = get_peer_connections(peer, tunnel, return_all = (tunnel == '' or tunnel == 'all')) if not conns: print('Peer not found, aborting') diff --git a/src/op_mode/vrf.py b/src/op_mode/vrf.py index 63d9b5ee5..f86516786 100755 --- a/src/op_mode/vrf.py +++ b/src/op_mode/vrf.py @@ -16,6 +16,7 @@ import json import sys +import typing from tabulate import tabulate from vyos.util import cmd @@ -23,12 +24,23 @@ from vyos.util import cmd import vyos.opmode -def _get_raw_data(): +def _get_raw_data(name=None): """ - :return: list + If vrf name is not set - get all VRFs + If vrf name is set - get only this name data + If vrf name set and not found - return [] """ output = cmd('sudo ip --json --brief link show type vrf') data = json.loads(output) + if not data: + return [] + if name: + is_vrf_exists = True if [vrf for vrf in data if vrf.get('ifname') == name] else False + if is_vrf_exists: + output = cmd(f'sudo ip --json --brief link show dev {name}') + data = json.loads(output) + return data + return [] return data @@ -62,8 +74,8 @@ def _get_formatted_output(raw_data): return output -def show(raw: bool): - vrf_data = _get_raw_data() +def show(raw: bool, name: typing.Optional[str]): + vrf_data = _get_raw_data(name=name) if raw: return vrf_data else: |