Authentication
By default the Kapkan API and dashboard are unauthenticated. This is safe only because
the default api.listen value binds to 127.0.0.1 — the listener is reachable only from
the host itself, so no token is required to read status, list attacks, or issue manual
bans. The moment you bind the listener to a routable address, that protection disappears.
!Set a token before exposing the listener
If you change api.listen to anything other than localhost — for example 0.0.0.0:8080
or a specific interface — configure a token first. An unauthenticated listener on a
routable address lets anyone reach the API, including the manual ban and unban
endpoints.
Setting a token
Add token_env to the api block. It names the environment variable that holds the
token — the secret itself is read from the process environment at startup, never from the
config file:
api:
listen: "0.0.0.0:8080"
token_env: "KAPKAN_API_TOKEN" # token read from this env var, never the file
Then provide the value out of band, for example through your service manager's environment or a secrets file:
export KAPKAN_API_TOKEN="$(openssl rand -hex 32)"
./kapkan -config configs/prod.yaml
Keeping the token in an environment variable rather than the YAML means the config remains safe to commit to git and diff alongside the rest of your configuration.
What it protects
With a token set, every /api/v1 request must carry it in an Authorization header using
the bearer scheme:
GET /api/v1/status HTTP/1.1
Host: kapkan.internal:8080
Authorization: Bearer <token>
The supplied token is compared to the configured one in constant time, so the comparison itself does not leak the secret through timing.
What stays open and what becomes protected:
| Path | With a token set |
|---|---|
GET /api/v1/* | Requires Authorization: Bearer <token> |
POST /api/v1/* | Requires the token and a JSON content type |
GET /metrics | Open — Prometheus scrape stays unauthenticated |
Static UI shell (/) | Open — but the data behind it is not |
The dashboard's static UI shell remains reachable so a browser can load it, but it has no
data of its own: it polls /api/v1, so it prompts for the token and keeps it in
sessionStorage. /metrics likewise stays open for your Prometheus scraper. See the
dashboard page for how the UI handles the token prompt.
CSRF
POST endpoints require Content-Type: application/json in addition to the token. A browser
performing a cross-site request cannot set a custom Authorization header without a CORS
preflight, and a simple form post cannot send the JSON content type. Requiring both — the
token in a request header and the JSON content type — blocks cross-site request forgery
against the manual ban, unban, and config-reload endpoints.
iMinimal by design
Single-token authentication is intentionally minimal: one shared bearer token gates the whole API. Per-user roles and scoped tokens are a later milestone, not part of this release.
Related
- REST API — the endpoints the token protects.
- Dashboard — how the embedded UI prompts for and stores the token.
- Deployment — binding the listener and supplying the token in production.