diff options
-rw-r--r-- | interface-definitions/include/firewall/common-rule.xml.i | 2 | ||||
-rw-r--r-- | interface-definitions/include/firewall/rule-log-options.xml.i | 89 | ||||
-rw-r--r-- | python/vyos/firewall.py | 17 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_firewall.py | 18 | ||||
-rwxr-xr-x | src/conf_mode/firewall.py | 10 | ||||
-rwxr-xr-x | src/migration-scripts/firewall/9-to-10 | 80 |
6 files changed, 205 insertions, 11 deletions
diff --git a/interface-definitions/include/firewall/common-rule.xml.i b/interface-definitions/include/firewall/common-rule.xml.i index 3fe3ca872..7417a3c58 100644 --- a/interface-definitions/include/firewall/common-rule.xml.i +++ b/interface-definitions/include/firewall/common-rule.xml.i @@ -119,7 +119,7 @@ </constraint> </properties> </leafNode> -#include <include/firewall/rule-log-level.xml.i> +#include <include/firewall/rule-log-options.xml.i> <node name="connection-status"> <properties> <help>Connection status</help> diff --git a/interface-definitions/include/firewall/rule-log-options.xml.i b/interface-definitions/include/firewall/rule-log-options.xml.i new file mode 100644 index 000000000..e8b0cdec3 --- /dev/null +++ b/interface-definitions/include/firewall/rule-log-options.xml.i @@ -0,0 +1,89 @@ +<!-- include start from firewall/rule-log-options.xml.i --> +<node name="log-options"> + <properties> + <help>Log options</help> + </properties> + <children> + <leafNode name="group"> + <properties> + <help>Set log group</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Log group to send messages to</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="snapshot-length"> + <properties> + <help>Length of packet payload to include in netlink message</help> + <valueHelp> + <format>u32:0-9000</format> + <description>Length of packet payload to include in netlink message</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-9000"/> + </constraint> + </properties> + </leafNode> + <leafNode name="queue-threshold"> + <properties> + <help>Number of packets to queue inside the kernel before sending them to userspace</help> + <valueHelp> + <format>u32:0-65535</format> + <description>Number of packets to queue inside the kernel before sending them to userspace</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-65535"/> + </constraint> + </properties> + </leafNode> + <leafNode name="level"> + <properties> + <help>Set log-level</help> + <completionHelp> + <list>emerg alert crit err warn notice info debug</list> + </completionHelp> + <valueHelp> + <format>emerg</format> + <description>Emerg log level</description> + </valueHelp> + <valueHelp> + <format>alert</format> + <description>Alert log level</description> + </valueHelp> + <valueHelp> + <format>crit</format> + <description>Critical log level</description> + </valueHelp> + <valueHelp> + <format>err</format> + <description>Error log level</description> + </valueHelp> + <valueHelp> + <format>warn</format> + <description>Warning log level</description> + </valueHelp> + <valueHelp> + <format>notice</format> + <description>Notice log level</description> + </valueHelp> + <valueHelp> + <format>info</format> + <description>Info log level</description> + </valueHelp> + <valueHelp> + <format>debug</format> + <description>Debug log level</description> + </valueHelp> + <constraint> + <regex>(emerg|alert|crit|err|warn|notice|info|debug)</regex> + </constraint> + <constraintErrorMessage>level must be alert, crit, debug, emerg, err, info, notice or warn</constraintErrorMessage> + </properties> + </leafNode> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index 5be897d5f..137fd2c56 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -223,10 +223,21 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name): action = rule_conf['action'] if 'action' in rule_conf else 'accept' output.append(f'log prefix "[{fw_name[:19]}-{rule_id}-{action[:1].upper()}]"') - if 'log_level' in rule_conf: - log_level = rule_conf['log_level'] - output.append(f'level {log_level}') + if 'level' in rule_conf['log_options']: + log_level = rule_conf['log_options']['level'] + output.append(f'log level {log_level}') + if 'group' in rule_conf['log_options']: + log_group = rule_conf['log_options']['group'] + output.append(f'log group {log_group}') + + if 'queue_threshold' in rule_conf['log_options']: + queue_threshold = rule_conf['log_options']['queue_threshold'] + output.append(f'queue-threshold {queue_threshold}') + + if 'snapshot_length' in rule_conf['log_options']: + log_snaplen = rule_conf['log_options']['snapshot_length'] + output.append(f'snaplen {log_snaplen}') if 'hop_limit' in rule_conf: operators = {'eq': '==', 'gt': '>', 'lt': '<'} diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index d61534d87..e071b7df9 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -207,13 +207,13 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'name', name, 'rule', '1', 'source', 'address', '172.16.20.10']) self.cli_set(['firewall', 'name', name, 'rule', '1', 'destination', 'address', '172.16.10.10']) self.cli_set(['firewall', 'name', name, 'rule', '1', 'log', 'enable']) - self.cli_set(['firewall', 'name', name, 'rule', '1', 'log-level', 'debug']) + self.cli_set(['firewall', 'name', name, 'rule', '1', 'log-options', 'level', 'debug']) self.cli_set(['firewall', 'name', name, 'rule', '1', 'ttl', 'eq', '15']) self.cli_set(['firewall', 'name', name, 'rule', '2', 'action', 'reject']) self.cli_set(['firewall', 'name', name, 'rule', '2', 'protocol', 'tcp']) self.cli_set(['firewall', 'name', name, 'rule', '2', 'destination', 'port', '8888']) self.cli_set(['firewall', 'name', name, 'rule', '2', 'log', 'enable']) - self.cli_set(['firewall', 'name', name, 'rule', '2', 'log-level', 'err']) + self.cli_set(['firewall', 'name', name, 'rule', '2', 'log-options', 'level', 'err']) self.cli_set(['firewall', 'name', name, 'rule', '2', 'tcp', 'flags', 'syn']) self.cli_set(['firewall', 'name', name, 'rule', '2', 'tcp', 'flags', 'not', 'ack']) self.cli_set(['firewall', 'name', name, 'rule', '2', 'ttl', 'gt', '102']) @@ -247,8 +247,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): nftables_search = [ [f'iifname "{interface}"', f'jump NAME_{name}'], - ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[smoketest-1-A]" level debug', 'ip ttl 15', 'return'], - ['tcp flags syn / syn,ack', 'tcp dport 8888', 'log prefix "[smoketest-2-R]" level err', 'ip ttl > 102', 'reject'], + ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[smoketest-1-A]" log level debug', 'ip ttl 15', 'return'], + ['tcp flags syn / syn,ack', 'tcp dport 8888', 'log prefix "[smoketest-2-R]" log level err', 'ip ttl > 102', 'reject'], ['tcp dport 22', 'limit rate 5/minute', 'return'], ['log prefix "[smoketest-default-D]"','smoketest default-action', 'drop'], ['tcp dport 22', 'add @RECENT_smoketest_4 { ip saddr limit rate over 10/minute burst 10 packets }', 'meta pkttype host', 'drop'], @@ -272,6 +272,10 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'name', name, 'rule', '6', 'packet-length', '1024']) self.cli_set(['firewall', 'name', name, 'rule', '6', 'dscp', '17']) self.cli_set(['firewall', 'name', name, 'rule', '6', 'dscp', '52']) + self.cli_set(['firewall', 'name', name, 'rule', '6', 'log', 'enable']) + self.cli_set(['firewall', 'name', name, 'rule', '6', 'log-options', 'group', '66']) + self.cli_set(['firewall', 'name', name, 'rule', '6', 'log-options', 'snapshot-length', '6666']) + self.cli_set(['firewall', 'name', name, 'rule', '6', 'log-options', 'queue-threshold','32000']) self.cli_set(['firewall', 'name', name, 'rule', '7', 'action', 'accept']) self.cli_set(['firewall', 'name', name, 'rule', '7', 'packet-length', '1-30000']) @@ -301,7 +305,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): nftables_search = [ [f'iifname "{interface}"', f'jump NAME_{name}'], - ['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', 'return'], + ['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', f'log prefix "[{name}-6-A]" log group 66 snaplen 6666 queue-threshold 32000', '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'], ['ip saddr 198.51.100.1', f'jump NAME_{name}'], @@ -357,7 +361,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'source', 'address', '2002::1']) self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'destination', 'address', '2002::1:1']) self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'log', 'enable']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'log-level', 'crit']) + self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'log-options', 'level', 'crit']) self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'action', 'reject']) self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'protocol', 'tcp_udp']) @@ -374,7 +378,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): nftables_search = [ [f'iifname "{interface}"', f'jump NAME6_{name}'], - ['saddr 2002::1', 'daddr 2002::1:1', 'log prefix "[v6-smoketest-1-A]" level crit', 'return'], + ['saddr 2002::1', 'daddr 2002::1:1', 'log prefix "[v6-smoketest-1-A]" log level crit', 'return'], ['meta l4proto { tcp, udp }', 'th dport 8888', f'iifname "{interface}"', 'reject'], ['meta l4proto gre', f'oifname "{interface}"', 'return'], ['smoketest default-action', f'log prefix "[{name}-default-D]"', 'drop'] diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index b63ed4eb9..c41a442df 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -282,6 +282,16 @@ def verify_rule(firewall, rule_conf, ipv6): if rule_conf['protocol'] not in ['tcp', 'udp', 'tcp_udp']: raise ConfigError('Protocol must be tcp, udp, or tcp_udp when specifying a port or port-group') + if 'log_options' in rule_conf: + if 'log' not in rule_conf or 'enable' not in rule_conf['log']: + raise ConfigError('log-options defined, but log is not enable') + + if 'snapshot_length' in rule_conf['log_options'] and 'group' not in rule_conf['log_options']: + raise ConfigError('log-options snapshot-length defined, but log group is not define') + + if 'queue_threshold' in rule_conf['log_options'] and 'group' not in rule_conf['log_options']: + raise ConfigError('log-options queue-threshold defined, but log group is not define') + def verify_nested_group(group_name, group, groups, seen): if 'include' not in group: return diff --git a/src/migration-scripts/firewall/9-to-10 b/src/migration-scripts/firewall/9-to-10 new file mode 100755 index 000000000..6f67cc512 --- /dev/null +++ b/src/migration-scripts/firewall/9-to-10 @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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/>. + +# T5050: Log options +# cli changes from: +# set firewall [name | ipv6-name] <name> rule <number> log-level <log_level> +# To +# set firewall [name | ipv6-name] <name> rule <number> log-options level <log_level> + +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']): + log_options_base = base + ['name', name, 'rule', rule, 'log-options'] + rule_log_level = base + ['name', name, 'rule', rule, 'log-level'] + + if config.exists(rule_log_level): + tmp = config.return_value(rule_log_level) + config.delete(rule_log_level) + config.set(log_options_base + ['level'], 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']): + log_options_base = base + ['ipv6-name', name, 'rule', rule, 'log-options'] + rule_log_level = base + ['ipv6-name', name, 'rule', rule, 'log-level'] + + if config.exists(rule_log_level): + tmp = config.return_value(rule_log_level) + config.delete(rule_log_level) + config.set(log_options_base + ['level'], 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 |