[C3] Control-plane credential & impersonation model (masquerade as any user)
closedGoal
Answer with code + ADR: what credential does the control plane hold, exactly what can it do on behalf of arbitrary users, and how? This is the masquerade question. Two distinct needs must be separated and tested independently:
- Provisioning authority —
Registry/seton Account/Domain objects (server-side admin operations; no user context needed). - User-context operations — JMAP calls that must execute as a specific user (initial mailbox setup, applying
com.sovrn.mail.settingslike vacation/forwarding via user-scope JMAP methods, support/debugging). Determine the correct Stalwart mechanism:Permission::Impersonatevs master-user impersonation separator (crates/common/src/auth/authentication.rs) vs issuing short-lived per-user credentials.
Context: docs/02-identity-and-auth.md §4/§6, docs/03-provisioning.md §6, docs/05-stalwart-integration.md §2–§3, docs/08-security-compliance.md §1/§3.
Tasks
- [ ] Provision least-privilege service role in harness:
SysAccountCreate/Update/Destroy,SysDomainCreate,ActionReloadSettings,Impersonate; verify each permission is individually required (negative tests) - [ ] Empirically test Impersonate: user-context JMAP call (e.g.,
VacationResponse/set) executed as another principal via service account; document exact request shape - [ ] Test master-user separator alternative; compare semantics (audit trail, scope, cache behaviour)
- [ ] Secret-store interface (
internal/store/secrets): file provider for dev; KMS interface stub; service credential never in env/config files - [ ] Audit-trail check: what does Stalwart log for impersonated calls (attribution of actor vs subject)
- [ ] ADR
docs/adr/0001-impersonation-model.md: chosen mechanism, least-privilege permission list, rotation story
Acceptance criteria
- Negative tests prove the role cannot: create tenants, change settings without Action phase, read other tenants’ objects
- One user-context operation demonstrably executes as an arbitrary user with correct attribution in logs
- ADR merged; design docs 02/05 updated if findings contradict them
Risks this should surface
- Impersonation may not work over Registry/JMAP the way source reading suggests — or may carry hidden audit/licensing implications
- Permission granularity gaps forcing broader grants than acceptable (escalate to design-doc revision)
2 Comments
Verified mechanics (pre-ADR evidence) — 2026-08-24
Traced in source @ rev 2add611e and partially exercised live during C1 (see ccd3c37 comment c7cfd93). The ADR this issue produces should start from these facts, not hypotheses.
Credential inventory (three distinct secrets)
STALWART_RECOVERY_ADMIN=user:pass)AccessToken::new_admin()(crates/common/src/auth/authentication.rs:90–127). Works in bootstrap mode AND normal mode; works even when the registry/data store is broken (break-glass). Dev harness persists it to/data/service-secretchmod 600 (nix/scripts/serve-stalwart.sh).admin@<domain>UserRoles::Admin, random 16-char alphanumeric secret hashed into registryx:Bootstrap/setwhen directory is internal; credentials returned ONLY in that response’supdatedcallback (crates/jmap/src/registry/mapping/bootstrap.rs:476–505). Our harness captures them to/data/admin-credentials.txt.ImpersonateMasquerade mechanics — VERIFIED
%, not*:UsernameParts::newsplits on%(authentication.rs:565–596). Wire format:<master>%<user>@<domain>e.g.sovrn-admin%[email protected].account_id_from_email(target)→access_token(...)).token.assert_has_permissions(&[Permission::Impersonate, Permission::Authenticate])(authentication.rs:261–263). Admin role carries Impersonate by default; our scoped role must include it deliberately.AuthenticateWithAliason the token (~line 253).What actually needs masquerade
Registry/setof Domain/Account/quotas/aliases is server-side administration under the service account.com.sovrn.mail.settingsrecords (vacation viaVacationResponse/set, forwarding/sieve edits, initial folder setup if done in user context).Open questions for the ADR
crates/jmap/src/registry/mapping/principal.rs:102). Likely answer: keep the service account on an internal-directory domain, or use the recovery admin as the operator credential in OIDC-mode deployments. Must be tested empirically.Details = master_address; confirm full request-level audit availability in OSS).Re-scoped: impersonation dropped — 2026-08-26
Design session decided the control plane does NOT need masquerade for the v1 mail-flow. Grounds:
The only remaining impersonation use case — applying per-user settings (vacation/forwarding/sieve) that lack a Registry admin surface — is deferred. Reopen (or file a new issue) if com.sovrn.mail.settings application becomes a requirement.
Pre-ADR source findings stay valid and preserved here for that future case: - Separator is %, wire format% (NOT % as an earlier note claimed — UsernameParts::new fills account from text before % and master_user from text after, authentication.rs:566-599).
- Enforcement: assert_has_permissions([Impersonate, Authenticate]); app passwords cannot impersonate.
- Audit event logs AccountName = target, Details = master.
Work continues under C4 (a589d34), C5 (c1c7e19), C6 (0d541aa).