Ankusa.Admin.Router (ankusa v0.7.0)

Copy Markdown View Source

The operator HTTP API: health, Prometheus metrics, the redacted config, the AsyncAPI document of the channels this instance publishes to, the dead-letter queue, replay jobs, and this node's quarantine pen (list and purge).

It exists so operating Ankusa does not require an Elixir shell. Everything here had an IEx-only affordance before (Ankusa.Dispatch.replay/2, Ankusa.Edge.Quarantine.recent/2, :sys.get_state/1-style poking); this is the same surface over HTTP.

Authentication and scopes

Every route but GET /health and GET /ready needs a credential (admin.auth, see Ankusa.Admin.Auth): 401 without one, 403 when the caller's scopes (Ankusa.Principal) do not cover the route. Scopes are checked before anything else, so a caller without them learns nothing about this node's roles or data.

RouteScope
GET /metrics, GET /v1/walmetrics:read
GET /v1/config, GET /asyncapi.jsonconfig:read
PUT /v1/log-levelconfig:write + tenant:*
POST /v1/config/reloadconfig:write + tenant:*
GET /v1/dlq, DELETE /v1/dlqdlq:read / dlq:write + a tenant (filtered)
POST /v1/replaysreplays:write + a tenant (filtered), and dlq:write / quarantine:write for those kinds
GET /v1/replays[/:id]replays:read + a tenant (filtered)
PATCH /v1/replays/:idreplays:write + a tenant (filtered)
GET /v1/quarantine, DELETE /v1/quarantinequarantine:read / quarantine:write + a tenant (filtered)
GET /v1/tenants/:tenant/sources[/:name]sources:read + tenant:<tenant>
POST/PUT/DELETE /v1/tenants/:tenant/sources[/:name]sources:write + tenant:<tenant>
GET /v1/rate-limitsrate_limits:read + a tenant (filtered)
GET/PUT/DELETE /v1/tenants/:tenant/rate-limitrate_limits:read / rate_limits:write + tenant:<tenant>
DELETE /v1/tenants/:tenant?confirm=<tenant>, GET /v1/tenants/:tenant/erasetenants:write + tenant:*

The DLQ, the quarantine pen, replay jobs and the rate-limit listing hold every tenant's rows, so they are filtered by tenant (Auth.authorize_tenants/4). A caller limited to some tenant:<id> scopes gets only those tenants' rows, whatever it asks: the filter is forced server-side. ?tenant=<id> (the body key tenant of POST /v1/replays) narrows the answer to one tenant, and naming a tenant the caller does not hold is 403. Only tenant:* reads or changes them unfiltered. A quarantine purge deletes, and a replay touches, only the caller's tenants' rows; a replay job is visible to (and steerable by) a limited caller only when it is scoped to tenants that caller holds. /v1/config is still redacted, because many people may hold config:read.

Node-local by design

A route that needs a role this node does not run returns 409 role_not_enabled rather than an empty success: the DLQ is the dispatch node's store, the quarantine list is the edge node's, so a wrong-node answer must be distinguishable from "nothing there". GET /v1/wal is the same kind of answer: it describes this node's own store, and under wal.type: none there is no queue to describe (409 wal_disabled). Aggregating across nodes is the operator's job (scrape every admin port).

Summary

Functions

Callback implementation for Plug.call/2.

Callback implementation for Plug.init/1.

Functions

call(conn, opts)

Callback implementation for Plug.call/2.

init(opts)

Callback implementation for Plug.init/1.