Skip to content

acl: domain drift — SSO selectors match claims, quiltx resolves against stored emails #123

Description

@drernie

Summary

When an identity provider renames a domain, the address in a user's SSO claims diverges from the address the registry has stored for that account. quiltx writes and validates SSO selectors against stored addresses, while the registry evaluates them against claims. After a rename the two describe the same person with different strings, and every selector written from the stale side silently stops matching. Nothing in quiltx can currently detect this.

The mechanism

Three properties of the registry, all observable from outside it, combine to produce the drift:

  1. SSO identity binds to the provider's stable subject identifier, not to the email. The first SSO login for an account records a link between that account and the provider's immutable subject id. Every subsequent login resolves through that link. The email claim is consulted only when no link exists yet.

  2. The stored email is a first-login snapshot and is never refreshed. Because the link resolves first, later logins never re-read the email claim, so the account keeps whatever address it carried when it was created. Nothing writes it back.

  3. SSO role mappings are evaluated against the claims. sso.email and sso.hd selectors are matched against the token the provider sends, not against the stored account record.

Taken together: after an IdP domain rename, one account has two addresses — the claim address (new domain) and the stored address (old domain) — and each half of the system sees only one of them. Nothing fails loudly. Users keep signing in and landing in the correct account, because (1) resolves them by subject id. Only the selectors quietly stop matching.

The same mechanism applies to an individual user rename; a domain rename is just the case that hits every account at once.

How this breaks quiltx

a. sso.email written from stored addresses matches nobody. Given local@old.example stored and local@new.example in the claims, a selector listing the former never fires. The grant simply does not happen. quiltx cannot observe claims, so no warning is possible today.

b. sso.hd for the old domain is dead, and the new domain must be declared instead. A config predating the rename declares the old domain across every role; those selectors match nobody. This is invisible because the roles still exist and users still hold whatever was assigned at their last pre-rename login — access looks correct right up until someone new signs in, or an existing user's roles are recomputed.

c. --yaml capture round-trips the stale side. Capture reads stored addresses and emits them as config. Replaying that capture writes selectors in an address space the registry will never match. So the tool's own capture/replay cycle manufactures dead config once a rename has happened, and the output looks entirely plausible.

d. Roster and users: resolution operate in stored-address space. _roster_address_is_held and _resolve_configured_user both index accounts by stored email. A roster written correctly — in claim space, new domain — therefore finds no account for a person who demonstrably has one, and pushes them toward creation.

This is the sharp edge: the same address is simultaneously the only correct value for the selector and an unknown value for resolution. An operator cannot satisfy both halves with one string.

e. The rename guard fires but names a remedy quiltx cannot perform. A domain rename preserves the local part, so _accounts_sharing_a_local_part catches it and refuses to create — the right call, and it prevents the duplicate. But its message tells the operator to set the existing account's email, and quiltx never calls set_email (#117). So the guard converts a silent duplication into a hard stop with a manual, out-of-band remedy.

Why a clean fix may not exist

  • quiltx cannot see claims. Nothing in the admin API exposes what the IdP sends, so the authoritative value for selector matching is not readable by the tool that writes selectors.
  • The stored email is not authoritative for matching, yet it is the only identity field quiltx can read. Every resolution path is built on it.
  • Realigning the stored address would make both halves agree, which is the real fix — but it is a write quiltx does not perform (acl: declare a changed sso.email address so a rename can be reconciled #117), and it is only safe once an operator confirms the rename actually happened.

So this issue is filed for the mechanism, not for a proposed patch. Recording it matters because the failure is silent in both directions and the tool currently reads as though it is working.

Possible mitigations — detection, not repair

  1. Domain census warning. Compare the set of domains appearing in sso.hd / sso.email against the domains present across registry accounts. A declared domain that no account uses, or an account domain no selector mentions, is a strong drift signal. This needs no new data — quiltx already reads every account.
  2. Extend the local-part check to selector validation, not just creation. An sso.email address matching no account, whose local part matches an account at a different domain, is probably drift rather than a new person. _accounts_sharing_a_local_part already computes exactly this.
  3. Document that selectors are claim-space while captures are stored-space, and that the two are not interchangeable after a rename. Cheapest of the three, and possibly the highest value, since it is the assumption that makes (c) dangerous.
  4. Mark capture output as needing review of selector addresses, so a replayed capture is not mistaken for a verified config.

Mitigation 1 would have surfaced a real instance: a config whose every role declared the old domain in sso.hd, against a registry in which no account's claims carried that domain any longer.

Not addressed

Repair. Realigning stored addresses to claim addresses is #117's territory; this issue is about detecting that the two have diverged at all, and about the fact that captures propagate the divergence.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions