fix(opnsense): correct Kea leases4 del_lease endpoint — path param, not body
CI / test (3.10) (push) Failing after 7s
CI / test (3.11) (push) Failing after 7s
CI / test (3.12) (push) Failing after 8s
CI / test (3.9) (push) Failing after 7s

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:
Christian Manivong
2026-07-09 10:31:05 +02:00
co-authored by Claude Sonnet 5
parent b8dac1db63
commit b8a68fc3a8
2 changed files with 23 additions and 10 deletions
+15 -8
View File
@@ -1221,13 +1221,20 @@ class OPNsenseDriver(FirewallDriver):
method's "never silently no-op on the thing the caller explicitly
asked for" contract.
Lease removal is best-effort and non-fatal: the exact Kea
lease-delete endpoint shape is unverified in this codebase (unlike
reservations, ``get_dhcp_leases()`` only ever implemented the read
path) — a failure here just means a stale lease record lingers in
Kea until its own natural cleanup, which is cosmetic, not a
functional problem (a deleted reservation already prevents the
client from getting the same IP back).
Lease removal is best-effort and non-fatal — a failure here just
means a stale lease record lingers in Kea until its own natural
cleanup, which is cosmetic, not a functional problem (a deleted
reservation already prevents the client from getting the same IP
back). Endpoint verified live against a real OPNsense instance:
``LeasesController`` is documented as "Abstract [non-callable]" with
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 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 []
if any((row.get("address") or row.get("ip-address")) == ip for row in rows):
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(
del_lease.get("result") == "deleted" or del_lease.get("status") == "ok"
)