Add namespace-scoped read-only middleware (Step 2)
What does this MR do and why?
This MR implements Step 2 of the POC for read-only mode for top-level groups, as tracked in #590009 (closed) (task #591689 (closed)).
It adds Gitlab::Middleware::NamespaceReadOnly, a Rack middleware that blocks write HTTP requests (POST/PATCH/PUT/DELETE) for namespaces in maintenance state. This is the core enforcement mechanism for TLG read-only mode on web and REST API routes.
Depends on: !226399 (closed) (Step 1 - state machine transitions)
Changes
Middleware (lib/gitlab/middleware/namespace_read_only.rb)
- Entry point that delegates to
Controller, mirroring the existingGitlab::Middleware::ReadOnlypattern
Controller (lib/gitlab/middleware/namespace_read_only/controller.rb)
- Blocks
POST/PATCH/PUT/DELETErequests targeting namespaces in:maintenancestate -
Namespace resolution: Extracts top-level namespace from request path using
route_hash(for accurate Rails route matching) with fallback to path-based extraction (stripping known prefixes like/api/v4/,/groups/, etc.) -
Allowlist: Reuses the same patterns as the existing
ReadOnlymiddleware (internal API routes, sessions, sidekiq admin, GraphQL queries) -
Response handling:
- JSON requests: returns
503withRetry-After: 900header and maintenance message - HTML requests: sets flash alert and redirects to last visited URL (301)
- JSON requests: returns
-
Caching: Uses
RequestStoreto cache namespace lookups within the same request cycle
Registration (config/application.rb)
- Added
require_dependencyfor the new middleware - Inserted after
Gitlab::Middleware::ReadOnlyin the Rack stack
Design decisions
-
Standalone middleware (Option A from the issue) for clean separation from instance-level read-only. Does not couple with or modify the existing
ReadOnlymiddleware. -
GraphQL queries are allowlisted intentionally. All GraphQL requests are POST to
/api/graphql, so the middleware cannot determine the target namespace from the URL. GraphQL mutation enforcement is Step 4 (#591691 (closed)).
How to set up and validate locally
- Apply Step 1 changes first (!226399 (closed))
- Open Rails console
- Put a namespace in maintenance:
ns = Namespace.find_by_path('my-group'); ns.start_maintenance!(transition_user: User.first) - Try a write request to that namespace (e.g., create an issue) - should get blocked with maintenance message
- Try a read request (e.g., view the group page) - should work normally
- Exit maintenance:
ns.complete_maintenance! - Write requests should work again
Related issues
- Parent issue: #590009 (closed)
- Task: #591689 (closed)
- Depends on: !226399 (closed) (Step 1)
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.