Block new-user row creation during org read-only mode (auth exemption gap)
Summary
ADR 010: Organization Read-Only Mode requires authentication endpoints to remain available during read_only_initialization/read_only, but is explicit that creating a brand-new users row is NOT exempt and must be blocked:
Creating a brand new
usersrow (first sign-in via SSO/SAML, JIT provisioning, or any other flow that wouldINSERTintousers) while the owning Organization is inread_only_initializationorread_onlyis not exempt and must be blocked. A row created during the read-only window lives only on the source Cell, is not part of the cutover snapshot, and would be lost when traffic moves to the destination Cell.
!242344 (closed) ("MR A") exempts the auth controllers from read-only enforcement via a blanket skip_before_action :enforce_read_only_organization. This correctly keeps sign-in/SSO/JWT available, but the blanket skip on OmniauthCallbacksController also removes enforcement from the new-user INSERT path. Merging MR A on its own therefore opens exactly the data-loss window ADR 010 exists to prevent (highest risk when the read-only window coincides with a Cell-to-Cell cutover).
There is currently no "MR B" in flight to close this gap.
What needs to happen
-
Block new-user-row creation during read-only. When the owning Organization is in
read_only_initialization/read_only, an SSO/SAML/JIT flow that wouldINSERTa newusersrow must be blocked and surface the standard read-only error to the caller, rather than silently creating an orphan row. Likely site:Gitlab::Auth::OAuth::User.- Updates to an existing
usersrow (last_sign_in_at,last_activity_on) remain permitted per the ADR.
- Updates to an existing
-
Cascading-write audit. ADR 010 requires that auth-time writes which cascade into org-owned state (e.g. a Topology Service
user.cell = …update) must still be blocked: the exemption is for the auth controller itself, not downstream services it calls. Audit existing auth-time cascades and ensure they are deferred, idempotent, or routed to the destination Cell.
Implementation options
- Preferred: fold the new-user block into MR A by narrowing the exemption (don't blanket-skip the new-user path) instead of a separate MR. With no MR B in flight, a self-contained MR A can't be merged into the data-loss window by accident.
- Alternative: standalone MR B implementing the
Gitlab::Auth::OAuth::Userblock, merged before (or together with) MR A so the exemption is never relaxed ahead of the block.
References
- ADR 010: https://gitlab.com/gitlab-com/content-sites/handbook/-/blob/main/content/handbook/engineering/architecture/design-documents/organization/decisions/010_organization_read_only_mode.md
- MR A: !242344 (closed)
- Org buildout epic: &20404
- Implementation tracking: #594327 (closed)