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 ( |
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 |
|
Sidecar returns a |
|
Sidecar returns |
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 |
Endpoints
| Endpoint | What it does |
|---|---|
|
Decides whether a user may operate on a named cloud resource. The only endpoint Kumoss uses. |
|
Returns the calling user’s record and roles, creating an empty record if none exists. |
|
Lists the role names the service knows. |
|
Lists all known users. Caller must hold the |
|
Assigns a role. |
|
Revokes a role. |
|
Liveness probe, answers |
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, andDELETE /v1/users/{user_id}/roles/{role}. The contract advertises no maximum length, deliberately, so that a boundary caller does not get a hard4xx; an implementation may clamp it instead. -
X-User-Email— caller-asserted email. Optional. Declared onGET /v1/users/mealone.
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 |
|---|---|---|
|
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. |
|
1-32 chars, required |
|
|
1-256 chars, required |
The resource being checked. |
|
1-32 chars, nullable |
Deployment dimension — |
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 |
|---|---|
|
Required boolean. The decision. |
|
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 |
|
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 |
|---|---|
|
A decision was computed, granted or denied. |
|
The body could not be parsed at the transport layer. |
|
Missing or invalid bearer token. Carries a |
|
Well-formed JSON that failed schema validation. |
|
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
-
Roles and permissions for the roles that actually gate Kumoss.
-
Core API for the one core route that consults this service.
-
the
servicessection of the configuration reference for enabling the sidecar.