Draft: Forward RubyGems package requests to rubygems.org

What

Forwards RubyGems per-gem requests to rubygems.org when the rubygems_package_requests_forwarding setting (added in !244621 (merged)) is enabled, and adds the admin checkbox that controls it.

Slice MR
Setting (data + API) !244621 (merged)merged
Forwarding + admin UI this MR (targets master)
Group + package-list UI (frontend) !244626 (closed) — stacked on this

⚠️ Known limitation — no firewall coverage for upstream packages

With forwarding on, packages resolved from rubygems.org do not pass through GitLab, so the dependency firewall cannot see, audit or block them.

Verified on GDK: with a policy in place that provably matches an upstream gem, fetching that gem produced no audit entry, while a local gem download in the same run produced one. RubyGems clients resolve through a catalogue; since we serve our own catalogue, nothing directs them to ask us for upstream gems.

Locally-published packages are unaffected and remain covered by the firewall.

This means the Forward RubyGems package requests setting does not imply firewall coverage for upstream packages. Whether to ship the toggle in this state is an open decision — see discussion thread.

Scope: per-gem forwarding only

An earlier revision forwarded the index endpoints as well. That made locally-published gems uninstallable: RubyGems clients resolve through a single catalogue of everything the server has, so handing over rubygems.org's catalogue hid our own gems even though their files were still served.

This MR forwards only the endpoints that are genuine local-miss lookups.

Changes

  • Per-gem forwarding: gems/:file_name and quick/Marshal.4.8/:file_name 302-redirect to rubygems.org on a local miss, via Packages::Rubygems::Upstream::Client
  • The legacy spec index is served locally regardless of the setting
  • /api/v1/dependencies serves locally as before
  • Admin "Forward RubyGems package requests" + "Enforce … for all subgroups" checkboxes
  • Gated on project.rubygems_package_requests_forwarding; off / CE unchanged (404 / local serve)

Upstream gems are reached by adding rubygems.org as a second source, which is the standard multi-source Ruby pattern.

Testing

bundle exec rspec \
  spec/requests/api/rubygem_packages_spec.rb \
  ee/spec/requests/api/rubygem_packages_spec.rb \
  spec/services/packages/rubygems/upstream/client_spec.rb

Manual GDK testing

Enable the registry flag (Feature.enable(:rubygem_packages)), turn the setting on for a project, publish a gem to it, then with SRC="http://__token__:<PAT>@gdk.test:3000/api/v4/projects/<id>/packages/rubygems":

Action Expected
gem install <your-gem> --clear-sources --source "$SRC" installs — served locally
gem install colorize --clear-sources --source "$SRC" fails — the index is local, so upstream gems aren't discoverable
gem install colorize --clear-sources --source "$SRC" --source https://rubygems.org installs — the documented second-source pattern
curl -s -o /dev/null -w '%{http_code}\n' "$SRC/gems/colorize-1.1.0.gem" 302https://rubygems.org/gems/colorize-1.1.0.gem
curl -s -o /dev/null -w '%{http_code}\n' "$SRC/specs.4.8.gz" 200, served locally

Turn the setting off → gems/:file_name and the Marshal gemspec 404 for gems we don't hold; everything else is unchanged.

Three gotchas when reproducing:

  • Use a GET, not curl -sI: Grape answers HEAD with 303 See Other, not 302.
  • After toggling the setting, allow ~30s — the application settings cache means a running server keeps serving the previous value.
  • Make sure your shell has a modern Ruby on PATH; a bare temp directory can fall back to system Ruby 2.6 and Bundler 1.17, which behave differently.

Related to #605489.

Edited by Hannah Baker

Merge request reports

Loading
Loading