Kapkandocs
GitHub

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:

PathWith a token set
GET /api/v1/*Requires Authorization: Bearer <token>
POST /api/v1/*Requires the token and a JSON content type
GET /metricsOpen — 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.

  • 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.