diff options
| author | Simon <965089+sarthurdev@users.noreply.github.com> | 2025-11-24 20:48:07 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-11-24 20:48:07 +0100 |
| commit | fdda826b48688e253c30d3578ca5edd45b034ab0 (patch) | |
| tree | e23d2dce4f302de5157604501188894a2abd31c5 | |
| parent | 1aec97d70ceaba9f89db8c592478e10ed1c0aefc (diff) | |
| parent | cc5895fe61f938189e229dffb7746fc93aac0f3b (diff) | |
| download | vyos-1x-fdda826b48688e253c30d3578ca5edd45b034ab0.tar.gz vyos-1x-fdda826b48688e253c30d3578ca5edd45b034ab0.zip | |
Merge pull request #4672 from apschultz/zone_default_firewall_ruleset
firewall: T7739: Default ruleset for firewall zones
| -rw-r--r-- | data/templates/firewall/nftables-zone.j2 | 22 | ||||
| -rw-r--r-- | interface-definitions/firewall.xml.in | 23 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_firewall.py | 44 | ||||
| -rwxr-xr-x | src/conf_mode/firewall.py | 19 |
4 files changed, 106 insertions, 2 deletions
diff --git a/data/templates/firewall/nftables-zone.j2 b/data/templates/firewall/nftables-zone.j2 index 5a2b8912e..7b4128082 100644 --- a/data/templates/firewall/nftables-zone.j2 +++ b/data/templates/firewall/nftables-zone.j2 @@ -56,6 +56,10 @@ {% endif %} {% endfor %} {% endif %} +{% if zone_conf.default_firewall is vyos_defined and zone_conf.default_firewall[fw_name] is vyos_defined %} + counter jump NAME{{ suffix }}_{{ zone_conf.default_firewall[fw_name] }} + counter return +{% endif %} {{ zone_conf | nft_default_rule('zone_' + zone_name, family) }} } chain VZONE_{{ zone_name }}_OUT { @@ -72,6 +76,20 @@ {% endif %} {% endfor %} {% endif %} +{% if zone_conf.default_local is vyos_defined %} +{% for from_zone, from_conf in zone_conf.default_local.items() if from_conf[fw_name] is vyos_defined %} +{% if 'interface' in zone[from_zone].member %} + oifname { {{ zone[from_zone].member.interface | quoted_join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf[fw_name] }} + oifname { {{ zone[from_zone].member.interface | quoted_join(",") }} } counter return +{% endif %} +{% if 'vrf' in zone[from_zone].member %} +{% for vrf_name in zone[from_zone].member.vrf %} + oifname { "{{ zone[from_zone]['vrf_interfaces'][vrf_name] }}" } counter jump NAME{{ suffix }}_{{ from_conf[fw_name] }} + oifname { "{{ zone[from_zone]['vrf_interfaces'][vrf_name] }}" } counter return +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} {{ zone_conf | nft_default_rule('zone_' + zone_name, family) }} } {% else %} @@ -104,6 +122,10 @@ {% endif %} {% endfor %} {% endif %} +{% if zone_conf.default_firewall is vyos_defined and zone_conf.default_firewall[fw_name] is vyos_defined %} + counter jump NAME{{ suffix }}_{{ zone_conf.default_firewall[fw_name] }} + counter return +{% endif %} {{ zone_conf | nft_default_rule('zone_' + zone_name, family) }} } {% endif %} diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index e51cb68a6..f5986164a 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -428,6 +428,29 @@ </properties> <defaultValue>drop</defaultValue> </leafNode> + <node name="default-firewall"> + <properties> + <help>Default firewall rules for traffic coming into this zone</help> + </properties> + <children> + <leafNode name="ipv6-name"> + <properties> + <help>IPv6 firewall ruleset</help> + <completionHelp> + <path>firewall ipv6 name</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="name"> + <properties> + <help>IPv4 firewall ruleset</help> + <completionHelp> + <path>firewall ipv4 name</path> + </completionHelp> + </properties> + </leafNode> + </children> + </node> <tagNode name="from"> <properties> <help>Zone from which to filter traffic</help> diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index c29471b63..ca08cc389 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -994,6 +994,50 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.verify_nftables(nftables_search, 'ip vyos_filter') self.verify_nftables(nftables_search_v6, 'ip6 vyos_filter') + def test_zone_with_default_firewall(self): + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest-default', 'default-action', 'drop']) + self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'member', 'interface', 'eth0']) + self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'from', 'smoketest-eth1', 'firewall', 'name', 'smoketest']) + self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'from', 'smoketest-local', 'firewall', 'name', 'smoketest']) + self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'default-firewall', 'name', 'smoketest-default']) + self.cli_set(['firewall', 'zone', 'smoketest-eth1', 'member', 'interface', 'eth1']) + self.cli_set(['firewall', 'zone', 'smoketest-eth1', 'default-firewall', 'name', 'smoketest-default']) + self.cli_set(['firewall', 'zone', 'smoketest-eth2', 'member', 'interface', 'eth2']) + self.cli_set(['firewall', 'zone', 'smoketest-local', 'local-zone']) + self.cli_set(['firewall', 'zone', 'smoketest-local', 'from', 'smoketest-eth0', 'firewall', 'name', 'smoketest']) + self.cli_set(['firewall', 'zone', 'smoketest-local', 'default-firewall', 'name', 'smoketest-default']) + self.cli_commit() + + smoketest_eth0_search = [ + ['iifname "eth1"', 'jump NAME_smoketest'], + ['jump NAME_smoketest-default'] + ] + self.verify_nftables_chain_exists('ip vyos_filter', 'VZONE_smoketest-eth0') + self.verify_nftables_chain(smoketest_eth0_search, 'ip vyos_filter', 'VZONE_smoketest-eth0') + + smoketest_eth1_search = [ + ['jump NAME_smoketest-default'] + ] + self.verify_nftables_chain_exists('ip vyos_filter', 'VZONE_smoketest-eth1') + self.verify_nftables_chain(smoketest_eth1_search, 'ip vyos_filter', 'VZONE_smoketest-eth1') + + self.verify_nftables_chain_exists('ip vyos_filter', 'VZONE_smoketest-eth2') + + smoketest_local_in_search = [ + ['iifname "eth0"', 'jump NAME_smoketest'], + ['jump NAME_smoketest-default'], + ] + self.verify_nftables_chain_exists('ip vyos_filter', 'VZONE_smoketest-local_IN') + self.verify_nftables_chain(smoketest_local_in_search, 'ip vyos_filter', 'VZONE_smoketest-local_IN') + + smoketest_local_out_search = [ + ['oifname "eth0"', 'jump NAME_smoketest'], + ['oifname "eth1"', 'jump NAME_smoketest-default'] + ] + self.verify_nftables_chain_exists('ip vyos_filter', 'VZONE_smoketest-local_OUT') + self.verify_nftables_chain(smoketest_local_out_search, 'ip vyos_filter', 'VZONE_smoketest-local_OUT') + def test_zone_with_vrf(self): self.cli_set(['firewall', 'ipv4', 'name', 'ZONE1-to-LOCAL', 'default-action', 'accept']) self.cli_set(['firewall', 'ipv4', 'name', 'ZONE2_to_ZONE1', 'default-action', 'continue']) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 4a4212a0f..a609e305c 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -154,12 +154,15 @@ def get_config(config=None): continue local_zone_conf['from_local'] = {} + local_zone_conf['default_local'] = {} for zone, zone_conf in firewall['zone'].items(): - if zone == local_zone or 'from' not in zone_conf: + if zone == local_zone: continue - if local_zone in zone_conf['from']: + if 'from' in zone_conf and local_zone in zone_conf['from']: local_zone_conf['from_local'][zone] = zone_conf['from'][local_zone] + elif 'default_firewall' in zone_conf: + local_zone_conf['default_local'][zone] = zone_conf['default_firewall'] set_dependents('conntrack', conf) @@ -627,6 +630,18 @@ def verify(firewall): if v6_name and not dict_search_args(firewall, 'ipv6', 'name', v6_name): raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') + if 'default_firewall' in zone_conf: + v4_name = dict_search_args(zone_conf, 'default_firewall', 'name') + if v4_name and not dict_search_args(firewall, 'ipv4', 'name', v4_name): + raise ConfigError(f'Firewall name "{v4_name}" does not exist') + + v6_name = dict_search_args(zone_conf, 'default_firewall', 'ipv6_name') + if v6_name and not dict_search_args(firewall, 'ipv6', 'name', v6_name): + raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') + + if not v4_name and not v6_name: + raise ConfigError('No firewall names specified for default-firewall') + return None def generate(firewall): |
