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_nameandquick/Marshal.4.8/:file_name302-redirect to rubygems.org on a local miss, viaPackages::Rubygems::Upstream::Client - The legacy spec index is served locally regardless of the setting
/api/v1/dependenciesserves 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.rbManual 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" |
302 → https://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 with303 See Other, not302. - 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.