From e8190bd3d0ad87b98f920f144e098a6a2e08cca0 Mon Sep 17 00:00:00 2001 From: Stan Hu <stanhu@gmail.com> Date: Mon, 17 Jun 2024 10:43:14 -0700 Subject: [PATCH] Skip IP lookups in validating URLs on certain conditions Previously anytime `ApplicationSetting` were updated `UrlBlocker` would attempt to resolve the IPs and determine whether they were allowed by the current settings. However, in an offline environment, services like Diagrams.net may not be resolved even if they are enabled by default. This commit skips the validations that require resolving IP addresses if there are no restrictions on outgoing requests: - Allow requests to the local network is checked - Allow requests to the local network from system hooks is checked - DNS rebinding attack protection is disabled - Block all requests, except for IP addresses, IP ranges, and domain names defined in the allowlist (`deny_all_requests_except_allowed`) is disabled Note that the URL validators in `ApplicationSetting` only pass in the current `deny_all_requests_except_allowed` setting, so if that is active then IP resolution will occur. Relates to https://gitlab.com/gitlab-org/gitlab/-/issues/467524 Changelog: fixed --- gems/gitlab-http/.rubocop.yml | 4 ++ gems/gitlab-http/Gemfile.lock | 23 ++++++++ gems/gitlab-http/gitlab-http.gemspec | 1 + .../lib/gitlab/http_v2/url_blocker.rb | 4 ++ .../spec/gitlab/http_v2/url_blocker_spec.rb | 59 +++++++++++++++++++ 5 files changed, 91 insertions(+) diff --git a/gems/gitlab-http/.rubocop.yml b/gems/gitlab-http/.rubocop.yml index 8bc6b6a4cfbed..75941091b8935 100644 --- a/gems/gitlab-http/.rubocop.yml +++ b/gems/gitlab-http/.rubocop.yml @@ -20,3 +20,7 @@ RSpec/MultipleMemoizedHelpers: RSpec/FilePath: CustomTransform: HTTP_V2: http_v2 + +Lint/BinaryOperatorWithIdenticalOperands: + Exclude: + - 'spec/**/*.rb' diff --git a/gems/gitlab-http/Gemfile.lock b/gems/gitlab-http/Gemfile.lock index afdf6f3dce282..91cd6eca94f8e 100644 --- a/gems/gitlab-http/Gemfile.lock +++ b/gems/gitlab-http/Gemfile.lock @@ -55,11 +55,14 @@ GEM addressable (2.8.4) public_suffix (>= 2.0.2, < 6.0) ast (2.4.2) + binding_of_caller (1.0.1) + debug_inspector (>= 1.2.0) builder (3.2.4) concurrent-ruby (1.2.2) crack (0.4.5) rexml crass (1.0.6) + debug_inspector (1.2.0) diff-lcs (1.5.0) erubi (1.12.0) gitlab-styles (10.1.0) @@ -91,6 +94,10 @@ GEM parser (3.2.2.3) ast (~> 2.4.1) racc + proc_to_ast (0.2.0) + parser + rouge + unparser public_suffix (5.0.1) racc (1.7.1) rack (2.2.7) @@ -113,6 +120,7 @@ GEM rake (13.0.6) regexp_parser (2.8.1) rexml (3.2.5) + rouge (4.3.0) rspec (3.12.0) rspec-core (~> 3.12.0) rspec-expectations (~> 3.12.0) @@ -125,6 +133,17 @@ GEM rspec-mocks (3.12.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) + rspec-parameterized (1.0.2) + rspec-parameterized-core (< 2) + rspec-parameterized-table_syntax (< 2) + rspec-parameterized-core (1.0.1) + parser + proc_to_ast (>= 0.2.0) + rspec (>= 2.13, < 4) + unparser + rspec-parameterized-table_syntax (1.0.1) + binding_of_caller + rspec-parameterized-core (< 2) rspec-rails (6.0.3) actionpack (>= 6.1) activesupport (>= 6.1) @@ -168,6 +187,9 @@ GEM tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.4.2) + unparser (0.6.8) + diff-lcs (~> 1.3) + parser (>= 3.2.0) uri (0.13.0) webmock (3.18.1) addressable (>= 2.8.0) @@ -184,6 +206,7 @@ DEPENDENCIES gitlab-rspec! gitlab-styles (~> 10.1.0) gitlab-utils! + rspec-parameterized (~> 1.0.2) rspec-rails (~> 6.0.3) rubocop (~> 1.50.2) rubocop-rspec (~> 2.22) diff --git a/gems/gitlab-http/gitlab-http.gemspec b/gems/gitlab-http/gitlab-http.gemspec index 2378945cbcc35..89306c0b21e2c 100644 --- a/gems/gitlab-http/gitlab-http.gemspec +++ b/gems/gitlab-http/gitlab-http.gemspec @@ -28,6 +28,7 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency "net-http", "= 0.4.1" spec.add_development_dependency 'gitlab-styles', '~> 10.1.0' + spec.add_development_dependency 'rspec-parameterized', '~> 1.0.2' spec.add_development_dependency 'rspec-rails', '~> 6.0.3' spec.add_development_dependency "rubocop", "~> 1.50.2" spec.add_development_dependency "rubocop-rspec", "~> 2.22" diff --git a/gems/gitlab-http/lib/gitlab/http_v2/url_blocker.rb b/gems/gitlab-http/lib/gitlab/http_v2/url_blocker.rb index 7c669a0d9c6f8..bfee1b2b40b62 100644 --- a/gems/gitlab-http/lib/gitlab/http_v2/url_blocker.rb +++ b/gems/gitlab-http/lib/gitlab/http_v2/url_blocker.rb @@ -81,6 +81,10 @@ def validate_url_with_proxy!( ascii_only: ascii_only ) + unless deny_all_requests_except_allowed || dns_rebind_protection || !allow_local_network || !allow_localhost + return Result.new(uri, nil, true) + end + validate_resolved_uri(uri, allow_localhost: allow_localhost, allow_local_network: allow_local_network, diff --git a/gems/gitlab-http/spec/gitlab/http_v2/url_blocker_spec.rb b/gems/gitlab-http/spec/gitlab/http_v2/url_blocker_spec.rb index 904fed9baefca..75f57e731222b 100644 --- a/gems/gitlab-http/spec/gitlab/http_v2/url_blocker_spec.rb +++ b/gems/gitlab-http/spec/gitlab/http_v2/url_blocker_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'spec_helper' +require 'rspec-parameterized' RSpec.describe Gitlab::HTTP_V2::UrlBlocker, :stub_invalid_dns_only, feature_category: :shared do let(:schemes) { %w[http https] } @@ -37,6 +38,64 @@ end end end + + context 'when one flag requring IP validation is enabled' do + using RSpec::Parameterized::TableSyntax + + let(:import_url) { 'https://localhost' } + + where(:deny_all_requests_except_allowed, :dns_rebind_protection, :allow_local_network, :allow_localhost) do + true | false | true | true + false | true | true | true + false | false | false | true + false | false | true | false + end + + with_them do + let(:options) do + { + deny_all_requests_except_allowed: deny_all_requests_except_allowed, + dns_rebind_protection: dns_rebind_protection, + allow_local_network: allow_local_network, + allow_localhost: allow_localhost, + schemes: schemes + } + end + + it 'attempts to validate IP' do + expect(described_class).to receive(:validate_resolved_uri).and_return( + described_class::Result.new(import_url, 'localhost', false)) + + subject + end + end + end + + context 'when all forms of IP validation are disabled' do + let(:options) do + { + deny_all_requests_except_allowed: false, + dns_rebind_protection: false, + allow_local_network: true, + allow_localhost: true, + schemes: schemes + } + end + + let(:import_url) { 'https://localhost' } + let(:expected_uri) { 'https://localhost' } + let(:expected_hostname) { nil } + let(:expected_use_proxy) { false } + + it 'does not attempt to resolve IPs' do + expect(Addrinfo).not_to receive(:getaddrinfo) + + uri, hostname = subject + + expect(uri).to eq(Addressable::URI.parse(expected_uri)) + expect(hostname).to eq(expected_hostname) + end + end end describe '#validate_url_with_proxy!' do -- GitLab