diff options
author | Christian Breunig <christian@breunig.cc> | 2024-08-04 09:07:15 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-08-04 09:07:15 +0200 |
commit | 15c77978f30bebe7c6d4f4e9a87c56e12e1382cd (patch) | |
tree | b27bd1e95b512a341a6591ef1435b73ff9531865 | |
parent | 998df24dc4ed0c1ccd572d09c438d96fe6b79ba8 (diff) | |
parent | c33cd6157ebc5c08dc1e3ff1aa36f2d2fbb9ca83 (diff) | |
download | vyos-1x-15c77978f30bebe7c6d4f4e9a87c56e12e1382cd.tar.gz vyos-1x-15c77978f30bebe7c6d4f4e9a87c56e12e1382cd.zip |
Merge pull request #3901 from nicolas-fort/T4072-extend-bridge-fwall
T4072: firewall extend bridge firewall
21 files changed, 773 insertions, 196 deletions
diff --git a/data/templates/firewall/nftables-bridge.j2 b/data/templates/firewall/nftables-bridge.j2 index dec027bf9..1975fb9b0 100644 --- a/data/templates/firewall/nftables-bridge.j2 +++ b/data/templates/firewall/nftables-bridge.j2 @@ -1,9 +1,13 @@ +{% import 'firewall/nftables-defines.j2' as group_tmpl %} {% macro bridge(bridge) %} {% set ns = namespace(sets=[]) %} {% if bridge.forward is vyos_defined %} {% for prior, conf in bridge.forward.items() %} chain VYOS_FORWARD_{{ prior }} { type filter hook forward priority {{ prior }}; policy accept; +{% if global_options.state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} {% if conf.rule is vyos_defined %} {% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} {{ rule_conf | nft_rule('FWD', prior, rule_id, 'bri') }} @@ -17,6 +21,46 @@ {% endfor %} {% endif %} +{% if bridge.input is vyos_defined %} +{% for prior, conf in bridge.input.items() %} + chain VYOS_INPUT_{{ prior }} { + type filter hook input priority {{ prior }}; policy accept; +{% if global_options.state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} +{% if conf.rule is vyos_defined %} +{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} + {{ rule_conf | nft_rule('INP', prior, rule_id, 'bri') }} +{% if rule_conf.recent is vyos_defined %} +{% set ns.sets = ns.sets + ['INP_' + prior + '_' + rule_id] %} +{% endif %} +{% endfor %} +{% endif %} + {{ conf | nft_default_rule('INP-filter', 'bri') }} + } +{% endfor %} +{% endif %} + +{% if bridge.output is vyos_defined %} +{% for prior, conf in bridge.output.items() %} + chain VYOS_OUTUT_{{ prior }} { + type filter hook output priority {{ prior }}; policy accept; +{% if global_options.state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} +{% if conf.rule is vyos_defined %} +{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} + {{ rule_conf | nft_rule('OUT', prior, rule_id, 'bri') }} +{% if rule_conf.recent is vyos_defined %} +{% set ns.sets = ns.sets + ['OUT_' + prior + '_' + rule_id] %} +{% endif %} +{% endfor %} +{% endif %} + {{ conf | nft_default_rule('OUT-filter', 'bri') }} + } +{% endfor %} +{% endif %} + {% if bridge.name is vyos_defined %} {% for name_text, conf in bridge.name.items() %} chain NAME_{{ name_text }} { @@ -32,4 +76,45 @@ } {% endfor %} {% endif %} + +{% for set_name in ns.sets %} + set RECENT_{{ set_name }} { + type ipv4_addr + size 65535 + flags dynamic + } +{% endfor %} +{% for set_name in ip_fqdn %} + set FQDN_{{ set_name }} { + type ipv4_addr + flags interval + } +{% endfor %} +{% if geoip_updated.name is vyos_defined %} +{% for setname in geoip_updated.name %} + set {{ setname }} { + type ipv4_addr + flags interval + } +{% endfor %} +{% endif %} + +{{ group_tmpl.groups(group, False, True) }} +{{ group_tmpl.groups(group, True, True) }} + +{% if global_options.state_policy is vyos_defined %} + chain VYOS_STATE_POLICY { +{% if global_options.state_policy.established is vyos_defined %} + {{ global_options.state_policy.established | nft_state_policy('established') }} +{% endif %} +{% if global_options.state_policy.invalid is vyos_defined %} + {{ global_options.state_policy.invalid | nft_state_policy('invalid') }} +{% endif %} +{% if global_options.state_policy.related is vyos_defined %} + {{ global_options.state_policy.related | nft_state_policy('related') }} +{% endif %} + return + } +{% endif %} + {% endmacro %} diff --git a/data/templates/firewall/nftables-defines.j2 b/data/templates/firewall/nftables-defines.j2 index 8a75ab2d6..fa6cd74c0 100644 --- a/data/templates/firewall/nftables-defines.j2 +++ b/data/templates/firewall/nftables-defines.j2 @@ -1,7 +1,7 @@ {% macro groups(group, is_ipv6, is_l3) %} {% if group is vyos_defined %} {% set ip_type = 'ipv6_addr' if is_ipv6 else 'ipv4_addr' %} -{% if group.address_group is vyos_defined and not is_ipv6 and is_l3 %} +{% if group.address_group is vyos_defined and not is_ipv6 %} {% for group_name, group_conf in group.address_group.items() %} {% set includes = group_conf.include if group_conf.include is vyos_defined else [] %} set A_{{ group_name }} { @@ -14,7 +14,7 @@ } {% endfor %} {% endif %} -{% if group.ipv6_address_group is vyos_defined and is_ipv6 and is_l3 %} +{% if group.ipv6_address_group is vyos_defined and is_ipv6 %} {% for group_name, group_conf in group.ipv6_address_group.items() %} {% set includes = group_conf.include if group_conf.include is vyos_defined else [] %} set A6_{{ group_name }} { @@ -46,7 +46,7 @@ } {% endfor %} {% endif %} -{% if group.network_group is vyos_defined and not is_ipv6 and is_l3 %} +{% if group.network_group is vyos_defined and not is_ipv6 %} {% for group_name, group_conf in group.network_group.items() %} {% set includes = group_conf.include if group_conf.include is vyos_defined else [] %} set N_{{ group_name }} { @@ -59,7 +59,7 @@ } {% endfor %} {% endif %} -{% if group.ipv6_network_group is vyos_defined and is_ipv6 and is_l3 %} +{% if group.ipv6_network_group is vyos_defined and is_ipv6 %} {% for group_name, group_conf in group.ipv6_network_group.items() %} {% set includes = group_conf.include if group_conf.include is vyos_defined else [] %} set N6_{{ group_name }} { @@ -72,7 +72,7 @@ } {% endfor %} {% endif %} -{% if group.port_group is vyos_defined and is_l3 %} +{% if group.port_group is vyos_defined %} {% for group_name, group_conf in group.port_group.items() %} {% set includes = group_conf.include if group_conf.include is vyos_defined else [] %} set P_{{ group_name }} { diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2 index 68a3bfd87..82dcefac0 100644 --- a/data/templates/firewall/nftables.j2 +++ b/data/templates/firewall/nftables.j2 @@ -339,7 +339,104 @@ table ip6 vyos_filter { delete table bridge vyos_filter {% endif %} table bridge vyos_filter { -{{ bridge_tmpl.bridge(bridge) }} +{% if bridge is vyos_defined %} +{% if bridge.forward is vyos_defined %} +{% for prior, conf in bridge.forward.items() %} + chain VYOS_FORWARD_{{ prior }} { + type filter hook forward priority {{ prior }}; policy accept; +{% if global_options.state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} +{% if conf.rule is vyos_defined %} +{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} + {{ rule_conf | nft_rule('FWD', prior, rule_id, 'bri') }} +{% endfor %} +{% endif %} + {{ conf | nft_default_rule('FWD-' + prior, 'bri') }} + } +{% endfor %} +{% endif %} + +{% if bridge.input is vyos_defined %} +{% for prior, conf in bridge.input.items() %} + chain VYOS_INPUT_{{ prior }} { + type filter hook input priority {{ prior }}; policy accept; +{% if global_options.state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} +{% if conf.rule is vyos_defined %} +{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} + {{ rule_conf | nft_rule('INP', prior, rule_id, 'bri') }} +{% endfor %} +{% endif %} + {{ conf | nft_default_rule('INP-' + prior, 'bri') }} + } +{% endfor %} +{% endif %} + +{% if bridge.output is vyos_defined %} +{% for prior, conf in bridge.output.items() %} + chain VYOS_OUTUT_{{ prior }} { + type filter hook output priority {{ prior }}; policy accept; +{% if global_options.state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} +{% if conf.rule is vyos_defined %} +{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} + {{ rule_conf | nft_rule('OUT', prior, rule_id, 'bri') }} +{% endfor %} +{% endif %} + {{ conf | nft_default_rule('OUT-' + prior, 'bri') }} + } +{% endfor %} +{% endif %} + +{% if bridge.prerouting is vyos_defined %} +{% for prior, conf in bridge.prerouting.items() %} + chain VYOS_PREROUTING_{{ prior }} { + type filter hook prerouting priority {{ prior }}; policy accept; +{% if conf.rule is vyos_defined %} +{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} + {{ rule_conf | nft_rule('PRE', prior, rule_id, 'bri') }} +{% endfor %} +{% endif %} + {{ conf | nft_default_rule('PRE-' + prior, 'bri') }} + } +{% endfor %} +{% endif %} + +{% if bridge.name is vyos_defined %} +{% for name_text, conf in bridge.name.items() %} + chain NAME_{{ name_text }} { +{% if conf.rule is vyos_defined %} +{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %} + {{ rule_conf | nft_rule('NAM', name_text, rule_id, 'bri') }} +{% if rule_conf.recent is vyos_defined %} +{% set ns.sets = ns.sets + ['NAM_' + name_text + '_' + rule_id] %} +{% endif %} +{% endfor %} +{% endif %} + {{ conf | nft_default_rule(name_text, 'bri') }} + } +{% endfor %} +{% endif %} + +{% endif %} {{ group_tmpl.groups(group, False, False) }} +{{ group_tmpl.groups(group, True, False) }} -} +{% if global_options.state_policy is vyos_defined %} + chain VYOS_STATE_POLICY { +{% if global_options.state_policy.established is vyos_defined %} + {{ global_options.state_policy.established | nft_state_policy('established') }} +{% endif %} +{% if global_options.state_policy.invalid is vyos_defined %} + {{ global_options.state_policy.invalid | nft_state_policy('invalid') }} +{% endif %} +{% if global_options.state_policy.related is vyos_defined %} + {{ global_options.state_policy.related | nft_state_policy('related') }} +{% endif %} + return + } +{% endif %} +}
\ No newline at end of file diff --git a/data/templates/firewall/sysctl-firewall.conf.j2 b/data/templates/firewall/sysctl-firewall.conf.j2 index b9c3311e2..6c33ffdc8 100644 --- a/data/templates/firewall/sysctl-firewall.conf.j2 +++ b/data/templates/firewall/sysctl-firewall.conf.j2 @@ -13,6 +13,14 @@ net.ipv4.conf.*.send_redirects = {{ 1 if global_options.send_redirects == 'enabl net.ipv4.tcp_syncookies = {{ 1 if global_options.syn_cookies == 'enable' else 0 }} net.ipv4.tcp_rfc1337 = {{ 1 if global_options.twa_hazards_protection == 'enable' else 0 }} +{% if global_options.apply_to_bridged_traffic is vyos_defined %} +net.bridge.bridge-nf-call-iptables = {{ 1 if global_options.apply_to_bridged_traffic.ipv4 is vyos_defined else 0 }} +net.bridge.bridge-nf-call-ip6tables = {{ 1 if global_options.apply_to_bridged_traffic.ipv6 is vyos_defined else 0 }} +{% else %} +net.bridge.bridge-nf-call-iptables = 0 +net.bridge.bridge-nf-call-ip6tables = 0 +{% endif %} + ## Timeout values: net.netfilter.nf_conntrack_icmp_timeout = {{ global_options.timeout.icmp }} net.netfilter.nf_conntrack_generic_timeout = {{ global_options.timeout.other }} diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index dc4625af0..816dd1855 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -367,6 +367,9 @@ </properties> <children> #include <include/firewall/bridge-hook-forward.xml.i> + #include <include/firewall/bridge-hook-input.xml.i> + #include <include/firewall/bridge-hook-output.xml.i> + #include <include/firewall/bridge-hook-prerouting.xml.i> #include <include/firewall/bridge-custom-name.xml.i> </children> </node> diff --git a/interface-definitions/include/firewall/address-inet.xml.i b/interface-definitions/include/firewall/address-inet.xml.i new file mode 100644 index 000000000..02ed8f6e4 --- /dev/null +++ b/interface-definitions/include/firewall/address-inet.xml.i @@ -0,0 +1,63 @@ +<!-- include start from firewall/address-inet.xml.i --> +<leafNode name="address"> + <properties> + <help>IP address, subnet, or range</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 address to match</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 prefix to match</description> + </valueHelp> + <valueHelp> + <format>ipv4range</format> + <description>IPv4 address range to match</description> + </valueHelp> + <valueHelp> + <format>!ipv4</format> + <description>Match everything except the specified address</description> + </valueHelp> + <valueHelp> + <format>!ipv4net</format> + <description>Match everything except the specified prefix</description> + </valueHelp> + <valueHelp> + <format>!ipv4range</format> + <description>Match everything except the specified range</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>Subnet to match</description> + </valueHelp> + <valueHelp> + <format>ipv6range</format> + <description>IP range to match</description> + </valueHelp> + <valueHelp> + <format>!ipv6</format> + <description>Match everything except the specified address</description> + </valueHelp> + <valueHelp> + <format>!ipv6net</format> + <description>Match everything except the specified prefix</description> + </valueHelp> + <valueHelp> + <format>!ipv6range</format> + <description>Match everything except the specified range</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv4-prefix"/> + <validator name="ipv4-range"/> + <validator name="ipv4-address-exclude"/> + <validator name="ipv4-prefix-exclude"/> + <validator name="ipv4-range-exclude"/> + <validator name="ipv6"/> + <validator name="ipv6-exclude"/> + <validator name="ipv6-range"/> + <validator name="ipv6-range-exclude"/> + </constraint> + </properties> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/address-mask-inet.xml.i b/interface-definitions/include/firewall/address-mask-inet.xml.i new file mode 100644 index 000000000..e2a5927ab --- /dev/null +++ b/interface-definitions/include/firewall/address-mask-inet.xml.i @@ -0,0 +1,19 @@ +<!-- include start from firewall/address-mask-inet.xml.i --> +<leafNode name="address-mask"> + <properties> + <help>IP mask</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 mask to apply</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>IP mask to apply</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ipv6"/> + </constraint> + </properties> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/bridge-custom-name.xml.i b/interface-definitions/include/firewall/bridge-custom-name.xml.i index 654493c0e..9a2a829d0 100644 --- a/interface-definitions/include/firewall/bridge-custom-name.xml.i +++ b/interface-definitions/include/firewall/bridge-custom-name.xml.i @@ -32,6 +32,12 @@ </properties> <children> #include <include/firewall/common-rule-bridge.xml.i> + #include <include/firewall/action-l2.xml.i> + #include <include/firewall/connection-mark.xml.i> + #include <include/firewall/connection-status.xml.i> + #include <include/firewall/state.xml.i> + #include <include/firewall/inbound-interface.xml.i> + #include <include/firewall/outbound-interface.xml.i> </children> </tagNode> </children> diff --git a/interface-definitions/include/firewall/bridge-hook-forward.xml.i b/interface-definitions/include/firewall/bridge-hook-forward.xml.i index 99f66ec77..fcc981925 100644 --- a/interface-definitions/include/firewall/bridge-hook-forward.xml.i +++ b/interface-definitions/include/firewall/bridge-hook-forward.xml.i @@ -26,6 +26,12 @@ </properties> <children> #include <include/firewall/common-rule-bridge.xml.i> + #include <include/firewall/action-l2.xml.i> + #include <include/firewall/connection-mark.xml.i> + #include <include/firewall/connection-status.xml.i> + #include <include/firewall/state.xml.i> + #include <include/firewall/inbound-interface.xml.i> + #include <include/firewall/outbound-interface.xml.i> </children> </tagNode> </children> diff --git a/interface-definitions/include/firewall/bridge-hook-input.xml.i b/interface-definitions/include/firewall/bridge-hook-input.xml.i new file mode 100644 index 000000000..f6a11f8da --- /dev/null +++ b/interface-definitions/include/firewall/bridge-hook-input.xml.i @@ -0,0 +1,40 @@ +<!-- include start from firewall/bridge-hook-input.xml.i --> +<node name="input"> + <properties> + <help>Bridge input firewall</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Bridge firewall input filter</help> + </properties> + <children> + #include <include/firewall/default-action-base-chains.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>Bridge Firewall input filter rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-bridge.xml.i> + #include <include/firewall/action-l2.xml.i> + #include <include/firewall/connection-mark.xml.i> + #include <include/firewall/connection-status.xml.i> + #include <include/firewall/state.xml.i> + #include <include/firewall/inbound-interface.xml.i> + </children> + </tagNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/bridge-hook-output.xml.i b/interface-definitions/include/firewall/bridge-hook-output.xml.i new file mode 100644 index 000000000..38b8b08ca --- /dev/null +++ b/interface-definitions/include/firewall/bridge-hook-output.xml.i @@ -0,0 +1,40 @@ +<!-- include start from firewall/bridge-hook-output.xml.i --> +<node name="output"> + <properties> + <help>Bridge output firewall</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Bridge firewall output filter</help> + </properties> + <children> + #include <include/firewall/default-action-base-chains.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>Bridge Firewall output filter rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-bridge.xml.i> + #include <include/firewall/action-l2.xml.i> + #include <include/firewall/connection-mark.xml.i> + #include <include/firewall/connection-status.xml.i> + #include <include/firewall/state.xml.i> + #include <include/firewall/outbound-interface.xml.i> + </children> + </tagNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/bridge-hook-prerouting.xml.i b/interface-definitions/include/firewall/bridge-hook-prerouting.xml.i new file mode 100644 index 000000000..ea567644f --- /dev/null +++ b/interface-definitions/include/firewall/bridge-hook-prerouting.xml.i @@ -0,0 +1,37 @@ +<!-- include start from firewall/bridge-hook-prerouting.xml.i --> +<node name="prerouting"> + <properties> + <help>Bridge prerouting firewall</help> + </properties> + <children> + <node name="filter"> + <properties> + <help>Bridge firewall prerouting filter</help> + </properties> + <children> + #include <include/firewall/default-action-base-chains.xml.i> + #include <include/firewall/default-log.xml.i> + #include <include/generic-description.xml.i> + <tagNode name="rule"> + <properties> + <help>Bridge firewall prerouting filter rule number</help> + <valueHelp> + <format>u32:1-999999</format> + <description>Number for this firewall rule</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-999999"/> + </constraint> + <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage> + </properties> + <children> + #include <include/firewall/common-rule-bridge.xml.i> + #include <include/firewall/action-and-notrack.xml.i> + #include <include/firewall/inbound-interface.xml.i> + </children> + </tagNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/firewall/common-rule-bridge.xml.i b/interface-definitions/include/firewall/common-rule-bridge.xml.i index dcdd970ac..9ae28f7be 100644 --- a/interface-definitions/include/firewall/common-rule-bridge.xml.i +++ b/interface-definitions/include/firewall/common-rule-bridge.xml.i @@ -1,15 +1,36 @@ <!-- include start from firewall/common-rule-bridge.xml.i --> -#include <include/firewall/action-l2.xml.i> +#include <include/generic-description.xml.i> +#include <include/generic-disable-node.xml.i> +#include <include/firewall/dscp.xml.i> +#include <include/firewall/firewall-mark.xml.i> +#include <include/firewall/fragment.xml.i> +#include <include/firewall/hop-limit.xml.i> +#include <include/firewall/icmp.xml.i> +#include <include/firewall/icmpv6.xml.i> +#include <include/firewall/limit.xml.i> +#include <include/firewall/log.xml.i> +#include <include/firewall/log-options.xml.i> +#include <include/firewall/match-ipsec.xml.i> +#include <include/firewall/match-vlan.xml.i> #include <include/firewall/nft-queue.xml.i> +#include <include/firewall/packet-options.xml.i> +#include <include/firewall/protocol.xml.i> +#include <include/firewall/tcp-flags.xml.i> +#include <include/firewall/tcp-mss.xml.i> +#include <include/firewall/time.xml.i> +#include <include/firewall/ttl.xml.i> <node name="destination"> <properties> <help>Destination parameters</help> </properties> <children> #include <include/firewall/mac-address.xml.i> + #include <include/firewall/address-inet.xml.i> + #include <include/firewall/address-mask-inet.xml.i> + #include <include/firewall/port.xml.i> + #include <include/firewall/source-destination-group-inet.xml.i> </children> </node> -#include <include/generic-disable-node.xml.i> <leafNode name="jump-target"> <properties> <help>Set jump target. Action jump must be defined to use this setting</help> @@ -18,17 +39,16 @@ </completionHelp> </properties> </leafNode> -#include <include/firewall/log.xml.i> -#include <include/firewall/log-options.xml.i> <node name="source"> <properties> <help>Source parameters</help> </properties> <children> #include <include/firewall/mac-address.xml.i> + #include <include/firewall/address-inet.xml.i> + #include <include/firewall/address-mask-inet.xml.i> + #include <include/firewall/port.xml.i> + #include <include/firewall/source-destination-group-inet.xml.i> </children> </node> -#include <include/firewall/inbound-interface.xml.i> -#include <include/firewall/outbound-interface.xml.i> -#include <include/firewall/match-vlan.xml.i> <!-- include end --> diff --git a/interface-definitions/include/firewall/global-options.xml.i b/interface-definitions/include/firewall/global-options.xml.i index 9039b76fd..cee8f1854 100644 --- a/interface-definitions/include/firewall/global-options.xml.i +++ b/interface-definitions/include/firewall/global-options.xml.i @@ -44,6 +44,25 @@ </properties> <defaultValue>disable</defaultValue> </leafNode> + <node name="apply-to-bridged-traffic"> + <properties> + <help>Apply configured firewall rules to traffic switched by bridges</help> + </properties> + <children> + <leafNode name="ipv4"> + <properties> + <help>Apply configured IPv4 firewall rules</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ipv6"> + <properties> + <help>Apply configured IPv6 firewall rules</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> <leafNode name="directed-broadcast"> <properties> <help>Policy for handling IPv4 directed broadcast forwarding on all interfaces</help> diff --git a/interface-definitions/include/firewall/set-packet-modifications.xml.i b/interface-definitions/include/firewall/set-packet-modifications.xml.i new file mode 100644 index 000000000..ee019b64e --- /dev/null +++ b/interface-definitions/include/firewall/set-packet-modifications.xml.i @@ -0,0 +1,96 @@ +<!-- include start from firewall/set-packet-modifications.xml.i --> +<node name="set"> + <properties> + <help>Packet modifications</help> + </properties> + <children> + <leafNode name="connection-mark"> + <properties> + <help>Set connection mark</help> + <valueHelp> + <format>u32:0-2147483647</format> + <description>Connection mark</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-2147483647"/> + </constraint> + </properties> + </leafNode> + <leafNode name="dscp"> + <properties> + <help>Set DSCP (Packet Differentiated Services Codepoint) bits</help> + <valueHelp> + <format>u32:0-63</format> + <description>DSCP number</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-63"/> + </constraint> + </properties> + </leafNode> + <leafNode name="mark"> + <properties> + <help>Set packet mark</help> + <valueHelp> + <format>u32:1-2147483647</format> + <description>Packet mark</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2147483647"/> + </constraint> + </properties> + </leafNode> + <leafNode name="table"> + <properties> + <help>Set the routing table for matched packets</help> + <valueHelp> + <format>u32:1-200</format> + <description>Table number</description> + </valueHelp> + <valueHelp> + <format>main</format> + <description>Main table</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-200"/> + <regex>(main)</regex> + </constraint> + <completionHelp> + <list>main</list> + <path>protocols static table</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="vrf"> + <properties> + <help>VRF to forward packet with</help> + <valueHelp> + <format>txt</format> + <description>VRF instance name</description> + </valueHelp> + <valueHelp> + <format>default</format> + <description>Forward into default global VRF</description> + </valueHelp> + <completionHelp> + <list>default</list> + <path>vrf name</path> + </completionHelp> + #include <include/constraint/vrf.xml.i> + </properties> + </leafNode> + <leafNode name="tcp-mss"> + <properties> + <help>Set TCP Maximum Segment Size</help> + <valueHelp> + <format>u32:500-1460</format> + <description>Explicitly set TCP MSS value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 500-1460"/> + </constraint> + </properties> + </leafNode> + </children> +</node> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/firewall/source-destination-group-inet.xml.i b/interface-definitions/include/firewall/source-destination-group-inet.xml.i new file mode 100644 index 000000000..174051624 --- /dev/null +++ b/interface-definitions/include/firewall/source-destination-group-inet.xml.i @@ -0,0 +1,50 @@ +<!-- include start from firewall/source-destination-group-inet.xml.i --> +<node name="group"> + <properties> + <help>Group</help> + </properties> + <children> + <leafNode name="ipv4-address-group"> + <properties> + <help>Group of IPv4 addresses</help> + <completionHelp> + <path>firewall group address-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="ipv6-address-group"> + <properties> + <help>Group of IPv6 addresses</help> + <completionHelp> + <path>firewall group ipv6-address-group</path> + </completionHelp> + </properties> + </leafNode> + #include <include/firewall/mac-group.xml.i> + <leafNode name="ipv4-network-group"> + <properties> + <help>Group of IPv4 networks</help> + <completionHelp> + <path>firewall group network-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="ipv6-network-group"> + <properties> + <help>Group of IPv6 networks</help> + <completionHelp> + <path>firewall group ipv6-network-group</path> + </completionHelp> + </properties> + </leafNode> + <leafNode name="port-group"> + <properties> + <help>Group of ports</help> + <completionHelp> + <path>firewall group port-group</path> + </completionHelp> + </properties> + </leafNode> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/policy/route-common.xml.i b/interface-definitions/include/policy/route-common.xml.i index 203be73e7..19ffc0506 100644 --- a/interface-definitions/include/policy/route-common.xml.i +++ b/interface-definitions/include/policy/route-common.xml.i @@ -66,100 +66,7 @@ </leafNode> </children> </node> -<node name="set"> - <properties> - <help>Packet modifications</help> - </properties> - <children> - <leafNode name="connection-mark"> - <properties> - <help>Connection marking</help> - <valueHelp> - <format>u32:0-2147483647</format> - <description>Connection marking</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-2147483647"/> - </constraint> - </properties> - </leafNode> - <leafNode name="dscp"> - <properties> - <help>Packet Differentiated Services Codepoint (DSCP)</help> - <valueHelp> - <format>u32:0-63</format> - <description>DSCP number</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-63"/> - </constraint> - </properties> - </leafNode> - <leafNode name="mark"> - <properties> - <help>Packet marking</help> - <valueHelp> - <format>u32:1-2147483647</format> - <description>Packet marking</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-2147483647"/> - </constraint> - </properties> - </leafNode> - <leafNode name="table"> - <properties> - <help>Routing table to forward packet with</help> - <valueHelp> - <format>u32:1-200</format> - <description>Table number</description> - </valueHelp> - <valueHelp> - <format>main</format> - <description>Main table</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-200"/> - <regex>(main)</regex> - </constraint> - <completionHelp> - <list>main</list> - <path>protocols static table</path> - </completionHelp> - </properties> - </leafNode> - <leafNode name="vrf"> - <properties> - <help>VRF to forward packet with</help> - <valueHelp> - <format>txt</format> - <description>VRF instance name</description> - </valueHelp> - <valueHelp> - <format>default</format> - <description>Forward into default global VRF</description> - </valueHelp> - <completionHelp> - <list>default</list> - <path>vrf name</path> - </completionHelp> - #include <include/constraint/vrf.xml.i> - </properties> - </leafNode> - <leafNode name="tcp-mss"> - <properties> - <help>TCP Maximum Segment Size</help> - <valueHelp> - <format>u32:500-1460</format> - <description>Explicitly set TCP MSS value</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 500-1460"/> - </constraint> - </properties> - </leafNode> - </children> -</node> +#include <include/firewall/set-packet-modifications.xml.i> #include <include/firewall/state.xml.i> #include <include/firewall/tcp-flags.xml.i> #include <include/firewall/tcp-mss.xml.i> diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py index facd498ca..cac6d2953 100644 --- a/python/vyos/firewall.py +++ b/python/vyos/firewall.py @@ -167,7 +167,10 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name): if address_mask: operator = '!=' if exclude else '==' operator = f'& {address_mask} {operator} ' - output.append(f'{ip_name} {prefix}addr {operator}{suffix}') + if is_ipv4(suffix): + output.append(f'ip {prefix}addr {operator}{suffix}') + else: + output.append(f'ip6 {prefix}addr {operator}{suffix}') if 'fqdn' in side_conf: fqdn = side_conf['fqdn'] @@ -236,22 +239,38 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name): if 'group' in side_conf: group = side_conf['group'] - if 'address_group' in group: - group_name = group['address_group'] - operator = '' - exclude = group_name[0] == "!" - if exclude: - operator = '!=' - group_name = group_name[1:] - if address_mask: - operator = '!=' if exclude else '==' - operator = f'& {address_mask} {operator}' - output.append(f'{ip_name} {prefix}addr {operator} @A{def_suffix}_{group_name}') - elif 'dynamic_address_group' in group: + for ipvx_address_group in ['address_group', 'ipv4_address_group', 'ipv6_address_group']: + if ipvx_address_group in group: + group_name = group[ipvx_address_group] + operator = '' + exclude = group_name[0] == "!" + if exclude: + operator = '!=' + group_name = group_name[1:] + if address_mask: + operator = '!=' if exclude else '==' + operator = f'& {address_mask} {operator}' + # for bridge, change ip_name + if ip_name == 'bri': + ip_name = 'ip' if ipvx_address_group == 'ipv4_address_group' else 'ip6' + def_suffix = '6' if ipvx_address_group == 'ipv6_address_group' else '' + output.append(f'{ip_name} {prefix}addr {operator} @A{def_suffix}_{group_name}') + for ipvx_network_group in ['network_group', 'ipv4_network_group', 'ipv6_network_group']: + if ipvx_network_group in group: + group_name = group[ipvx_network_group] + operator = '' + if group_name[0] == "!": + operator = '!=' + group_name = group_name[1:] + # for bridge, change ip_name + if ip_name == 'bri': + ip_name = 'ip' if ipvx_network_group == 'ipv4_network_group' else 'ip6' + def_suffix = '6' if ipvx_network_group == 'ipv6_network_group' else '' + output.append(f'{ip_name} {prefix}addr {operator} @N{def_suffix}_{group_name}') + if 'dynamic_address_group' in group: group_name = group['dynamic_address_group'] operator = '' - exclude = group_name[0] == "!" - if exclude: + if group_name[0] == "!": operator = '!=' group_name = group_name[1:] output.append(f'{ip_name} {prefix}addr {operator} @DA{def_suffix}_{group_name}') @@ -263,13 +282,6 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name): operator = '!=' group_name = group_name[1:] output.append(f'{ip_name} {prefix}addr {operator} @D_{group_name}') - elif 'network_group' in group: - group_name = group['network_group'] - operator = '' - if group_name[0] == '!': - operator = '!=' - group_name = group_name[1:] - output.append(f'{ip_name} {prefix}addr {operator} @N{def_suffix}_{group_name}') if 'mac_group' in group: group_name = group['mac_group'] operator = '' diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index e6317050c..551f8ce65 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -695,13 +695,21 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK') self.verify_nftables_chain([['return']], 'ip6 vyos_conntrack', 'FW_CONNTRACK') - def test_bridge_basic_rules(self): + def test_bridge_firewall(self): name = 'smoketest' interface_in = 'eth0' mac_address = '00:53:00:00:00:01' vlan_id = '12' vlan_prior = '3' + # Check bridge-nf-call-iptables default value: 0 + self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-iptables'), '0') + self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-ip6tables'), '0') + + self.cli_set(['firewall', 'group', 'ipv6-address-group', 'AGV6', 'address', '2001:db1::1']) + self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'action', 'accept']) + self.cli_set(['firewall', 'global-options', 'apply-to-bridged-traffic', 'ipv4']) + self.cli_set(['firewall', 'bridge', 'name', name, 'default-action', 'accept']) self.cli_set(['firewall', 'bridge', 'name', name, 'default-log']) self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'action', 'accept']) @@ -718,20 +726,41 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'jump-target', name]) self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'vlan', 'priority', vlan_prior]) + self.cli_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'inbound-interface', 'name', interface_in]) + self.cli_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'source', 'address', '192.0.2.2']) + self.cli_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'state', 'new']) + + self.cli_set(['firewall', 'bridge', 'prerouting', 'filter', 'rule', '1', 'action', 'notrack']) + self.cli_set(['firewall', 'bridge', 'prerouting', 'filter', 'rule', '1', 'destination', 'group', 'ipv6-address-group', 'AGV6']) + self.cli_commit() nftables_search = [ + ['set A6_AGV6'], + ['type ipv6_addr'], + ['elements', '2001:db1::1'], ['chain VYOS_FORWARD_filter'], ['type filter hook forward priority filter; policy accept;'], + ['jump VYOS_STATE_POLICY'], [f'vlan id {vlan_id}', 'accept'], [f'vlan pcp {vlan_prior}', f'jump NAME_{name}'], ['log prefix "[bri-FWD-filter-default-D]"', 'drop', 'FWD-filter default-action drop'], [f'chain NAME_{name}'], [f'ether saddr {mac_address}', f'iifname "{interface_in}"', f'log prefix "[bri-NAM-{name}-1-A]" log level crit', 'accept'], - ['accept', f'{name} default-action accept'] + ['accept', f'{name} default-action accept'], + ['chain VYOS_INPUT_filter'], + ['type filter hook input priority filter; policy accept;'], + ['ct state new', 'ip saddr 192.0.2.2', f'iifname "{interface_in}"', 'accept'], + ['chain VYOS_PREROUTING_filter'], + ['type filter hook prerouting priority filter; policy accept;'], + ['ip6 daddr @A6_AGV6', 'notrack'] ] self.verify_nftables(nftables_search, 'bridge vyos_filter') + ## Check bridge-nf-call-iptables is set to 1, and for ipv6 remains on default 0 + self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-iptables'), '1') + self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-ip6tables'), '0') def test_source_validation(self): # Strict diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index 352d5cbb1..02bf00bcc 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -36,6 +36,7 @@ from vyos.utils.process import cmd from vyos.utils.process import rc_cmd from vyos import ConfigError from vyos import airbag +from subprocess import run as subp_run airbag.enable() @@ -47,7 +48,12 @@ valid_groups = [ 'domain_group', 'network_group', 'port_group', - 'interface_group' + 'interface_group', + ## Added for group ussage in bridge firewall + 'ipv4_address_group', + 'ipv6_address_group', + 'ipv4_network_group', + 'ipv6_network_group' ] nested_group_types = [ @@ -128,41 +134,32 @@ def get_config(config=None): return firewall -def verify_jump_target(firewall, root_chain, jump_target, ipv6, recursive=False): +def verify_jump_target(firewall, hook, jump_target, family, recursive=False): targets_seen = [] targets_pending = [jump_target] while targets_pending: target = targets_pending.pop() - if not ipv6: - if target not in dict_search_args(firewall, 'ipv4', 'name'): - raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system') - target_rules = dict_search_args(firewall, 'ipv4', 'name', target, 'rule') - 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') - target_rules = dict_search_args(firewall, 'ipv6', 'name', target, 'rule') + if 'name' not in firewall[family]: + raise ConfigError(f'Invalid jump-target. Firewall {family} name {target} does not exist on the system') + elif target not in dict_search_args(firewall, family, 'name'): + raise ConfigError(f'Invalid jump-target. Firewall {family} name {target} does not exist on the system') - no_ipsec_in = root_chain in ('output', ) + target_rules = dict_search_args(firewall, family, 'name', target, 'rule') + no_ipsec_in = hook in ('output', ) if target_rules: for target_rule_conf in target_rules.values(): # Output hook types will not tolerate 'meta ipsec exists' matches even in jump targets: if no_ipsec_in and (dict_search_args(target_rule_conf, 'ipsec', 'match_ipsec_in') is not None \ or dict_search_args(target_rule_conf, 'ipsec', 'match_none_in') is not None): - if not ipv6: - raise ConfigError(f'Invalid jump-target for {root_chain}. Firewall name {target} rules contain incompatible ipsec inbound matches') - else: - raise ConfigError(f'Invalid jump-target for {root_chain}. Firewall ipv6 name {target} rules contain incompatible ipsec inbound matches') + raise ConfigError(f'Invalid jump-target for {hook}. Firewall {family} name {target} rules contain incompatible ipsec inbound matches') # Make sure we're not looping back on ourselves somewhere: if recursive and 'jump_target' in target_rule_conf: child_target = target_rule_conf['jump_target'] if child_target in targets_seen: - if not ipv6: - raise ConfigError(f'Loop detected in jump-targets, firewall name {target} refers to previously traversed name {child_target}') - else: - raise ConfigError(f'Loop detected in jump-targets, firewall ipv6 name {target} refers to previously traversed ipv6 name {child_target}') + raise ConfigError(f'Loop detected in jump-targets, firewall {family} name {target} refers to previously traversed {family} name {child_target}') targets_pending.append(child_target) if len(targets_seen) == 7: path_txt = ' -> '.join(targets_seen) @@ -170,7 +167,7 @@ def verify_jump_target(firewall, root_chain, jump_target, ipv6, recursive=False) targets_seen.append(target) -def verify_rule(firewall, chain_name, rule_conf, ipv6): +def verify_rule(firewall, family, hook, priority, rule_id, rule_conf): if 'action' not in rule_conf: raise ConfigError('Rule action must be defined') @@ -181,10 +178,10 @@ def verify_rule(firewall, chain_name, rule_conf, ipv6): 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 chain_name != 'name': # This is a bit clumsy, but consolidates a chunk of code. - verify_jump_target(firewall, chain_name, target, ipv6, recursive=True) + if hook != 'name': # This is a bit clumsy, but consolidates a chunk of code. + verify_jump_target(firewall, hook, target, family, recursive=True) else: - verify_jump_target(firewall, chain_name, target, ipv6, recursive=False) + verify_jump_target(firewall, hook, target, family, recursive=False) if rule_conf['action'] == 'offload': if 'offload_target' not in rule_conf: @@ -246,9 +243,9 @@ def verify_rule(firewall, chain_name, rule_conf, ipv6): raise ConfigError(f'Cannot match a tcp flag as set and not set') if 'protocol' in rule_conf: - if rule_conf['protocol'] == 'icmp' and ipv6: + if rule_conf['protocol'] == 'icmp' and family == 'ipv6': raise ConfigError(f'Cannot match IPv4 ICMP protocol on IPv6, use ipv6-icmp') - if rule_conf['protocol'] == 'ipv6-icmp' and not ipv6: + if rule_conf['protocol'] == 'ipv6-icmp' and family == 'ipv4': raise ConfigError(f'Cannot match IPv6 ICMP protocol on IPv4, use icmp') for side in ['destination', 'source']: @@ -266,7 +263,18 @@ def verify_rule(firewall, chain_name, rule_conf, ipv6): if group in side_conf['group']: group_name = side_conf['group'][group] - fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group + if family == 'ipv6' and group in ['address_group', 'network_group']: + fw_group = f'ipv6_{group}' + elif family == 'bridge': + if group =='ipv4_address_group': + fw_group = 'address_group' + elif group == 'ipv4_network_group': + fw_group = 'network_group' + else: + fw_group = group + else: + fw_group = group + error_group = fw_group.replace("_", "-") if group in ['address_group', 'network_group', 'domain_group']: @@ -302,7 +310,7 @@ def verify_rule(firewall, chain_name, rule_conf, ipv6): raise ConfigError(f'Dynamic address group must be defined.') else: target = rule_conf['add_address_to_group'][type]['address_group'] - fwall_group = 'ipv6_address_group' if ipv6 else 'address_group' + fwall_group = 'ipv6_address_group' if family == 'ipv6' else 'address_group' group_obj = dict_search_args(firewall, 'group', 'dynamic_group', fwall_group, target) if group_obj is None: raise ConfigError(f'Invalid dynamic address group on firewall rule') @@ -379,43 +387,25 @@ def verify(firewall): for group_name, group in groups.items(): verify_nested_group(group_name, group, groups, []) - if 'ipv4' in firewall: - for name in ['name','forward','input','output', 'prerouting']: - if name in firewall['ipv4']: - for name_id, name_conf in firewall['ipv4'][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.') - verify_jump_target(firewall, name, target, False, recursive=True) - - if 'rule' in name_conf: - for rule_id, rule_conf in name_conf['rule'].items(): - verify_rule(firewall, name, rule_conf, False) - - if 'ipv6' in firewall: - for name in ['name','forward','input','output', 'prerouting']: - if name in firewall['ipv6']: - for name_id, name_conf in firewall['ipv6'][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.') - verify_jump_target(firewall, name, target, True, recursive=True) - - if 'rule' in name_conf: - for rule_id, rule_conf in name_conf['rule'].items(): - verify_rule(firewall, name, rule_conf, True) - - #### ZONESSSS + for family in ['ipv4', 'ipv6', 'bridge']: + if family in firewall: + for chain in ['name','forward','input','output', 'prerouting']: + if chain in firewall[family]: + for priority, priority_conf in firewall[family][chain].items(): + if 'jump' in priority_conf['default_action'] and 'default_jump_target' not in priority_conf: + raise ConfigError('default-action set to jump, but no default-jump-target specified') + if 'default_jump_target' in priority_conf: + target = priority_conf['default_jump_target'] + if 'jump' not in priority_conf['default_action']: + raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined') + if priority_conf['default_jump_target'] == priority: + raise ConfigError(f'Loop detected on default-jump-target.') + if target not in dict_search_args(firewall[family], 'name'): + raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system') + if 'rule' in priority_conf: + for rule_id, rule_conf in priority_conf['rule'].items(): + verify_rule(firewall, family, chain, priority, rule_id, rule_conf) + local_zone = False zone_interfaces = [] @@ -495,8 +485,53 @@ def generate(firewall): render(sysctl_file, 'firewall/sysctl-firewall.conf.j2', firewall) return None +def parse_firewall_error(output): + # Define the regex patterns to extract the error message and the comment + error_pattern = re.compile(r'Error:\s*(.*?)\n') + comment_pattern = re.compile(r'comment\s+"([^"]+)"') + error_output = [] + + # Find all error messages in the output + error_matches = error_pattern.findall(output) + # Find all comment matches in the output + comment_matches = comment_pattern.findall(output) + + if not error_matches or not comment_matches: + raise ConfigError(f'Unknown firewall error detected: {output}') + + error_output.append('Fail to apply firewall') + # Loop over the matches and process them + for error_message, comment in zip(error_matches, comment_matches): + # Parse the comment + parsed_entries = comment.split('-') + family = 'bridge' if parsed_entries[0] == 'bri' else parsed_entries[0] + if parsed_entries[1] == 'NAM': + chain = 'name' + elif parsed_entries[1] == 'FWD': + chain = 'forward' + elif parsed_entries[1] == 'INP': + chain = 'input' + elif parsed_entries[1] == 'OUT': + chain = 'output' + elif parsed_entries[1] == 'PRE': + chain = 'prerouting' + error_output.append(f'Error found on: firewall {family} {chain} {parsed_entries[2]} rule {parsed_entries[3]}') + error_output.append(f'\tError message: {error_message.strip()}') + + raise ConfigError('\n'.join(error_output)) + def apply(firewall): + # Use nft -c option to check current configuration file + completed_process = subp_run(['nft', '-c', '--file', nftables_conf], capture_output=True) + install_result = completed_process.returncode + if install_result == 1: + # We need to handle firewall error + output = completed_process.stderr + parse_firewall_error(output.decode()) + + # No error detected during check, we can apply the new configuration install_result, output = rc_cmd(f'nft --file {nftables_conf}') + # Double check just in case if install_result == 1: raise ConfigError(f'Failed to apply firewall: {output}') diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf index c9b8ef8fe..76be41ddc 100644 --- a/src/etc/sysctl.d/30-vyos-router.conf +++ b/src/etc/sysctl.d/30-vyos-router.conf @@ -110,3 +110,8 @@ net.ipv6.conf.all.seg6_enabled = 0 net.ipv6.conf.default.seg6_enabled = 0 net.vrf.strict_mode = 1 + +# https://vyos.dev/T6570 +# By default, do not forward traffic from bridge to IPvX layer +net.bridge.bridge-nf-call-iptables = 0 +net.bridge.bridge-nf-call-ip6tables = 0
\ No newline at end of file |