diff options
| -rw-r--r-- | data/templates/firewall/nftables-defines.j2 | 13 | ||||
| -rw-r--r-- | interface-definitions/firewall.xml.in | 29 | ||||
| -rw-r--r-- | interface-definitions/include/firewall/common-rule.xml.i | 26 | ||||
| -rw-r--r-- | interface-definitions/include/firewall/match-interface.xml.i | 18 | ||||
| -rw-r--r-- | interface-definitions/include/version/firewall-version.xml.i | 2 | ||||
| -rw-r--r-- | python/vyos/firewall.py | 16 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_firewall.py | 15 | ||||
| -rwxr-xr-x | src/conf_mode/firewall.py | 3 | ||||
| -rwxr-xr-x | src/conf_mode/policy-route.py | 3 | ||||
| -rwxr-xr-x | src/migration-scripts/firewall/8-to-9 | 91 | 
10 files changed, 191 insertions, 25 deletions
| diff --git a/data/templates/firewall/nftables-defines.j2 b/data/templates/firewall/nftables-defines.j2 index dd06dee28..0a7e79edd 100644 --- a/data/templates/firewall/nftables-defines.j2 +++ b/data/templates/firewall/nftables-defines.j2 @@ -85,5 +85,18 @@      }  {%         endfor %}  {%     endif %} +{%     if group.interface_group is vyos_defined %} +{%         for group_name, group_conf in group.interface_group.items() %} +{%             set includes = group_conf.include if group_conf.include is vyos_defined else [] %} +    set I_{{ group_name }} { +        type ifname +        flags interval +        auto-merge +{%             if group_conf.interface is vyos_defined or includes %} +        elements = { {{ group_conf.interface | nft_nested_group(includes, group.interface_group, 'interface') | join(",") }} } +{%             endif %} +    } +{%         endfor %} +{%     endif %}  {% endif %}  {% endmacro %} diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index 3bce69fc4..c964abb41 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -134,6 +134,35 @@                #include <include/generic-description.xml.i>              </children>            </tagNode> +          <tagNode name="interface-group"> +            <properties> +              <help>Firewall interface-group</help> +              <constraint> +                <regex>[a-zA-Z0-9][\w\-\.]*</regex> +              </constraint> +            </properties> +            <children> +              <leafNode name="interface"> +                <properties> +                  <help>Interface-group member</help> +                  <completionHelp> +                    <script>${vyos_completion_dir}/list_interfaces.py</script> +                  </completionHelp> +                  <multi/> +                </properties> +              </leafNode> +              <leafNode name="include"> +                <properties> +                  <help>Include another interface-group</help> +                  <completionHelp> +                    <path>firewall group interface-group</path> +                  </completionHelp> +                  <multi/> +                </properties> +              </leafNode> +              #include <include/generic-description.xml.i> +            </children> +          </tagNode>            <tagNode name="ipv6-address-group">              <properties>                <help>Firewall ipv6-address-group</help> diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i index 75ad427f9..75acefd96 100644 --- a/interface-definitions/include/firewall/common-rule.xml.i +++ b/interface-definitions/include/firewall/common-rule.xml.i @@ -26,14 +26,22 @@      </leafNode>    </children>  </node> -<leafNode name="inbound-interface"> +<node name="inbound-interface">    <properties>      <help>Match inbound-interface</help> -    <completionHelp> -      <script>${vyos_completion_dir}/list_interfaces.py</script> -    </completionHelp>    </properties> -</leafNode> +  <children> +    #include <include/firewall/match-interface.xml.i> +  </children> +</node> +<node name="outbound-interface"> +  <properties> +    <help>Match outbound-interface</help> +  </properties> +  <children> +    #include <include/firewall/match-interface.xml.i> +  </children> +</node>  <node name="ipsec">    <properties>      <help>Inbound IPsec packets</help> @@ -130,14 +138,6 @@      </leafNode>    </children>  </node> -<leafNode name="outbound-interface"> -  <properties> -    <help>Match outbound-interface</help> -    <completionHelp> -      <script>${vyos_completion_dir}/list_interfaces.py</script> -    </completionHelp> -  </properties> -</leafNode>  <leafNode name="protocol">    <properties>      <help>Protocol to match (protocol name, number, or "all")</help> diff --git a/interface-definitions/include/firewall/match-interface.xml.i b/interface-definitions/include/firewall/match-interface.xml.i new file mode 100644 index 000000000..675a87574 --- /dev/null +++ b/interface-definitions/include/firewall/match-interface.xml.i @@ -0,0 +1,18 @@ +<!-- include start from firewall/match-interface.xml.i --> +<leafNode name="interface-name"> +  <properties> +    <help>Match interface</help> +    <completionHelp> +      <script>${vyos_completion_dir}/list_interfaces.py</script> +    </completionHelp> +  </properties> +</leafNode> +<leafNode name="interface-group"> +  <properties> +    <help>Match interface-group</help> +    <completionHelp> +      <path>firewall group interface-group</path> +    </completionHelp> +  </properties> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/version/firewall-version.xml.i b/interface-definitions/include/version/firewall-version.xml.i index 065925319..bc04f8d51 100644 --- a/interface-definitions/include/version/firewall-version.xml.i +++ b/interface-definitions/include/version/firewall-version.xml.i @@ -1,3 +1,3 @@  <!-- include start from include/version/firewall-version.xml.i --> -<syntaxVersion component='firewall' version='8'></syntaxVersion> +<syntaxVersion component='firewall' version='9'></syntaxVersion>  <!-- include end --> diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index 48263eef5..429c44802 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -236,12 +236,20 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):                  output.append(f'ip6 hoplimit {operator} {value}')      if 'inbound_interface' in rule_conf: -        iiface = rule_conf['inbound_interface'] -        output.append(f'iifname {iiface}') +        if 'interface_name' in rule_conf['inbound_interface']: +            iiface = rule_conf['inbound_interface']['interface_name'] +            output.append(f'iifname {{{iiface}}}') +        else: +            iiface = rule_conf['inbound_interface']['interface_group'] +            output.append(f'iifname @I_{iiface}')      if 'outbound_interface' in rule_conf: -        oiface = rule_conf['outbound_interface'] -        output.append(f'oifname {oiface}') +        if 'interface_name' in rule_conf['outbound_interface']: +            oiface = rule_conf['outbound_interface']['interface_name'] +            output.append(f'oifname {{{oiface}}}') +        else: +            oiface = rule_conf['outbound_interface']['interface_group'] +            output.append(f'oifname @I_{oiface}')      if 'ttl' in rule_conf:          operators = {'eq': '==', 'gt': '>', 'lt': '<'} diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 09b520b72..9b28eb81b 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -124,6 +124,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):          self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '123'])          self.cli_set(['firewall', 'group', 'domain-group', 'smoketest_domain', 'address', 'example.com'])          self.cli_set(['firewall', 'group', 'domain-group', 'smoketest_domain', 'address', 'example.org']) +        self.cli_set(['firewall', 'group', 'interface-group', 'smoketest_interface', 'interface', 'eth0']) +        self.cli_set(['firewall', 'group', 'interface-group', 'smoketest_interface', 'interface', 'vtun0'])          self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'accept'])          self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network']) @@ -134,6 +136,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):          self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'source', 'group', 'mac-group', 'smoketest_mac'])          self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'action', 'accept'])          self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'source', 'group', 'domain-group', 'smoketest_domain']) +        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'action', 'accept']) +        self.cli_set(['firewall', 'name', 'smoketest', 'rule', '4', 'outbound-interface', 'interface-group', 'smoketest_interface'])          self.cli_set(['firewall', 'interface', 'eth0', 'in', 'name', 'smoketest']) @@ -151,7 +155,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):              ['set D_smoketest_domain'],              ['elements = { 192.0.2.5, 192.0.2.8,'],              ['192.0.2.10, 192.0.2.11 }'], -            ['ip saddr @D_smoketest_domain', 'return'] +            ['ip saddr @D_smoketest_domain', 'return'], +            ['oifname @I_smoketest_interface', 'return']          ]          self.verify_nftables(nftables_search, 'ip vyos_filter') @@ -225,10 +230,10 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):          self.cli_set(['firewall', 'name', name, 'rule', '5', 'protocol', 'tcp'])          self.cli_set(['firewall', 'name', name, 'rule', '5', 'tcp', 'flags', 'syn'])          self.cli_set(['firewall', 'name', name, 'rule', '5', 'tcp', 'mss', mss_range]) -        self.cli_set(['firewall', 'name', name, 'rule', '5', 'inbound-interface', interface]) +        self.cli_set(['firewall', 'name', name, 'rule', '5', 'inbound-interface', 'interface-name', interface])          self.cli_set(['firewall', 'name', name, 'rule', '6', 'action', 'return'])          self.cli_set(['firewall', 'name', name, 'rule', '6', 'protocol', 'gre']) -        self.cli_set(['firewall', 'name', name, 'rule', '6', 'outbound-interface', interface]) +        self.cli_set(['firewall', 'name', name, 'rule', '6', 'outbound-interface', 'interface-name', interface])          self.cli_set(['firewall', 'interface', interface, 'in', 'name', name]) @@ -340,11 +345,11 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):          self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'action', 'reject'])          self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'protocol', 'tcp_udp'])          self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'destination', 'port', '8888']) -        self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'inbound-interface', interface]) +        self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'inbound-interface', 'interface-name', interface])          self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'action', 'return'])          self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'protocol', 'gre']) -        self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'outbound-interface', interface]) +        self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'outbound-interface', 'interface-name', interface])          self.cli_set(['firewall', 'interface', interface, 'in', 'ipv6-name', name]) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index f68acfe02..20cf1ead1 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -65,7 +65,8 @@ valid_groups = [      'address_group',      'domain_group',      'network_group', -    'port_group' +    'port_group', +    'interface_group'  ]  nested_group_types = [ diff --git a/src/conf_mode/policy-route.py b/src/conf_mode/policy-route.py index 1d016695e..40a32efb3 100755 --- a/src/conf_mode/policy-route.py +++ b/src/conf_mode/policy-route.py @@ -36,7 +36,8 @@ valid_groups = [      'address_group',      'domain_group',      'network_group', -    'port_group' +    'port_group', +    'interface_group'  ]  def get_config(config=None): diff --git a/src/migration-scripts/firewall/8-to-9 b/src/migration-scripts/firewall/8-to-9 new file mode 100755 index 000000000..f7c1bb90d --- /dev/null +++ b/src/migration-scripts/firewall/8-to-9 @@ -0,0 +1,91 @@ +#!/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/>. + +# T4780: Add firewall interface group +#  cli changes from:        +#  set firewall [name | ipv6-name] <name> rule <number> [inbound-interface | outbound-interface] <interface_name> +#  To +#  set firewall [name | ipv6-name] <name> rule <number> [inbound-interface | outbound-interface]  [interface-name | interface-group] <interface_name | interface_group> + +import re + +from sys import argv +from sys import exit + +from vyos.configtree import ConfigTree +from vyos.ifconfig import Section + +if (len(argv) < 1): +    print("Must specify file name!") +    exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: +    config_file = f.read() + +base = ['firewall'] +config = ConfigTree(config_file) + +if not config.exists(base): +    # Nothing to do +    exit(0) + +if config.exists(base + ['name']): +    for name in config.list_nodes(base + ['name']): +        if not config.exists(base + ['name', name, 'rule']): +            continue + +        for rule in config.list_nodes(base + ['name', name, 'rule']): +            rule_iiface = base + ['name', name, 'rule', rule, 'inbound-interface'] +            rule_oiface = base + ['name', name, 'rule', rule, 'outbound-interface'] + +            if config.exists(rule_iiface): +                tmp = config.return_value(rule_iiface) +                config.delete(rule_iiface) +                config.set(rule_iiface + ['interface-name'], value=tmp) + +            if config.exists(rule_oiface): +                tmp = config.return_value(rule_oiface) +                config.delete(rule_oiface) +                config.set(rule_oiface + ['interface-name'], value=tmp) + + +if config.exists(base + ['ipv6-name']): +    for name in config.list_nodes(base + ['ipv6-name']): +        if not config.exists(base + ['ipv6-name', name, 'rule']): +            continue + +        for rule in config.list_nodes(base + ['ipv6-name', name, 'rule']): +            rule_iiface = base + ['ipv6-name', name, 'rule', rule, 'inbound-interface'] +            rule_oiface = base + ['ipv6-name', name, 'rule', rule, 'outbound-interface'] + +            if config.exists(rule_iiface): +                tmp = config.return_value(rule_iiface) +                config.delete(rule_iiface) +                config.set(rule_iiface + ['interface-name'], value=tmp) + +            if config.exists(rule_oiface): +                tmp = config.return_value(rule_oiface) +                config.delete(rule_oiface) +                config.set(rule_oiface + ['interface-name'], value=tmp) + +try: +    with open(file_name, 'w') as f: +        f.write(config.to_string()) +except OSError as e: +    print("Failed to save the modified config: {}".format(e)) +    exit(1)
\ No newline at end of file | 
