From 99155150df9ceed0be4df46351844451b0683b3b Mon Sep 17 00:00:00 2001 From: Nicolas Fort Date: Fri, 16 Sep 2022 16:35:11 +0000 Subject: T4699: Firewall: Add jump action in firewall rulest --- data/templates/firewall/nftables.j2 | 2 +- interface-definitions/firewall.xml.in | 32 ++++++++++++++++++++++ .../include/firewall/action.xml.i | 8 ++++-- .../include/firewall/default-action.xml.i | 8 ++++-- python/vyos/firewall.py | 4 +++ python/vyos/template.py | 7 ++++- smoketest/scripts/cli/test_firewall.py | 24 ++++++++++++++-- src/conf_mode/firewall.py | 26 ++++++++++++++++++ 8 files changed, 103 insertions(+), 8 deletions(-) diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2 index c0780dad5..9d609f73f 100644 --- a/data/templates/firewall/nftables.j2 +++ b/data/templates/firewall/nftables.j2 @@ -175,7 +175,7 @@ table ip6 vyos_filter { {% endif %} {% endfor %} {% endif %} - {{ conf | nft_default_rule(name_text) }} + {{ conf | nft_default_rule(name_text, ipv6=True) }} } {% endfor %} {% for set_name in ns.sets %} diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index d39dddc77..d6fa76892 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -379,6 +379,14 @@ #include #include #include + + + Set jump target. Action jump must be defined in default-action to use this setting + + firewall ipv6-name + + + Firewall rule number (IPv6) @@ -452,6 +460,14 @@ #include + + + Set jump target. Action jump must be defined to use this setting + + firewall ipv6-name + + + @@ -527,6 +543,14 @@ #include #include #include + + + Set jump target. Action jump must be defined in default-action to use this setting + + firewall name + + + Firewall rule number (IPv4) @@ -599,6 +623,14 @@ #include + + + Set jump target. Action jump must be defined to use this setting + + firewall name + + + #include diff --git a/interface-definitions/include/firewall/action.xml.i b/interface-definitions/include/firewall/action.xml.i index 512cc23bd..0738fa503 100644 --- a/interface-definitions/include/firewall/action.xml.i +++ b/interface-definitions/include/firewall/action.xml.i @@ -3,12 +3,16 @@ Rule action - accept reject drop + accept jump reject drop accept Accept matching entries + + jump + Jump to another chain + reject Reject matching entries @@ -18,7 +22,7 @@ Drop matching entries - (accept|reject|drop) + (accept|jump|reject|drop) diff --git a/interface-definitions/include/firewall/default-action.xml.i b/interface-definitions/include/firewall/default-action.xml.i index 92a2fcaaf..5107768d3 100644 --- a/interface-definitions/include/firewall/default-action.xml.i +++ b/interface-definitions/include/firewall/default-action.xml.i @@ -3,12 +3,16 @@ Default-action for rule-set - drop reject accept + drop jump reject accept drop Drop if no prior rules are hit + + jump + Jump to another chain if no prior rules are hit + reject Drop and notify source if no prior rules are hit @@ -18,7 +22,7 @@ Accept if no prior rules are hit - (drop|reject|accept) + (drop|jump|reject|accept) drop diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index b56caef71..f9b7222fd 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -326,6 +326,10 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): if 'action' in rule_conf: output.append(nft_action(rule_conf['action'])) + if 'jump' in rule_conf['action']: + target = rule_conf['jump_target'] + output.append(f'NAME{def_suffix}_{target}') + else: output.append('return') diff --git a/python/vyos/template.py b/python/vyos/template.py index 9804308c1..2bad6df06 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -548,7 +548,7 @@ def nft_rule(rule_conf, fw_name, rule_id, ip_name='ip'): return parse_rule(rule_conf, fw_name, rule_id, ip_name) @register_filter('nft_default_rule') -def nft_default_rule(fw_conf, fw_name): +def nft_default_rule(fw_conf, fw_name, ipv6=False): output = ['counter'] default_action = fw_conf['default_action'] @@ -557,6 +557,11 @@ def nft_default_rule(fw_conf, fw_name): output.append(f'log prefix "[{fw_name[:19]}-default-{action_suffix}]"') output.append(nft_action(default_action)) + if 'default_jump_target' in fw_conf: + target = fw_conf['default_jump_target'] + def_suffix = '6' if ipv6 else '' + output.append(f'NAME{def_suffix}_{target}') + output.append(f'comment "{fw_name} default-action {default_action}"') return " ".join(output) diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 8e4aac788..c54cba027 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -228,6 +228,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): def test_ipv4_advanced(self): name = 'smoketest-adv' + name2 = 'smoketest-adv2' interface = 'eth0' self.cli_set(['firewall', 'name', name, 'default-action', 'drop']) @@ -246,6 +247,13 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'name', name, 'rule', '7', 'dscp', '3-11']) self.cli_set(['firewall', 'name', name, 'rule', '7', 'dscp-exclude', '21-25']) + self.cli_set(['firewall', 'name', name2, 'default-action', 'jump']) + self.cli_set(['firewall', 'name', name2, 'default-jump-target', name]) + self.cli_set(['firewall', 'name', name2, 'enable-default-log']) + self.cli_set(['firewall', 'name', name2, 'rule', '1', 'source', 'address', '198.51.100.1']) + self.cli_set(['firewall', 'name', name2, 'rule', '1', 'action', 'jump']) + self.cli_set(['firewall', 'name', name2, 'rule', '1', 'jump-target', name]) + self.cli_set(['firewall', 'interface', interface, 'in', 'name', name]) self.cli_commit() @@ -254,7 +262,9 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): [f'iifname "{interface}"', f'jump NAME_{name}'], ['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', 'return'], ['ip length 1-30000', 'ip length != 60000-65535', 'ip dscp 0x03-0x0b', 'ip dscp != 0x15-0x19', 'return'], - [f'log prefix "[{name}-default-D]"', 'drop'] + [f'log prefix "[{name}-default-D]"', 'drop'], + ['ip saddr 198.51.100.1', f'jump NAME_{name}'], + [f'log prefix "[{name2}-default-J]"', f'jump NAME_{name}'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') @@ -291,6 +301,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): def test_ipv6_advanced(self): name = 'v6-smoketest-adv' + name2 = 'v6-smoketest-adv2' interface = 'eth0' self.cli_set(['firewall', 'ipv6-name', name, 'default-action', 'drop']) @@ -309,6 +320,13 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'dscp', '4-14']) self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'dscp-exclude', '31-35']) + self.cli_set(['firewall', 'ipv6-name', name2, 'default-action', 'jump']) + self.cli_set(['firewall', 'ipv6-name', name2, 'default-jump-target', name]) + self.cli_set(['firewall', 'ipv6-name', name2, 'enable-default-log']) + self.cli_set(['firewall', 'ipv6-name', name2, 'rule', '1', 'source', 'address', '2001:db8::/64']) + self.cli_set(['firewall', 'ipv6-name', name2, 'rule', '1', 'action', 'jump']) + self.cli_set(['firewall', 'ipv6-name', name2, 'rule', '1', 'jump-target', name]) + self.cli_set(['firewall', 'interface', interface, 'in', 'ipv6-name', name]) self.cli_commit() @@ -317,7 +335,9 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): [f'iifname "{interface}"', f'jump NAME6_{name}'], ['ip6 length { 65, 513, 1025 }', 'ip6 dscp { af21, 0x35 }', 'return'], ['ip6 length 1-1999', 'ip6 length != 60000-65535', 'ip6 dscp 0x04-0x0e', 'ip6 dscp != 0x1f-0x23', 'return'], - [f'log prefix "[{name}-default-D]"', 'drop'] + [f'log prefix "[{name}-default-D]"', 'drop'], + ['ip6 saddr 2001:db8::/64', f'jump NAME6_{name}'], + [f'log prefix "[{name2}-default-J]"', f'jump NAME6_{name}'] ] self.verify_nftables(nftables_search, 'ip6 vyos_filter') diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index eeb57bd30..cbd9cbe90 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -179,6 +179,20 @@ def verify_rule(firewall, rule_conf, ipv6): if 'action' not in rule_conf: raise ConfigError('Rule action must be defined') + if 'jump' in rule_conf['action'] and 'jump_target' not in rule_conf: + raise ConfigError('Action set to jump, but no jump-target specified') + + if 'jump_target' in rule_conf: + if 'jump' not in rule_conf['action']: + raise ConfigError('jump-target defined, but action jump needed and it is not defined') + target = rule_conf['jump_target'] + if not ipv6: + if target not in dict_search_args(firewall, 'name'): + raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system') + else: + if target not in dict_search_args(firewall, 'ipv6_name'): + raise ConfigError(f'Invalid jump-target. Firewall ipv6-name {target} does not exist on the system') + if 'fragment' in rule_conf: if {'match_frag', 'match_non_frag'} <= set(rule_conf['fragment']): raise ConfigError('Cannot specify both "match-frag" and "match-non-frag"') @@ -287,6 +301,18 @@ def verify(firewall): for name in ['name', 'ipv6_name']: if name in firewall: for name_id, name_conf in firewall[name].items(): + if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf: + raise ConfigError('default-action set to jump, but no default-jump-target specified') + if 'default_jump_target' in name_conf: + target = name_conf['default_jump_target'] + if 'jump' not in name_conf['default_action']: + raise ConfigError('default-jump-target defined,but default-action jump needed and it is not defined') + if name_conf['default_jump_target'] == name_id: + raise ConfigError(f'Loop detected on default-jump-target.') + ## Now need to check that default-jump-target exists (other firewall chain/name) + if target not in dict_search_args(firewall, name): + raise ConfigError(f'Invalid jump-target. Firewall {name} {target} does not exist on the system') + if 'rule' in name_conf: for rule_id, rule_conf in name_conf['rule'].items(): verify_rule(firewall, rule_conf, name == 'ipv6_name') -- cgit v1.2.3