diff --git a/lib/proxy/request.rb b/lib/proxy/request.rb index c0fd4df9b..88a0e67b0 100644 --- a/lib/proxy/request.rb +++ b/lib/proxy/request.rb @@ -1,4 +1,5 @@ require 'net/http' +require 'net/http/persistent' require 'net/https' require 'uri' require 'cgi' @@ -44,8 +45,36 @@ def create_post(path, body, headers = {}, query = {}) end class ForemanRequest + class << self + def http + @http ||= begin + uri = URI.parse(Proxy::SETTINGS.foreman_url.to_s) + http = Net::HTTP::Persistent.new(name: 'smart_proxy_foreman') + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + + if uri.scheme == 'https' + ca_file = Proxy::SETTINGS.foreman_ssl_ca || Proxy::SETTINGS.ssl_ca_file + certificate = Proxy::SETTINGS.foreman_ssl_cert || Proxy::SETTINGS.ssl_certificate + private_key = Proxy::SETTINGS.foreman_ssl_key || Proxy::SETTINGS.ssl_private_key + + if ca_file && !ca_file.to_s.empty? + http.ca_file = ca_file + http.verify_mode = OpenSSL::SSL::VERIFY_PEER + end + + if certificate && !certificate.to_s.empty? && private_key && !private_key.to_s.empty? + http.certificate = OpenSSL::X509::Certificate.new(File.read(certificate)) + http.private_key = OpenSSL::PKey.read(File.read(private_key), nil) + end + end + + http + end + end + end + def send_request(request) - http.request(request) + http.request(uri, request) end def request_factory @@ -57,32 +86,7 @@ def uri end def http - @http ||= http_init - end - - private - - def http_init - http = Net::HTTP.new(uri.host, uri.port) - http.use_ssl = uri.scheme == 'https' - http.verify_mode = OpenSSL::SSL::VERIFY_NONE - - if http.use_ssl? - ca_file = Proxy::SETTINGS.foreman_ssl_ca || Proxy::SETTINGS.ssl_ca_file - certificate = Proxy::SETTINGS.foreman_ssl_cert || Proxy::SETTINGS.ssl_certificate - private_key = Proxy::SETTINGS.foreman_ssl_key || Proxy::SETTINGS.ssl_private_key - - if ca_file && !ca_file.to_s.empty? - http.ca_file = ca_file - http.verify_mode = OpenSSL::SSL::VERIFY_PEER - end - - if certificate && !certificate.to_s.empty? && private_key && !private_key.to_s.empty? - http.cert = OpenSSL::X509::Certificate.new(File.read(certificate)) - http.key = OpenSSL::PKey.read(File.read(private_key), nil) - end - end - http + self.class.http end end end diff --git a/smart_proxy.gemspec b/smart_proxy.gemspec index 4dc05065d..6a6dd2825 100644 --- a/smart_proxy.gemspec +++ b/smart_proxy.gemspec @@ -16,6 +16,7 @@ Gem::Specification.new do |s| s.add_dependency 'base64' s.add_dependency 'json' s.add_dependency 'logging' + s.add_dependency 'net-http-persistent', '~> 4.0' s.add_dependency 'ostruct' s.add_dependency 'rack', '>= 1.3' s.add_dependency 'rexml', '~> 3.2' diff --git a/test/registration/registration_api_test.rb b/test/registration/registration_api_test.rb index bd9ff7b1b..6f678ba87 100644 --- a/test/registration/registration_api_test.rb +++ b/test/registration/registration_api_test.rb @@ -1,5 +1,6 @@ require 'test_helper' require 'registration/registration_api' +require 'registration/registration_plugin' class RegistrationRegisterApiTest < Test::Unit::TestCase include Rack::Test::Methods @@ -11,6 +12,7 @@ def app def setup @foreman_url = 'http://foreman.example.com' Proxy::SETTINGS.stubs(:foreman_url).returns(@foreman_url) + Proxy::Registration::Plugin.load_test_settings end def test_global_register_template diff --git a/test/request_test.rb b/test/request_test.rb index 81d6f53b0..065f71d95 100644 --- a/test/request_test.rb +++ b/test/request_test.rb @@ -1,6 +1,8 @@ require 'test_helper' require 'uri' require 'net/http' +require 'openssl' +require 'tempfile' require 'mocha' require 'templates/templates_plugin' require "proxy/util" @@ -11,6 +13,15 @@ class RequestTest < Test::Unit::TestCase def setup @foreman_url = 'https://foreman.example.com' Proxy::SETTINGS.stubs(:foreman_url).returns(@foreman_url) + Proxy::SETTINGS.stubs(:foreman_ssl_ca).returns(nil) + Proxy::SETTINGS.stubs(:ssl_ca_file).returns(nil) + Proxy::SETTINGS.stubs(:foreman_ssl_cert).returns(nil) + Proxy::SETTINGS.stubs(:ssl_certificate).returns(nil) + Proxy::SETTINGS.stubs(:foreman_ssl_key).returns(nil) + Proxy::SETTINGS.stubs(:ssl_private_key).returns(nil) + cached_connection = Proxy::HttpRequest::ForemanRequest.instance_variable_get(:@http) + cached_connection&.shutdown + Proxy::HttpRequest::ForemanRequest.instance_variable_set(:@http, nil) @template_url = 'http://proxy.lan:8443' Proxy::Templates::Plugin.load_test_settings(:template_url => @template_url) @request = Proxy::HttpRequest::ForemanRequest.new @@ -54,6 +65,33 @@ def test_post assert_equal("body", result.body) end + def test_connection_is_shared_across_request_instances + request_a = Proxy::HttpRequest::ForemanRequest.new + request_b = Proxy::HttpRequest::ForemanRequest.new + + assert_same request_a.http, request_b.http + assert_kind_of Net::HTTP::Persistent, request_a.http + end + + def test_ssl_verify_peer_when_ca_configured + ca_file = Tempfile.new(['foreman-ca', '.pem']) + ca_file.write("ca-content") + ca_file.flush + + Proxy::SETTINGS.stubs(:foreman_ssl_ca).returns(ca_file.path) + cached_connection = Proxy::HttpRequest::ForemanRequest.instance_variable_get(:@http) + cached_connection&.shutdown + Proxy::HttpRequest::ForemanRequest.instance_variable_set(:@http, nil) + + connection = Proxy::HttpRequest::ForemanRequest.new.http + + assert_equal OpenSSL::SSL::VERIFY_PEER, connection.verify_mode + assert_equal ca_file.path, connection.ca_file + ensure + ca_file&.close + ca_file&.unlink + end + def test_post_with_nested_params stub_request(:post, @foreman_url + '/register?activation_keys%5B%5D=ac_AlmaLinux8&location_id=2&organization_id=1&repo_data%5B%5D%5Brepo%5D=repo1&repo_data%5B%5D%5Brepo_gpg_key_url%5D=key1&repo_data%5B%5D%5Brepo%5D=repo2&repo_data%5B%5D%5Brepo_gpg_key_url%5D=key2&update_packages=false') .to_return(status: 200, body: "body", headers: {h1: "header"}) diff --git a/test/templates/template_proxy_request_test.rb b/test/templates/template_proxy_request_test.rb index a942f34e9..5a418d574 100644 --- a/test/templates/template_proxy_request_test.rb +++ b/test/templates/template_proxy_request_test.rb @@ -52,7 +52,9 @@ def test_template_requests_via_post headers: { 'Accept' => ['*/*', 'application/json,version=2'], 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', + 'Connection' => 'keep-alive', 'Content-Type' => 'application/json', + 'Keep-Alive' => '30', 'User-Agent' => 'Ruby', 'X-Forwarded-For' => '1.2.3.4', }).