Add opt-in offline egress guard for RSpec
What does this MR do and why?
An instance on an offline cloud licence must never reach a GitLab cloud host. Today a spec can pass while product code on that licence resolves cloud.gitlab.com, so a new call path can merge with no failing spec.
This adds a guard to ee/spec/support that turns each such resolution into a named failure. It is opt-in: with OFFLINE_EGRESS_GUARD unset, nothing is installed.
- Set
OFFLINE_EGRESS_GUARD=1and the guard watches four resolver methods and every WebMock request for a cloud host:Gitlab::AiGateway.cloud_connector_urland.cloud_connector_auth_urlGitlab::DuoWorkflow::Client.cloud_connected_urlGitlab::TelemetryEndpoint.version_check_url
- It raises
OfflineEgressGuard::OfflineEgressErrorfor a cloud host only whenLicense.currentis an offline cloud licence, so a spec that builds its own offline licence is covered with no setup. For the version host it raises on any licence while the version check is off, and only whereversion_check_urlresolves it: Service Ping posts to the same host and that setting does not govern it. - The error names the URL, the resolver, the first
apporlibline that called it, and the example. - An example that expects the call tags itself
:allow_offline_egress, a key added tospec/support/known_rspec_metadata_keys.yml. - The error inherits from
Exception, so arescue StandardErrorin product code cannot hide it. - It follows the env-gated prepend in
spec/support/permissions_check.rb:6(GITLAB_DEBUG_POLICIES), and reads its switch the same way, throughGitlab::Utils.to_boolean. - The WebMock check uses
WebMock.globally_stub_request, which nothing else inspec/uses: it runs before local stubs, so a spec's own stub cannot hide a cloud request. - The hosts are
cloud.gitlab.com,customers.gitlab.com, their staging twins,*.runway.gitlab.netand the host ofgitlab.cloud_connector.base_url.
flowchart TD
S["OFFLINE_EGRESS_GUARD=1<br/>set at suite start?"] -->|"no"| U["nothing installed<br/>suite unchanged"]
S -->|"yes"| I["install: prepend resolver checks<br/>and a WebMock stub before local stubs"]
I --> C{"example resolves a cloud or version host,<br/>or requests a cloud host?"}
C -->|"no"| P["passes through"]
C -->|"yes"| L{"cloud host and an offline cloud licence,<br/>or version host and version check off?"}
L -->|"no"| P
L -->|"yes"| T{"example tagged<br/>:allow_offline_egress?"}
T -->|"yes"| P
T -->|"no"| X["OfflineEgressError<br/>names URL, resolver, caller, example"]The guard sees URLs the four resolver methods return and requests made through WebMock. It does not see a request stubbed with an RSpec double, a gRPC channel opened from a URL it did not resolve, Workhorse, or the Python AI gateway.
A CI job that runs specs on an offline licence is a separate, later step and is not in this MR.
This is an offer. Reviewers can take it, reshape it or close it, and the choices above (the env switch, the tag, the host list) are easy to change.
References
- #634528: the issue for this guard. @mkomor1 said it is reasonable to build alongside https://gitlab.com/gitlab-org/gitlab/-/work_items/633150 and that their group can help review (note), and @treagitlab said
@gl-dxcould help review theee/spec/supportside (note).
Review areas:
group::ai model services(@mkomor1): the host list and the resolver wrappers, which have to track AI gateway and Cloud Connector changes.- Developer Experience (
group::development health, @treagitlab):ee/spec/support, the env switch and the metadata key..gitlab/CODEOWNERSlists@gl-dx/maintainersfor/spec/and/ee/spec/.
Screenshots or screen recordings
Not applicable: this MR changes specs only.
How to set up and validate locally
- Run the guard's own spec, which covers the check, the allowlist tag, the resolver wrappers and the WebMock hook:
bundle exec rspec ee/spec/support_specs/offline_egress_guard_spec.rb - Run the specs for the resolvers with the guard on. On the default licence it adds no failures:
That is 583 examples and 0 failures, the same as without the variable.
OFFLINE_EGRESS_GUARD=1 bundle exec rspec ee/spec/lib/gitlab/ai_gateway_spec.rb ee/spec/lib/gitlab/duo_workflow ee/spec/lib/gitlab/ai spec/lib/gitlab/telemetry_endpoint_spec.rb ee/spec/support_specs - To see it fire, stub
License.currentwithbuild_stubbed(:license, data: build(:gitlab_license, :offline).export)in an example and callGitlab::AiGateway.cloud_connector_url. With the variable set it raisesOfflineEgressError. Without it, nothing raises.
I ran that last check through all four resolver methods, a direct Net::HTTP request to cloud.gitlab.com and a Gitlab::HTTP request to customers.gitlab.com, in one throwaway spec of 10 examples: 10 pass with the variable set, and the 6 that expect a raise fail without it.
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.
An AI coding assistant wrote this change and ran RuboCop, the guard's spec, the 583-example run and the throwaway spec above on a development GitLab instance on a test VM.