Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HostAuthorization rack-protection middleware #2053

Merged
merged 34 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
fb2d76d
Add `permitted_hosts` setting
dentarg Nov 5, 2024
b5df6e1
Split bad/good cases in `HostAuthorization` specs
dentarg Nov 8, 2024
492589f
Less brittle `HostAuthorization` specs
dentarg Nov 8, 2024
841ee31
Simplify `HostAuthorization` tests for Sinatra
dentarg Nov 8, 2024
d2293d9
Document `HostAuthorization` in rack-protection README
dentarg Nov 8, 2024
c9c2625
Expose all the options for `HostAuthorization`
dentarg Nov 8, 2024
6e3711e
Test with multiple hostnames in headers
dentarg Nov 8, 2024
1701645
Improve test descriptions
dentarg Nov 8, 2024
071358e
Port should not matter for `HostAuthorization`
dentarg Nov 8, 2024
856d8e6
No need for `Rack::Protection::HostAuthorization.forwarded?` now
dentarg Nov 8, 2024
ffaf412
Support for `IPAddr` hosts
dentarg Nov 8, 2024
44dd856
Always look at the `Host` header
dentarg Nov 12, 2024
76e614a
Better grouping of `HostAuthorization` specs
dentarg Nov 12, 2024
e6264be
Test with more exotic header values
dentarg Nov 12, 2024
d179c0c
Support for subdomains
dentarg Nov 12, 2024
4652ad2
Support `Forwarded` header in `#forwarded?`
dentarg Nov 12, 2024
a167d8b
Use the same `require` order as other protections
dentarg Nov 13, 2024
3d5383d
Better `allow_if` example in tests
dentarg Nov 13, 2024
dd73d0e
`HostAuthorization` development settings
dentarg Nov 13, 2024
8466a9e
Test with `nil` `HTTP_HOST`
dentarg Nov 13, 2024
1081bf8
Fix typo in README
dentarg Nov 14, 2024
85d2079
Mention `IPAddr` objects
dentarg Nov 14, 2024
801e821
Add (optional) debug logging in `HostAuthorization`
dentarg Nov 14, 2024
579a2e6
Fix incorrect test description
dentarg Nov 15, 2024
affc961
Use keyword argument for `mock_app` helper
dentarg Nov 15, 2024
543bac2
Better test description
dentarg Nov 15, 2024
b1bf256
Fix typo in test description
dentarg Nov 15, 2024
9f0d130
Reject invalid hostnames
dentarg Nov 17, 2024
5947a6d
Document `host_authorization` defaults
dentarg Nov 17, 2024
b29d698
Lint `HostAuthorization` middleware
dentarg Nov 18, 2024
5ba1250
Lint `HostAuthorization` specs
dentarg Nov 18, 2024
3059727
Avoid `warning: character class has '-' without escape: ...`
dentarg Nov 18, 2024
4dfd160
Silence `Style/SafeNavigationChainLength` cop
dentarg Nov 18, 2024
7697337
No need to silence `SafeNavigationChainLength` cop
dentarg Nov 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Expose all the options for HostAuthorization
  • Loading branch information
dentarg committed Nov 8, 2024
commit c9c26253d509aaeb1ec334376ddfd834a424e8a5
29 changes: 19 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1992,6 +1992,25 @@ set :protection, :session => true
<tt>"development"</tt> if not available.
</dd>

<dt>host_authorization</dt>
dentarg marked this conversation as resolved.
Show resolved Hide resolved
<dd>
You can pass a hash of options to <tt>host_authorization</tt>,
to be used by the <tt>Rack::Protection::HostAuthorization</tt> middleware.
<dd>
<dd>
The middleware can block requests with unrecognized hostnames, to prevent DNS rebinding
and other host header attacks. It checks the <tt>Host</tt>, <tt>X-Forwarded-Host</tt>
and <tt>Forwarded</tt> headers.
</dd>
<dd>
Useful options are:
<ul>
<li><tt>permitted_hosts</tt> – an array of hostnames your app recognizes, if empty all hostnames are permitted</li>
dentarg marked this conversation as resolved.
Show resolved Hide resolved
<li><tt>status</tt> – the HTTP status code used in the response when a request is blcoked</li>
dentarg marked this conversation as resolved.
Show resolved Hide resolved
<li><tt>message</tt> – the body used in the response when a request is blocked</li>
<li><tt>allow_if</tt> – supply a <tt>Proc</tt> to use custom allow/deny logic, the proc is passed the request environment</li>
</dd>

