The host_authorization plugin allows configuring an authorized host or an array of authorized hosts. Then in the routing tree, you can check whether the request uses an authorized host via the check_host_authorized!
method.
If the request doesn’t match one of the authorized hosts, the request processing stops at that point. Using this plugin can prevent DNS rebinding attacks if the application can receive requests for arbitrary hosts.
By default, an empty response using status 403 will be returned for requests with unauthorized hosts.
Because check_host_authorized!
is an instance method, you can easily choose to only check for authorization in certain routes, or to check it after other processing. For example, you could check for authorized hosts after serving static files, since the serving of static files should not be vulnerable to DNS rebinding attacks.
Usage¶ ↑
In your routing tree, call the check_host_authorized!
method at the point you want to check for authorized hosts:
plugin :host_authorization, 'www.example.com' plugin :public route do |r| r.public check_host_authorized! # ... end
Specifying authorized hosts¶ ↑
For applications hosted on a single domain name, you can use a single string:
plugin :host_authorization, 'www.example.com'
For applications hosted on multiple domain names, you can use an array of strings:
plugin :host_authorization, %w'www.example.com www.example2.com'
For applications supporting arbitrary subdomains, you can use a regexp. If using a regexp, make sure you use \A<tt> and <tt>\z
in your regexp, and restrict the allowed characters to the minimum required, otherwise you can potentionally introduce a security issue:
plugin :host_authorization, /\A[-0-9a-f]+\.example\.com\z/
For applications with more complex requirements, you can use a proc. Similarly to the regexp case, the proc should be aware the host contains user-submitted values, and not assume it is in any particular format:
plugin :host_authorization, proc{|host| ExternalService.allowed_host?(host)}
If an array of values is passed as the host argument, the host is authorized if it matches any value in the array. All host authorization checks use the ===
method, which is why it works for strings, regexps, and procs. It can also work with arbitrary objects that support ===
.
For security reasons, only the Host
header is checked by default. If you are sure that your application is being run behind a forwarding proxy that sets the X-Forwarded-Host
header, you should enable support for checking that header using the :check_forwarded
option:
plugin :host_authorization, 'www.example.com', check_forwarded: true
In this case, the trailing host in the X-Forwarded-Host
header is checked, which should be the host set by the forwarding proxy closest to the application. In cases where multiple forwarding proxies are used that append to the X-Forwarded-Host
header, you should not use this plugin.
Customizing behavior¶ ↑
By default, an unauthorized host will receive an empty 403 response. You can customize this by passing a block when loading the plugin. For example, for sites using the render plugin, you could return a page that uses your default layout:
plugin :render plugin :host_authorization, 'www.example.com' do |r| response.status = 403 view(:content=>"<h1>Forbidden</h1>") end
The block passed to this plugin is treated as a match block.
Classes and Modules
Public Class methods
# File lib/roda/plugins/host_authorization.rb 95 def self.configure(app, host, opts=OPTS, &block) 96 app.opts[:host_authorization_host] = host 97 app.opts[:host_authorization_check_forwarded] = opts[:check_forwarded] if opts.key?(:check_forwarded) 98 99 if block 100 app.define_roda_method(:host_authorization_unauthorized, 1, &block) 101 end 102 end