Tenancy Overview
Stella Ops is designed for strict multi-tenancy. Tenancy is an explicit input to requests, storage, and exported evidence so decisions remain auditable and replayable (including in air-gapped deployments). This overview is the entry point for engineers and operators who need to understand how the tenant boundary is identified, isolated, and enforced across the platform.
Tenant Identity
- A tenant is the primary isolation boundary for data, policies, issuers/trust settings, and workflow objects (exceptions, approvals, exports).
- Tenant identity travels in the access token, not in client-supplied headers. The canonical tenant claim is
stellaops:tenant(StellaOpsClaimTypes.Tenant). The Gateway derives this from the validated JWT and resolvers accept the alias claimstenant_idandtenant, plus the legacytid, in that priority order (StellaOpsTenantResolver.TryResolveTenant). - A token may also carry
stellaops:allowed_tenants(StellaOpsClaimTypes.AllowedTenants), a space-separated set bounding which tenants a subject/client may act for. Forclient_credentialsgrants the active tenant is selected against the client’s allowed-tenant set (ClientCredentialsHandlers). - The
X-StellaOps-TenantIdheader (StellaOpsHttpHeaderNames.Tenant) is a client request override, not an authoritative tenancy source. At the Gateway it is stripped on ingress and re-emitted from the validatedstellaops:tenantclaim, so a client cannot spoof tenancy (seedocs/api/gateway/tenant-auth.md). Downstream services MUST resolve tenancy fromIStellaOpsTenantAccessor/ the claim and not from raw header reads (enforced byTenantHeaderCallSiteConformanceTests).
Optional sub-scoping within a tenant is supported but does not replace the tenant boundary:
- Project — the
stellaops:projectclaim (StellaOpsClaimTypes.Project), optionally surfaced via theX-Stella-Projectheader (X-StellaOps-Projectat the Gateway). Resolved alongside the tenant byStellaOpsTenantResolver.ResolveProject/PlatformRequestContextResolver. - Environment / ownership ABAC — attribute claims
stellaops:attr:env,stellaops:attr:owner, andstellaops:attr:business_tier(Vuln Explorer visibility filters), evaluated by the Gateway’s ABAC overlay after RBAC succeeds.
Note: there is no
workspacetenant sub-scope claim today; “Policy Studio workspaces” are an authoring concept gated by thepolicy:authorscope, not a tenancy boundary.
Isolation Guarantees
StellaOps aims for defense-in-depth isolation:
- Database isolation: each service owns its schema; tenant scoping is enforced on every query, typically via
tenant_idcolumns and application-layer filtering. Some schemas additionally enforce PostgreSQL Row-Level Security — e.g. the Policy Engine enables RLS across thepolicy.*schema and scopes rows from theapp.tenant_idsession GUC set per request (set_config); seeTenantContextConstantsandPolicy.Persistence/Migrations/001_initial_schema.sql. - Cache isolation: cache keys and stream partitions are tenant-aware (and may use per-tenant databases in Redis/Valkey-compatible stores when configured).
- Object storage isolation: stored artifacts and evidence bundles are content-addressed and namespaced so tenant boundaries are preserved even when sharing infrastructure.
- Audit boundaries: audit trails are tenant-scoped and exported with tenant identifiers so offline reviewers can verify context.
Enforcement Stack
- Authority issues tokens containing the
stellaops:tenantclaim (and optionalstellaops:allowed_tenants,stellaops:project, andstellaops:attr:*ABAC claims). Requested scopes are filtered againstauthority.clients.allowed_scopesat issuance — theauthority.permissionstable is an RBAC catalog for the Console role editor and is not consulted at token issuance. - Gateway strips reserved identity headers (
X-StellaOps-*,X-Stella-*,sub,tid,scope,scp,cnf) on ingress, validates the JWT, re-emits canonical headers from claims, and signs the result into the identity envelope (X-StellaOps-Identity-Envelope+-Signature); it then enforces RBAC (scope) and the ABAC overlay per route. - Services hydrate
HttpContext.Userfrom the signed envelope (UseIdentityEnvelopeAuthentication()), resolve tenant context viaStellaOpsTenantMiddleware/IStellaOpsTenantAccessor, and reject tenantless requests on tenant-required routes with HTTP 400 (RequireTenant()endpoint filter; error codestenant_missing,tenant_conflict,tenant_invalid_format). Global/system endpoints may proceed without a tenant.
Offline / Air-Gap Considerations
- Offline Kit snapshots and exported evidence bundles include tenant identifiers and deterministic manifests so verification does not rely on online services.
- Cross-tenant exports are not supported by default; when an operator needs cross-tenant reporting, it should be implemented as an explicit, audited workflow.
References
- Scope taxonomy: scopes-and-roles.md (source of truth:
StellaOps.Auth.Abstractions/StellaOpsScopes.cs) - Gateway tenant auth + ABAC contract: …/api/gateway/tenant-auth.md
- Identity envelope middleware: …/modules/router/IDENTITY_ENVELOPE_MIDDLEWARE.md
- Canonical claim/header names (source):
StellaOps.Auth.Abstractions/StellaOpsClaimTypes.cs,StellaOpsHttpHeaderNames.cs - Tenant resolution (source):
StellaOps.Auth.ServerIntegration/Tenancy/StellaOpsTenantResolver.cs,StellaOpsTenantMiddleware.cs
