102 lines
3.9 KiB
Markdown
102 lines
3.9 KiB
Markdown
# ADR-002: Multi-Tenant Selection With Same API Key
|
|
|
|
**Status:** Accepted
|
|
**Date:** 2026-02-22
|
|
**Sprint:** `SPRINT_20260222_053_DOCS_multi_tenant_same_api_key_contract_baseline.md`
|
|
|
|
## Context
|
|
|
|
Stella Ops must support clients that are assigned to more than one tenant while still using the same API key/client registration. Existing behavior assumes a scalar tenant assignment (`tenant`) and cannot safely select among multiple tenant memberships.
|
|
|
|
Without a canonical contract, modules diverge on claim names, header behavior, default selection, and mismatch handling. This creates cross-tenant leakage risk and migration churn.
|
|
|
|
## Decision
|
|
|
|
Use **one selected tenant per access token**, chosen at token issuance time.
|
|
|
|
### Selected model (accepted)
|
|
|
|
1. Client metadata supports both:
|
|
- `tenant` (scalar compatibility/default tenant)
|
|
- `tenants` (space-delimited assignment set; normalized lowercase, unique, sorted)
|
|
2. Token request may include `tenant=<id>`.
|
|
3. Authority resolves selected tenant deterministically:
|
|
- If `tenant` parameter is present: it must exist in assigned tenants.
|
|
- If no parameter:
|
|
- use scalar `tenant` when configured, otherwise
|
|
- use single-entry `tenants`, otherwise
|
|
- reject as ambiguous.
|
|
4. Issued tokens carry:
|
|
- `stellaops:tenant` (selected tenant)
|
|
- `stellaops:allowed_tenants` (space-delimited assigned set, optional)
|
|
5. Gateway and services continue operating with one effective tenant per request.
|
|
|
|
### Fallback model (rejected for default path)
|
|
|
|
Multi-tenant token + per-request header override (`X-StellaOps-Tenant`) as primary selector.
|
|
|
|
Reason rejected:
|
|
- Increases header spoofing and token confusion risk.
|
|
- Creates inconsistent downstream behavior where services interpret tenant from different sources.
|
|
- Expands change surface across all modules immediately.
|
|
|
|
## Canonical Contract
|
|
|
|
### Claims
|
|
|
|
- `stellaops:tenant`: selected tenant for this token.
|
|
- `stellaops:allowed_tenants`: assigned tenant set (space-delimited, sorted).
|
|
|
|
### Client metadata
|
|
|
|
- `tenant`: scalar assignment / deterministic default.
|
|
- `tenants`: assigned set (space-delimited).
|
|
|
|
### Headers
|
|
|
|
- Canonical tenant header: `X-StellaOps-Tenant`.
|
|
- Legacy compatibility header: `X-Stella-Tenant` (bounded migration use only).
|
|
|
|
### Error semantics
|
|
|
|
- Requested tenant not assigned: reject `invalid_request`.
|
|
- Missing tenant for tenant-required scope: reject `invalid_client` / `invalid_request` depending on grant validation stage.
|
|
- Ambiguous tenant selection (multi-assigned, no default, no request): reject `invalid_request`.
|
|
- Token tenant not in client assignments during validation: reject `invalid_token`.
|
|
|
|
## Threat Model
|
|
|
|
### Header spoofing
|
|
|
|
Risk: caller supplies tenant headers to escalate into another tenant.
|
|
Mitigation: gateway strips inbound identity headers and rewrites from validated claims.
|
|
|
|
### Token confusion
|
|
|
|
Risk: token tenant differs from issuing client assignment or persisted token document.
|
|
Mitigation: validation enforces principal/document/client assignment consistency.
|
|
|
|
### Cross-tenant leakage
|
|
|
|
Risk: silent default tenant fallback routes tenant-scoped requests incorrectly.
|
|
Mitigation: remove authenticated `"default"` tenant fallback and fail ambiguous selections.
|
|
|
|
## Consequences
|
|
|
|
### Positive
|
|
|
|
- Keeps downstream service model stable: one tenant per token/request.
|
|
- Enables same-key multi-tenant clients without global API contract break.
|
|
- Supports UI hydration with explicit assigned tenant set.
|
|
|
|
### Tradeoff
|
|
|
|
- Tenant switching requires requesting a token for the target tenant.
|
|
- Multi-assigned clients without explicit default must send `tenant` during token issuance.
|
|
|
|
## References
|
|
|
|
- `docs/implplan/SPRINT_20260222_053_DOCS_multi_tenant_same_api_key_contract_baseline.md`
|
|
- `docs/implplan/SPRINT_20260222_054_Authority_same_key_multi_tenant_token_selection.md`
|
|
- `docs/implplan/SPRINT_20260222_055_Router_tenant_header_enforcement_and_selection_flow.md`
|