02 — Identity Model
The principals BitVault must authenticate, authorize, and isolate: tenants, organizations, teams, users, service accounts, and API keys — plus the anonymous public-link holder. Getting this hierarchy right is the prerequisite for everything in 03–06.
1. The hierarchy
flowchart TB
classDef b fill:#fecaca,stroke:#b91c1c,color:#111827;
classDef o fill:#fde68a,stroke:#b45309,color:#111827;
classDef u fill:#bbf7d0,stroke:#15803d,color:#111827;
classDef c fill:#c7d2fe,stroke:#3730a3,color:#111827;
T["TENANT — the hard isolation boundary ([05])"]:::b
T --> O["Organization — top admin/billing entity (often 1:1 with tenant)"]:::o
O --> Tm["Teams / Groups — membership units"]:::o
Tm --> U["Users — human principals"]:::u
Tm --> SA["Service Accounts — machine principals"]:::u
U --> K1["API key (scoped, hashed)"]:::c
SA --> K2["API key (scoped, hashed)"]:::c
SA --> W["workload OIDC (mTLS/JWT)"]:::c
ext["Anonymous public-link holder — capability bearer ([06])"]:::c
| Concept | Definition | Security role |
|---|---|---|
| Tenant | one customer’s isolated universe | the hard isolation boundary (05); every owned row carries tenant_id |
| Organization | the top administrative/billing entity within a tenant (usually 1:1; a reseller tenant may host several) | admin scope; the unit SSO/SCIM bind to |
| Team / Group | a collection of members | the subject of ReBAC role grants (04) |
| User | a human member | interactive principal; MFA-capable (03) |
| Service account | a non-human member owned by an org/team | automation principal; no interactive login |
| API key | a credential, not a principal | authenticates a user or service account with a scope |
| Anonymous | holder of a share/presigned token | a capability, scoped to one resource (06) |
Tenant ≠ Organization (on purpose). The tenant is the security/isolation boundary; the organization is an administrative construct inside it. Keeping them distinct lets us support reseller/holding structures later without weakening the isolation boundary, which always stays at the tenant.
2. Service accounts (machine principals)
- Owned by an org/team; have roles/scopes like users but no interactive login, no MFA, no human session — they exist to be used by code.
- Authenticate via an API key (§3) or workload OIDC (a short-lived JWT/mTLS identity from the runtime — preferred for in-cluster/cloud workloads, no static secret, ADR-0030).
- Lifecycle: created by an admin, least-privilege by default, rotatable, disable/enable, deletable; all actions attributed in the audit log (07) to the service account (not a human).
- Cannot escalate: a service account can’t grant itself roles or create more- privileged accounts (deny-by-default, 04).
3. API keys (credentials)
Design (decision: ADR-0035):
| Property | Design |
|---|---|
| Format | bv_<env>_<keyid>_<secret> — a public prefix/keyid (for lookup + display) + a high-entropy secret (≥256-bit) |
| Storage | hashed at rest (the keyid indexes the row; the secret is verified by hash + constant-time compare) — never stored or logged in plaintext |
| Display | shown once at creation; unrecoverable afterward |
| Scope | bound to a principal (user/SA) + scoped to actions and resource prefixes (least privilege) |
| Lifetime | optional expiry; rotatable (overlap window) and revocable instantly |
| Audit | creation/use/rotation/revocation logged; last-used tracked for stale-key cleanup |
Key risks & defenses: leakage to Git/logs → secret scanning in CI (platform/07) + short scope + rotation; an API key is not a session → it grants only API scope, no MFA-gated or admin-destructive actions without step-up (03). Prefer workload OIDC over long-lived keys wherever the runtime supports it.
4. External / guest principals
Sharing creates limited principals: guest users (external collaborators with a constrained role on specific resources) and anonymous public-link holders (a pure capability, no account). Both are sandboxed to exactly what’s shared — never the tenant (06).
5. Identity lifecycle (Joiner / Mover / Leaver)
flowchart LR
classDef a fill:#fde68a,stroke:#b45309,color:#111827;
classDef d fill:#fecaca,stroke:#b91c1c,color:#111827;
j["Joiner: SCIM provision → roles by group ([03])"]:::a --> m["Mover: group change → roles auto-update"]:::a
m --> l["Leaver: SCIM deprovision → revoke sessions, keys, grants"]:::d
l --> orphan["sweep orphaned API keys / service accounts"]:::d
Deprovisioning is the most-skipped, highest-risk step: a leaver’s lingering session, API key, or service-account ownership is a breach waiting to happen. SCIM automates JML; sessions + keys are revoked on deactivation; orphaned service accounts are reassigned or disabled (03 §SSO/SCIM, 11 compliance).
6. Trust relationships (who may act on whom)
- A principal acts only within its tenant (isolation, 05).
- Admins manage users/SAs/keys within their org; never cross-tenant.
- Service accounts are managed by humans, act as themselves (attributable).
- No principal can read another principal’s secret (keys hashed; tokens opaque).
This model is the substrate the rest of the review enforces.