"""WireGuard peers in ``get_vpn_tunnels()`` keep their key when others come and go (#32). The key was a running counter over the peer rows, so a peer added ahead of an existing one renumbered every peer after it, and whatever a consumer stored per key (netOrk's site link, its tunnel check) silently moved to another peer. A peer is its interface and its public key, so that is what the key is made of. """ from __future__ import annotations from unittest.mock import MagicMock, patch from napalm_opnsense.opnsense import OPNsenseDriver OFFICE = { "if": "wg0", "type": "peer", "name": "office", "public-key": "aBcD1234efGH5678ijKL9012mnOP3456qrST7890uvw=", "endpoint": "198.51.100.7:51820", "peer-status": "online", "transfer-rx": 10, "transfer-tx": 20, "latest-handshake-age": 45, } LAPTOP = { "if": "wg0", "type": "peer", "name": "laptop", "public-key": "Zz9/+yXw8765vuTS4321rqPO0987nmLK6543jiHG210=", "endpoint": "(none)", "peer-status": "offline", } SERVER = { "if": "wg1", "type": "peer", "name": "server", "public-key": "QqQqWwWwEeEeRrRrTtTtYyYyUuUuIiIiOoOoPpPp123=", "endpoint": "203.0.113.9:51821", "peer-status": "online", } def _driver(*rows: dict) -> OPNsenseDriver: with patch("napalm_opnsense.opnsense.requests.Session"): drv = OPNsenseDriver(hostname="fw", username="k", password="s") drv.session = MagicMock() show = {"rows": [{"if": "wg0", "type": "interface"}, *rows]} def get(path: str) -> dict: if path == "/api/wireguard/service/show": return show raise RuntimeError("not installed") # IPsec and OpenVPN are not part of this drv._get = get # type: ignore[method-assign] return drv def _by_name(tunnels: dict) -> dict[str, str]: return {t["description"]: key for key, t in tunnels.items()} class TestTheKey: def test_is_the_interface_and_the_start_of_the_public_key(self): assert list(_driver(OFFICE).get_vpn_tunnels()) == ["wireguard-wg0-aBcD1234"] def test_survives_a_peer_added_ahead_of_it(self): before = _by_name(_driver(OFFICE, SERVER).get_vpn_tunnels()) after = _by_name(_driver(OFFICE, LAPTOP, SERVER).get_vpn_tunnels()) assert after["server"] == before["server"] assert after["office"] == before["office"] def test_is_safe_in_a_url_path(self): # The key is a path segment (PATCH /devices/{id}/vpn-tunnels/{key}), and # a "/" from base64 would split it even when encoded. key = _by_name(_driver(LAPTOP).get_vpn_tunnels())["laptop"] assert key == "wireguard-wg0-Zz9_-yXw" def test_two_peers_with_the_same_key_start_both_stay(self): twin = { **SERVER, "if": "wg0", "name": "twin", "public-key": OFFICE["public-key"][:8] + "x=", } tunnels = _driver(OFFICE, twin).get_vpn_tunnels() assert len(tunnels) == 2 def test_without_a_public_key_falls_back_to_the_position(self): keyless = {k: v for k, v in OFFICE.items() if k != "public-key"} assert list(_driver(keyless).get_vpn_tunnels()) == ["wireguard-1"] class TestTheEntry: def test_carries_the_peer_s_reading(self): tunnel = _driver(OFFICE).get_vpn_tunnels()["wireguard-wg0-aBcD1234"] assert tunnel == { "type": "WireGuard", "local_endpoint": "", "remote_endpoint": "198.51.100.7", "is_up": True, "uptime": 45, "bytes_in": 10, "bytes_out": 20, "description": "office", }