Step 2: Create namespace-scoped read-only middleware

Summary

Create 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.

POC approach: This will be part of a single POC MR (does not need to merge to master). Maintenance mode will be toggled via Rails console.

Dependencies

  • Depends on: Step 1 (#591688) — state machine transitions
  • Parallel with: Step 3 (#591690 (closed)) and Step 4 (#591691 (closed))
  • Step 3 (error page) integrates into this middleware for rendering responses

Context

Parent issue: #590009 (closed)

The existing Gitlab::Middleware::ReadOnly (lib/gitlab/middleware/read_only.rb) blocks writes at the instance level. We need a similar mechanism scoped to individual namespaces.

Tasks

  • Create Gitlab::Middleware::NamespaceReadOnly (or extend existing ReadOnly middleware)
  • Implement namespace resolution from request paths:
    • Parse path to extract top-level namespace segment (strip known prefixes like /api/v4/, /groups/, etc.)
    • Use Gitlab::PathRegex.full_namespace_route_regex and route_hash for accurate matching
    • For API routes, extract from route params (:namespace_id, :group_id, :id)
  • Look up root namespace and check effective_state == :maintenance
  • Implement caching strategy for namespace state lookups (request-store + short Redis TTL to avoid per-request DB hits)
  • Return 503 with maintenance message and Retry-After header for blocked requests
  • Reuse existing allowlist pattern from ReadOnly::Controller (internal routes, sessions, sidekiq admin, etc.)
  • Register middleware in the Rack stack
  • Add specs covering: write blocking, read passthrough, allowlisted routes, cache behavior, nested namespace resolution

Key Files

  • lib/gitlab/middleware/read_only.rb (reference pattern)
  • lib/gitlab/middleware/read_only/controller.rb (reference: allowlists, route_hash, error responses)
  • ee/lib/ee/gitlab/middleware/read_only/controller.rb (reference: 503 maintenance response pattern)
  • lib/gitlab/path_regex.rb (namespace path parsing)
  • config/initializers/rack_attack.rb or middleware registration location

Design Decisions

  • Option A (Recommended): New standalone middleware — cleaner separation, doesn't couple with instance-level read-only
  • Option B: Extend existing ReadOnly::Controller#read_only? — simpler but mixes concerns

Effort Estimate

Medium (3-5 days)

Edited by Chen Zhang