From 76386fe9f3bf89c273b280366d706a5dba45cee6 Mon Sep 17 00:00:00 2001 From: Stan Hu <stanhu@gmail.com> Date: Fri, 24 May 2024 17:20:18 -0700 Subject: [PATCH] Add a test for Net::HTTP#connect This will ensure that SNI is used with the hostname override patch. --- gems/gitlab-http/Gemfile.lock | 2 + gems/gitlab-http/gitlab-http.gemspec | 1 + .../http_v2/net_http_connect_patch_spec.rb | 102 ++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 gems/gitlab-http/spec/gitlab/http_v2/net_http_connect_patch_spec.rb diff --git a/gems/gitlab-http/Gemfile.lock b/gems/gitlab-http/Gemfile.lock index da9a1398e1cfa..afdf6f3dce282 100644 --- a/gems/gitlab-http/Gemfile.lock +++ b/gems/gitlab-http/Gemfile.lock @@ -173,6 +173,7 @@ GEM addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) + webrick (1.8.1) zeitwerk (2.6.8) PLATFORMS @@ -187,6 +188,7 @@ DEPENDENCIES rubocop (~> 1.50.2) rubocop-rspec (~> 2.22) webmock (~> 3.18.1) + webrick (~> 1.8) BUNDLED WITH 2.4.14 diff --git a/gems/gitlab-http/gitlab-http.gemspec b/gems/gitlab-http/gitlab-http.gemspec index ada9d672acd41..2378945cbcc35 100644 --- a/gems/gitlab-http/gitlab-http.gemspec +++ b/gems/gitlab-http/gitlab-http.gemspec @@ -32,4 +32,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "rubocop", "~> 1.50.2" spec.add_development_dependency "rubocop-rspec", "~> 2.22" spec.add_development_dependency 'webmock', '~> 3.18.1' + spec.add_development_dependency 'webrick', '~> 1.8' end diff --git a/gems/gitlab-http/spec/gitlab/http_v2/net_http_connect_patch_spec.rb b/gems/gitlab-http/spec/gitlab/http_v2/net_http_connect_patch_spec.rb new file mode 100644 index 0000000000000..548c33058bed0 --- /dev/null +++ b/gems/gitlab-http/spec/gitlab/http_v2/net_http_connect_patch_spec.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'webrick' +require 'webrick/https' + +RSpec.describe 'Net::HTTP#connect DNS rebinding tests', feature_category: :shared do + let(:host) { 'localhost' } + let(:host_ip) { '127.0.0.1' } + let(:rack_app) do + proc do |_env| + ['200', { 'Content-Type' => 'text/plain' }, ['Hello, world!']] + end + end + + let!(:http_server) do + Class.new do + attr_accessor :sni_hostname + + def initialize + @server = WEBrick::HTTPServer.new( + Port: 0, + SSLEnable: true, + SSLCertName: [%w[CN localhost]], + SSLServerNameCallback: proc { |args| sni_callback(*args) }, + Logger: WEBrick::Log.new('/dev/null'), + AccessLog: [] + ) + + @server.mount_proc '/' do |_req, res| + res.body = 'Hello, world!' + end + + Thread.new { @server.start } + end + + def port + @server.config[:Port] + end + + def shutdown + @server.shutdown + end + + def sni_callback(sslsocket, hostname = nil) + @sni_hostname = hostname + @server.ssl_servername_callback(sslsocket, hostname) + end + end.new + end + + describe '#connect' do + before do + WebMock.allow_net_connect! + end + + after do + WebMock.disable_net_connect! # rubocop:disable RSpec/WebMockEnable -- method not available in gem + http_server.shutdown + end + + shared_examples 'GET request' do + it 'makes a successful HTTPS connection' do + http = Net::HTTP.new(http_host, http_server.port) + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + http.hostname_override = hostname_override if hostname_override + + request = Net::HTTP::Get.new('/') + + response = http.start { http.request(request) } + expect(response.code).to eq('200') + expect(response.body).to include('Hello, world!') + expect(http_server.sni_hostname).to eq(expected_sni) + end + end + + context 'with hostname' do + let(:http_host) { host } + let(:expected_sni) { host } + let(:hostname_override) { nil } + + it_behaves_like 'GET request' + end + + context 'with IP address' do + let(:http_host) { host_ip } + let(:expected_sni) { nil } + let(:hostname_override) { nil } + + it_behaves_like 'GET request' + end + + context 'with hostname override' do + let(:http_host) { host_ip } + let(:hostname_override) { host } + let(:expected_sni) { host } + + it_behaves_like 'GET request' + end + end +end -- GitLab