diff --git a/lib/launcher.rb b/lib/launcher.rb index 0967485bb..996810599 100644 --- a/lib/launcher.rb +++ b/lib/launcher.rb @@ -1,5 +1,13 @@ require 'openssl' require 'proxy/log' + +begin + require 'rackup/handler/webrick' + WEBRICK_HANDLER = Rackup::Handler::WEBrick +rescue LoadError + require 'rack' + WEBRICK_HANDLER = Rack::Handler::WEBrick +end require 'proxy/settings' require 'proxy/signal_handler' require 'proxy/log_buffer/trace_decorator' @@ -191,7 +199,7 @@ def webrick_server(app, addresses, port) rescue ::OpenSSL::SSL::SSLError => e raise "Invalid tls_ciphers value '#{app[:SSLCiphers]}': #{e.message}" end - server.mount "/", Rack::Handler::WEBrick, app[:app] + server.mount "/", WEBRICK_HANDLER, app[:app] # WEBrick 1.9.x does not support :SSLMinVersion in its config hash, so we # apply min_version= directly on the SSL context after WEBrick creates it. diff --git a/lib/proxy/hsts_middleware.rb b/lib/proxy/hsts_middleware.rb index 4b982deef..1aa0085b6 100644 --- a/lib/proxy/hsts_middleware.rb +++ b/lib/proxy/hsts_middleware.rb @@ -4,14 +4,18 @@ module Proxy # is needed. # https://www.tenable.com/plugins/nessus/142960 class HstsMiddleware + # Lowercase is required by Rack 3 (https://github.com/rack/rack/issues/1592); + # Rack 2 doesn't care about header key case, so this works for both. + HEADER_KEY = 'strict-transport-security'.freeze + def initialize(app) @app = app end def call(env) status, headers, body = @app.call(env) - if env['HTTPS'] == 'on' && !headers.include?('Strict-Transport-Security') - headers['Strict-Transport-Security'] = 'max-age=31536000' + if env['HTTPS'] == 'on' && !headers.include?(HEADER_KEY) + headers[HEADER_KEY] = 'max-age=31536000' end [status, headers, body] end diff --git a/lib/smart_proxy_main.rb b/lib/smart_proxy_main.rb index 581a07c6e..14a979111 100644 --- a/lib/smart_proxy_main.rb +++ b/lib/smart_proxy_main.rb @@ -46,7 +46,11 @@ module Proxy ::Sinatra::Base.use ::Proxy::RequestIdMiddleware ::Sinatra::Base.use ::Proxy::LoggerMiddleware ::Sinatra::Base.use ::Proxy::HstsMiddleware - ::Sinatra::Base.set :env, :production + # Without this, Sinatra defaults to :development, which in Sinatra 4 + # enables strict Rack::Protection::HostAuthorization that rejects + # requests whose Host header isn't localhost — breaking all real + # deployments where Foreman reaches smart-proxy by hostname. + ::Sinatra::Base.set :environment, :production ::Sinatra::Base.register ::Sinatra::Authorization require 'root/root' diff --git a/modules/registration/registration_api.rb b/modules/registration/registration_api.rb index a45af152b..6fa43c16d 100644 --- a/modules/registration/registration_api.rb +++ b/modules/registration/registration_api.rb @@ -1,6 +1,11 @@ require 'registration/proxy_request' class Proxy::Registration::Api < ::Sinatra::Base + # Needed so `logger` resolves to Proxy::LogBuffer::Decorator (which implements + # #exception, used in the rescue blocks below) instead of Sinatra's own null + # logger, which is a plain ::Logger with no #exception method as of Sinatra 4. + helpers ::Proxy::Helpers + # Cache for the global registration script (GET /register). # # The script is identical for all hosts sharing the same registration diff --git a/smart_proxy.gemspec b/smart_proxy.gemspec index 4dc05065d..cb6b7b58c 100644 --- a/smart_proxy.gemspec +++ b/smart_proxy.gemspec @@ -17,10 +17,11 @@ Gem::Specification.new do |s| s.add_dependency 'json' s.add_dependency 'logging' s.add_dependency 'ostruct' - s.add_dependency 'rack', '>= 1.3' + s.add_dependency 'rack', '>= 2.0', '< 4' + s.add_dependency 'rackup' s.add_dependency 'rexml', '~> 3.2' s.add_dependency 'sd_notify', '~> 0.1' - s.add_dependency 'sinatra', '~> 2.0' + s.add_dependency 'sinatra', '>= 2.0', '< 5' s.add_dependency 'webrick', '~> 1.0' s.description = <<~EOF Foreman Proxy is used via The Foreman Project, it allows Foreman to manage