fix(opnsense): correct Kea leases4 del_lease endpoint — path param, not body
The lease-delete call never actually worked: it posted {"ip-address": ip}
to /api/kea/leases4/delLease, both wrong. Verified live against a real
OPNsense instance while cleaning up stale leases left by failed NetOrk VM
provisioning attempts — every call returned {"status": "error", "message":
"Missing lease IP parameter"} despite three different body-parameter
guesses (ips as list, ips as string, ip singular). The official API docs
(docs.opnsense.org/development/api/core/kea.html) show LeasesController as
"Abstract [non-callable]" with a del_lease($ips=null) action; despite that
signature looking like a body field, the concrete leases4 route only
accepts the IP as a URL path segment: POST /api/kea/leases4/del_lease/{ip}
confirmed {"status": "ok"} and the lease actually gone from a follow-up
search.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
b8dac1db63
commit
b8a68fc3a8
@@ -1221,13 +1221,20 @@ class OPNsenseDriver(FirewallDriver):
|
|||||||
method's "never silently no-op on the thing the caller explicitly
|
method's "never silently no-op on the thing the caller explicitly
|
||||||
asked for" contract.
|
asked for" contract.
|
||||||
|
|
||||||
Lease removal is best-effort and non-fatal: the exact Kea
|
Lease removal is best-effort and non-fatal — a failure here just
|
||||||
lease-delete endpoint shape is unverified in this codebase (unlike
|
means a stale lease record lingers in Kea until its own natural
|
||||||
reservations, ``get_dhcp_leases()`` only ever implemented the read
|
cleanup, which is cosmetic, not a functional problem (a deleted
|
||||||
path) — a failure here just means a stale lease record lingers in
|
reservation already prevents the client from getting the same IP
|
||||||
Kea until its own natural cleanup, which is cosmetic, not a
|
back). Endpoint verified live against a real OPNsense instance:
|
||||||
functional problem (a deleted reservation already prevents the
|
``LeasesController`` is documented as "Abstract [non-callable]" with
|
||||||
client from getting the same IP back).
|
a ``del_lease($ips=null)`` action
|
||||||
|
(https://docs.opnsense.org/development/api/core/kea.html) — the
|
||||||
|
concrete, callable route is the ``leases4`` controller (matching
|
||||||
|
``search``, used above), and despite the ``$ips`` parameter name the
|
||||||
|
IP is passed as a URL path segment, not a JSON body field — a POST
|
||||||
|
body of ``{"ips": [ip]}`` (the natural reading of the signature)
|
||||||
|
returns ``{"status": "error", "message": "Missing lease IP
|
||||||
|
parameter"}``; only ``POST /api/kea/leases4/del_lease/{ip}`` works.
|
||||||
|
|
||||||
:param mac: NIC MAC address of the reservation to remove.
|
:param mac: NIC MAC address of the reservation to remove.
|
||||||
:param ip: IP address of the reservation/lease to remove.
|
:param ip: IP address of the reservation/lease to remove.
|
||||||
@@ -1272,7 +1279,7 @@ class OPNsenseDriver(FirewallDriver):
|
|||||||
rows = leases.get("rows") or leases.get("leases") or []
|
rows = leases.get("rows") or leases.get("leases") or []
|
||||||
if any((row.get("address") or row.get("ip-address")) == ip for row in rows):
|
if any((row.get("address") or row.get("ip-address")) == ip for row in rows):
|
||||||
result["lease_found"] = True
|
result["lease_found"] = True
|
||||||
del_lease = self._post("/api/kea/leases4/delLease", {"ip-address": ip})
|
del_lease = self._post(f"/api/kea/leases4/del_lease/{ip}")
|
||||||
result["lease_deleted"] = bool(
|
result["lease_deleted"] = bool(
|
||||||
del_lease.get("result") == "deleted" or del_lease.get("status") == "ok"
|
del_lease.get("result") == "deleted" or del_lease.get("status") == "ok"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1379,8 +1379,14 @@ class TestDeleteDhcpReservationAndLease:
|
|||||||
reconfigure_call = driver.session.post.call_args_list[2]
|
reconfigure_call = driver.session.post.call_args_list[2]
|
||||||
assert reconfigure_call.args[0] == "https://opnsense.example.com/api/kea/service/reconfigure"
|
assert reconfigure_call.args[0] == "https://opnsense.example.com/api/kea/service/reconfigure"
|
||||||
del_lease_call = driver.session.post.call_args_list[3]
|
del_lease_call = driver.session.post.call_args_list[3]
|
||||||
assert del_lease_call.args[0] == "https://opnsense.example.com/api/kea/leases4/delLease"
|
# IP is a URL path segment, not a JSON body field — verified live
|
||||||
assert del_lease_call.kwargs["json"] == {"ip-address": "172.22.8.253"}
|
# against a real OPNsense instance; a {"ips": [ip]} body (the
|
||||||
|
# natural reading of the documented del_lease($ips=null) signature)
|
||||||
|
# returns {"status": "error", "message": "Missing lease IP parameter"}.
|
||||||
|
assert (
|
||||||
|
del_lease_call.args[0]
|
||||||
|
== "https://opnsense.example.com/api/kea/leases4/del_lease/172.22.8.253"
|
||||||
|
)
|
||||||
|
|
||||||
def test_noop_when_no_reservation_and_no_lease_found(self, driver):
|
def test_noop_when_no_reservation_and_no_lease_found(self, driver):
|
||||||
driver.session.post.return_value = _make_json_response({"rows": []})
|
driver.session.post.return_value = _make_json_response({"rows": []})
|
||||||
|
|||||||
Reference in New Issue
Block a user