Merge pull request #38829 from cbisnett/host_authorization_exclude
Add request exclusion to Host Authorization
This commit is contained in:
commit
67e69e8db4
@ -1,3 +1,9 @@
|
||||
* Allow `ActionDispatch::HostAuthorization` to exclude specific requests.
|
||||
|
||||
Host Authorization checks can be skipped for specific requests. This allows for health check requests to be permitted for requests with missing or non-matching host headers.
|
||||
|
||||
*Chris Bisnett*
|
||||
|
||||
* Add `config.action_dispatch.request_id_header` to allow changing the name of
|
||||
the unique X-Request-Id header
|
||||
|
||||
|
@ -4,7 +4,12 @@
|
||||
|
||||
module ActionDispatch
|
||||
# This middleware guards from DNS rebinding attacks by explicitly permitting
|
||||
# the hosts a request can be sent to.
|
||||
# the hosts a request can be sent to, and is passed the options set in
|
||||
# +config.host_authorization+.
|
||||
#
|
||||
# Requests can opt-out of Host Authorization with +exclude+:
|
||||
#
|
||||
# config.host_authorization = { exclude: ->(request) { request.path =~ /healthcheck/ } }
|
||||
#
|
||||
# When a request comes to an unauthorized host, the +response_app+
|
||||
# application will be executed and rendered. If no +response_app+ is given, a
|
||||
@ -66,9 +71,20 @@ def sanitize_string(host)
|
||||
}, [body]]
|
||||
end
|
||||
|
||||
def initialize(app, hosts, response_app = nil)
|
||||
def initialize(app, hosts, deprecated_response_app = nil, exclude: nil, response_app: nil)
|
||||
@app = app
|
||||
@permissions = Permissions.new(hosts)
|
||||
@exclude = exclude
|
||||
|
||||
unless deprecated_response_app.nil?
|
||||
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
||||
`action_dispatch.hosts_response_app` is deprecated and will be ignored in Rails 6.2.
|
||||
Use the Host Authorization `response_app` setting instead.
|
||||
MSG
|
||||
|
||||
response_app ||= deprecated_response_app
|
||||
end
|
||||
|
||||
@response_app = response_app || DEFAULT_RESPONSE_APP
|
||||
end
|
||||
|
||||
@ -77,7 +93,7 @@ def call(env)
|
||||
|
||||
request = Request.new(env)
|
||||
|
||||
if authorized?(request)
|
||||
if authorized?(request) || excluded?(request)
|
||||
mark_as_authorized(request)
|
||||
@app.call(env)
|
||||
else
|
||||
@ -94,6 +110,10 @@ def authorized?(request)
|
||||
(forwarded_host.blank? || @permissions.allows?(forwarded_host))
|
||||
end
|
||||
|
||||
def excluded?(request)
|
||||
@exclude && @exclude.call(request)
|
||||
end
|
||||
|
||||
def mark_as_authorized(request)
|
||||
request.set_header("action_dispatch.authorized_host", request.host)
|
||||
end
|
||||
|
@ -89,7 +89,7 @@ class HostAuthorizationTest < ActionDispatch::IntegrationTest
|
||||
end
|
||||
|
||||
test "blocks requests to unallowed host supporting custom responses" do
|
||||
@app = ActionDispatch::HostAuthorization.new(App, ["w.example.co"], -> env do
|
||||
@app = ActionDispatch::HostAuthorization.new(App, ["w.example.co"], response_app: -> env do
|
||||
[401, {}, %w(Custom)]
|
||||
end)
|
||||
|
||||
@ -158,4 +158,28 @@ class HostAuthorizationTest < ActionDispatch::IntegrationTest
|
||||
assert_response :ok
|
||||
assert_equal "Success", body
|
||||
end
|
||||
|
||||
test "exclude matches allow any host" do
|
||||
@app = ActionDispatch::HostAuthorization.new(App, "only.com", exclude: ->(req) { req.path == "/foo" })
|
||||
|
||||
get "/foo"
|
||||
|
||||
assert_response :ok
|
||||
assert_equal "Success", body
|
||||
end
|
||||
|
||||
test "exclude misses block unallowed hosts" do
|
||||
@app = ActionDispatch::HostAuthorization.new(App, "only.com", exclude: ->(req) { req.path == "/bar" })
|
||||
|
||||
get "/foo"
|
||||
|
||||
assert_response :forbidden
|
||||
assert_match "Blocked host: www.example.com", response.body
|
||||
end
|
||||
|
||||
test "config setting action_dispatch.hosts_response_app is deprecated" do
|
||||
assert_deprecated do
|
||||
ActionDispatch::HostAuthorization.new(App, "example.com", ->(env) { true })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -14,8 +14,8 @@ class Configuration < ::Rails::Engine::Configuration
|
||||
attr_accessor :allow_concurrency, :asset_host, :autoflush_log,
|
||||
:cache_classes, :cache_store, :consider_all_requests_local, :console,
|
||||
:eager_load, :exceptions_app, :file_watcher, :filter_parameters,
|
||||
:force_ssl, :helpers_paths, :hosts, :logger, :log_formatter, :log_tags,
|
||||
:railties_order, :relative_url_root, :secret_key_base,
|
||||
:force_ssl, :helpers_paths, :hosts, :host_authorization, :logger, :log_formatter,
|
||||
:log_tags, :railties_order, :relative_url_root, :secret_key_base,
|
||||
:ssl_options, :public_file_server,
|
||||
:session_options, :time_zone, :reload_classes_only_on_change,
|
||||
:beginning_of_week, :filter_redirect, :x, :enable_dependency_loading,
|
||||
@ -35,6 +35,7 @@ def initialize(*)
|
||||
@filter_redirect = []
|
||||
@helpers_paths = []
|
||||
@hosts = Array(([".localhost", IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0")] if Rails.env.development?))
|
||||
@host_authorization = {}
|
||||
@public_file_server = ActiveSupport::OrderedOptions.new
|
||||
@public_file_server.enabled = true
|
||||
@public_file_server.index_name = "index"
|
||||
|
@ -13,7 +13,7 @@ def initialize(app, config, paths)
|
||||
|
||||
def build_stack
|
||||
ActionDispatch::MiddlewareStack.new do |middleware|
|
||||
middleware.use ::ActionDispatch::HostAuthorization, config.hosts, config.action_dispatch.hosts_response_app
|
||||
middleware.use ::ActionDispatch::HostAuthorization, config.hosts, config.action_dispatch.hosts_response_app, **config.host_authorization
|
||||
|
||||
if config.force_ssl
|
||||
middleware.use ::ActionDispatch::SSL, **config.ssl_options,
|
||||
|
Loading…
Reference in New Issue
Block a user