From 450ca9a9b46d69036af432ddad316d4ddb126085 Mon Sep 17 00:00:00 2001 From: sarthurdev <965089+sarthurdev@users.noreply.github.com> Date: Tue, 30 Aug 2022 11:46:16 +0200 Subject: firewall: T2199: Refactor firewall + zone-policy, move interfaces under firewall node * Refactor firewall and zone-policy rule creation and cleanup * Migrate interface firewall values to `firewall interfaces name/ipv6-name ` * Remove `firewall-interface.py` conf script --- data/templates/firewall/nftables.j2 | 72 +++++++- data/templates/zone_policy/nftables.j2 | 114 +++++------- data/templates/zone_policy/nftables6.j2 | 77 ++++++++ interface-definitions/firewall.xml.in | 34 ++++ interface-definitions/include/firewall/name.xml.i | 18 ++ .../interface/interface-firewall-vif-c.xml.i | 79 --------- .../include/interface/interface-firewall-vif.xml.i | 79 --------- .../include/interface/interface-firewall.xml.i | 79 --------- .../include/interface/vif-s.xml.i | 2 - interface-definitions/include/interface/vif.xml.i | 1 - .../include/version/firewall-version.xml.i | 2 +- interface-definitions/interfaces-bonding.xml.in | 1 - interface-definitions/interfaces-bridge.xml.in | 1 - interface-definitions/interfaces-dummy.xml.in | 1 - interface-definitions/interfaces-ethernet.xml.in | 1 - interface-definitions/interfaces-geneve.xml.in | 1 - interface-definitions/interfaces-input.xml.in | 1 - interface-definitions/interfaces-l2tpv3.xml.in | 1 - interface-definitions/interfaces-macsec.xml.in | 1 - interface-definitions/interfaces-openvpn.xml.in | 1 - interface-definitions/interfaces-pppoe.xml.in | 1 - .../interfaces-pseudo-ethernet.xml.in | 1 - interface-definitions/interfaces-tunnel.xml.in | 1 - interface-definitions/interfaces-vti.xml.in | 1 - interface-definitions/interfaces-vxlan.xml.in | 1 - interface-definitions/interfaces-wireguard.xml.in | 1 - interface-definitions/interfaces-wireless.xml.in | 1 - interface-definitions/interfaces-wwan.xml.in | 1 - interface-definitions/zone-policy.xml.in | 2 +- smoketest/scripts/cli/test_firewall.py | 20 +-- src/conf_mode/firewall-interface.py | 186 -------------------- src/conf_mode/firewall.py | 193 ++++----------------- src/conf_mode/zone_policy.py | 65 +++---- src/migration-scripts/firewall/7-to-8 | 88 ++++++++++ 34 files changed, 385 insertions(+), 743 deletions(-) create mode 100644 data/templates/zone_policy/nftables6.j2 create mode 100644 interface-definitions/include/firewall/name.xml.i delete mode 100644 interface-definitions/include/interface/interface-firewall-vif-c.xml.i delete mode 100644 interface-definitions/include/interface/interface-firewall-vif.xml.i delete mode 100644 interface-definitions/include/interface/interface-firewall.xml.i delete mode 100755 src/conf_mode/firewall-interface.py create mode 100755 src/migration-scripts/firewall/7-to-8 diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2 index 5971e1bbc..3beb7fb92 100644 --- a/data/templates/firewall/nftables.j2 +++ b/data/templates/firewall/nftables.j2 @@ -2,24 +2,46 @@ {% import 'firewall/nftables-defines.j2' as group_tmpl %} -{% if cleanup_commands is vyos_defined %} -{% for command in cleanup_commands %} -{{ command }} -{% endfor %} +{% if first_install is not vyos_defined %} +delete table ip vyos_filter {% endif %} - table ip filter { -{% if first_install is vyos_defined %} chain VYOS_FW_FORWARD { type filter hook forward priority 0; policy accept; +{% if state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} +{% if interface is vyos_defined %} +{% for ifname, ifconf in interface.items() %} +{% if ifconf.in is vyos_defined and ifconf.in.name is vyos_defined %} + iifname {{ ifname }} counter jump NAME_{{ ifconf.in.name }} +{% endif %} +{% if ifconf.out is vyos_defined and ifconf.out.name is vyos_defined %} + oifname {{ ifname }} counter jump NAME_{{ ifconf.out.name }} +{% endif %} +{% endfor %} +{% endif %} jump VYOS_POST_FW } chain VYOS_FW_LOCAL { type filter hook input priority 0; policy accept; +{% if state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} +{% if interface is vyos_defined %} +{% for ifname, ifconf in interface.items() %} +{% if ifconf.local is vyos_defined and ifconf.local.name is vyos_defined %} + iifname {{ ifname }} counter jump NAME_{{ ifconf.local.name }} +{% endif %} +{% endfor %} +{% endif %} jump VYOS_POST_FW } chain VYOS_FW_OUTPUT { type filter hook output priority 0; policy accept; +{% if state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} jump VYOS_POST_FW } chain VYOS_POST_FW { @@ -29,7 +51,6 @@ table ip filter { type filter hook prerouting priority -450; policy accept; ip frag-off & 0x3fff != 0 meta mark set 0xffff1 return } -{% endif %} {% if name is vyos_defined %} {% set ns = namespace(sets=[]) %} {% for name_text, conf in name.items() %} @@ -86,20 +107,51 @@ table ip filter { return } {% endif %} +{% if zone_conf is vyos_defined %} + include "{{ zone_conf }}" +{% endif %} } +{% if first_install is not vyos_defined %} +delete table ip6 vyos_filter +{% endif %} table ip6 filter { -{% if first_install is vyos_defined %} chain VYOS_FW6_FORWARD { type filter hook forward priority 0; policy accept; +{% if state_policy is vyos_defined %} + jump VYOS_STATE_POLICY6 +{% endif %} +{% if interface is vyos_defined %} +{% for ifname, ifconf in interface.items() %} +{% if ifconf.in is vyos_defined and ifconf.in.ipv6_name is vyos_defined %} + iifname {{ ifname }} counter jump NAME6_{{ ifconf.in.ipv6_name }} +{% endif %} +{% if ifconf.out is vyos_defined and ifconf.out.ipv6_name is vyos_defined %} + oifname {{ ifname }} counter jump NAME6_{{ ifconf.out.ipv6_name }} +{% endif %} +{% endfor %} +{% endif %} jump VYOS_POST_FW6 } chain VYOS_FW6_LOCAL { type filter hook input priority 0; policy accept; +{% if state_policy is vyos_defined %} + jump VYOS_STATE_POLICY6 +{% endif %} +{% if interface is vyos_defined %} +{% for ifname, ifconf in interface.items() %} +{% if ifconf.local is vyos_defined and ifconf.local.ipv6_name is vyos_defined %} + iifname {{ ifname }} counter jump NAME6_{{ ifconf.local.ipv6_name }} +{% endif %} +{% endfor %} +{% endif %} jump VYOS_POST_FW6 } chain VYOS_FW6_OUTPUT { type filter hook output priority 0; policy accept; +{% if state_policy is vyos_defined %} + jump VYOS_STATE_POLICY6 +{% endif %} jump VYOS_POST_FW6 } chain VYOS_POST_FW6 { @@ -109,7 +161,6 @@ table ip6 filter { type filter hook prerouting priority -450; policy accept; exthdr frag exists meta mark set 0xffff1 return } -{% endif %} {% if ipv6_name is vyos_defined %} {% set ns = namespace(sets=[]) %} {% for name_text, conf in ipv6_name.items() %} @@ -158,6 +209,9 @@ table ip6 filter { return } {% endif %} +{% if zone6_conf is vyos_defined %} + include "{{ zone6_conf }}" +{% endif %} } {% if first_install is vyos_defined %} diff --git a/data/templates/zone_policy/nftables.j2 b/data/templates/zone_policy/nftables.j2 index fe941f9f8..09140519f 100644 --- a/data/templates/zone_policy/nftables.j2 +++ b/data/templates/zone_policy/nftables.j2 @@ -1,13 +1,45 @@ #!/usr/sbin/nft -f -{% if cleanup_commands is vyos_defined %} -{% for command in cleanup_commands %} -{{ command }} -{% endfor %} -{% endif %} - {% if zone is vyos_defined %} -table ip filter { + chain VYOS_ZONE_FORWARD { + type filter hook forward priority -1; policy accept; +{% if firewall.state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} +{% for zone_name, zone_conf in zone.items() %} +{% if zone_conf.ipv4 %} +{% if 'local_zone' not in zone_conf %} + oifname { {{ zone_conf.interface | join(',') }} } counter jump VZONE_{{ zone_name }} +{% endif %} +{% endif %} +{% endfor %} + } + chain VYOS_ZONE_LOCAL { + type filter hook input priority -1; policy accept; +{% if firewall.state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} +{% for zone_name, zone_conf in zone.items() %} +{% if zone_conf.ipv4 %} +{% if 'local_zone' in zone_conf %} + counter jump VZONE_{{ zone_name }}_IN +{% endif %} +{% endif %} +{% endfor %} + } + chain VYOS_ZONE_OUTPUT { + type filter hook output priority -1; policy accept; +{% if firewall.state_policy is vyos_defined %} + jump VYOS_STATE_POLICY +{% endif %} +{% for zone_name, zone_conf in zone.items() %} +{% if zone_conf.ipv4 %} +{% if 'local_zone' in zone_conf %} + counter jump VZONE_{{ zone_name }}_OUT +{% endif %} +{% endif %} +{% endfor %} + } {% for zone_name, zone_conf in zone.items() if zone_conf.ipv4 %} {% if zone_conf.local_zone is vyos_defined %} chain VZONE_{{ zone_name }}_IN { @@ -42,72 +74,4 @@ table ip filter { } {% endif %} {% endfor %} -} - -table ip6 filter { -{% for zone_name, zone_conf in zone.items() if zone_conf.ipv6 %} -{% if zone_conf.local_zone is vyos_defined %} - chain VZONE6_{{ zone_name }}_IN { - iifname lo counter return -{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.ipv6_name is vyos_defined %} - iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }} - iifname { {{ zone[from_zone].interface | join(",") }} } counter return -{% endfor %} - {{ zone_conf | nft_default_rule('zone6_' + zone_name) }} - } - chain VZONE6_{{ zone_name }}_OUT { - oifname lo counter return -{% for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall.ipv6_name is vyos_defined %} - oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }} - oifname { {{ zone[from_zone].interface | join(",") }} } counter return -{% endfor %} - {{ zone_conf | nft_default_rule('zone6_' + zone_name) }} - } -{% else %} - chain VZONE6_{{ zone_name }} { - iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6=True) }} -{% if zone_conf.intra_zone_filtering is vyos_defined %} - iifname { {{ zone_conf.interface | join(",") }} } counter return -{% endif %} -{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.ipv6_name is vyos_defined %} -{% if zone[from_zone].local_zone is not defined %} - iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }} - iifname { {{ zone[from_zone].interface | join(",") }} } counter return -{% endif %} -{% endfor %} - {{ zone_conf | nft_default_rule('zone6_' + zone_name) }} - } -{% endif %} -{% endfor %} -} - -{% for zone_name, zone_conf in zone.items() %} -{% if zone_conf.ipv4 %} -{% if 'local_zone' in zone_conf %} -insert rule ip filter VYOS_FW_LOCAL counter jump VZONE_{{ zone_name }}_IN -insert rule ip filter VYOS_FW_OUTPUT counter jump VZONE_{{ zone_name }}_OUT -{% else %} -insert rule ip filter VYOS_FW_FORWARD oifname { {{ zone_conf.interface | join(',') }} } counter jump VZONE_{{ zone_name }} -{% endif %} -{% endif %} -{% if zone_conf.ipv6 %} -{% if 'local_zone' in zone_conf %} -insert rule ip6 filter VYOS_FW6_LOCAL counter jump VZONE6_{{ zone_name }}_IN -insert rule ip6 filter VYOS_FW6_OUTPUT counter jump VZONE6_{{ zone_name }}_OUT -{% else %} -insert rule ip6 filter VYOS_FW6_FORWARD oifname { {{ zone_conf.interface | join(',') }} } counter jump VZONE6_{{ zone_name }} -{% endif %} -{% endif %} -{% endfor %} - -{# Ensure that state-policy rule is first in the chain #} -{% if firewall.state_policy is vyos_defined %} -{% for chain in ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL'] %} -insert rule ip filter {{ chain }} jump VYOS_STATE_POLICY -{% endfor %} -{% for chain in ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL'] %} -insert rule ip6 filter {{ chain }} jump VYOS_STATE_POLICY6 -{% endfor %} -{% endif %} - {% endif %} diff --git a/data/templates/zone_policy/nftables6.j2 b/data/templates/zone_policy/nftables6.j2 new file mode 100644 index 000000000..f7123d63d --- /dev/null +++ b/data/templates/zone_policy/nftables6.j2 @@ -0,0 +1,77 @@ +#!/usr/sbin/nft -f + +{% if zone is vyos_defined %} + chain VYOS_ZONE6_FORWARD { + type filter hook forward priority -1; policy accept; +{% if state_policy is vyos_defined %} + jump VYOS_STATE_POLICY6 +{% endif %} +{% for zone_name, zone_conf in zone.items() %} +{% if zone_conf.ipv6 %} +{% if 'local_zone' not in zone_conf %} + oifname { {{ zone_conf.interface | join(',') }} } counter jump VZONE6_{{ zone_name }} +{% endif %} +{% endif %} +{% endfor %} + } + chain VYOS_ZONE6_LOCAL { + type filter hook input priority -1; policy accept; +{% if firewall.state_policy is vyos_defined %} + jump VYOS_STATE_POLICY6 +{% endif %} +{% for zone_name, zone_conf in zone.items() %} +{% if zone_conf.ipv6 %} +{% if 'local_zone' in zone_conf %} + counter jump VZONE6_{{ zone_name }}_IN +{% endif %} +{% endif %} +{% endfor %} + } + chain VYOS_ZONE6_OUTPUT { + type filter hook output priority -1; policy accept; +{% if firewall.state_policy is vyos_defined %} + jump VYOS_STATE_POLICY6 +{% endif %} +{% for zone_name, zone_conf in zone.items() %} +{% if zone_conf.ipv6 %} +{% if 'local_zone' in zone_conf %} + counter jump VZONE6_{{ zone_name }}_OUT +{% endif %} +{% endif %} +{% endfor %} + } +{% for zone_name, zone_conf in zone.items() if zone_conf.ipv6 %} +{% if zone_conf.local_zone is vyos_defined %} + chain VZONE6_{{ zone_name }}_IN { + iifname lo counter return +{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.ipv6_name is vyos_defined %} + iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }} + iifname { {{ zone[from_zone].interface | join(",") }} } counter return +{% endfor %} + {{ zone_conf | nft_default_rule('zone6_' + zone_name) }} + } + chain VZONE6_{{ zone_name }}_OUT { + oifname lo counter return +{% for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall.ipv6_name is vyos_defined %} + oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }} + oifname { {{ zone[from_zone].interface | join(",") }} } counter return +{% endfor %} + {{ zone_conf | nft_default_rule('zone6_' + zone_name) }} + } +{% else %} + chain VZONE6_{{ zone_name }} { + iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6=True) }} +{% if zone_conf.intra_zone_filtering is vyos_defined %} + iifname { {{ zone_conf.interface | join(",") }} } counter return +{% endif %} +{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall.ipv6_name is vyos_defined %} +{% if zone[from_zone].local_zone is not defined %} + iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME6_{{ from_conf.firewall.ipv6_name }} + iifname { {{ zone[from_zone].interface | join(",") }} } counter return +{% endif %} +{% endfor %} + {{ zone_conf | nft_default_rule('zone6_' + zone_name) }} + } +{% endif %} +{% endfor %} +{% endif %} diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index d1497d572..fb24cd558 100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -314,6 +314,40 @@ + + + Interface name + + + + + + + + Forwarded packets on inbound interface + + + #include + + + + + Forwarded packets on outbound interface + + + #include + + + + + Packets destined for this router + + + #include + + + + Policy for handling IPv4 packets with source route option diff --git a/interface-definitions/include/firewall/name.xml.i b/interface-definitions/include/firewall/name.xml.i new file mode 100644 index 000000000..231b9b144 --- /dev/null +++ b/interface-definitions/include/firewall/name.xml.i @@ -0,0 +1,18 @@ + + + + Local IPv4 firewall ruleset name for interface + + firewall name + + + + + + Local IPv6 firewall ruleset name for interface + + firewall ipv6-name + + + + \ No newline at end of file diff --git a/interface-definitions/include/interface/interface-firewall-vif-c.xml.i b/interface-definitions/include/interface/interface-firewall-vif-c.xml.i deleted file mode 100644 index 1bc235fcb..000000000 --- a/interface-definitions/include/interface/interface-firewall-vif-c.xml.i +++ /dev/null @@ -1,79 +0,0 @@ - - - - 615 - Firewall options - - - - - forwarded packets on inbound interface - - - - - Inbound IPv4 firewall ruleset name for interface - - firewall name - - - - - - Inbound IPv6 firewall ruleset name for interface - - firewall ipv6-name - - - - - - - - forwarded packets on outbound interface - - - - - Outbound IPv4 firewall ruleset name for interface - - firewall name - - - - - - Outbound IPv6 firewall ruleset name for interface - - firewall ipv6-name - - - - - - - - packets destined for this router - - - - - Local IPv4 firewall ruleset name for interface - - firewall name - - - - - - Local IPv6 firewall ruleset name for interface - - firewall ipv6-name - - - - - - - - diff --git a/interface-definitions/include/interface/interface-firewall-vif.xml.i b/interface-definitions/include/interface/interface-firewall-vif.xml.i deleted file mode 100644 index a37ac5c4a..000000000 --- a/interface-definitions/include/interface/interface-firewall-vif.xml.i +++ /dev/null @@ -1,79 +0,0 @@ - - - - 615 - Firewall options - - - - - forwarded packets on inbound interface - - - - - Inbound IPv4 firewall ruleset name for interface - - firewall name - - - - - - Inbound IPv6 firewall ruleset name for interface - - firewall ipv6-name - - - - - - - - forwarded packets on outbound interface - - - - - Outbound IPv4 firewall ruleset name for interface - - firewall name - - - - - - Outbound IPv6 firewall ruleset name for interface - - firewall ipv6-name - - - - - - - - packets destined for this router - - - - - Local IPv4 firewall ruleset name for interface - - firewall name - - - - - - Local IPv6 firewall ruleset name for interface - - firewall ipv6-name - - - - - - - - diff --git a/interface-definitions/include/interface/interface-firewall.xml.i b/interface-definitions/include/interface/interface-firewall.xml.i deleted file mode 100644 index b3f20c3bf..000000000 --- a/interface-definitions/include/interface/interface-firewall.xml.i +++ /dev/null @@ -1,79 +0,0 @@ - - - - 615 - Firewall options - - - - - forwarded packets on inbound interface - - - - - Inbound IPv4 firewall ruleset name for interface - - firewall name - - - - - - Inbound IPv6 firewall ruleset name for interface - - firewall ipv6-name - - - - - - - - forwarded packets on outbound interface - - - - - Outbound IPv4 firewall ruleset name for interface - - firewall name - - - - - - Outbound IPv6 firewall ruleset name for interface - - firewall ipv6-name - - - - - - - - packets destined for this router - - - - - Local IPv4 firewall ruleset name for interface - - firewall name - - - - - - Local IPv6 firewall ruleset name for interface - - firewall ipv6-name - - - - - - - - diff --git a/interface-definitions/include/interface/vif-s.xml.i b/interface-definitions/include/interface/vif-s.xml.i index c1af9f9e3..916349ade 100644 --- a/interface-definitions/include/interface/vif-s.xml.i +++ b/interface-definitions/include/interface/vif-s.xml.i @@ -18,7 +18,6 @@ #include #include #include - #include #include @@ -68,7 +67,6 @@ #include #include #include - #include #include diff --git a/interface-definitions/include/interface/vif.xml.i b/interface-definitions/include/interface/vif.xml.i index 57ef8d64c..73a8c98ff 100644 --- a/interface-definitions/include/interface/vif.xml.i +++ b/interface-definitions/include/interface/vif.xml.i @@ -18,7 +18,6 @@ #include #include #include - #include #include diff --git a/interface-definitions/include/version/firewall-version.xml.i b/interface-definitions/include/version/firewall-version.xml.i index 059a89f24..065925319 100644 --- a/interface-definitions/include/version/firewall-version.xml.i +++ b/interface-definitions/include/version/firewall-version.xml.i @@ -1,3 +1,3 @@ - + diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in index 8b6c6ef62..41e4a68a8 100644 --- a/interface-definitions/interfaces-bonding.xml.in +++ b/interface-definitions/interfaces-bonding.xml.in @@ -56,7 +56,6 @@ #include #include #include - #include #include diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index 48ee1efbc..1e11cd4c6 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -41,7 +41,6 @@ #include #include #include - #include #include diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in index 01438de31..fb36741f7 100644 --- a/interface-definitions/interfaces-dummy.xml.in +++ b/interface-definitions/interfaces-dummy.xml.in @@ -19,7 +19,6 @@ #include #include #include - #include #include diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index c821f04b2..ab65a93f3 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -31,7 +31,6 @@ #include #include - #include #include diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in index 6e8a8fee2..b959c787d 100644 --- a/interface-definitions/interfaces-geneve.xml.in +++ b/interface-definitions/interfaces-geneve.xml.in @@ -23,7 +23,6 @@ #include #include #include - #include #include diff --git a/interface-definitions/interfaces-input.xml.in b/interface-definitions/interfaces-input.xml.in index 2164bfa4e..d01c760f8 100644 --- a/interface-definitions/interfaces-input.xml.in +++ b/interface-definitions/interfaces-input.xml.in @@ -19,7 +19,6 @@ #include #include - #include #include #include diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in index 6a85064cd..bde68dd5a 100644 --- a/interface-definitions/interfaces-l2tpv3.xml.in +++ b/interface-definitions/interfaces-l2tpv3.xml.in @@ -32,7 +32,6 @@ 5000 #include - #include #include diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index adb48813f..5c9f4cd76 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -21,7 +21,6 @@ #include #include #include - #include #include #include diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in index 6cbd91ff4..3876e31da 100644 --- a/interface-definitions/interfaces-openvpn.xml.in +++ b/interface-definitions/interfaces-openvpn.xml.in @@ -34,7 +34,6 @@ #include - #include #include diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in index 9674cfc0e..84f76a7ee 100644 --- a/interface-definitions/interfaces-pppoe.xml.in +++ b/interface-definitions/interfaces-pppoe.xml.in @@ -19,7 +19,6 @@ #include #include #include - #include #include #include #include diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in index 53e6445fa..4eb9bf111 100644 --- a/interface-definitions/interfaces-pseudo-ethernet.xml.in +++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in @@ -28,7 +28,6 @@ #include #include #include - #include #include diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in index 98ff878ba..fe49d337a 100644 --- a/interface-definitions/interfaces-tunnel.xml.in +++ b/interface-definitions/interfaces-tunnel.xml.in @@ -29,7 +29,6 @@ #include #include #include - #include #include diff --git a/interface-definitions/interfaces-vti.xml.in b/interface-definitions/interfaces-vti.xml.in index aa83a04b2..eeaea0dc3 100644 --- a/interface-definitions/interfaces-vti.xml.in +++ b/interface-definitions/interfaces-vti.xml.in @@ -25,7 +25,6 @@ #include #include #include - #include #include diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index faa3dd5e0..4902ff36d 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -54,7 +54,6 @@ #include #include #include - #include #include 1450 diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in index 4a1b4ac68..23f50d146 100644 --- a/interface-definitions/interfaces-wireguard.xml.in +++ b/interface-definitions/interfaces-wireguard.xml.in @@ -21,7 +21,6 @@ #include #include #include - #include #include #include diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index daee770a9..9e7fc29bc 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -20,7 +20,6 @@ #include - #include #include diff --git a/interface-definitions/interfaces-wwan.xml.in b/interface-definitions/interfaces-wwan.xml.in index 3071e6091..b0b8367dc 100644 --- a/interface-definitions/interfaces-wwan.xml.in +++ b/interface-definitions/interfaces-wwan.xml.in @@ -39,7 +39,6 @@ #include #include #include - #include #include #include #include diff --git a/interface-definitions/zone-policy.xml.in b/interface-definitions/zone-policy.xml.in index dc3408c3d..cf53e2bc8 100644 --- a/interface-definitions/zone-policy.xml.in +++ b/interface-definitions/zone-policy.xml.in @@ -3,7 +3,7 @@ Configure zone-policy - 250 + 198 diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 49d4d6170..cc436d4d0 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -36,8 +36,6 @@ sysfs_config = { 'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337', 'default': '0', 'test_value': 'enable'} } -eth0_addr = '172.16.10.1/24' - class TestFirewall(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): @@ -47,15 +45,11 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): # out the current configuration :) cls.cli_delete(cls, ['firewall']) - cls.cli_set(cls, ['interfaces', 'ethernet', 'eth0', 'address', eth0_addr]) - @classmethod def tearDownClass(cls): - cls.cli_delete(cls, ['interfaces', 'ethernet', 'eth0', 'address', eth0_addr]) super(TestFirewall, cls).tearDownClass() def tearDown(self): - self.cli_delete(['interfaces', 'ethernet', 'eth0', 'firewall']) self.cli_delete(['firewall']) self.cli_commit() @@ -128,7 +122,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'action', 'accept']) self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'source', 'group', 'domain-group', 'smoketest_domain']) - self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest']) + self.cli_set(['firewall', 'interface', 'eth0', 'in', 'name', 'smoketest']) self.cli_commit() nftables_search = [ @@ -160,7 +154,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port1']) self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp']) - self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'name', 'smoketest']) + self.cli_set(['firewall', 'interface', 'eth0', 'in', 'name', 'smoketest']) self.cli_commit() @@ -216,7 +210,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'name', name, 'rule', '5', 'tcp', 'flags', 'syn']) self.cli_set(['firewall', 'name', name, 'rule', '5', 'tcp', 'mss', mss_range]) - self.cli_set(['interfaces', 'ethernet', interface, 'firewall', 'in', 'name', name]) + self.cli_set(['firewall', 'interface', interface, 'in', 'name', name]) self.cli_commit() @@ -252,7 +246,7 @@ 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(['interfaces', 'ethernet', interface, 'firewall', 'in', 'name', name]) + self.cli_set(['firewall', 'interface', interface, 'in', 'name', name]) self.cli_commit() @@ -282,7 +276,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'protocol', 'tcp_udp']) self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'destination', 'port', '8888']) - self.cli_set(['interfaces', 'ethernet', 'eth0', 'firewall', 'in', 'ipv6-name', name]) + self.cli_set(['firewall', 'interface', interface, 'in', 'ipv6-name', name]) self.cli_commit() @@ -315,7 +309,7 @@ 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(['interfaces', 'ethernet', interface, 'firewall', 'in', 'ipv6-name', name]) + self.cli_set(['firewall', 'interface', interface, 'in', 'ipv6-name', name]) self.cli_commit() @@ -364,7 +358,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'name', name, 'rule', '4', 'state', 'established', 'enable']) self.cli_set(['firewall', 'name', name, 'rule', '4', 'connection-status', 'nat', 'source']) - self.cli_set(['interfaces', 'ethernet', interface, 'firewall', 'in', 'name', name]) + self.cli_set(['firewall', 'interface', interface, 'in', 'name', name]) self.cli_commit() diff --git a/src/conf_mode/firewall-interface.py b/src/conf_mode/firewall-interface.py deleted file mode 100755 index ab1c69259..000000000 --- a/src/conf_mode/firewall-interface.py +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2021 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 . - -import os -import re - -from sys import argv -from sys import exit - -from vyos.config import Config -from vyos.configdict import leaf_node_changed -from vyos.ifconfig import Section -from vyos.template import render -from vyos.util import cmd -from vyos.util import dict_search_args -from vyos.util import run -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -NAME_PREFIX = 'NAME_' -NAME6_PREFIX = 'NAME6_' - -NFT_CHAINS = { - 'in': 'VYOS_FW_FORWARD', - 'out': 'VYOS_FW_FORWARD', - 'local': 'VYOS_FW_LOCAL' -} -NFT6_CHAINS = { - 'in': 'VYOS_FW6_FORWARD', - 'out': 'VYOS_FW6_FORWARD', - 'local': 'VYOS_FW6_LOCAL' -} - -def get_config(config=None): - if config: - conf = config - else: - conf = Config() - - ifname = argv[1] - ifpath = Section.get_config_path(ifname) - if_firewall_path = f'interfaces {ifpath} firewall' - - if_firewall = conf.get_config_dict(if_firewall_path, key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) - - if_firewall['ifname'] = ifname - if_firewall['firewall'] = conf.get_config_dict(['firewall'], key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) - - return if_firewall - -def verify_chain(table, chain): - # Verify firewall applied - code = run(f'nft list chain {table} {chain}') - return code == 0 - -def verify(if_firewall): - # bail out early - looks like removal from running config - if not if_firewall: - return None - - for direction in ['in', 'out', 'local']: - if direction in if_firewall: - if 'name' in if_firewall[direction]: - name = if_firewall[direction]['name'] - - if 'name' not in if_firewall['firewall']: - raise ConfigError('Firewall name not configured') - - if name not in if_firewall['firewall']['name']: - raise ConfigError(f'Invalid firewall name "{name}"') - - if not verify_chain('ip filter', f'{NAME_PREFIX}{name}'): - raise ConfigError('Firewall did not apply') - - if 'ipv6_name' in if_firewall[direction]: - name = if_firewall[direction]['ipv6_name'] - - if 'ipv6_name' not in if_firewall['firewall']: - raise ConfigError('Firewall ipv6-name not configured') - - if name not in if_firewall['firewall']['ipv6_name']: - raise ConfigError(f'Invalid firewall ipv6-name "{name}"') - - if not verify_chain('ip6 filter', f'{NAME6_PREFIX}{name}'): - raise ConfigError('Firewall did not apply') - - return None - -def generate(if_firewall): - return None - -def cleanup_rule(table, chain, prefix, ifname, new_name=None): - results = cmd(f'nft -a list chain {table} {chain}').split("\n") - retval = None - for line in results: - if f'{prefix}ifname "{ifname}"' in line: - if new_name and f'jump {new_name}' in line: - # new_name is used to clear rules for any previously referenced chains - # returns true when rule exists and doesn't need to be created - retval = True - continue - - handle_search = re.search('handle (\d+)', line) - if handle_search: - run(f'nft delete rule {table} {chain} handle {handle_search[1]}') - return retval - -def state_policy_handle(table, chain): - # Find any state-policy rule to ensure interface rules are only inserted afterwards - results = cmd(f'nft -a list chain {table} {chain}').split("\n") - for line in results: - if 'jump VYOS_STATE_POLICY' in line: - handle_search = re.search('handle (\d+)', line) - if handle_search: - return handle_search[1] - return None - -def apply(if_firewall): - ifname = if_firewall['ifname'] - - for direction in ['in', 'out', 'local']: - chain = NFT_CHAINS[direction] - ipv6_chain = NFT6_CHAINS[direction] - if_prefix = 'i' if direction in ['in', 'local'] else 'o' - - name = dict_search_args(if_firewall, direction, 'name') - if name: - rule_exists = cleanup_rule('ip filter', chain, if_prefix, ifname, f'{NAME_PREFIX}{name}') - - if not rule_exists: - rule_action = 'insert' - rule_prefix = '' - - handle = state_policy_handle('ip filter', chain) - if handle: - rule_action = 'add' - rule_prefix = f'position {handle}' - - run(f'nft {rule_action} rule ip filter {chain} {rule_prefix} {if_prefix}ifname {ifname} counter jump {NAME_PREFIX}{name}') - else: - cleanup_rule('ip filter', chain, if_prefix, ifname) - - ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name') - if ipv6_name: - rule_exists = cleanup_rule('ip6 filter', ipv6_chain, if_prefix, ifname, f'{NAME6_PREFIX}{ipv6_name}') - - if not rule_exists: - rule_action = 'insert' - rule_prefix = '' - - handle = state_policy_handle('ip6 filter', ipv6_chain) - if handle: - rule_action = 'add' - rule_prefix = f'position {handle}' - - run(f'nft {rule_action} rule ip6 filter {ipv6_chain} {rule_prefix} {if_prefix}ifname {ifname} counter jump {NAME6_PREFIX}{ipv6_name}') - else: - cleanup_rule('ip6 filter', ipv6_chain, if_prefix, ifname) - - return None - -if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index f0ea1a1e5..86793ba86 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -26,6 +26,7 @@ from vyos.config import Config from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.configdiff import get_config_diff, Diff +# from vyos.configverify import verify_interface_exists from vyos.firewall import geoip_update from vyos.firewall import get_ips_domains_dict from vyos.firewall import nft_add_set_elements @@ -38,7 +39,7 @@ from vyos.util import cmd from vyos.util import dict_search_args from vyos.util import dict_search_recursive from vyos.util import process_named_running -from vyos.util import run +from vyos.util import rc_cmd from vyos.xml import defaults from vyos import ConfigError from vyos import airbag @@ -47,7 +48,9 @@ airbag.enable() policy_route_conf_script = '/usr/libexec/vyos/conf_mode/policy-route.py' nftables_conf = '/run/nftables.conf' -nftables_defines_conf = '/run/nftables_defines.conf' + +nftables_zone_conf = '/run/nftables_zone.conf' +nftables6_zone_conf = '/run/nftables_zone6.conf' sysfs_config = { 'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'enable': '0', 'disable': '1'}, @@ -63,28 +66,6 @@ sysfs_config = { 'twa_hazards_protection': {'sysfs': '/proc/sys/net/ipv4/tcp_rfc1337'} } -NAME_PREFIX = 'NAME_' -NAME6_PREFIX = 'NAME6_' - -preserve_chains = [ - 'INPUT', - 'FORWARD', - 'OUTPUT', - 'VYOS_FW_FORWARD', - 'VYOS_FW_LOCAL', - 'VYOS_FW_OUTPUT', - 'VYOS_POST_FW', - 'VYOS_FRAG_MARK', - 'VYOS_FW6_FORWARD', - 'VYOS_FW6_LOCAL', - 'VYOS_FW6_OUTPUT', - 'VYOS_POST_FW6', - 'VYOS_FRAG6_MARK' -] - -nft_iface_chains = ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL'] -nft6_iface_chains = ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL'] - valid_groups = [ 'address_group', 'domain_group', @@ -97,16 +78,6 @@ nested_group_types = [ 'port_group', 'ipv6_address_group', 'ipv6_network_group' ] -group_set_prefix = { - 'A_': 'address_group', - 'A6_': 'ipv6_address_group', - 'D_': 'domain_group', - 'M_': 'mac_group', - 'N_': 'network_group', - 'N6_': 'ipv6_network_group', - 'P_': 'port_group' -} - snmp_change_type = { 'unknown': 0, 'add': 1, @@ -117,22 +88,6 @@ snmp_event_source = 1 snmp_trap_mib = 'VYATTA-TRAP-MIB' snmp_trap_name = 'mgmtEventTrap' -def get_firewall_interfaces(conf): - out = {} - interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'), get_first_key=True, - no_tag_node_value_mangle=True) - def find_interfaces(iftype_conf, output={}, prefix=''): - for ifname, if_conf in iftype_conf.items(): - if 'firewall' in if_conf: - output[prefix + ifname] = if_conf['firewall'] - for vif in ['vif', 'vif_s', 'vif_c']: - if vif in if_conf: - output.update(find_interfaces(if_conf[vif], output, f'{prefix}{ifname}.')) - return output - for iftype, iftype_conf in interfaces.items(): - out.update(find_interfaces(iftype_conf)) - return out - def get_firewall_zones(conf): used_v4 = [] used_v6 = [] @@ -159,6 +114,8 @@ def get_firewall_zones(conf): ipv6_name = dict_search_args(zone_conf, 'intra_zone_filtering', 'firewall', 'ipv6_name') if ipv6_name: used_v6.append(ipv6_name) + else: + return None return {'name': used_v4, 'ipv6_name': used_v6} @@ -232,7 +189,6 @@ def get_config(config=None): firewall['ipv6_name'][ipv6_name]) firewall['policy_resync'] = bool('group' in firewall or node_changed(conf, base + ['group'])) - firewall['interfaces'] = get_firewall_interfaces(conf) firewall['zone_policy'] = get_firewall_zones(conf) if 'config_trap' in firewall and firewall['config_trap'] == 'enable': @@ -358,109 +314,42 @@ def verify(firewall): for name in ['name', 'ipv6_name']: if name in firewall: for name_id, name_conf in firewall[name].items(): - if name_id in preserve_chains: - raise ConfigError(f'Firewall name "{name_id}" is reserved for VyOS') - - if name_id.startswith("VZONE"): - raise ConfigError(f'Firewall name "{name_id}" uses reserved prefix') - if 'rule' in name_conf: for rule_id, rule_conf in name_conf['rule'].items(): verify_rule(firewall, rule_conf, name == 'ipv6_name') - for ifname, if_firewall in firewall['interfaces'].items(): - for direction in ['in', 'out', 'local']: - name = dict_search_args(if_firewall, direction, 'name') - ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name') + if 'interface' in firewall: + for ifname, if_firewall in firewall['interface'].items(): + # verify ifname needs to be disabled, dynamic devices come up later + # verify_interface_exists(ifname) - if name and dict_search_args(firewall, 'name', name) == None: - raise ConfigError(f'Firewall name "{name}" is still referenced on interface {ifname}') + for direction in ['in', 'out', 'local']: + name = dict_search_args(if_firewall, direction, 'name') + ipv6_name = dict_search_args(if_firewall, direction, 'ipv6_name') - if ipv6_name and dict_search_args(firewall, 'ipv6_name', ipv6_name) == None: - raise ConfigError(f'Firewall ipv6-name "{ipv6_name}" is still referenced on interface {ifname}') + if name and dict_search_args(firewall, 'name', name) == None: + raise ConfigError(f'Invalid firewall name "{name}" referenced on interface {ifname}') - for fw_name, used_names in firewall['zone_policy'].items(): - for name in used_names: - if dict_search_args(firewall, fw_name, name) == None: - raise ConfigError(f'Firewall {fw_name.replace("_", "-")} "{name}" is still referenced in zone-policy') + if ipv6_name and dict_search_args(firewall, 'ipv6_name', ipv6_name) == None: + raise ConfigError(f'Invalid firewall ipv6-name "{ipv6_name}" referenced on interface {ifname}') - return None + if firewall['zone_policy']: + for fw_name, used_names in firewall['zone_policy'].items(): + for name in used_names: + if dict_search_args(firewall, fw_name, name) == None: + raise ConfigError(f'Firewall {fw_name.replace("_", "-")} "{name}" is still referenced in zone-policy') -def cleanup_commands(firewall): - commands = [] - commands_chains = [] - commands_sets = [] - for table in ['ip filter', 'ip6 filter']: - name_node = 'name' if table == 'ip filter' else 'ipv6_name' - chain_prefix = NAME_PREFIX if table == 'ip filter' else NAME6_PREFIX - state_chain = 'VYOS_STATE_POLICY' if table == 'ip filter' else 'VYOS_STATE_POLICY6' - iface_chains = nft_iface_chains if table == 'ip filter' else nft6_iface_chains - - geoip_list = [] - if firewall['geoip_updated']: - geoip_key = 'deleted_ipv6_name' if table == 'ip6 filter' else 'deleted_name' - geoip_list = dict_search_args(firewall, 'geoip_updated', geoip_key) or [] - - json_str = cmd(f'nft -t -j list table {table}') - obj = loads(json_str) - - if 'nftables' not in obj: - continue - - for item in obj['nftables']: - if 'chain' in item: - chain = item['chain']['name'] - if chain in preserve_chains or chain.startswith("VZONE"): - continue - - if chain == state_chain: - command = 'delete' if 'state_policy' not in firewall else 'flush' - commands_chains.append(f'{command} chain {table} {chain}') - elif dict_search_args(firewall, name_node, chain.replace(chain_prefix, "", 1)) != None: - commands.append(f'flush chain {table} {chain}') - else: - commands_chains.append(f'delete chain {table} {chain}') - - if 'rule' in item: - rule = item['rule'] - chain = rule['chain'] - handle = rule['handle'] - - if chain in iface_chains: - target, _ = next(dict_search_recursive(rule['expr'], 'target')) - - if target == state_chain and 'state_policy' not in firewall: - commands.append(f'delete rule {table} {chain} handle {handle}') - - if target.startswith(chain_prefix): - if dict_search_args(firewall, name_node, target.replace(chain_prefix, "", 1)) == None: - commands.append(f'delete rule {table} {chain} handle {handle}') - - if 'set' in item: - set_name = item['set']['name'] - - if set_name.startswith('GEOIP_CC_') and set_name in geoip_list: - commands_sets.append(f'delete set {table} {set_name}') - continue - - if set_name.startswith("RECENT_"): - commands_sets.append(f'delete set {table} {set_name}') - continue - - for prefix, group_type in group_set_prefix.items(): - if set_name.startswith(prefix): - group_name = set_name.replace(prefix, "", 1) - if dict_search_args(firewall, 'group', group_type, group_name) != None: - commands_sets.append(f'flush set {table} {set_name}') - else: - commands_sets.append(f'delete set {table} {set_name}') - return commands + commands_chains + commands_sets + return None def generate(firewall): if not os.path.exists(nftables_conf): firewall['first_install'] = True - else: - firewall['cleanup_commands'] = cleanup_commands(firewall) + + if os.path.exists(nftables_zone_conf): + firewall['zone_conf'] = nftables_zone_conf + + if os.path.exists(nftables6_zone_conf): + firewall['zone6_conf'] = nftables6_zone_conf render(nftables_conf, 'firewall/nftables.j2', firewall) return None @@ -521,26 +410,21 @@ def post_apply_trap(firewall): cmd(base_cmd + ' '.join(objects)) -def state_policy_rule_exists(): - # Determine if state policy rules already exist in nft - search_str = cmd(f'nft list chain ip filter VYOS_FW_FORWARD') - return 'VYOS_STATE_POLICY' in search_str - def resync_policy_route(): # Update policy route as firewall groups were updated - tmp = run(policy_route_conf_script) + tmp, out = rc_cmd(policy_route_conf_script) if tmp > 0: - Warning('Failed to re-apply policy route configuration!') + Warning(f'Failed to re-apply policy route configuration! {out}') def apply(firewall): if 'first_install' in firewall: run('nfct helper add rpc inet tcp') run('nfct helper add rpc inet udp') run('nfct helper add tns inet tcp') - - install_result = run(f'nft -f {nftables_conf}') + + install_result, output = rc_cmd(f'nft -f {nftables_conf}') if install_result == 1: - raise ConfigError('Failed to apply firewall') + raise ConfigError(f'Failed to apply firewall: {output}') # set firewall group domain-group xxx if 'group' in firewall: @@ -563,13 +447,6 @@ def apply(firewall): else: call('systemctl stop vyos-domain-group-resolve.service') - if 'state_policy' in firewall and not state_policy_rule_exists(): - for chain in ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL']: - cmd(f'nft insert rule ip filter {chain} jump VYOS_STATE_POLICY') - - for chain in ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL']: - cmd(f'nft insert rule ip6 filter {chain} jump VYOS_STATE_POLICY6') - apply_sysfs(firewall) if firewall['policy_resync']: diff --git a/src/conf_mode/zone_policy.py b/src/conf_mode/zone_policy.py index a52c52706..c6ab4e304 100755 --- a/src/conf_mode/zone_policy.py +++ b/src/conf_mode/zone_policy.py @@ -21,6 +21,7 @@ from sys import exit from vyos.config import Config from vyos.configdict import dict_merge +from vyos.configdiff import get_config_diff from vyos.template import render from vyos.util import cmd from vyos.util import dict_search_args @@ -30,7 +31,9 @@ from vyos import ConfigError from vyos import airbag airbag.enable() +firewall_conf_script = '/usr/libexec/vyos/conf_mode/firewall.py' nftables_conf = '/run/nftables_zone.conf' +nftables6_conf = '/run/nftables_zone6.conf' def get_config(config=None): if config: @@ -47,6 +50,9 @@ def get_config(config=None): get_first_key=True, no_tag_node_value_mangle=True) + diff = get_config_diff(conf) + zone_policy['firewall_changed'] = diff.is_node_changed(['firewall']) + if 'zone' in zone_policy: # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. @@ -111,20 +117,12 @@ def verify(zone_policy): raise ConfigError(f'Zone "{zone}" refers to a non-existent or deleted zone "{from_zone}"') v4_name = dict_search_args(from_conf, 'firewall', 'name') - if v4_name: - if 'name' not in zone_policy['firewall']: - raise ConfigError(f'Firewall name "{v4_name}" does not exist') - - if not dict_search_args(zone_policy, 'firewall', 'name', v4_name): - raise ConfigError(f'Firewall name "{v4_name}" does not exist') + if v4_name and not dict_search_args(zone_policy, 'firewall', 'name', v4_name): + raise ConfigError(f'Firewall name "{v4_name}" does not exist') v6_name = dict_search_args(from_conf, 'firewall', 'v6_name') - if v6_name: - if 'ipv6_name' not in zone_policy['firewall']: - raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') - - if not dict_search_args(zone_policy, 'firewall', 'ipv6_name', v6_name): - raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') + if v6_name and not dict_search_args(zone_policy, 'firewall', 'ipv6_name', v6_name): + raise ConfigError(f'Firewall ipv6-name "{v6_name}" does not exist') return None @@ -152,37 +150,11 @@ def get_local_from(zone_policy, local_zone_name): out[zone] = zone_conf['from'][local_zone_name] return out -def cleanup_commands(): - commands = [] - for table in ['ip filter', 'ip6 filter']: - json_str = cmd(f'nft -t -j list table {table}') - obj = loads(json_str) - if 'nftables' not in obj: - continue - for item in obj['nftables']: - if 'rule' in item: - chain = item['rule']['chain'] - handle = item['rule']['handle'] - if 'expr' not in item['rule']: - continue - for expr in item['rule']['expr']: - target = dict_search_args(expr, 'jump', 'target') - if not target: - continue - if target.startswith("VZONE") or target.startswith("VYOS_STATE_POLICY"): - commands.append(f'delete rule {table} {chain} handle {handle}') - for item in obj['nftables']: - if 'chain' in item: - if item['chain']['name'].startswith("VZONE"): - chain = item['chain']['name'] - commands.append(f'delete chain {table} {chain}') - return commands - def generate(zone_policy): data = zone_policy or {} - if os.path.exists(nftables_conf): # Check to see if we've run before - data['cleanup_commands'] = cleanup_commands() + if not os.path.exists(nftables_conf): + data['first_install'] = True if 'zone' in data: for zone, zone_conf in data['zone'].items(): @@ -193,12 +165,19 @@ def generate(zone_policy): zone_conf['from_local'] = get_local_from(data, zone) render(nftables_conf, 'zone_policy/nftables.j2', data) + render(nftables6_conf, 'zone_policy/nftables6.j2', data) return None +def update_firewall(): + # Update firewall to refresh nftables + tmp = run(firewall_conf_script) + if tmp > 0: + Warning('Failed to update firewall configuration!') + def apply(zone_policy): - install_result = run(f'nft -f {nftables_conf}') - if install_result != 0: - raise ConfigError('Failed to apply zone-policy') + # If firewall will not update in this commit, we need to call the conf script + if not zone_policy['firewall_changed']: + update_firewall() return None diff --git a/src/migration-scripts/firewall/7-to-8 b/src/migration-scripts/firewall/7-to-8 new file mode 100755 index 000000000..6929e20a5 --- /dev/null +++ b/src/migration-scripts/firewall/7-to-8 @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 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 . + +# T2199: Migrate interface firewall nodes to firewall interfaces name/ipv6-name + +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) + +def migrate_interface(config, iftype, ifname, vif=None, vifs=None, vifc=None): + if_path = ['interfaces', iftype, ifname] + ifname_full = ifname + + if vif: + if_path += ['vif', vif] + ifname_full = f'{ifname}.{vif}' + elif vifs: + if_path += ['vif-s', vifs] + ifname_full = f'{ifname}.{vifs}' + if vifc: + if_path += ['vif-c', vifc] + ifname_full = f'{ifname}.{vifs}.{vifc}' + + if not config.exists(if_path + ['firewall']): + return + + if not config.exists(['firewall', 'interface']): + config.set(['firewall', 'interface']) + config.set_tag(['firewall', 'interface']) + + config.copy(if_path + ['firewall'], ['firewall', 'interface', ifname_full]) + config.delete(if_path + ['firewall']) + +for iftype in config.list_nodes(['interfaces']): + for ifname in config.list_nodes(['interfaces', iftype]): + migrate_interface(config, iftype, ifname) + + if config.exists(['interfaces', iftype, ifname, 'vif']): + for vif in config.list_nodes(['interfaces', iftype, ifname, 'vif']): + migrate_interface(config, iftype, ifname, vif=vif) + + if config.exists(['interfaces', iftype, ifname, 'vif-s']): + for vifs in config.list_nodes(['interfaces', iftype, ifname, 'vif-s']): + migrate_interface(config, iftype, ifname, vifs=vifs) + + if config.exists(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + for vifc in config.list_nodes(['interfaces', iftype, ifname, 'vif-s', vifs, 'vif-c']): + migrate_interface(config, iftype, ifname, vifs=vifs, vifc=vifc) + +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) -- cgit v1.2.3