Skip to content

Fixes #38120 - Support Sinatra 4 and Rack 3 - #955

Merged
ekohl merged 1 commit into
theforeman:developfrom
lhellebr:sinatra4
Aug 25, 2026
Merged

Fixes #38120 - Support Sinatra 4 and Rack 3#955
ekohl merged 1 commit into
theforeman:developfrom
lhellebr:sinatra4

Conversation

@lhellebr

@lhellebr lhellebr commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Updates bundle to new versions.

In Rack 3, headers must be lowercase rack/rack#1592.

We now use Rackup::... instead of Rack::...

test_global_500 and test_host_500 in RegistrationRegisterApiTest have been failing, both raising NoMethodError: undefined method 'exception' for #<Logger:...> from modules/registration/registration_api.rb's error-handling blocks. The cause was that Proxy::Registration::Api never included helpers ::Proxy::Helpers (unlike every sibling API class), so its logger calls resolved through Sinatra's own built-in logger helper rather than smart-proxy's real logger; under Sinatra 2 that helper pointed at Rack::NullLogger when logging was disabled, and while Rack::NullLogger never actually had an #exception method either, the test's Mocha stub on that exact class silently intercepted the call anyway, whereas Sinatra 4 replaced that null-logger path with a plain stdlib ::Logger, which the test's stub no longer targets and which genuinely has no #exception method, turning a previously-masked bug into a real crash. The fix was adding helpers ::Proxy::Helpers to Proxy::Registration::Api, which makes logger resolve to Proxy::LogBuffer::Decorator.instance (which does implement #exception), matching the pattern already used by every other API class in the codebase — after which the full test suite passed.

@lhellebr lhellebr changed the title Support Sinatra 4 and Rack 3 Fixes #38120 Support Sinatra 4 and Rack 3 Aug 18, 2026
@lhellebr
lhellebr marked this pull request as draft August 18, 2026 14:55
@lhellebr

Copy link
Copy Markdown
Contributor Author

Converting to draft because we need to investigate how this will work on rpm based deployments with Foreman: it will likely require bump to Rails 7.1.

@ekohl

ekohl commented Aug 18, 2026

Copy link
Copy Markdown
Member

This is largely a duplicate of #952. Based on #952 (comment) it would be great to separate the Rack 3 compatibility into its own PR to be cherry picked into Foreman 5.0.

@lhellebr

Copy link
Copy Markdown
Contributor Author

Updated so this supports EITHER Sinatra 2 + Rack 2 OR Sinatra 4 + Rack 3.
This way, this PR shouldn't depend on Foreman's Rails 7.1 PR.

@lhellebr
lhellebr marked this pull request as ready for review August 20, 2026 12:34
Comment on lines +4 to +7
# 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this is a bug we've had that went unnoticed for a long time

Comment thread lib/proxy/hsts_middleware.rb Outdated
Comment thread lib/launcher.rb Outdated
@ibuclaw

ibuclaw commented Aug 21, 2026

Copy link
Copy Markdown

Note: you'll also need to set APP_ENV or RACK_ENV to something other than development, otherwise the proxy will refuse all connections even if :trusted_hosts: is unset.

See sinatra/sinatra#2053 which defaults to permitting localhost and test domains only.

Alternatively forwarding foreman-proxy trusted hosts setting to Sinatra permitted hosts I guess could also work. To be added in all classes that inherit from Sinatra::Base via configure do ... end?

Comment thread lib/smart_proxy_main.rb
# 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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intended to be a fix for an issue that @ibuclaw raised.
The other option (using trusted_hosts in permitted_hosts context) wouldn't IMO make sense because one is about request origin, the other is about request target.

This line has been there for a long time and I think it was a bug - the correct symbol is :environment https://sinatrarb.com/configuration.html . It seems like it has been unnoticed for a long time. Note that changing environment can have consequences beyond just this fix - different security rules or logging content and others - I'm not certain what exactly. So this change needs to be weighed carefully.
In either case, the test suite is still green and the proxy still seems to work after this change.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks.

That wrong name explains why I couldn't find environment anywhere.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for warning me about this, @ibuclaw !

@lhellebr lhellebr changed the title Fixes #38120 Support Sinatra 4 and Rack 3 Fixes #38120 - Support Sinatra 4 and Rack 3 Aug 24, 2026

@michalgritzbach michalgritzbach left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, I think that the hardcoded :production env is a sane default. From what I've found, it could potentially hide traces in failed tests - if this would be an issue, adding ::Sinatra::Base.set :raise_errors, true to lib/smart_proxy_for_testing.rb should fix that. But I wouldn't consider it a blocker.

@ekohl

ekohl commented Aug 24, 2026

Copy link
Copy Markdown
Member

@lhellebr could you please fix up the Redmine issue reference in the commit message so that test passes?

Allow smart-proxy to run on either Sinatra 2 + Rack 2 or
Sinatra 4 + Rack 3, depending on what is installed. This enables
Debian 13 support (which ships Rack 3) without forcing all platforms
to upgrade simultaneously.

gemspec: Widen constraints to rack >= 2.0 < 4, sinatra >= 2.0 < 5,
add rackup (needed for Rack 3, harmless on Rack 2).

launcher.rb: Resolve the WEBrick handler at load time via
require/rescue LoadError — Rackup::Handler::WEBrick for Rack 3,
Rack::Handler::WEBrick for Rack 2.

hsts_middleware.rb: Use unconditional lowercase header key. Rack 3
requires it; Rack 2 does case-insensitive lookups internally so
lowercase works for both.

registration_api.rb: Add missing helpers ::Proxy::Helpers so logger
resolves to Proxy::LogBuffer::Decorator (which implements #exception)
instead of Sinatra's built-in logger. Under Sinatra 2 this was masked
by Rack::NullLogger swallowing unknown methods; Sinatra 4 uses a real
::Logger that crashes on #exception.

smart_proxy_main.rb: Fix Sinatra environment setting from :env (a
no-op custom attribute) to :environment (the real Sinatra setting).
Without this, Sinatra defaults to development mode, which in Sinatra 4
enables Rack::Protection::HostAuthorization that rejects requests
whose Host header isn't localhost — breaking all deployments where
Foreman reaches smart-proxy by hostname.

Note: Sinatra 4's Rack::Protection::HostAuthorization can restrict
accepted Host headers to a configured allow-list, but in production
mode it allows all hosts by default. This means we don't yet benefit
from the additional security Sinatra 4 offers here. Configuring
permitted hosts (e.g. via foremanctl or the installer) should be
addressed as follow-up work; for now this is not a regression since
Sinatra 2 never had this protection at all.
@lhellebr

Copy link
Copy Markdown
Contributor Author

@ekohl done

@ofedoren ofedoren left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM as well. Do we consider configuring permitted_hosts in a follow up?

@lhellebr

Copy link
Copy Markdown
Contributor Author

@ekohl ekohl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@ekohl
ekohl merged commit c2af3d3 into theforeman:develop Aug 25, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants