Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
- Introduced `ui_bench_driver.mjs` to read scenarios and fixture manifest, generating a deterministic run plan. - Created `ui_bench_plan.md` outlining the purpose, scope, and next steps for the benchmark. - Added `ui_bench_scenarios.json` containing various scenarios for graph UI interactions. - Implemented tests for CLI commands, ensuring bundle verification and telemetry defaults. - Developed schemas for orchestrator components, including replay manifests and event envelopes. - Added mock API for risk management, including listing and statistics functionalities. - Implemented models for risk profiles and query options to support the new API.
5.0 KiB
5.0 KiB
Gateway Tenant Auth & ABAC Contract (Web V)
Status
- Final v1.0 (2025-12-01); aligns with Policy Guild checkpoint for Sprint 0216.
Decisions (2025-12-01)
- Proof-of-possession: DPoP is optional for Web V. If a
DPoPheader is present the gateway verifies it; interactive clients SHOULD send DPoP, service tokens MAY omit it. A cluster flagGateway:Auth:RequireDpopForInteractivecan make DPoP mandatory later without changing the contract. - Scope override header:
X-Stella-Scopesis accepted only in pre-prod/offline bundles or whenGateway:Auth:AllowScopeHeader=true; otherwise the request is rejected withERR_SCOPE_HEADER_FORBIDDEN. - ABAC overlay: evaluated on every tenant-scoped route after RBAC success; failures are hard denies (no fallback). Attribute sources are frozen for Web V as listed below to keep determism.
Scope
- Gateway header/claim contract for tenant activation and scope validation across Web V endpoints.
- ABAC overlay hooks with Policy Engine (attributes, evaluation order, failure modes).
- Audit emission requirements for auth decisions (RBAC + ABAC).
Header & Claim Inputs
| Name | Required | Notes |
|---|---|---|
Authorization: Bearer <jwt> |
Yes | RS256/ES256; claims: iss, sub, aud, exp, iat, nbf, jti, optional scp (space-delimited), ten (tenant). DPoP proof verified when DPoP header present. |
DPoP |
Cond. | Proof-of-possession JWS for interactive clients; validated against htm/htu and access token jti. Ignored for service tokens when absent. |
X-Stella-Tenant |
Yes | Tenant slug/UUID; must equal ten claim when provided. Missing or mismatch → ERR_TENANT_MISMATCH (400). |
X-Stella-Project |
Cond. | Required for project-scoped routes; otherwise optional. |
X-Stella-Scopes |
Cond. | Only honored when Gateway:Auth:AllowScopeHeader=true; rejected with 403 otherwise. Value is space-delimited scopes. |
X-Stella-Trace-Id |
Optional | If absent the gateway issues a ULID trace id and propagates downstream. |
X-Request-Id |
Optional | Echoed for idempotency diagnostics and response envelopes. |
Processing Rules
- Validate JWT signature against offline bundle trust roots;
audmust be one ofstellaops-weborstellaops-gateway; reject onexp/nbfdrift > 60s. - Resolve tenant: prefer
X-Stella-Tenant, otherwisetenclaim. Any mismatch →ERR_TENANT_MISMATCH(400). - Resolve project: from
X-Stella-Projectwhen route is project-scoped; otherwise null. - Build scope set: start from
scpclaim; ifX-Stella-Scopesis allowed and present, replace the set with its value. - RBAC: check required scopes per route (matrix below). Missing scope →
ERR_SCOPE_MISMATCH(403). - ABAC overlay:
- Attributes:
subject,roles,org,tenant_id,project_id, route vars (e.g.,finding_id,policy_id), and request body keys explicitly listed in the route contract. - Order: RBAC allow → ABAC evaluate → deny overrides → allow.
- Fail closed: on evaluation error or missing attributes return
ERR_ABAC_DENY(403) withreason+trace_id.
- Attributes:
- Determinism: tenant header is mandatory; anonymous/implicit tenants are not allowed. Error codes are stable and surfaced in the response envelope.
Route Scope Matrix (Web V)
/risk/*→risk:readfor GET,risk:writefor POST/PUT; severity events additionally requirenotify:emit./vuln/*→vuln:readfor GET,vuln:writefor mutations; exports requirevuln:export./signals/*→signals:read(GET) /signals:write(write APIs)./policy/*simulation/abac →policy:simulate(read) orpolicy:abac(overlay hooks)./vex/consensus*→vex:read(stream/read) orvex:writewhen mutating cache./audit/decisions,/tenant/*→tenant:admin.- Gateway health/info endpoints remain unauthenticated but include
trace_id.
Outputs
- Success: downstream context includes
tenant_id,project_id,subject,scopes,abac_result,trace_id,request_id. - Failure envelope (deterministic):
- 401:
ERR_TOKEN_INVALID,ERR_TOKEN_EXPIRED,ERR_DPOP_INVALID. - 400:
ERR_TENANT_MISSING,ERR_TENANT_MISMATCH. - 403:
ERR_SCOPE_MISMATCH,ERR_SCOPE_HEADER_FORBIDDEN,ERR_ABAC_DENY. Body:{ "error": {"code": "ERR_SCOPE_MISMATCH", "message": "scope risk:read required"}, "trace_id": "01HXYZ...", "request_id": "abc" }.
- 401:
Audit & Telemetry
- Emit DSSE-wrapped audit record:
{ tenant_id, project_id, subject, scopes, decision, reason_code, trace_id, request_id, route, ts_utc }. - Counters:
gateway.auth.success,gateway.auth.denied,gateway.auth.abac_denied,gateway.auth.tenant_missing, labeled by route and tenant.
Examples
Successful read
curl -H "Authorization: Bearer $TOKEN" \
-H "DPoP: $PROOF" \
-H "X-Stella-Tenant: acme-tenant" \
-H "X-Stella-Trace-Id: 01HXYZABCD1234567890" \
https://gateway.stellaops.local/risk/status
Scope/ABAC deny
{
"error": {"code": "ERR_ABAC_DENY", "message": "project scope mismatch"},
"trace_id": "01HXYZABCD1234567890",
"request_id": "req-77c4"
}