From d62f8ed1e3608d82e3e4fb7566817839023aa39c Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Thu, 29 Sep 2022 13:59:10 +0200 Subject: firewall: T3509: Add support for IPv6 return path filtering --- data/templates/firewall/nftables.j2 | 14 ++++++++++++++ smoketest/scripts/cli/test_firewall.py | 21 +++++++++++++++++++++ src/conf_mode/firewall.py | 1 - 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2 index 10cbc68cb..d889a505d 100644 --- a/data/templates/firewall/nftables.j2 +++ b/data/templates/firewall/nftables.j2 @@ -2,6 +2,20 @@ {% import 'firewall/nftables-defines.j2' as group_tmpl %} +{% if first_install is not vyos_defined %} +delete table inet vyos_rpfilter +{% endif %} +table inet vyos_rpfilter { + chain PREROUTING { + type filter hook prerouting priority -300; policy accept; +{% if global_options.source_validation is vyos_defined('loose') %} + fib saddr oif 0 counter drop +{% elif global_options.source_validation is vyos_defined('strict') %} + fib saddr . iif oif 0 counter drop +{% endif %} + } +} + {% if first_install is not vyos_defined %} delete table ip vyos_filter {% endif %} diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index b2076c077..c6514210b 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -511,6 +511,27 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.verify_nftables(nftables_search, 'ip vyos_filter') + def test_source_validation(self): + # Strict + self.cli_set(['firewall', 'global-options', 'source-validation', 'strict']) + self.cli_commit() + + nftables_strict_search = [ + ['fib saddr . iif oif 0', 'drop'] + ] + + self.verify_nftables(nftables_strict_search, 'inet vyos_global_rpfilter') + + # Loose + self.cli_set(['firewall', 'global-options', 'source-validation', 'loose']) + self.cli_commit() + + nftables_loose_search = [ + ['fib saddr oif 0', 'drop'] + ] + + self.verify_nftables(nftables_loose_search, 'inet vyos_global_rpfilter') + def test_sysfs(self): for name, conf in sysfs_config.items(): paths = glob(conf['sysfs']) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 8ad3f27fc..62116358e 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -54,7 +54,6 @@ sysfs_config = { 'log_martians': {'sysfs': '/proc/sys/net/ipv4/conf/all/log_martians'}, 'receive_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/accept_redirects'}, 'send_redirects': {'sysfs': '/proc/sys/net/ipv4/conf/*/send_redirects'}, - 'source_validation': {'sysfs': '/proc/sys/net/ipv4/conf/*/rp_filter', 'disable': '0', 'strict': '1', 'loose': '2'}, 'syn_cookies': {'sysfs': '/proc/sys/net/ipv4/tcp_syncookies'}, 'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337'} } -- cgit v1.2.3 From b6f742716da5f89c7f3f3501220e0f3ae1df45d8 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Fri, 25 Aug 2023 13:54:47 +0200 Subject: interface: T3509: Add per-interface IPv6 source validation --- data/templates/firewall/nftables.j2 | 4 +-- data/vyos-firewall-init.conf | 4 +++ .../include/interface/ipv6-options.xml.i | 1 + python/vyos/ifconfig/interface.py | 29 ++++++++++++++++++++++ smoketest/scripts/cli/base_interfaces_test.py | 12 +++++++++ 5 files changed, 48 insertions(+), 2 deletions(-) diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2 index d889a505d..84af0449a 100644 --- a/data/templates/firewall/nftables.j2 +++ b/data/templates/firewall/nftables.j2 @@ -3,9 +3,9 @@ {% import 'firewall/nftables-defines.j2' as group_tmpl %} {% if first_install is not vyos_defined %} -delete table inet vyos_rpfilter +delete table inet vyos_global_rpfilter {% endif %} -table inet vyos_rpfilter { +table inet vyos_global_rpfilter { chain PREROUTING { type filter hook prerouting priority -300; policy accept; {% if global_options.source_validation is vyos_defined('loose') %} diff --git a/data/vyos-firewall-init.conf b/data/vyos-firewall-init.conf index 36d92fe93..ab25ab4bd 100644 --- a/data/vyos-firewall-init.conf +++ b/data/vyos-firewall-init.conf @@ -82,6 +82,10 @@ table ip6 raw { type filter hook forward priority -300; policy accept; } + chain vyos_rpfilter { + type filter hook prerouting priority -300; policy accept; + } + chain PREROUTING { type filter hook prerouting priority -300; policy accept; counter jump VYOS_CT_PREROUTING_HOOK diff --git a/interface-definitions/include/interface/ipv6-options.xml.i b/interface-definitions/include/interface/ipv6-options.xml.i index d2e47de91..edb4a74f9 100644 --- a/interface-definitions/include/interface/ipv6-options.xml.i +++ b/interface-definitions/include/interface/ipv6-options.xml.i @@ -9,6 +9,7 @@ #include #include #include + #include diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index ddac387e7..41ce352ad 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -777,6 +777,30 @@ class Interface(Control): return None return self.set_interface('rp_filter', value) + def _cleanup_ipv6_source_validation_rules(self, ifname): + commands = [] + results = self._cmd(f'nft -a list chain ip6 raw vyos_rpfilter').split("\n") + for line in results: + if f'iifname "{ifname}"' in line: + handle_search = re.search('handle (\d+)', line) + if handle_search: + self._cmd(f'nft delete rule ip6 raw vyos_rpfilter handle {handle_search[1]}') + + def set_ipv6_source_validation(self, mode): + """ + Set IPv6 reverse path validation + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0').set_ipv6_source_validation('strict') + """ + self._cleanup_ipv6_source_validation_rules(self.ifname) + nft_prefix = f'nft add rule ip6 raw vyos_rpfilter iifname "{self.ifname}"' + if mode == 'strict': + self._cmd(f"{nft_prefix} fib saddr . iif oif 0 counter drop") + elif mode == 'loose': + self._cmd(f"{nft_prefix} fib saddr oif 0 counter drop") + def set_ipv6_accept_ra(self, accept_ra): """ Accept Router Advertisements; autoconfigure using them. @@ -1568,6 +1592,11 @@ class Interface(Control): value = tmp if (tmp != None) else '0' self.set_ipv4_source_validation(value) + # IPv6 source-validation + tmp = dict_search('ipv6.source_validation', config) + value = tmp if (tmp != None) else '0' + self.set_ipv6_source_validation(value) + # MTU - Maximum Transfer Unit has a default value. It must ALWAYS be set # before mangling any IPv6 option. If MTU is less then 1280 IPv6 will be # automatically disabled by the kernel. Also MTU must be increased before diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index b5b65e253..820024dc9 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -844,6 +844,7 @@ class BasicInterfaceTest: mss = '1400' dad_transmits = '10' accept_dad = '0' + source_validation = 'strict' for interface in self._interfaces: path = self._base_path + [interface] @@ -863,6 +864,9 @@ class BasicInterfaceTest: if cli_defined(self._base_path + ['ipv6'], 'disable-forwarding'): self.cli_set(path + ['ipv6', 'disable-forwarding']) + if cli_defined(self._base_path + ['ipv6'], 'source-validation'): + self.cli_set(path + ['ipv6', 'source-validation', source_validation]) + self.cli_commit() for interface in self._interfaces: @@ -886,6 +890,14 @@ class BasicInterfaceTest: tmp = read_file(f'{proc_base}/forwarding') self.assertEqual('0', tmp) + if cli_defined(self._base_path + ['ipv6'], 'source-validation'): + base_options = f'iifname "{interface}"' + out = cmd('sudo nft list chain ip6 raw vyos_rpfilter') + for line in out.splitlines(): + if line.startswith(base_options): + self.assertIn('fib saddr . iif oif 0', line) + self.assertIn('drop', line) + def test_dhcpv6_client_options(self): if not self._test_ipv6_dhcpc6: self.skipTest('not supported') -- cgit v1.2.3 From 2509a1ab84cdb6d9389b547f93b0904cf329e78a Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Fri, 25 Aug 2023 13:59:31 +0200 Subject: firewall: T5160: Remove unused zone template --- data/templates/firewall/nftables-zone.j2 | 78 -------------------------------- 1 file changed, 78 deletions(-) delete mode 100644 data/templates/firewall/nftables-zone.j2 diff --git a/data/templates/firewall/nftables-zone.j2 b/data/templates/firewall/nftables-zone.j2 deleted file mode 100644 index 17ef5101d..000000000 --- a/data/templates/firewall/nftables-zone.j2 +++ /dev/null @@ -1,78 +0,0 @@ - -{% macro zone_chains(zone, state_policy=False, ipv6=False) %} -{% set fw_name = 'ipv6_name' if ipv6 else 'name' %} -{% set suffix = '6' if ipv6 else '' %} - chain VYOS_ZONE_FORWARD { - type filter hook forward priority 1; policy accept; -{% if state_policy %} - jump VYOS_STATE_POLICY{{ suffix }} -{% endif %} -{% for zone_name, zone_conf in zone.items() %} -{% if 'local_zone' not in zone_conf %} - oifname { {{ zone_conf.interface | join(',') }} } counter jump VZONE_{{ zone_name }} -{% endif %} -{% endfor %} - } - chain VYOS_ZONE_LOCAL { - type filter hook input priority 1; policy accept; -{% if state_policy %} - jump VYOS_STATE_POLICY{{ suffix }} -{% endif %} -{% for zone_name, zone_conf in zone.items() %} -{% if 'local_zone' in zone_conf %} - counter jump VZONE_{{ zone_name }}_IN -{% endif %} -{% endfor %} - } - chain VYOS_ZONE_OUTPUT { - type filter hook output priority 1; policy accept; -{% if state_policy %} - jump VYOS_STATE_POLICY{{ suffix }} -{% endif %} -{% for zone_name, zone_conf in zone.items() %} -{% if 'local_zone' in zone_conf %} - counter jump VZONE_{{ zone_name }}_OUT -{% endif %} -{% endfor %} - } -{% for zone_name, zone_conf in zone.items() %} -{% if zone_conf.local_zone is vyos_defined %} - chain VZONE_{{ zone_name }}_IN { - iifname lo counter return -{% if zone_conf.from is vyos_defined %} -{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall[fw_name] is vyos_defined %} - iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }} - iifname { {{ zone[from_zone].interface | join(",") }} } counter return -{% endfor %} -{% endif %} - {{ zone_conf | nft_default_rule('zone_' + zone_name) }} - } - chain VZONE_{{ zone_name }}_OUT { - oifname lo counter return -{% if zone_conf.from_local is vyos_defined %} -{% for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall[fw_name] is vyos_defined %} - oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }} - oifname { {{ zone[from_zone].interface | join(",") }} } counter return -{% endfor %} -{% endif %} - {{ zone_conf | nft_default_rule('zone_' + zone_name) }} - } -{% else %} - chain VZONE_{{ zone_name }} { - iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6) }} -{% if zone_conf.intra_zone_filtering is vyos_defined %} - iifname { {{ zone_conf.interface | join(",") }} } counter return -{% endif %} -{% if zone_conf.from is vyos_defined %} -{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall[fw_name] is vyos_defined %} -{% if zone[from_zone].local_zone is not defined %} - iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }} - iifname { {{ zone[from_zone].interface | join(",") }} } counter return -{% endif %} -{% endfor %} -{% endif %} - {{ zone_conf | nft_default_rule('zone_' + zone_name) }} - } -{% endif %} -{% endfor %} -{% endmacro %} -- cgit v1.2.3