Files
git.stella-ops.org/docs/api/gateway/tenant-auth.md
2025-12-24 12:38:14 +02:00

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:

  1. All reserved identity headers (X-StellaOps-*, X-Stella-*, sub, tid, scope, scp, cnf) are stripped from incoming requests.
  2. Downstream identity headers are overwritten from validated JWT claims—clients cannot spoof identity.
  3. For anonymous requests, explicit anonymous identity 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 DPoP header is present the gateway verifies it; interactive clients SHOULD send DPoP, service tokens MAY omit it. A cluster flag Gateway:Auth:RequireDpopForInteractive can make DPoP mandatory later without changing the contract.
  • Scope override header: X-StellaOps-Scopes (or legacy X-Stella-Scopes) is forbidden by default; accepted only when Gateway:Auth:AllowScopeHeader=true for offline/pre-prod bundles. Violation returns ERR_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

  1. Strip reserved headers: Remove all reserved identity headers from the incoming request (see table above).
  2. Validate JWT: Verify signature against offline bundle trust roots; aud must be one of stellaops-web or stellaops-gateway; reject on exp/nbf drift > 60s.
  3. Extract identity from claims:
    • Tenant: stellaops:tenant claim, fallback to tid claim.
    • Project: stellaops:project claim (optional).
    • Actor: sub claim.
    • Scopes: scp claims (individual items) or scope claim (space-separated).
  4. Write downstream headers: Overwrite X-StellaOps-* headers from extracted claims. Legacy X-Stella-* headers written when enabled.
  5. Anonymous handling: If unauthenticated and AllowAnonymous=true, set explicit anonymous identity (X-StellaOps-Actor: anonymous, empty scopes).
  6. RBAC: Check required scopes per route (matrix below). Missing scope → ERR_SCOPE_MISMATCH (403).
  7. 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) with reason + trace_id.
  8. 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:read for GET, risk:write for POST/PUT; severity events additionally require notify:emit.
  • /vuln/*vuln:read for GET, vuln:write for mutations; exports require vuln:export.
  • /signals/*signals:read (GET) / signals:write (write APIs).
  • /policy/* simulation/abac → policy:simulate (read) or policy:abac (overlay hooks).
  • /vex/consensus*vex:read (stream/read) or vex:write when 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" }.

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"
}