Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions data/templates/firewall/nftables-zone.j2
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@
iifname { {{ zone[from_zone].member.vrf | quoted_join(",") }} } counter return
{% 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) }}
}
Expand All @@ -71,6 +75,20 @@
oifname { {{ zone[from_zone].member.vrf | quoted_join(",") }} } counter return
{% 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) }}
}
Expand Down Expand Up @@ -103,6 +121,10 @@
{% endif %}
{% 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) }}
}
Expand Down
23 changes: 23 additions & 0 deletions interface-definitions/firewall.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
41 changes: 41 additions & 0 deletions smoketest/scripts/cli/test_firewall.py
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,47 @@ def test_zone_basic(self):
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()

nftables_search = [
['chain NAME_smoketest'],
['chain NAME_smoketest-default'],
['chain VYOS_ZONE_FORWARD'],
['type filter hook forward priority filter + 1'],
['chain VYOS_ZONE_OUTPUT'],
['type filter hook output priority filter + 1'],
['chain VYOS_ZONE_LOCAL'],
['type filter hook input priority filter + 1'],
['chain VZONE_smoketest-eth0'],
['iifname "eth1"', 'jump NAME_smoketest'],
['jump NAME_smoketest-default'],
['chain VZONE_smoketest-eth1'],
['jump NAME_smoketest-default'],
['chain VZONE_smoketest-eth2'],
['chain VZONE_smoketest-local_IN'],
['iifname "eth0"', 'jump NAME_smoketest'],
['jump NAME_smoketest-default'],
Comment on lines +1022 to +1028
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These duplicate ['jump NAME_smoketest-default'] searchs are redundant.

If you want to check for these jumps in specific chains you can use verify_nftables_chain https://github.com/vyos/vyos-1x/blob/current/smoketest/scripts/cli/base_vyostest_shim.py#L181

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've struggled to understand exactly how the smoke tests actually function. I more or less hoped there was some ordering expectation in the matching. I'll investigate verify_nftables_chain as just looking to see that something like the string I want does not actually make sure the feature works as intended.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the nftables verify functions, each list item is expecting to match all of the specified criteria on a single line of nftables output.

For example ['iifname "eth0"', 'jump NAME_smoketest'], expects to find a rule matching both the interface and the jump.

verify_nftables_chain is best suited to assert your jump NAME_smoketest-default is present on each expected chain.

For example (excuse formatting, also not tested):

self.verify_nftables_chain([['jump NAME_smoketest-default']], 'ip vyos_filter', 'VZONE_smoketest-eth1')
self.verify_nftables_chain([['jump NAME_smoketest-default']], 'ip vyos_filter', 'VZONE_smoketest-eth2')

['chain VZONE_smoketest-local_OUT'],
['iifname "eth0"', 'jump NAME_smoketest'],
['oifname "eth1"', 'jump NAME_smoketest-default']
]

self.verify_nftables(nftables_search, 'ip vyos_filter')

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'])
Expand Down
19 changes: 17 additions & 2 deletions src/conf_mode/firewall.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,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)

Expand Down Expand Up @@ -594,6 +597,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):
Expand Down
Loading