- Which never-used pool address a new client gets: sequential (the lowest, as before), reverse or random, in the settings, a subnet or a single pool (FIRST-LAST:ORDER). When no never-used address is left, the one free the longest, whatever the order; returning clients keep theirs. - A subnet's pools are used in the order listed, or one at random weighted by its never-used addresses. Pools now keep the order they are given in (no more sorting by address). - CLI --address-order and --pool-order; --add-pool of an existing range changes its order in place. Web UI: per-pool order and a move-up button, pool and address order on the subnet and settings pages. - Kea's allocator: random is imported. Schema migration 5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7.4 KiB
The REST API
Everything the web UI and the CLI do goes through this API, under
/api/v1. Requests and answers are JSON.
Authentication
- API tokens:
Authorization: Bearer steward_.... Make one in the web UI (your account), withsteward token create NAME, or withstewardd user token USER NAMEon the server. - Sessions:
POST /auth/loginwith{"username", "password"}sets a cookie and returnscsrf_token. Requests other than GET must send it asX-CSRF-Token. - The local socket (
/run/steward/steward.sock): root and stewardd's own user need nothing; others send a token.
Failed logins and token attempts are rate limited per address and per user name. permissions.md has what each call needs.
Errors
{"error": {"code": "validation", "message": "subnet iot (10.0.20.0/24): it overlaps subnet office (10.0.10.0/24)",
"problems": [{"severity": "error", "code": "subnet_overlap", "message": "...", "object": "subnet iot (10.0.20.0/24)"}]}}
| Status | Code | |
|---|---|---|
| 400 | bad_request |
the request can't be read (an unknown option name, a bad address) |
| 401 | unauthenticated, session_expired, login_failed |
|
| 403 | forbidden, csrf |
|
| 404 | not_found |
|
| 409 | conflict |
e.g. undoing a change after the objects changed again |
| 422 | validation |
the change would make the configuration invalid; problems lists why |
| 429 | rate_limited |
Changes
Calls that change the configuration answer:
{"applied": true, "history_id": 7,
"changes": [{"kind": "subnet", "id": 12, "label": "iot (10.0.20.0/24)", "before": {...}, "after": {...}}],
"warnings": [{"severity": "warning", "code": "interface_unknown", "message": "...", "object": "..."}],
"object": {...}}
?dry_run=1checks the change and returns what it would do, with"applied": false, "dry_run": true.beforeis missing for a create andafterfor a delete.warningsare the problems the change brings; older ones are in/status.objectis the created or changed object.
Creating answers 201, other changes 200.
Endpoints
Status
GET /healthz |
{"ok": true, "version"}, without authentication |
GET /status |
build, start time, interfaces (listening, error, addresses, subnets), subnets with usage, problems, leases (counts by state), messages (counts by type), ping_problem |
GET /interfaces |
the host's interfaces, their addresses and subnets, leaving out those matching interfaces.exclude |
GET /catalog |
the option catalog, value types, reserved codes, roles |
Settings
GET /settings, PATCH /settings with any of:
{"lease_time": 43200, "ping_check": true, "ping_timeout_ms": 500, "decline_hold": 86400,
"not_authoritative": false, "address_order": "sequential",
"options": [{"option": "domain-name-servers", "value": "10.0.10.2"}]}
address_order is sequential (left out when it is), reverse or
random (subnets.md).
options replaces the list. Each option is {"option": NAME or NUMBER, "value": TEXT}, with "type" for numbers the catalog doesn't know
(options.md).
Subnets
GET /subnets |
every subnet, with usage (pool_size, leased, declined, reservations, reservations_leased) |
POST /subnets |
create; needs prefix and interface |
GET /subnets/{ref} |
by ID, name, or prefix with _ for / (10.0.10.0_24) |
PATCH /subnets/{ref} |
change the given fields |
DELETE /subnets/{ref} |
delete, with its reservations |
{"name": "office", "interface": "eth0", "prefix": "10.0.10.0/24",
"pools": ["10.0.10.100-10.0.10.199", "10.0.10.224/28:random"], "pool_order": "listed",
"address_order": "default", "lease_time": 0,
"options": [{"option": "routers", "value": "10.0.10.1"}], "disabled": false, "comment": ""}
pools and options replace the lists; the pools keep their order. A
pool's :ORDER is its own address order. pool_order is listed or
random; address_order is sequential, reverse, random or
default (the settings'). Subnets come back with pools as
{"start", "end", "order"}, and the defaults (listed, default) left
out. lease_time 0 means the settings'.
Reservations
GET /reservations[?subnet=REF] |
each with subnet_id, subnet and its current lease |
POST /reservations |
{"ip", "mac", "client_id", "hostname", "options", "disabled", "comment"}; ip and mac or client_id needed |
GET, PATCH, DELETE /reservations/{ref} |
by ID, address, or MAC address (when it has one reservation) |
Leases and activity
GET /leases[?subnet=&state=&interface=&q=] |
every lease, with its state now (active, expired, released, declined), subnet and its reservation, and last_seen when the client last answered a ping (left out if it never has); q searches address, MAC and host name |
GET /leases/{ip} |
one |
DELETE /leases/{ip} |
forget it (audited) |
POST /leases/{ip}/reserve |
make it a reservation; optional {"hostname", "comment"} |
GET /events[?mac=&subnet=&interface=&limit=] |
the latest DHCP messages, newest first (the last 1000 are kept in memory). subnet (ID, name or prefix) keeps messages for its addresses, and those without an address on its interface |
History and import
GET /history[?limit=&before=] |
changes, newest first |
GET /history/{id} |
one, with each object before and after |
POST /history/{id}/revert |
undo it; 409 if an object it touched has changed since, or it was undone already |
POST /import/kea |
{"config": TEXT, "leases": [TEXT, ...], "interfaces": {"PREFIX": "IFACE"}, "replace": false} (kea.md); answers with leases, notes, skipped too |
Certificate
Administrators only (certificates.md).
GET /certificate |
{"configured", "source": "acme"|"file"|"none", "status": {"domain", "directory", "staging", "state": "none"|"ok"|"working"|"error", "not_before", "not_after", "issuer", "renew_at", "last_error", "last_attempt", "next_attempt", "check"}} |
POST /certificate/check |
run the check now (up to a few minutes); answers {"time", "domain", "ok", "steps": [{"name", "status", "detail", "queries"}]} |
POST /certificate/renew |
get a new certificate now, in the background (202); follow it with GET /certificate |
GET /status lists the certificate's problems (certificate_failed,
certificate_expiring, certificate_staging) for administrators.
Users, tokens, audit
POST /auth/login, POST /auth/logout, GET /auth/whoami |
whoami has token_id when the caller uses a token |
POST /me/password |
{"current", "password"} |
GET /me/tokens, POST /me/tokens |
{"name", "role", "grants", "expires_in"}; the answer's secret is shown once |
GET /tokens, DELETE /tokens/{id} |
all tokens (administrators), revoke |
POST /tokens/{id}/rotate |
a new secret: {"grace": "1h", "expires_in": "90d", "no_expiry": false}, all optional; answers with secret and previous_secret_valid_until |
GET, POST /users; PATCH, DELETE /users/{id} |
administrators; {"username", "password", "admin", "role", "grants", "disabled", "comment"} |
GET, POST /users/{id}/tokens |
|
GET /audit[?user=&action=&result=&q=&before=&limit=] |