Skip to content

Commit b7a8bc8

Browse files
committed
plugin opnsense: fix for chains (remove hooks and use goto instead)
1 parent 0df8543 commit b7a8bc8

File tree

6 files changed

+134
-13
lines changed

6 files changed

+134
-13
lines changed

docs/source/plugins/firewall_opnsense.rst

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,105 @@ Here is an example on how to run it with the exported config:
4141
--src-ip 10.57.65.44 \
4242
--dst-ip 1.1.1.1
4343
44+
Example
45+
*******
46+
47+
**Note**: NAT is not yet implemented for this example.
48+
49+
.. code-block:: bash
50+
51+
ftf-cli --firewall-system 'opnsense' \
52+
--file-ruleset 'testdata/plugin_translate_opnsense_config.xml' \
53+
--file-interfaces 'testdata/plugin_translate_opnsense_network.json' \
54+
--file-routes 'testdata/plugin_translate_opnsense_network.json' \
55+
--src-ip 10.34.28.206 \
56+
--dst-ip 1.1.1.1 \
57+
--port 993
58+
59+
> ⚠ FIREWALL PLUGIN: Unable to resolve alias DNS: "test.some-invalid-domain.oxl.aaaa"
60+
> ⚠ FIREWALL PLUGIN: Unsupported alias-type "geoip" will be skipped: "GEOIP_NEARBY"
61+
> ⚠ FIREWALL PLUGIN: Unable to parse rule-address: "GEOIP_NEARBY"
62+
> ⚠ FIREWALL PLUGIN: Unsupported rule: Chain floating, Rule 15
63+
> ⚠ FIREWALL PLUGIN: Unable to parse rule-address: "GEOIP_NEARBY"
64+
> ⚠ FIREWALL PLUGIN: Unsupported rule: Chain interfaces, Rule 9 (GeoIP Block)
65+
> ⚠ FIREWALL PLUGIN: Unable to parse rule-address: "GEOIP_NEARBY"
66+
> ⚠ FIREWALL PLUGIN: Unsupported rule: Chain interfaces, Rule 80
67+
> ⚠ FIREWALL PLUGIN: Unable to parse rule-address: "GEOIP_NEARBY"
68+
> ⚠ FIREWALL PLUGIN: Unsupported rule: Chain interfaces, Rule 85 (SVC_1 Proxies)
69+
> 🛈 ROUTER: Packet inbound-interface: lan (LAN)
70+
> 🛈 ROUTER: Packet inbound-route: 10.34.28.0/24, scope link
71+
> 🛈 FIREWALL: Processing Chain: Table "default" ip | Chain "dnat" ip nat (0 rules)
72+
> 🛈 ROUTER: Packet outbound-interface: opt5 (WAN2)
73+
> 🛈 ROUTER: Packet outbound-route: 0.0.0.0/0, gw 169.169.169.1, scope global
74+
> 🛈 FIREWALL: Processing Chain: Table "default" ip | Chain "floating" ip filter (15 rules)
75+
> 🛈 FIREWALL: > Chain floating | Rule 1
76+
> 🛈 FIREWALL: > Chain floating | Rule 2
77+
> 🛈 FIREWALL: > Chain floating | Rule 3
78+
> 🛈 FIREWALL: > Chain floating | Rule 4
79+
> 🛈 FIREWALL: > Chain floating | Rule 5
80+
> 🛈 FIREWALL: > Chain floating | Rule 6
81+
> 🛈 FIREWALL: > Chain floating | Rule 7
82+
> 🛈 FIREWALL: > Chain floating | Rule 8
83+
> 🛈 FIREWALL: > Chain floating | Rule 9
84+
> 🛈 FIREWALL: > Chain floating | Rule 10
85+
> 🛈 FIREWALL: > Chain floating | Rule 11
86+
> 🛈 FIREWALL: > Chain floating | Rule 12
87+
> 🛈 FIREWALL: > Chain floating | Rule 13
88+
> 🛈 FIREWALL: > Chain floating | Rule 14
89+
> 🛈 FIREWALL: > Chain floating | Rule 1000000 | Match => goto
90+
> 🛈 FIREWALL: > Chain floating | Sub-Chain: interface_groups (8 rules)
91+
> 🛈 FIREWALL: > Chain interface_groups | Rule 1
92+
> 🛈 FIREWALL: > Chain interface_groups | Rule 2
93+
> 🛈 FIREWALL: > Chain interface_groups | Rule 3
94+
> 🛈 FIREWALL: > Chain interface_groups | Rule 4
95+
> 🛈 FIREWALL: > Chain interface_groups | Rule 5 | Match => accept
96+
> 🛈 FIREWALL: Processing Chain: Table "default" ip | Chain "snat" ip nat (0 rules)
97+
> ⚠ FIREWALL: Source is bogon-network and heading to Public-WAN without SNAT!
98+
> ✓ FIREWALL: Packet passed
99+
100+
**Block Example**:
101+
102+
.. code-block:: bash
103+
104+
ftf-cli ... --src-ip 10.34.28.206 --dst-ip 1.10.16.4
105+
106+
...
107+
> 🛈 ROUTER: Packet inbound-interface: lan (LAN)
108+
> 🛈 ROUTER: Packet inbound-route: 10.34.28.0/24, scope link
109+
> 🛈 FIREWALL: Processing Chain: Table "default" ip | Chain "dnat" ip nat (0 rules)
110+
> 🛈 ROUTER: Packet outbound-interface: opt5 (WAN2)
111+
> 🛈 ROUTER: Packet outbound-route: 0.0.0.0/0, gw 169.169.169.1, scope global
112+
> 🛈 FIREWALL: Processing Chain: Table "default" ip | Chain "floating" ip filter (15 rules)
113+
> 🛈 FIREWALL: > Chain floating | Rule 1
114+
> 🛈 FIREWALL: > Chain floating | Rule 2 | Match => drop
115+
> ✖ FIREWALL: Packet blocked by rule: Seq 2, Action: drop, Rule: #2 "SpamHaus DROP Block Outbound"
116+
> > Matches: {'proto_l3': 'ip4', 'ip_saddr': {'==': 'any'}, 'ip_daddr': {'==': ['1.10.16.0/20', '1.19.0.0/16', '1.32.128.0/18', '2.56.192.0/22', '2.57.122.0/24', '2.57.232.0/23', '2.57.234.0/23', '2.59.152.0/24', '2.59.154.0/24', '5.42.92.0/24', '5.105.220.0/24', '5.133.101.0/24', '5.134.128.0/19', '5.183.60.0/22', '5.183.129.0/24', '5.188.10.0/23', '5.188.11.0/24', '5.188.236.0/23', '14.128.32.0/20', '14.128.48.0/21', '14.152.94.0/24', '23.94.58.0/24', '23.129.252.0/23', '23.137.100.0/24', '23.146.240.0/24'...
117+
118+
**Increased Verbosity**:
119+
120+
Use the :code:`verbosity` flag to get more information about the rules and matching.
121+
122+
.. code-block:: bash
123+
124+
ftf-cli ... --src-ip 10.34.28.206 --dst-ip 1.10.16.4 --verbosity 2
125+
126+
...
127+
> 🛈 ROUTER: Packet inbound-interface: lan (LAN)
128+
> 🛈 ROUTER: Packet inbound-route: 10.34.28.0/24, scope link
129+
> 🛈 FIREWALL: Processing Chain: Table "default" ip | Chain "dnat" ip nat (0 rules)
130+
> 🛈 FIREWALL: Flow-type: forward
131+
> 🛈 ROUTER: Packet outbound-interface: opt5 (WAN2)
132+
> 🛈 ROUTER: Packet outbound-route: 0.0.0.0/0, gw 169.169.169.1, scope global
133+
> 🛈 FIREWALL: Processing Chain: Table "default" ip | Chain "floating" ip filter (15 rules)
134+
> 🛈 FIREWALL: > Chain floating | Rule 1 | Seq 1, Action: accept, Rule: #1
135+
> > Matches: {'proto_l3': 'ip4', 'ip_saddr': {'==': ['192.168.0.0/30']}, 'ip_daddr': {'==': ['192.168.0.0/30']}}
136+
>
137+
> 🛈 FIREWALL: > Chain floating | Rule 2 | Match => drop | Seq 2, Action: drop, Rule: #2 "SpamHaus DROP Block Outbound"
138+
> > Matches: {'proto_l3': 'ip4', 'ip_saddr': {'==': 'any'}, 'ip_daddr': {'==': ['1.10.16.0/20', '1.19.0.0/16', '1.32.128.0/18', '2.56.192.0/22', '2.57.122.0/24', '2.57.232.0/23', '2.57.234.0/23', '2.59.152.0/24', '2.59.154.0/24', '5.42.92.0/24', '5.105.220.0/24', '5.133.101.0/24', '5.134.128.0/19', '5.183.60.0/22', '5.183.129.0/24', '5.188.10.0/23', '5.188.11.0/24', '5.188.236.0/23', '14.128.32.0/20', '14.128.48.0/21', '14.152.94.0/24', '23.94.58.0/24', '23.129.252.0/23', '23.137.100.0/24', '23.146.240.0/24'...
139+
>
140+
> ✖ FIREWALL: Packet blocked by rule: Seq 2, Action: drop, Rule: #2 "SpamHaus DROP Block Outbound"
141+
> > Matches: {'proto_l3': 'ip4', 'ip_saddr': {'==': 'any'}, 'ip_daddr': {'==': ['1.10.16.0/20', '1.19.0.0/16', '1.32.128.0/18', '2.56.192.0/22', '2.57.122.0/24', '2.57.232.0/23', '2.57.234.0/23', '2.59.152.0/24', '2.59.154.0/24', '5.42.92.0/24', '5.105.220.0/24', '5.133.101.0/24', '5.134.128.0/19', '5.183.60.0/22', '5.183.129.0/24', '5.188.10.0/23', '5.188.11.0/24', '5.188.236.0/23', '14.128.32.0/20', '14.128.48.0/21', '14.152.94.0/24', '23.94.58.0/24', '23.129.252.0/23', '23.137.100.0/24', '23.146.240.0/24'...
142+
44143
----
45144
46145
Source Code

src/firewall_test/plugins/system/firewall_opnsense.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from config import ProtoL3IP4IP6
1+
from config import ProtoL3IP4IP6, RuleActionGoTo
22
from simulator.packet import PACKET_KINDS, PacketTCPUDP
33
from plugins.translate.abstract import Rule
44
from plugins.system.abstract_rule_match import RuleMatcher, RuleMatchResult
5-
from plugins.translate.opnsense.rule import OPNsenseRule
5+
from plugins.translate.opnsense.rule import OPNsenseRule, RULE_SEQUENCE_NEXT_CHAIN
66
from utils.logger import log_warn, log_debug
77

88
# todo: add explicit match-tests
@@ -16,8 +16,17 @@ def matches(self, packet: PACKET_KINDS, rule: Rule) -> RuleMatchResult:
1616
:param rule: Rule to check
1717
:return: RuleMatchResult
1818
"""
19-
opn_rule: OPNsenseRule = rule.raw
2019

20+
if rule.action == RuleActionGoTo and rule.seq == RULE_SEQUENCE_NEXT_CHAIN:
21+
return RuleMatchResult(
22+
matched=True,
23+
action=rule.action,
24+
target_chain_name=rule.raw, # will only contain chain-name in that case
25+
target_nat_ip=None,
26+
target_nat_port=None,
27+
)
28+
29+
opn_rule: OPNsenseRule = rule.raw
2130
results = []
2231
### NETWORK INTERFACES ###
2332
if opn_rule.match_ni_in:

src/firewall_test/plugins/system/system_opnsense.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ class SystemOPNsense(FirewallSystem):
2121

2222
# see: https://docs.opnsense.org/manual/firewall.html#processing-order
2323
FIREWALL_HOOKS = {
24-
FlowInput: ['dnat', 'floating', 'interface_groups', 'interfaces'],
25-
FlowForward: ['dnat', 'floating', 'interface_groups', 'interfaces', 'snat'],
26-
FlowOutput: ['dnat', 'floating', 'interface_groups', 'interfaces', 'snat'],
27-
'full': ['dnat', 'floating', 'interface_groups', 'interfaces', 'snat'],
24+
FlowInput: ['dnat', 'filters'],
25+
FlowForward: ['dnat', 'filters', 'snat'],
26+
FlowOutput: ['dnat', 'filters', 'snat'],
27+
'full': ['dnat', 'filters', 'snat'],
2828
}
2929
FIREWALL_INGRESS = {
3030
FlowInput: {'hook': 'prerouting', 'priority': 1000},

src/firewall_test/plugins/translate/abstract.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ class Chain(TranslateOutput):
304304

305305
# pylint: disable=W0622
306306
def __init__(
307-
self, name: str, hook: str, policy: (None, RuleActionAccept, RuleActionDrop, RuleActionReject),
307+
self, name: str, hook: (str, None), policy: (None, RuleActionAccept, RuleActionDrop, RuleActionReject),
308308
rules: list[Rule], priority: int = 0, type: str = 'filter', family: type[ProtoL3] = ProtoL3IP4IP6,
309309
):
310310
self.name = name

src/firewall_test/plugins/translate/opnsense/rule.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
# pylint: disable=R0801
77

8+
RULE_SEQUENCE_NEXT_CHAIN = 1_000_000
9+
10+
811
# pylint: disable=R0912,R0913,R0914,R0915,R0917
912
class OPNsenseRule:
1013
DIRECTION_IN = 'in'

src/firewall_test/plugins/translate/opnsense/ruleset.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
IPLIST_COMMENT_CHARS, ProtoL4ICMP, ProtoL4TCP, ProtoL4UDP, ProtoL3IP4, ProtoL3IP6, BOGONS
1515
from plugins.system.system_opnsense import SystemOPNsense
1616
from plugins.translate.abstract import Ruleset, TranslatePluginRuleset, Table, Chain, Rule, \
17-
RuleActionAccept, RuleActionDrop
18-
from plugins.translate.opnsense.rule import OPNsenseRule
17+
RuleActionAccept, RuleActionDrop, RuleActionGoTo
18+
from plugins.translate.opnsense.rule import OPNsenseRule, RULE_SEQUENCE_NEXT_CHAIN
1919
from utils.logger import log_warn
2020

2121
XML_ELEMENT_NIS = 'interfaces'
@@ -100,13 +100,13 @@ def __init__(self, raw: str):
100100
name='dnat', hook='dnat', policy=RuleActionAccept, type='nat', rules=[],
101101
)
102102
self.chain_floating = OPNsenseChainOutput(
103-
name='floating', hook='floating', policy=RuleActionAccept, rules=[],
103+
name='floating', hook='filters', policy=RuleActionDrop, rules=[],
104104
)
105105
self.chain_ni_grp = OPNsenseChainOutput(
106-
name='interface_groups', hook='interface_groups', policy=RuleActionAccept, rules=[],
106+
name='interface_groups', hook=None, policy=RuleActionAccept, rules=[],
107107
)
108108
self.chain_ni = OPNsenseChainOutput(
109-
name='interfaces', hook='interfaces', policy=RuleActionDrop, rules=[],
109+
name='interfaces', hook=None, policy=RuleActionDrop, rules=[],
110110
)
111111
self.chain_snat = OPNsenseChainOutput(
112112
name='snat', hook='snat', policy=RuleActionAccept, type='nat', rules=[],
@@ -133,6 +133,16 @@ def get(self) -> Ruleset:
133133
self.aliases['bogons'] = BOGONS
134134

135135
self._parse_rules_old()
136+
self.chain_floating.rules.append(Rule(
137+
action=RuleActionGoTo,
138+
seq=RULE_SEQUENCE_NEXT_CHAIN,
139+
raw='interface_groups',
140+
))
141+
self.chain_ni_grp.rules.append(Rule(
142+
action=RuleActionGoTo,
143+
seq=RULE_SEQUENCE_NEXT_CHAIN,
144+
raw='interfaces',
145+
))
136146

137147
return Ruleset(tables=[
138148
Table(

0 commit comments

Comments
 (0)