Skip to content

Commit 3daabbf

Browse files
committed
added basic opnsense rule-parsing
1 parent bf0eecd commit 3daabbf

File tree

15 files changed

+834
-13
lines changed

15 files changed

+834
-13
lines changed

.github/workflows/lint.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ on:
99
paths:
1010
- '**.py'
1111
- '.github/workflows/lint.yml'
12+
- 'requirements.txt'
1213
- 'requirements_lint.txt'
1314
pull_request:
1415
branches: [latest]
1516
paths:
1617
- '**.py'
1718
- '.github/workflows/lint.yml'
19+
- 'requirements.txt'
1820
- 'requirements_lint.txt'
1921
jobs:
2022
lint:

docs/source/plugins/firewall_opnsense.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
.. _plugins_fw_opnsense:
22

33
.. |export_backup| image:: ../_static/img/plugin-opnsense-backup.png
4-
:class: wiki-img
4+
:class: wiki-img-sm
55

66
.. |export_network| image:: ../_static/img/plugin-opnsense-export.png
7-
:class: wiki-img
7+
:class: wiki-img-sm
88

99
.. include:: ../_include/head.rst
1010

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
oxl-utils
2+
requests

src/firewall_test/config.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,47 @@
1111
DEFAULT_ROUTE_IP6 = ip_network('::/0')
1212
DEFAULT_ROUTES = [DEFAULT_ROUTE_IP4, DEFAULT_ROUTE_IP6]
1313
LINK_LOCAL_IP6 = ip_network('fe80::/64')
14+
BOGONS_IP4 = [
15+
ip_network('0.0.0.0/8'),
16+
ip_network('10.0.0.0/8'),
17+
ip_network('100.64.0.0/10'),
18+
ip_network('127.0.0.0/8'),
19+
ip_network('127.0.53.53'),
20+
ip_network('169.254.0.0/16'),
21+
ip_network('172.16.0.0/12'),
22+
ip_network('192.0.0.0/24'),
23+
ip_network('192.0.2.0/24'),
24+
ip_network('192.168.0.0/16'),
25+
ip_network('198.18.0.0/15'),
26+
ip_network('198.51.100.0/24'),
27+
ip_network('203.0.113.0/24'),
28+
ip_network('224.0.0.0/4'),
29+
ip_network('240.0.0.0/4'),
30+
ip_network('255.255.255.255/32'),
31+
]
32+
BOGONS_IP6 = [
33+
ip_network('::/128'),
34+
ip_network('::1/128'),
35+
ip_network('::ffff:0:0/96'),
36+
ip_network('::/96'),
37+
ip_network('100::/64'),
38+
ip_network('2001:10::/28'),
39+
ip_network('2001:db8::/32'),
40+
ip_network('3fff::/20'),
41+
ip_network('fc00::/7'),
42+
ip_network('fe80::/10'),
43+
ip_network('fec0::/10'),
44+
ip_network('ff00::/8'),
45+
]
46+
BOGONS = BOGONS_IP4.copy()
47+
BOGONS.extend(BOGONS_IP6)
48+
49+
# dns-resolving firewall-rule content
50+
DNS_RESOLVE_TIMEOUT = 1
51+
DNS_RESOLVE_THREADS = 50
52+
53+
IPLIST_DOWNLOAD_TIMEOUT = 3
54+
IPLIST_COMMENT_CHARS = ['#', ';']
1455

1556

1657
class Proto(ABC):
@@ -33,10 +74,11 @@ class ProtoL3IP4IP6(ProtoL3):
3374
N = 'ip'
3475

3576

36-
PROTOS_L3 = [ProtoL3IP4, ProtoL3IP6]
77+
PROTOS_L3 = [ProtoL3IP4, ProtoL3IP6, ProtoL3IP4IP6]
3778
PROTO_L3_MAPPING = {
3879
ProtoL3IP4.N: ProtoL3IP4,
3980
ProtoL3IP6.N: ProtoL3IP6,
81+
ProtoL3IP4IP6.N: ProtoL3IP4IP6,
4082
}
4183

4284
class ProtoL4(Proto):
Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
from plugins.system.system_linux_netfilter import SystemLinuxNetfilter
22
from plugins.translate.netfilter.ruleset import NetfilterRuleset
33
from plugins.translate.linux import LinuxRoutes, LinuxRouteRules, LinuxNetworkInterfaces
4+
from plugins.system.system_opnsense import SystemOPNsense
5+
from plugins.translate.opnsense.interfaces import OPNsenseNetworkInterfaces
6+
from plugins.translate.opnsense.routes import OPNsenseRoutes
7+
from plugins.translate.opnsense.ruleset import OPNsenseRuleset
48

59
SYSTEM_MAPPING = {
610
'linux_netfilter': SystemLinuxNetfilter,
11+
'opnsense': SystemOPNsense,
712
}
813

914
COMPONENT_MAPPING = {
@@ -12,5 +17,10 @@
1217
'routes': LinuxRoutes,
1318
'route_rules': LinuxRouteRules,
1419
'ruleset': NetfilterRuleset,
15-
}
20+
},
21+
SystemOPNsense: {
22+
'nis': OPNsenseNetworkInterfaces,
23+
'routes': OPNsenseRoutes,
24+
'ruleset': OPNsenseRuleset,
25+
},
1626
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from simulator.packet import PacketIP
2+
from plugins.translate.abstract import Rule
3+
from plugins.system.abstract_rule_match import RuleMatcher, RuleMatchResult
4+
from plugins.translate.opnsense.rule import OPNsenseRule
5+
6+
7+
class RuleMatcherOPNsense(RuleMatcher):
8+
def matches(self, packet: PacketIP, rule: Rule) -> RuleMatchResult:
9+
"""
10+
:param packet: Packet to match
11+
:param rule: Rule to check
12+
:return: RuleMatchResult
13+
"""
14+
opn_rule: OPNsenseRule = rule.raw
15+
del opn_rule
16+
return RuleMatchResult(False, None, None, None, None)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# pylint: disable=R0801
2+
3+
from config import FlowInput, FlowOutput, FlowForward
4+
from plugins.system.abstract import FirewallSystem
5+
from plugins.system.firewall_opnsense import RuleMatcher, RuleMatcherOPNsense
6+
7+
8+
class SystemOPNsense(FirewallSystem):
9+
ROUTE_STATIC = True
10+
ROUTE_STATIC_RULES = False
11+
12+
SYSTEM_DROP_WAN_BOGONS = True # todo: instance-specific => read from config (interfaces-wan-blockbogons)
13+
SYSTEM_DROP_FORWARD = False
14+
15+
FIREWALL_ACTION_LAZY = True
16+
FIREWALL_CT = True
17+
FIREWALL_PRIO_LOWER_BETTER = True
18+
FIREWALL_PRIO_TABLE_FULL = False
19+
FIREWALL_DNAT = True
20+
FIREWALL_SNAT = True
21+
22+
# see: https://docs.opnsense.org/manual/firewall.html#processing-order
23+
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'],
28+
}
29+
FIREWALL_INGRESS = {
30+
FlowInput: {'hook': 'prerouting', 'priority': 1000},
31+
FlowForward: {'hook': 'prerouting', 'priority': 1000},
32+
FlowOutput: {'hook': 'output', 'priority': -100},
33+
}
34+
_chain_dnat = {'hook': 'dnat', 'priority': 0}
35+
_chain_snat = {'hook': 'snat', 'priority': 0}
36+
FIREWALL_NAT = {
37+
FlowInput: {
38+
'dnat': _chain_dnat,
39+
},
40+
FlowForward: {
41+
'dnat': _chain_dnat,
42+
'snat': _chain_snat,
43+
},
44+
FlowOutput: {
45+
'dnat': _chain_dnat,
46+
'snat': _chain_snat,
47+
},
48+
}
49+
50+
@classmethod
51+
def get_rule_matcher(cls) -> type[RuleMatcher]:
52+
"""
53+
Property to return the system-specific rule-matcher (plugins.system.abstract_rule_match.RuleMatcher)
54+
55+
:return: RuleMatcher
56+
"""
57+
return RuleMatcherOPNsense

src/firewall_test/plugins/translate/netfilter/elements.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from plugins.translate.netfilter.parts import RULE_ACTIONS, IGNORE_RULE_EXPRESSIONS, IGNORE_LEFT
77
from utils.logger import log_warn
88

9+
# pylint: disable=R0801
10+
911
# for schema see: https://www.mankier.com/5/libnftables-json
1012

1113
def translate_family(family: str) -> type[ProtoL3]:

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def get(self) -> Rule:
2727

2828
class NetfilterChainOutput(Chain):
2929
def _validate_hooks(self):
30-
assert self.hook is None or self.hook in SystemLinuxNetfilter.FIREWALL_HOOKS
30+
assert self.hook is None or self.hook in SystemLinuxNetfilter.FIREWALL_HOOKS['full']
3131

3232

3333
class NetfilterChain(TranslatePluginChain):

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

Whitespace-only changes.

0 commit comments

Comments
 (0)