<dt>logging</dt>
<dd>Use the logger.</dd>

Expand All @@ -2014,16 +2033,6 @@ set :protection, :session => true
paths.
</dd>

<dt>permitted_hosts</dt>
<dd>
An array of hostnames your app recognizes. Requests with an unrecognized hostname
will be stopped to prevent DNS rebinding and other host header attacks.
Checks the <tt>Host</tt>, <tt>X-Forwarded-Host</tt> and <tt>Forwarded</tt> headers.
</dd>
<dd>
By default the array is empty and all hostnames are permitted.
</dd>

<dt>port</dt>
<dd>Port to listen on. Only used for built-in server.</dd>

Expand Down
5 changes: 2 additions & 3 deletions lib/sinatra/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1867,8 +1867,7 @@ def setup_protection(builder)
end

def setup_host_authorization(builder)
options = { permitted_hosts: permitted_hosts }
builder.use Rack::Protection::HostAuthorization, options
builder.use Rack::Protection::HostAuthorization, host_authorization
end

def setup_sessions(builder)
Expand Down Expand Up @@ -1936,7 +1935,7 @@ def force_encoding(*args)
set :sessions, false
set :session_store, Rack::Session::Cookie
set :logging, false
set :permitted_hosts, []
set :host_authorization, {}
set :protection, true
set :method_override, false
set :use_code, false
Expand Down
2 changes: 1 addition & 1 deletion test/helpers_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ def status_app(code, &block)

it 'stops requests to non-permitted hosts' do
mock_app do
set :permitted_hosts, ['example.com']
set :host_authorization, { permitted_hosts: ['example.com'] }

get('/') { redirect '/foo' }
end
Expand Down
47 changes: 44 additions & 3 deletions test/host_authorization_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class HostAuthorization < Minitest::Test
it "allows requests based on the permitted hosts specified" do
allowed_host = "allowed.org"
mock_app do
set :permitted_hosts, [allowed_host]
set :host_authorization, { permitted_hosts: [allowed_host] }

get("/") { "OK" }
end
Expand All @@ -22,7 +22,7 @@ class HostAuthorization < Minitest::Test
it "stops requests based on the permitted hosts specified" do
allowed_host = "allowed.org"
mock_app do
set :permitted_hosts, [allowed_host]
set :host_authorization, { permitted_hosts: [allowed_host] }

get("/") { "OK" }
end
Expand All @@ -37,7 +37,48 @@ class HostAuthorization < Minitest::Test

it "allows any requests when no permitted hosts are specified" do
mock_app do
set :permitted_hosts, []
set :host_authorization, { permitted_hosts: [] }

get("/") { "OK" }
end

headers = { "HTTP_HOST" => "some-host.org" }
request = Rack::MockRequest.new(@app)
response = request.get("/", headers)

assert_equal 200, response.status
assert_equal "OK", response.body
end

it "stops the request using the configured response" do
allowed_host = "allowed.org"
status = 418
message = "No coffee for you"
mock_app do
set :host_authorization, {
permitted_hosts: [allowed_host],
status: status,
message: message,
}

get("/") { "OK" }
end

headers = { "HTTP_HOST" => "bad-host.org" }
request = Rack::MockRequest.new(@app)
response = request.get("/", headers)

assert_equal status, response.status
assert_equal message, response.body
end

it "allows custom logic with 'allow_if'" do
allowed_host = "allowed.org"
mock_app do
set :host_authorization, {
permitted_hosts: [allowed_host],
allow_if: ->(_env) { true },
}

get("/") { "OK" }
end
Expand Down