Skip to content

feat(auth): API authentication with admin and read-only roles - #2

Merged
ritikbansod merged 1 commit into
mainfrom
feat/api-auth
Sep 28, 2026
Merged

ritikbansod merged 1 commit into
mainfrom
feat/api-auth

Conversation

@ritikbansod

Copy link
Copy Markdown
Owner

Implements F1 from the deep analysis — the high-severity finding that the entire API is unauthenticated with allowedOriginPatterns("*") CORS, meaning any web page an operator visits could silently drive destructive endpoints (topic delete, group delete, offset resets) from their browser.

Stacked on #1 (touches WebConfig.java, CHANGELOG.md, README.md there) — merge #1 first.

Design

kview.auth.mode — off by default, so the zero-config localhost story is unchanged (regression-tested by NoAuthByDefaultTest):

Mode Behavior
none (default) API open, permissive dev CORS kept — exactly as v1.0.0.
token Static bearer tokens from KVIEW_AUTH_TOKENS (TOKEN = admin, TOKEN:readonly = read-only).
oidc JWTs from any OIDC issuer (Keycloak/Entra/Okta via spring.security.oauth2.resourceserver.jwt.issuer-uri), optional aud check, admin vs read-only decided by a configurable claim (roles: kview-admin by default); every other valid token is read-only (least privilege).

Role rules (one place, SecurityConfig): reads under /api/** need any role; mutations need admin; POSTs that are semantically reads (**/browse, **/decode, **/encode, **/test) stay available to read-only. Static UI and /actuator/health stay public. 401/403 answers are JSON in the API's existing {error, detail} shape.

CORS: permissive only in none mode; authenticated modes close it unless KVIEW_AUTH_ALLOWED_ORIGINS lists origins — this kills the drive-by-mutation hole at the root.

SSE: EventSource cannot send headers, so ?access_token= is accepted (rewritten to the Authorization header by a filter) — live tail works in both modes.

Clients: web UI prompts for the token on 401 plus a 🔑 top-bar button; CLI takes --token / KVIEW_TOKEN; MCP server takes KVIEW_TOKEN.

Verification

  • New tests (13): token-mode 401/403/200 matrix, unknown-token rejection, read-shaped POSTs allowed for read-only, admin mutations pass, static UI/health public, default-mode regression, JWT role-converter unit tests.
  • Full suite: 43/43 green (./mvnw -B test).
  • Live end-to-end against java -jar with token mode: no token → 401, wrong token → 401, read-only GET → 200, read-only DELETE → 403, admin DELETE → past authorization, UI/health public, and node cli/kview.mjs --token view clusters works against the running server.
  • Not covered: a live OIDC issuer round-trip (unit-tested converter + standard Spring resource-server machinery); manual check recommended against a real Keycloak/Entra before tagging a release.

Implements F1 from the deep analysis — the high-severity finding that the
entire API was unauthenticated with permissive CORS (WebConfig.java).

- kview.auth.mode=none (default): zero-config localhost behavior, exactly as before
- kview.auth.mode=token: static bearer tokens via KVIEW_AUTH_TOKENS
  (TOKEN = admin, TOKEN:readonly = read-only)
- kview.auth.mode=oidc: JWTs from any OIDC issuer (issuer-uri/jwk-set-uri via
  spring.security.oauth2.resourceserver.jwt.*), optional audience check, role
  decided by a configurable claim (default roles: kview-admin)
- roles: reads under /api/** need any role; mutations (POST/PUT/DELETE except
  the read-shaped posts browse/decode/encode/test) need admin; static UI and
  /actuator/health stay public
- CORS stays permissive only in none-mode; authenticated modes close it unless
  KVIEW_AUTH_ALLOWED_ORIGINS lists origins
- SSE clients (EventSource cannot send headers) can pass ?access_token=
- web UI prompts for the token on 401 (top-bar button to set/clear)
- CLI: --token flag / KVIEW_TOKEN env on every call incl. tail
- MCP server: KVIEW_TOKEN env on all tool calls
- tests: 13 new (token-mode 401/403/200 matrix, read-shaped POSTs, public
  static/health, default-mode regression, JWT role converter) — full suite 43/43
- docs: README Authentication section + config rows, SECURITY.md rewrite
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant