diff options
| -rw-r--r-- | data/op-mode-standardized.json | 3 | ||||
| -rw-r--r-- | interface-definitions/include/policy/local-route_rule_protocol.xml.i | 21 | ||||
| -rw-r--r-- | interface-definitions/policy-local-route.xml.in | 1 | ||||
| -rw-r--r-- | op-mode-definitions/zone-policy.xml.in | 24 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_policy.py | 22 | ||||
| -rwxr-xr-x | src/conf_mode/policy-local-route.py | 103 | ||||
| -rwxr-xr-x | src/op_mode/zone.py | 215 | 
7 files changed, 105 insertions, 284 deletions
| diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json index ded934bff..ed9bb6cad 100644 --- a/data/op-mode-standardized.json +++ b/data/op-mode-standardized.json @@ -26,6 +26,5 @@  "storage.py",  "uptime.py",  "version.py", -"vrf.py", -"zone.py" +"vrf.py"  ] diff --git a/interface-definitions/include/policy/local-route_rule_protocol.xml.i b/interface-definitions/include/policy/local-route_rule_protocol.xml.i new file mode 100644 index 000000000..57582eb37 --- /dev/null +++ b/interface-definitions/include/policy/local-route_rule_protocol.xml.i @@ -0,0 +1,21 @@ +<!-- include start from policy/local-route_rule_protocol.xml.i --> +<leafNode name="protocol"> +  <properties> +    <help>Protocol to match (protocol name or number)</help> +    <completionHelp> +      <script>${vyos_completion_dir}/list_protocols.sh</script> +    </completionHelp> +    <valueHelp> +      <format>u32:0-255</format> +      <description>IP protocol number</description> +    </valueHelp> +    <valueHelp> +      <format><protocol></format> +      <description>IP protocol name</description> +    </valueHelp> +    <constraint> +      <validator name="ip-protocol"/> +    </constraint> +  </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/policy-local-route.xml.in b/interface-definitions/policy-local-route.xml.in index 8619e839e..0a5b81dfa 100644 --- a/interface-definitions/policy-local-route.xml.in +++ b/interface-definitions/policy-local-route.xml.in @@ -53,6 +53,7 @@                    </constraint>                  </properties>                </leafNode> +              #include <include/policy/local-route_rule_protocol.xml.i>                <leafNode name="source">                  <properties>                    <help>Source address or prefix</help> diff --git a/op-mode-definitions/zone-policy.xml.in b/op-mode-definitions/zone-policy.xml.in deleted file mode 100644 index 9d65ddd3d..000000000 --- a/op-mode-definitions/zone-policy.xml.in +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0"?> -<interfaceDefinition> -  <node name="show"> -    <children> -      <node name="zone-policy"> -        <properties> -          <help>Show zone policy information</help> -        </properties> -        <children> -          <tagNode name="zone"> -            <properties> -              <help>Show summary of zone policy for a specific zone</help> -              <completionHelp> -                <path>firewall zone</path> -              </completionHelp> -            </properties> -            <command>sudo ${vyos_op_scripts_dir}/zone.py show --zone $4</command> -          </tagNode> -        </children> -        <command>sudo ${vyos_op_scripts_dir}/zone.py show</command> -      </node> -    </children> -  </node> -</interfaceDefinition> diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py index 354f791bd..e868895ce 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -1519,6 +1519,28 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):          self.assertEqual(sort_ip(tmp), sort_ip(original)) +    # Test set table for destination and protocol +    def test_protocol_destination_table_id(self): +        path = base_path + ['local-route'] + +        dst = '203.0.113.12' +        rule = '85' +        table = '104' +        proto = 'tcp' + +        self.cli_set(path + ['rule', rule, 'set', 'table', table]) +        self.cli_set(path + ['rule', rule, 'destination', dst]) +        self.cli_set(path + ['rule', rule, 'protocol', proto]) + +        self.cli_commit() + +        original = """ +        85:	from all to 203.0.113.12 ipproto tcp lookup 104 +        """ +        tmp = cmd('ip rule show prio 85') + +        self.assertEqual(sort_ip(tmp), sort_ip(original)) +      # Test set table for sources with fwmark      def test_fwmark_sources_table_id(self):          path = base_path + ['local-route'] diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py index 79526f82a..d3c307cdc 100755 --- a/src/conf_mode/policy-local-route.py +++ b/src/conf_mode/policy-local-route.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -16,6 +16,7 @@  import os +from itertools import product  from sys import exit  from netifaces import interfaces @@ -54,6 +55,7 @@ def get_config(config=None):                  fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark'])                  iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface'])                  dst = leaf_node_changed(conf, base_rule + [rule, 'destination']) +                proto = leaf_node_changed(conf, base_rule + [rule, 'protocol'])                  rule_def = {}                  if src:                      rule_def = dict_merge({'source' : src}, rule_def) @@ -63,6 +65,8 @@ def get_config(config=None):                      rule_def = dict_merge({'inbound_interface' : iif}, rule_def)                  if dst:                      rule_def = dict_merge({'destination' : dst}, rule_def) +                if proto: +                    rule_def = dict_merge({'protocol' : proto}, rule_def)                  dict = dict_merge({dict_id : {rule : rule_def}}, dict)                  pbr.update(dict) @@ -78,6 +82,7 @@ def get_config(config=None):                  fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark'])                  iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface'])                  dst = leaf_node_changed(conf, base_rule + [rule, 'destination']) +                proto = leaf_node_changed(conf, base_rule + [rule, 'protocol'])                  # keep track of changes in configuration                  # otherwise we might remove an existing node although nothing else has changed                  changed = False @@ -119,6 +124,13 @@ def get_config(config=None):                      changed = True                      if len(dst) > 0:                          rule_def = dict_merge({'destination' : dst}, rule_def) +                if proto is None: +                    if 'protocol' in rule_config: +                        rule_def = dict_merge({'protocol': rule_config['protocol']}, rule_def) +                else: +                    changed = True +                    if len(proto) > 0: +                        rule_def = dict_merge({'protocol' : proto}, rule_def)                  if changed:                      dict = dict_merge({dict_id : {rule : rule_def}}, dict)                      pbr.update(dict) @@ -137,18 +149,22 @@ def verify(pbr):          pbr_route = pbr[route]          if 'rule' in pbr_route:              for rule in pbr_route['rule']: -                if 'source' not in pbr_route['rule'][rule] \ -                        and 'destination' not in pbr_route['rule'][rule] \ -                        and 'fwmark' not in pbr_route['rule'][rule] \ -                        and 'inbound_interface' not in pbr_route['rule'][rule]: -                    raise ConfigError('Source or destination address or fwmark or inbound-interface is required!') -                else: -                    if 'set' not in pbr_route['rule'][rule] or 'table' not in pbr_route['rule'][rule]['set']: -                        raise ConfigError('Table set is required!') -                    if 'inbound_interface' in pbr_route['rule'][rule]: -                        interface = pbr_route['rule'][rule]['inbound_interface'] -                        if interface not in interfaces(): -                            raise ConfigError(f'Interface "{interface}" does not exist') +                if ( +                    'source' not in pbr_route['rule'][rule] and +                    'destination' not in pbr_route['rule'][rule] and +                    'fwmark' not in pbr_route['rule'][rule] and +                    'inbound_interface' not in pbr_route['rule'][rule] and +                    'protocol' not in pbr_route['rule'][rule] +                ): +                    raise ConfigError('Source or destination address or fwmark or inbound-interface or protocol is required!') + +                if 'set' not in pbr_route['rule'][rule] or 'table' not in pbr_route['rule'][rule]['set']: +                    raise ConfigError('Table set is required!') + +                if 'inbound_interface' in pbr_route['rule'][rule]: +                    interface = pbr_route['rule'][rule]['inbound_interface'] +                    if interface not in interfaces(): +                        raise ConfigError(f'Interface "{interface}" does not exist')      return None @@ -166,20 +182,22 @@ def apply(pbr):      for rule_rm in ['rule_remove', 'rule6_remove']:          if rule_rm in pbr:              v6 = " -6" if rule_rm == 'rule6_remove' else "" +              for rule, rule_config in pbr[rule_rm].items(): -                rule_config['source'] = rule_config['source'] if 'source' in rule_config else [''] -                for src in rule_config['source']: +                source = rule_config.get('source', ['']) +                destination = rule_config.get('destination', ['']) +                fwmark = rule_config.get('fwmark', ['']) +                inbound_interface = rule_config.get('inbound_interface', ['']) +                protocol = rule_config.get('protocol', ['']) + +                for src, dst, fwmk, iif, proto in product(source, destination, fwmark, inbound_interface, protocol):                      f_src = '' if src == '' else f' from {src} ' -                    rule_config['destination'] = rule_config['destination'] if 'destination' in rule_config else [''] -                    for dst in rule_config['destination']: -                        f_dst = '' if dst == '' else f' to {dst} ' -                        rule_config['fwmark'] = rule_config['fwmark'] if 'fwmark' in rule_config else [''] -                        for fwmk in rule_config['fwmark']: -                            f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} ' -                            rule_config['inbound_interface'] = rule_config['inbound_interface'] if 'inbound_interface' in rule_config else [''] -                            for iif in rule_config['inbound_interface']: -                                f_iif = '' if iif == '' else f' iif {iif} ' -                                call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif}') +                    f_dst = '' if dst == '' else f' to {dst} ' +                    f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} ' +                    f_iif = '' if iif == '' else f' iif {iif} ' +                    f_proto = '' if proto == '' else f' ipproto {proto} ' + +                    call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif}')      # Generate new config      for route in ['local_route', 'local_route6']: @@ -187,27 +205,26 @@ def apply(pbr):              continue          v6 = " -6" if route == 'local_route6' else "" -          pbr_route = pbr[route] +          if 'rule' in pbr_route:              for rule, rule_config in pbr_route['rule'].items(): -                table = rule_config['set']['table'] - -                rule_config['source'] = rule_config['source'] if 'source' in rule_config else ['all'] -                for src in rule_config['source'] or ['all']: -                    f_src = '' if src == '' else f' from {src} ' -                    rule_config['destination'] = rule_config['destination'] if 'destination' in rule_config else ['all'] -                    for dst in rule_config['destination']: -                        f_dst = '' if dst == '' else f' to {dst} ' -                        f_fwmk = '' -                        if 'fwmark' in rule_config: -                            fwmk = rule_config['fwmark'] -                            f_fwmk = f' fwmark {fwmk} ' -                        f_iif = '' -                        if 'inbound_interface' in rule_config: -                            iif = rule_config['inbound_interface'] -                            f_iif = f' iif {iif} ' -                        call(f'ip{v6} rule add prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif} lookup {table}') +                table = rule_config['set'].get('table', '') +                source = rule_config.get('source', ['all']) +                destination = rule_config.get('destination', ['all']) +                fwmark = rule_config.get('fwmark', '') +                inbound_interface = rule_config.get('inbound_interface', '') +                protocol = rule_config.get('protocol', '') + +                for src in source: +                    f_src = f' from {src} ' if src else '' +                    for dst in destination: +                        f_dst = f' to {dst} ' if dst else '' +                        f_fwmk = f' fwmark {fwmark} ' if fwmark else '' +                        f_iif = f' iif {inbound_interface} ' if inbound_interface else '' +                        f_proto = f' ipproto {protocol} ' if protocol else '' + +                        call(f'ip{v6} rule add prio {rule}{f_src}{f_dst}{f_proto}{f_fwmk}{f_iif} lookup {table}')      return None diff --git a/src/op_mode/zone.py b/src/op_mode/zone.py deleted file mode 100755 index 17ce90396..000000000 --- a/src/op_mode/zone.py +++ /dev/null @@ -1,215 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 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 typing -import sys -import vyos.opmode - -import tabulate -from vyos.configquery import ConfigTreeQuery -from vyos.utils.dict import dict_search_args -from vyos.utils.dict import dict_search - - -def get_config_zone(conf, name=None): -    config_path = ['firewall', 'zone'] -    if name: -        config_path += [name] - -    zone_policy = conf.get_config_dict(config_path, key_mangling=('-', '_'), -                                       get_first_key=True, -                                       no_tag_node_value_mangle=True) -    return zone_policy - - -def _convert_one_zone_data(zone: str, zone_config: dict) -> dict: -    """ -    Convert config dictionary of one zone to API dictionary -    :param zone: Zone name -    :type zone: str -    :param zone_config: config dictionary -    :type zone_config: dict -    :return: AP dictionary -    :rtype: dict -    """ -    list_of_rules = [] -    intrazone_dict = {} -    if dict_search('from', zone_config): -        for from_zone, from_zone_config in zone_config['from'].items(): -            from_zone_dict = {'name': from_zone} -            if dict_search('firewall.name', from_zone_config): -                from_zone_dict['firewall'] = dict_search('firewall.name', -                                                         from_zone_config) -            if dict_search('firewall.ipv6_name', from_zone_config): -                from_zone_dict['firewall_v6'] = dict_search( -                    'firewall.ipv6_name', from_zone_config) -            list_of_rules.append(from_zone_dict) - -    zone_dict = { -        'name': zone, -        'interface': dict_search('interface', zone_config), -        'type': 'LOCAL' if dict_search('local_zone', -                                       zone_config) is not None else None, -    } -    if list_of_rules: -        zone_dict['from'] = list_of_rules -    if dict_search('intra_zone_filtering.firewall.name', zone_config): -        intrazone_dict['firewall'] = dict_search( -            'intra_zone_filtering.firewall.name', zone_config) -    if dict_search('intra_zone_filtering.firewall.ipv6_name', zone_config): -        intrazone_dict['firewall_v6'] = dict_search( -            'intra_zone_filtering.firewall.ipv6_name', zone_config) -    if intrazone_dict: -        zone_dict['intrazone'] = intrazone_dict -    return zone_dict - - -def _convert_zones_data(zone_policies: dict) -> list: -    """ -    Convert all config dictionary to API list of zone dictionaries -    :param zone_policies: config dictionary -    :type zone_policies: dict -    :return: API list -    :rtype: list -    """ -    zone_list = [] -    for zone, zone_config in zone_policies.items(): -        zone_list.append(_convert_one_zone_data(zone, zone_config)) -    return zone_list - - -def _convert_config(zones_config: dict, zone: str = None) -> list: -    """ -    convert config to API list -    :param zones_config: zones config -    :type zones_config: -    :param zone: zone name -    :type zone: str -    :return: API list -    :rtype: list -    """ -    if zone: -        if zones_config: -            output = [_convert_one_zone_data(zone, zones_config)] -        else: -            raise vyos.opmode.DataUnavailable(f'Zone {zone} not found') -    else: -        if zones_config: -            output = _convert_zones_data(zones_config) -        else: -            raise vyos.opmode.UnconfiguredSubsystem( -                'Zone entries are not configured') -    return output - - -def output_zone_list(zone_conf: dict) -> list: -    """ -    Format one zone row -    :param zone_conf: zone config -    :type zone_conf: dict -    :return: formatted list of zones -    :rtype: list -    """ -    zone_info = [zone_conf['name']] -    if zone_conf['type'] == 'LOCAL': -        zone_info.append('LOCAL') -    else: -        zone_info.append("\n".join(zone_conf['interface'])) - -    from_zone = [] -    firewall = [] -    firewall_v6 = [] -    if 'intrazone' in zone_conf: -        from_zone.append(zone_conf['name']) - -        v4_name = dict_search_args(zone_conf['intrazone'], 'firewall') -        v6_name = dict_search_args(zone_conf['intrazone'], 'firewall_v6') -        if v4_name: -            firewall.append(v4_name) -        else: -            firewall.append('') -        if v6_name: -            firewall_v6.append(v6_name) -        else: -            firewall_v6.append('') - -    if 'from' in zone_conf: -        for from_conf in zone_conf['from']: -            from_zone.append(from_conf['name']) - -            v4_name = dict_search_args(from_conf, 'firewall') -            v6_name = dict_search_args(from_conf, 'firewall_v6') -            if v4_name: -                firewall.append(v4_name) -            else: -                firewall.append('') -            if v6_name: -                firewall_v6.append(v6_name) -            else: -                firewall_v6.append('') - -    zone_info.append("\n".join(from_zone)) -    zone_info.append("\n".join(firewall)) -    zone_info.append("\n".join(firewall_v6)) -    return zone_info - - -def get_formatted_output(zone_policy: list) -> str: -    """ -    Formatted output of all zones -    :param zone_policy: list of zones -    :type zone_policy: list -    :return: formatted table with zones -    :rtype: str -    """ -    headers = ["Zone", -               "Interfaces", -               "From Zone", -               "Firewall IPv4", -               "Firewall IPv6" -               ] -    formatted_list = [] -    for zone_conf in zone_policy: -        formatted_list.append(output_zone_list(zone_conf)) -    tabulate.PRESERVE_WHITESPACE = True -    output = tabulate.tabulate(formatted_list, headers, numalign="left") -    return output - - -def show(raw: bool, zone: typing.Optional[str]): -    """ -    Show zone-policy command -    :param raw: if API -    :type raw: bool -    :param zone: zone name -    :type zone: str -    """ -    conf: ConfigTreeQuery = ConfigTreeQuery() -    zones_config: dict = get_config_zone(conf, zone) -    zone_policy_api: list = _convert_config(zones_config, zone) -    if raw: -        return zone_policy_api -    else: -        return get_formatted_output(zone_policy_api) - - -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) | 
