fix: say what uninstall_package cannot reach, instead of posting anyway
`firmware/remove` acts on the OPNsense plugin set, and `get_packages` reads the same list — so software installed as a plain FreeBSD package is invisible to the one and unreachable by the other. The Wazuh agent is exactly that, on a driver the agent plugin lists as supported. Observed during a fleet-wide rollback on 2026-09-19: the `gw` device could not be handled through netOrk at all, and the request posted for it could never have succeeded. A name that is not a plugin now raises NotImplementedError rather than being POSTed. A request that cannot work reports failure for the wrong reason and sends whoever reads it looking in the wrong place; netOrk turns NotImplementedError into a 501, which is the accurate answer. Reaching plain packages would need shell access, and the credentials stored for these devices are frequently API-key only — that is a decision of its own, not a detail of this one. The injection guard still runs first: a malformed name is a ValueError before anything asks whether it is a plugin. netork#241
This commit is contained in:
@@ -2126,10 +2126,30 @@ class OPNsenseDriver(OPNsensePingMixin, FirewallDriver):
|
||||
"""Remove an OPNsense plugin by name.
|
||||
|
||||
Calls ``POST /api/core/firmware/remove/{name}``.
|
||||
|
||||
**Plugins only, and it says so.** ``firmware/remove`` acts on the
|
||||
plugin set, and ``get_packages`` reads the same list — so software
|
||||
installed as a plain FreeBSD package is invisible to the one and
|
||||
unreachable by the other. The Wazuh agent is exactly that, on a driver
|
||||
the agent plugin lists as supported, and during a fleet-wide rollback
|
||||
the request posted for it could never have succeeded.
|
||||
|
||||
Raising beats posting. A request that cannot work reports failure for
|
||||
the wrong reason and sends whoever reads it looking in the wrong place;
|
||||
netOrk turns ``NotImplementedError`` into a 501, which is the accurate
|
||||
answer. Reaching plain packages would need shell access, and the
|
||||
credentials stored for these devices are frequently API-key only —
|
||||
a decision of its own rather than a detail of this one.
|
||||
"""
|
||||
import re as _re
|
||||
if not _re.match(r'^[a-zA-Z0-9_\-\.]+$', name):
|
||||
raise ValueError(f"Invalid package name: {name!r}")
|
||||
if not name.startswith("os-"):
|
||||
raise NotImplementedError(
|
||||
f"{name!r} is not an OPNsense plugin. This driver can remove plugins "
|
||||
"(os-*) through the firmware API; a FreeBSD package needs shell access, "
|
||||
"which is not configured for OPNsense devices."
|
||||
)
|
||||
try:
|
||||
result = self._post(f"/api/core/firmware/remove/{name}")
|
||||
return {"success": True, "output": str(result)}
|
||||
|
||||
@@ -2544,3 +2544,53 @@ class TestSyncDnsZone:
|
||||
|
||||
added = [d for p, d in calls if p.endswith("addhostoverride")]
|
||||
assert len(added) == 1
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# uninstall_package – says what it cannot do
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestUninstallPackage:
|
||||
"""`firmware/remove` reaches OPNsense plugins and nothing else.
|
||||
|
||||
`get_packages` reads `/api/core/firmware/info` filtered to the plugin list,
|
||||
and `firmware/remove` acts on the same set. Software installed as a plain
|
||||
FreeBSD package is invisible to the first and unreachable by the second —
|
||||
the Wazuh agent being exactly that, on a driver the agent plugin lists as
|
||||
supported.
|
||||
|
||||
Observed during a fleet-wide rollback on 2026-09-19: the `gw` device could
|
||||
not be handled through netOrk at all, and the request that was posted for
|
||||
it could never have succeeded.
|
||||
|
||||
So it refuses instead of posting. A request that cannot work is worse than
|
||||
an honest no: the caller stops looking for the real problem. The API turns
|
||||
NotImplementedError into 501, which is the accurate answer.
|
||||
"""
|
||||
|
||||
def test_a_plugin_is_removed(self, driver):
|
||||
driver._post = MagicMock(return_value={"status": "ok"})
|
||||
|
||||
result = driver.uninstall_package("os-wazuh-agent")
|
||||
|
||||
assert result["success"] is True
|
||||
driver._post.assert_called_once()
|
||||
|
||||
def test_a_freebsd_package_is_refused(self, driver):
|
||||
driver._post = MagicMock()
|
||||
|
||||
with pytest.raises(NotImplementedError, match="plugin"):
|
||||
driver.uninstall_package("wazuh-agent")
|
||||
|
||||
driver._post.assert_not_called()
|
||||
|
||||
def test_the_refusal_names_the_package(self, driver):
|
||||
"""Whoever reads the 501 needs to know which name was rejected."""
|
||||
with pytest.raises(NotImplementedError, match="wazuh-agent"):
|
||||
driver.uninstall_package("wazuh-agent")
|
||||
|
||||
def test_a_malformed_name_is_still_a_ValueError(self, driver):
|
||||
"""Refusing non-plugins must not swallow the injection guard."""
|
||||
with pytest.raises(ValueError):
|
||||
driver.uninstall_package("os-thing; rm -rf /")
|
||||
|
||||
Reference in New Issue
Block a user