prerelease Prerelease stable Latest
Kumoss
prerelease Prerelease stable Latest

Authorization sidecar API

The per-resource cloud access decision contract. Kumoss consults exactly one of its endpoints, from exactly one core route, and ships with it disabled.

The authorization service answers may this user operate on this cloud resource? — a question about a subscription, a project, or an account, asked before Kumoss runs anything against a live environment.

Contract: contracts/openapi/authz.v1.yaml. Kumoss bundles no implementation of it: authorization is an optional extension point you implement yourself and point services.authz.endpoint at. Bearer token: the value of the variable named by services.authz.token_env, KUMOSS_AUTHZ_TOKEN by default. The service is optional and ships disabled (services.authz.enabled: false, endpoint: "").

Kumoss’s own roles do not live here. The operation roles (developer < devops) and panel roles (viewer < editor < admin) that decide what a signed-in user can do are rows in the core database, managed from the admin panel. The contract’s user and role endpoints describe an implementation’s own store, which has no effect on any of that. See Roles and permissions.

What the core actually calls

The contract describes seven paths. The core calls one of them.

POST /api/v1/auth/authorize — and nothing else in the core — forwards to POST /v1/check. The other five /v1 paths exist for callers of this service directly, or for an implementation that keeps a role store of its own, to administer that service’s own users and roles. The generated client contains methods for them; nothing in the core invokes them.

When services.authz.enabled is false, the shipped default, the core does not call out at all and answers authorized. When it is enabled, the core never fails open:

Situation What the core answers

Sidecar unreachable, or the call times out

502 or 504

Sidecar returns a 4xx or 5xx

502

Sidecar returns 200

The decision it computed, passed through

A 422 from the sidecar is therefore visible at the browser as a 502, which matters for one specific shape of request:

A long IaC path produces a 502. The web application sends the repository URL as project and the IaC path as environment. The contract caps environment at 32 characters, so a longer path fails schema validation with 422 — which the core, refusing to fail open, surfaces as 502. This only bites when services.authz.enabled is true.

Endpoints

Endpoint What it does

POST /v1/check

Decides whether a user may operate on a named cloud resource. The only endpoint Kumoss uses.

GET /v1/users/me

Returns the calling user’s record and roles, creating an empty record if none exists.

GET /v1/roles

Lists the role names the service knows.

GET /v1/users

Lists all known users. Caller must hold the admin role.

POST /v1/users/{user_id}/roles

Assigns a role. 204 on success, 404 if the role is not in the catalogue. Caller must hold admin.

DELETE /v1/users/{user_id}/roles/{role}

Revokes a role. 204 on success, and also 204 if the role was already absent. Caller must hold admin.

GET /healthz

Liveness probe, answers {"status":"ok"}.

Asserted identity

The administrative endpoints take identity from headers, not from a token this service validates. The two are declared unevenly:

  • X-User-Id — caller-asserted user identifier. Optional. Declared on four operations: GET /v1/users/me, GET /v1/users, POST /v1/users/{user_id}/roles, and DELETE /v1/users/{user_id}/roles/{role}. The contract advertises no maximum length, deliberately, so that a boundary caller does not get a hard 4xx; an implementation may clamp it instead.

  • X-User-Email — caller-asserted email. Optional. Declared on GET /v1/users/me alone.

GET /v1/roles declares no parameters at all and therefore takes neither header.

OIDC token validation lives entirely in the core, so this service makes no assumption about the upstream identity provider and does not verify the assertion. That is only safe because the service is reachable from the core alone. The core sends neither header on any live path — they matter only if you call these endpoints yourself.

POST /v1/check

{
  "user_id": "someone@example.com",
  "cloud": "azure",
  "project": "my-project",
  "environment": "dev"
}

cloud and project are required; user_id and environment are nullable. additionalProperties is false.

Field Limits Meaning

user_id

1-1024 chars, nullable

The user to check. Null for an anonymous flow. The core sends the caller’s email, falling back to the token subject when there is no email.

cloud

1-32 chars, required

azure, gcp, and so on.

project

1-256 chars, required

The resource being checked.

environment

1-32 chars, nullable

Deployment dimension — dev, pro.

The response:

{
  "authorized": true,
  "portal_url": null,
  "reason": null
}

A policy-backed implementation puts its own explanation in reason and a console deep link in portal_url; both may be null.

Field Meaning

authorized

Required boolean. The decision.

portal_url

Nullable, ≤2048 chars. A link to the cloud console for the resource. Null when the implementation has no portal-URL convention. The core passes it through as portalUrl.

reason

Nullable, ≤1024 chars. Human-readable explanation, useful in the UI and in debugging.

A denial is still 200: the decision was computed, and it was "no". The error statuses are for cases where no decision could be reached.

Status Meaning

200

A decision was computed, granted or denied.

400

The body could not be parsed at the transport layer.

401

Missing or invalid bearer token. Carries a WWW-Authenticate: Bearer challenge.

422

Well-formed JSON that failed schema validation.

502

The downstream identity or cloud backend returned an error.

Conformance

The implementation-agnostic Schemathesis suite at contracts/conformance/authz/ runs against any implementation of this contract:

cd contracts/conformance/authz
uv sync
uv run pytest \
  --service-url=https://authz.your.example \
  --service-token=$YOUR_TOKEN

--service-token is needed only if the implementation enforces authentication.

The suite checks schema conformance of the responses and that invalid bodies do not produce undocumented 5xx answers. It cannot check that the decisions are correct — a permissive implementation that grants everything passes it. Correctness of the policy is your own test suite’s job.

Next steps