"""Filter rules as ``get_firewall_rules`` reports them. A pass rule with a gateway is policy routing: OPNsense hands what it matches to that gateway, local destinations included. A caller judging which network can reach which host has to know that, or a rule meant for internet traffic reads as a hole into every server (netOrk #575: on the first real box, "pass UDP IOT -> any" via WAN_GW made 61 hosts look reachable from the IoT segment). The row shape follows that box's ``/api/firewall/filter/searchRule``. """ from __future__ import annotations from unittest.mock import patch import pytest from napalm_opnsense.opnsense import OPNsenseDriver @pytest.fixture def driver(): with patch("napalm_opnsense.opnsense.requests.Session"): yield OPNsenseDriver( hostname="opnsense.example.com", username="api_key", password="api_secret", optional_args={"verify": False}, ) def _row(**over) -> dict: """One rule row as the API returns it; booleans are "0"/"1" strings.""" row = { "uuid": "u1", "sequence": "1", "action": "pass", "quick": "1", "interface": "opt3", "%interface": "IOT", "direction": "in", "ipprotocol": "inet", "protocol": "UDP", "%source_net": "HOME_OFFICE_IOT_NET", "%destination_net": "any", "destination_port": "", "description": "reolink_udp_long_state_timeout", "enabled": "1", "gateway": "WAN_GW", } row.update(over) return row def _rules(driver, *rows): def fake_get(path): return {"rows": list(rows)} if "searchRule" in path else {"rows": []} with patch.object(driver, "_get", side_effect=fake_get): return driver.get_firewall_rules() def test_a_policy_routed_rule_reports_its_gateway(driver): [rule] = _rules(driver, _row(gateway="WAN_GW")) assert rule["gateway"] == "WAN_GW" def test_a_rule_that_routes_normally_reports_no_gateway(driver): [rule] = _rules(driver, _row(gateway="")) assert rule["gateway"] == "" def test_a_row_without_the_field_reports_no_gateway(driver): # Older firmware, or a rule type that never carries one. row = _row() del row["gateway"] [rule] = _rules(driver, row) assert rule["gateway"] == ""