From c44a96733fd967c043cacd75cbaa36ce4bb4f986 Mon Sep 17 00:00:00 2001 From: Furkan Ayhan <furkanayhn@gmail.com> Date: Mon, 25 Sep 2023 11:04:16 +0000 Subject: [PATCH] Enable gitlab-http gem and its patches - Adding the gem to the Gemfile - Initializing it and require its dependencies - Removing the old patches --- .rubocop_todo/rspec/file_path.yml | 1 + Gemfile | 1 + Gemfile.lock | 11 ++++ config/initializers/gitlab_http.rb | 26 ++++++++++ config/initializers/http_hostname_override.rb | 52 ------------------- config/initializers/net_http_patch.rb | 39 -------------- .../initializers/net_http_response_patch.rb | 48 ----------------- .../lib/net_http/response_patch.rb | 11 ++-- .../gitlab/http_v2/net_http_patch_spec.rb | 2 +- spec/initializers/gitlab_http_spec.rb | 47 +++++++++++++++++ spec/initializers/net_http_patch_spec.rb | 8 +-- .../net_http_response_patch_spec.rb | 3 ++ 12 files changed, 100 insertions(+), 149 deletions(-) create mode 100644 config/initializers/gitlab_http.rb delete mode 100644 config/initializers/http_hostname_override.rb delete mode 100644 config/initializers/net_http_patch.rb delete mode 100644 config/initializers/net_http_response_patch.rb create mode 100644 spec/initializers/gitlab_http_spec.rb diff --git a/.rubocop_todo/rspec/file_path.yml b/.rubocop_todo/rspec/file_path.yml index ea19394890944..06832184c04e8 100644 --- a/.rubocop_todo/rspec/file_path.yml +++ b/.rubocop_todo/rspec/file_path.yml @@ -24,6 +24,7 @@ RSpec/FilePath: - 'spec/benchmarks/banzai_benchmark.rb' - 'spec/docs_screenshots/container_registry_docs.rb' - 'spec/docs_screenshots/wiki_docs.rb' + - 'spec/initializers/gitlab_http_spec.rb' - 'spec/lib/error_tracking/sentry_client/event_spec.rb' - 'spec/lib/gitlab/import_export/import_export_spec.rb' - 'spec/lib/gitlab/mail_room/mail_room_spec.rb' diff --git a/Gemfile b/Gemfile index ec3e8d3b0009d..b96b331d1e6ac 100644 --- a/Gemfile +++ b/Gemfile @@ -349,6 +349,7 @@ gem 'sentry-sidekiq', '~> 5.8.0' gem 'pg_query', '~> 4.2.3' gem 'gitlab-schema-validation', path: 'gems/gitlab-schema-validation' +gem 'gitlab-http', path: 'gems/gitlab-http' gem 'premailer-rails', '~> 1.10.3' diff --git a/Gemfile.lock b/Gemfile.lock index d6911ef9114c5..931c8e41944f5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -23,6 +23,16 @@ PATH error_tracking_open_api (1.0.0) typhoeus (~> 1.0, >= 1.0.1) +PATH + remote: gems/gitlab-http + specs: + gitlab-http (0.1.0) + activesupport (~> 7.0.6) + httparty (~> 0.21.0) + ipaddress (~> 0.8.3) + nokogiri (~> 1.15.4) + railties (~> 7.0.6) + PATH remote: gems/gitlab-rspec specs: @@ -1812,6 +1822,7 @@ DEPENDENCIES gitlab-dangerfiles (~> 4.0.0) gitlab-experiment (~> 0.8.0) gitlab-fog-azure-rm (~> 1.8.0) + gitlab-http! gitlab-labkit (~> 0.34.0) gitlab-license (~> 2.3) gitlab-mail_room (~> 0.0.23) diff --git a/config/initializers/gitlab_http.rb b/config/initializers/gitlab_http.rb new file mode 100644 index 0000000000000..8a84313a7fb92 --- /dev/null +++ b/config/initializers/gitlab_http.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# When including this gem, we also initialize the patch / override classes in the gem. +require 'gitlab-http' + +Gitlab::HTTP_V2.configure do |config| + config.allowed_internal_uris = [ + URI::HTTP.build( + scheme: Gitlab.config.gitlab.protocol, + host: Gitlab.config.gitlab.host, + port: Gitlab.config.gitlab.port + ), + URI::Generic.build( + scheme: 'ssh', + host: Gitlab.config.gitlab_shell.ssh_host, + port: Gitlab.config.gitlab_shell.ssh_port + ) + ] + + config.log_exception_proc = ->(exception, extra_info) do + Gitlab::ErrorTracking.log_exception(exception, extra_info) + end + config.silent_mode_log_info_proc = ->(message, http_method) do + Gitlab::SilentMode.log_info(message: message, outbound_http_request_method: http_method) + end +end diff --git a/config/initializers/http_hostname_override.rb b/config/initializers/http_hostname_override.rb deleted file mode 100644 index 3d840cd32511c..0000000000000 --- a/config/initializers/http_hostname_override.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -# This override allows passing `@hostname_override` to the SNI protocol, -# which is used to lookup the correct SSL certificate in the -# request handshake process. -# -# Given we've forced the HTTP request to be sent to the resolved -# IP address in a few scenarios (e.g.: `Gitlab::HTTP` through -# `Gitlab::UrlBlocker.validate!`), we need to provide the _original_ -# hostname via SNI in order to have a clean connection setup. -# -# This is ultimately needed in order to avoid DNS rebinding attacks -# through HTTP requests. -# -class OpenSSL::SSL::SSLContext - attr_accessor :hostname_override -end - -class OpenSSL::SSL::SSLSocket - module HostnameOverride - # rubocop: disable Gitlab/ModuleWithInstanceVariables - def hostname=(hostname) - super(@context.hostname_override || hostname) - end - - def post_connection_check(hostname) - super(@context.hostname_override || hostname) - end - # rubocop: enable Gitlab/ModuleWithInstanceVariables - end - - prepend HostnameOverride -end - -class Net::HTTP - attr_accessor :hostname_override - - SSL_IVNAMES << :@hostname_override - SSL_ATTRIBUTES << :hostname_override - - module HostnameOverride - def addr_port - return super unless hostname_override - - addr = hostname_override - default_port = use_ssl? ? Net::HTTP.https_default_port : Net::HTTP.http_default_port - default_port == port ? addr : "#{addr}:#{port}" - end - end - - prepend HostnameOverride -end diff --git a/config/initializers/net_http_patch.rb b/config/initializers/net_http_patch.rb deleted file mode 100644 index 8231423e1a518..0000000000000 --- a/config/initializers/net_http_patch.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -# Monkey patch Net::HTTP to fix missing URL decoding for username and password in proxy settings -# -# See proposed upstream fix https://github.com/ruby/net-http/pull/5 -# See Ruby-lang issue https://bugs.ruby-lang.org/issues/17542 -# See issue on GitLab https://gitlab.com/gitlab-org/gitlab/-/issues/289836 - -require 'net/http' - -# This file can be removed once Ruby 3.0 is no longer supported: -# https://gitlab.com/gitlab-org/gitlab/-/issues/396223 -return if Gem::Version.new(Net::HTTP::VERSION) >= Gem::Version.new('0.2.0') - -module Net - class HTTP < Protocol - def proxy_user - if environment_variable_is_multiuser_safe? && @proxy_from_env - user = proxy_uri&.user - CGI.unescape(user) unless user.nil? - else - @proxy_user - end - end - - def proxy_pass - if environment_variable_is_multiuser_safe? && @proxy_from_env - pass = proxy_uri&.password - CGI.unescape(pass) unless pass.nil? - else - @proxy_pass - end - end - - def environment_variable_is_multiuser_safe? - ENVIRONMENT_VARIABLE_IS_MULTIUSER_SAFE - end - end -end diff --git a/config/initializers/net_http_response_patch.rb b/config/initializers/net_http_response_patch.rb deleted file mode 100644 index 4f3eaeec24a5c..0000000000000 --- a/config/initializers/net_http_response_patch.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -module Net - class HTTPResponse - # rubocop: disable Cop/LineBreakAfterGuardClauses - # rubocop: disable Cop/LineBreakAroundConditionalBlock - # rubocop: disable Layout/EmptyLineAfterGuardClause - # rubocop: disable Style/AndOr - # rubocop: disable Style/CharacterLiteral - # rubocop: disable Style/InfiniteLoop - - # Original method: - # https://github.com/ruby/ruby/blob/v2_7_5/lib/net/http/response.rb#L54-L69 - # - # Our changes: - # - Pass along the `start_time` to `Gitlab::BufferedIo`, so we can raise a timeout - # if reading the headers takes too long. - # - Limit the regexes to avoid ReDoS attacks. - def self.each_response_header(sock) - start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - key = value = nil - while true - line = sock.is_a?(Gitlab::BufferedIo) ? sock.readuntil("\n", true, start_time) : sock.readuntil("\n", true) - line = line.sub(/\s{0,10}\z/, '') - break if line.empty? - if line[0] == ?\s or line[0] == ?\t and value - # rubocop:disable Gitlab/NoCodeCoverageComment - # :nocov: - value << ' ' unless value.empty? - value << line.strip - # :nocov: - # rubocop:enable Gitlab/NoCodeCoverageComment - else - yield key, value if key - key, value = line.strip.split(/\s{0,10}:\s{0,10}/, 2) - raise Net::HTTPBadResponse, 'wrong header line format' if value.nil? - end - end - yield key, value if key - end - # rubocop: enable Cop/LineBreakAfterGuardClauses - # rubocop: enable Cop/LineBreakAroundConditionalBlock - # rubocop: enable Layout/EmptyLineAfterGuardClause - # rubocop: enable Style/AndOr - # rubocop: enable Style/CharacterLiteral - # rubocop: enable Style/InfiniteLoop - end -end diff --git a/gems/gitlab-http/lib/net_http/response_patch.rb b/gems/gitlab-http/lib/net_http/response_patch.rb index e5477a3131829..303d629b32e4f 100644 --- a/gems/gitlab-http/lib/net_http/response_patch.rb +++ b/gems/gitlab-http/lib/net_http/response_patch.rb @@ -20,11 +20,12 @@ def self.each_response_header(sock) start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) key = value = nil while true - line = if sock.is_a?(Gitlab::HTTP_V2::BufferedIo) - sock.readuntil("\n", true, start_time) - else - sock.readuntil("\n", true) - end + uses_buffered_io = sock.is_a?(Gitlab::HTTP_V2::BufferedIo) + + # TODO: Gitlab::BufferedIo is temporarily used for an easy migration. + uses_buffered_io ||= sock.is_a?(Gitlab::BufferedIo) if defined?(Gitlab::BufferedIo) + + line = uses_buffered_io ? sock.readuntil("\n", true, start_time) : sock.readuntil("\n", true) line = line.sub(/\s{0,10}\z/, '') break if line.empty? if line[0] == ?\s or line[0] == ?\t and value diff --git a/gems/gitlab-http/spec/gitlab/http_v2/net_http_patch_spec.rb b/gems/gitlab-http/spec/gitlab/http_v2/net_http_patch_spec.rb index b82646fb3657e..f34b0d9840359 100644 --- a/gems/gitlab-http/spec/gitlab/http_v2/net_http_patch_spec.rb +++ b/gems/gitlab-http/spec/gitlab/http_v2/net_http_patch_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require 'net/http' -RSpec.describe 'Net::HTTP patch proxy user and password encoding' do +RSpec.describe 'Net::HTTP patch proxy user and password encoding', feature_category: :shared do let(:net_http) { Net::HTTP.new('hostname.example') } before do diff --git a/spec/initializers/gitlab_http_spec.rb b/spec/initializers/gitlab_http_spec.rb new file mode 100644 index 0000000000000..7715112abf4aa --- /dev/null +++ b/spec/initializers/gitlab_http_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::HTTP_V2, feature_category: :shared do + it 'handles log_exception_proc' do + expect(Gitlab::HTTP_V2::Client).to receive(:httparty_perform_request) + .and_raise(Net::ReadTimeout) + + expect(Gitlab::ErrorTracking).to receive(:log_exception) + .with(Net::ReadTimeout, {}) + + expect { described_class.get('http://example.org') }.to raise_error(Net::ReadTimeout) + end + + context 'when silent_mode_enabled is true' do + before do + stub_application_setting(silent_mode_enabled: true) + end + + context 'when sending a POST request' do + it 'handles silent_mode_log_info_proc' do + expect(::Gitlab::AppJsonLogger).to receive(:info).with( + message: "Outbound HTTP request blocked", + outbound_http_request_method: 'Net::HTTP::Post', + silent_mode_enabled: true + ) + + expect { described_class.post('http://example.org', silent_mode_enabled: true) }.to raise_error( + Gitlab::HTTP_V2::SilentModeBlockedError + ) + end + end + + context 'when sending a GET request' do + before do + stub_request(:get, 'http://example.org').to_return(body: 'hello') + end + + it 'does not raise an error' do + expect(::Gitlab::AppJsonLogger).not_to receive(:info) + + expect(described_class.get('http://example.org', silent_mode_enabled: true).body).to eq('hello') + end + end + end +end diff --git a/spec/initializers/net_http_patch_spec.rb b/spec/initializers/net_http_patch_spec.rb index b9f5299b58cfa..959eae954c487 100644 --- a/spec/initializers/net_http_patch_spec.rb +++ b/spec/initializers/net_http_patch_spec.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require 'fast_spec_helper' -require 'net/http' +# TODO: This spec file can be removed after fully migration to the gitlab-http gem. +# It's already covered in gems/gitlab-http/spec/gitlab/http_v2/net_http_patch_spec.rb -require_relative '../../config/initializers/net_http_patch' +require 'spec_helper' -RSpec.describe 'Net::HTTP patch proxy user and password encoding' do +RSpec.describe 'Net::HTTP patch proxy user and password encoding', feature_category: :shared do let(:net_http) { Net::HTTP.new('hostname.example') } before do diff --git a/spec/initializers/net_http_response_patch_spec.rb b/spec/initializers/net_http_response_patch_spec.rb index cd261d7b997c6..8074047d6aa36 100644 --- a/spec/initializers/net_http_response_patch_spec.rb +++ b/spec/initializers/net_http_response_patch_spec.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +# TODO: This spec file can be removed after fully migration to the gitlab-http gem. +# It's already covered in gems/gitlab-http/spec/gitlab/http_v2/net_http_response_patch_spec.rb + require 'spec_helper' RSpec.describe 'Net::HTTPResponse patch header read timeout', feature_category: :shared do -- GitLab