6.8 KiB
6.8 KiB
Gateway Tenant Auth & ABAC Contract (Web V)
Status
- v1.1 (2025-12-24); updated for identity header hardening (Sprint 8100.0011.0002).
- v1.0 (2025-12-01); aligns with Policy Guild checkpoint for Sprint 0216.
Header Naming Migration
As of Sprint 8100.0011.0002, the Gateway uses canonical X-StellaOps-* headers:
X-StellaOps-Tenant,X-StellaOps-Project,X-StellaOps-Actor,X-StellaOps-Scopes
Legacy X-Stella-* headers are emitted when Gateway:Auth:EnableLegacyHeaders=true (default) for backward compatibility. Clients should migrate to X-StellaOps-* headers.
Security: Identity Header Hardening (v1.1)
CRITICAL: As of Sprint 8100.0011.0002, the Gateway enforces a strip-and-overwrite policy for identity headers:
- All reserved identity headers (
X-StellaOps-*,X-Stella-*,sub,tid,scope,scp,cnf) are stripped from incoming requests. - Downstream identity headers are overwritten from validated JWT claims—clients cannot spoof identity.
- For anonymous requests, explicit
anonymousidentity is set to prevent ambiguity.
This replaces the legacy "set-if-missing" behavior which allowed header spoofing.
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-StellaOps-Scopes(or legacyX-Stella-Scopes) is forbidden by default; accepted only whenGateway:Auth:AllowScopeHeader=truefor offline/pre-prod bundles. Violation returnsERR_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/scope, stellaops: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-StellaOps-Trace-Id |
Optional | If absent the gateway issues a ULID trace id and propagates downstream. Legacy: X-Stella-Trace-Id. |
X-Request-Id |
Optional | Echoed for idempotency diagnostics and response envelopes. |
Reserved Identity Headers (Client-Provided Values Ignored)
The following headers are stripped from client requests and overwritten from validated claims:
| Header | Source Claim | Notes |
|---|---|---|
X-StellaOps-Tenant |
stellaops:tenant (fallback: tid) |
Tenant identifier from token claims. |
X-StellaOps-Project |
stellaops:project |
Project identifier (optional). |
X-StellaOps-Actor |
sub |
Subject/actor from token claims. |
X-StellaOps-Scopes |
scp (individual) or scope (space-separated) |
Scopes from token claims, sorted deterministically. |
Legacy X-Stella-* headers are emitted alongside canonical headers when EnableLegacyHeaders=true.
Processing Rules
- Strip reserved headers: Remove all reserved identity headers from the incoming request (see table above).
- Validate JWT: Verify signature against offline bundle trust roots;
audmust be one ofstellaops-weborstellaops-gateway; reject onexp/nbfdrift > 60s. - Extract identity from claims:
- Tenant:
stellaops:tenantclaim, fallback totidclaim. - Project:
stellaops:projectclaim (optional). - Actor:
subclaim. - Scopes:
scpclaims (individual items) orscopeclaim (space-separated).
- Tenant:
- Write downstream headers: Overwrite
X-StellaOps-*headers from extracted claims. LegacyX-Stella-*headers written when enabled. - Anonymous handling: If unauthenticated and
AllowAnonymous=true, set explicit anonymous identity (X-StellaOps-Actor: anonymous, empty scopes). - 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: Identity headers are always overwritten from claims; 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"
}