From 669d4eda8e0e4fd7bdcb9f26c080d45bb0d1adbb Mon Sep 17 00:00:00 2001 From: Ewoud Kohl van Wijngaarden Date: Fri, 2 Oct 2020 21:18:56 +0200 Subject: [PATCH] Extract webrick launcher to a separate file --- lib/proxy/launcher.rb | 136 ++----------------------------- lib/proxy/launcher/webrick.rb | 148 ++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 128 deletions(-) create mode 100644 lib/proxy/launcher/webrick.rb diff --git a/lib/proxy/launcher.rb b/lib/proxy/launcher.rb index 020968e7e..f7b8a6a03 100644 --- a/lib/proxy/launcher.rb +++ b/lib/proxy/launcher.rb @@ -2,7 +2,6 @@ require 'proxy/settings' require 'proxy/signal_handler' require 'proxy/log_buffer/trace_decorator' -require 'sd_notify' CIPHERS = ['ECDHE-RSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES256-GCM-SHA384', 'AES128-GCM-SHA256', 'AES256-GCM-SHA384', 'AES128-SHA256', @@ -14,7 +13,7 @@ class Launcher attr_reader :settings - def initialize(settings = SETTINGS) + def initialize(settings = ::Proxy::SETTINGS) @settings = settings end @@ -31,94 +30,7 @@ def https_enabled? end def plugins - ::Proxy::Plugins.instance.select { |p| p[:state] == :running } - end - - def http_plugins - plugins.select { |p| p[:http_enabled] }.map { |p| p[:class] } - end - - def https_plugins - plugins.select { |p| p[:https_enabled] }.map { |p| p[:class] } - end - - def http_app(http_port, plugins = http_plugins) - return nil unless http_enabled? - app = Rack::Builder.new do - plugins.each { |p| instance_eval(p.http_rackup) } - end - - { - :app => app, - :server => :webrick, - :DoNotListen => true, - :Port => http_port, # only being used to correctly log http port being used - :Logger => ::Proxy::LogBuffer::TraceDecorator.instance, - :AccessLog => [], - :ServerSoftware => "foreman-proxy/#{Proxy::VERSION}", - :daemonize => false, - } - end - - def https_app(https_port, plugins = https_plugins) - unless https_enabled? - logger.warn "Missing SSL setup, https is disabled." - return nil - end - - app = Rack::Builder.new do - plugins.each { |p| instance_eval(p.https_rackup) } - end - - ssl_options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] - ssl_options |= OpenSSL::SSL::OP_CIPHER_SERVER_PREFERENCE if defined?(OpenSSL::SSL::OP_CIPHER_SERVER_PREFERENCE) - # This is required to disable SSLv3 on Ruby 1.8.7 - ssl_options |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2) - ssl_options |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3) - ssl_options |= OpenSSL::SSL::OP_NO_TLSv1 if defined?(OpenSSL::SSL::OP_NO_TLSv1) - ssl_options |= OpenSSL::SSL::OP_NO_TLSv1_1 if defined?(OpenSSL::SSL::OP_NO_TLSv1_1) - - Proxy::SETTINGS.tls_disabled_versions&.each do |version| - constant = OpenSSL::SSL.const_get("OP_NO_TLSv#{version.to_s.tr('.', '_')}") rescue nil - - if constant - logger.info "TLSv#{version} will be disabled." - ssl_options |= constant - else - logger.warn "TLSv#{version} was not found." - end - end - - { - :app => app, - :server => :webrick, - :DoNotListen => true, - :Port => https_port, # only being used to correctly log https port being used - :Logger => ::Proxy::LogBuffer::Decorator.instance, - :ServerSoftware => "foreman-proxy/#{Proxy::VERSION}", - :SSLEnable => true, - :SSLVerifyClient => OpenSSL::SSL::VERIFY_PEER, - :SSLPrivateKey => load_ssl_private_key(settings.ssl_private_key), - :SSLCertificate => load_ssl_certificate(settings.ssl_certificate), - :SSLCACertificateFile => settings.ssl_ca_file, - :SSLOptions => ssl_options, - :SSLCiphers => CIPHERS - Proxy::SETTINGS.ssl_disabled_ciphers, - :daemonize => false, - } - end - - def load_ssl_private_key(path) - OpenSSL::PKey::RSA.new(File.read(path)) - rescue Exception => e - logger.error "Unable to load private SSL key. Are the values correct in settings.yml and do permissions allow reading?", e - raise e - end - - def load_ssl_certificate(path) - OpenSSL::X509::Certificate.new(File.read(path)) - rescue Exception => e - logger.error "Unable to load SSL certificate. Are the values correct in settings.yml and do permissions allow reading?", e - raise e + ::Proxy::Plugins.instance end def pid_status @@ -152,11 +64,8 @@ def write_pid retry end - def webrick_server(app, addresses, port) - server = ::WEBrick::HTTPServer.new(app) - addresses.each { |a| server.listen(a, port) } - server.mount "/", Rack::Handler::WEBrick, app[:app] - server + def ciphers + CIPHERS - settings.ssl_disabled_ciphers end def launch @@ -168,18 +77,12 @@ def launch write_pid end - ::Proxy::PluginInitializer.new(::Proxy::Plugins.instance).initialize_plugins + ::Proxy::PluginInitializer.new(plugins).initialize_plugins - http_app = http_app(settings.http_port) - https_app = https_app(settings.https_port) - install_webrick_callback!(http_app, https_app) + require 'proxy/launcher/webrick' + launcher = ::Launcher::Webrick.new(self) - t1 = Thread.new { webrick_server(https_app, settings.bind_host, settings.https_port).start } unless https_app.nil? - t2 = Thread.new { webrick_server(http_app, settings.bind_host, settings.http_port).start } unless http_app.nil? - - Proxy::SignalHandler.install_traps - - (t1 || t2).join + launcher.launch rescue SignalException => e logger.debug("Caught #{e}. Exiting") raise @@ -191,28 +94,5 @@ def launch puts "Errors detected on startup, see log for details. Exiting: #{e}" exit(1) end - - def install_webrick_callback!(*apps) - apps.compact! - - # track how many webrick apps are still starting up - @pending_webrick = apps.size - @pending_webrick_lock = Mutex.new - - apps.each do |app| - # add a callback to each server, decrementing the pending counter - app[:StartCallback] = lambda do - @pending_webrick_lock.synchronize do - @pending_webrick -= 1 - launched(apps) if @pending_webrick.zero? - end - end - end - end - - def launched(apps) - logger.info("Smart proxy has launched on #{apps.size} socket(s), waiting for requests") - SdNotify.ready - end end end diff --git a/lib/proxy/launcher/webrick.rb b/lib/proxy/launcher/webrick.rb new file mode 100644 index 000000000..8adc4e349 --- /dev/null +++ b/lib/proxy/launcher/webrick.rb @@ -0,0 +1,148 @@ +require 'sd_notify' + +module Launcher + class Webrick + include ::Proxy::Log + + attr_reader :launcher + + def initialize(launcher) + @launcher = launcher + end + + def launch + http_app = build_http_app + https_app = build_https_app + install_webrick_callback!(http_app, https_app) + + t1 = Thread.new { webrick_server(https_app, settings.bind_host, settings.https_port).start } unless https_app.nil? + t2 = Thread.new { webrick_server(http_app, settings.bind_host, settings.http_port).start } unless http_app.nil? + + Proxy::SignalHandler.install_traps + + (t1 || t2).join + end + + private + + def settings + launcher.settings + end + + def webrick_server(app, addresses, port) + server = ::WEBrick::HTTPServer.new(app) + addresses.each { |a| server.listen(a, port) } + server.mount "/", Rack::Handler::WEBrick, app[:app] + server + end + + def build_http_app + return unless launcher.http_enabled? + + plugins = launcher.plugins.select { |p| p[:state] == :running && p[:http_enabled] } + return unless plugins.any? + + app = Rack::Builder.new do + plugins.each { |p| instance_eval(p[:class].http_rackup) } + end + + { + :app => app, + :server => :webrick, + :DoNotListen => true, + :Port => settings.http_port, # only being used to correctly log http port being used + :Logger => ::Proxy::LogBuffer::TraceDecorator.instance, + :AccessLog => [], + :ServerSoftware => "foreman-proxy/#{Proxy::VERSION}", + :daemonize => false, + } + end + + def build_https_app + unless launcher.https_enabled? + logger.warn "Missing SSL setup, https is disabled." + return + end + + plugins = launcher.plugins.select { |p| p[:state] == :running && p[:https_enabled] } + return unless plugins.any? + + app = Rack::Builder.new do + plugins.each { |p| instance_eval(p[:class].https_rackup) } + end + + ssl_options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options] + ssl_options |= OpenSSL::SSL::OP_CIPHER_SERVER_PREFERENCE if defined?(OpenSSL::SSL::OP_CIPHER_SERVER_PREFERENCE) + # This is required to disable SSLv3 on Ruby 1.8.7 + ssl_options |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2) + ssl_options |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3) + ssl_options |= OpenSSL::SSL::OP_NO_TLSv1 if defined?(OpenSSL::SSL::OP_NO_TLSv1) + ssl_options |= OpenSSL::SSL::OP_NO_TLSv1_1 if defined?(OpenSSL::SSL::OP_NO_TLSv1_1) + + settings.tls_disabled_versions&.each do |version| + constant = OpenSSL::SSL.const_get("OP_NO_TLSv#{version.to_s.tr('.', '_')}") rescue nil + + if constant + logger.info "TLSv#{version} will be disabled." + ssl_options |= constant + else + logger.warn "TLSv#{version} was not found." + end + end + + { + :app => app, + :server => :webrick, + :DoNotListen => true, + :Port => settings.https_port, # only being used to correctly log https port being used + :Logger => ::Proxy::LogBuffer::Decorator.instance, + :ServerSoftware => "foreman-proxy/#{Proxy::VERSION}", + :SSLEnable => true, + :SSLVerifyClient => OpenSSL::SSL::VERIFY_PEER, + :SSLPrivateKey => load_ssl_private_key(settings.ssl_private_key), + :SSLCertificate => load_ssl_certificate(settings.ssl_certificate), + :SSLCACertificateFile => settings.ssl_ca_file, + :SSLOptions => ssl_options, + :SSLCiphers => launcher.ciphers, + :daemonize => false, + } + end + + def load_ssl_private_key(path) + OpenSSL::PKey::RSA.new(File.read(path)) + rescue Exception => e + logger.error "Unable to load private SSL key. Are the values correct in settings.yml and do permissions allow reading?", e + raise e + end + + def load_ssl_certificate(path) + OpenSSL::X509::Certificate.new(File.read(path)) + rescue Exception => e + logger.error "Unable to load SSL certificate. Are the values correct in settings.yml and do permissions allow reading?", e + raise e + end + + def install_webrick_callback!(*apps) + apps.compact! + + # track how many webrick apps are still starting up + @pending_webrick = apps.size + @pending_webrick_lock = Mutex.new + + apps.each do |app| + # add a callback to each server, decrementing the pending counter + app[:StartCallback] = lambda do + @pending_webrick_lock.synchronize do + @pending_webrick -= 1 + launched(apps) if @pending_webrick.zero? + end + end + end + end + + def launched(apps) + logger.info("Smart proxy has launched on #{apps.size} socket(s), waiting for requests") + SdNotify.ready + end + end +end