resqueを使っているのですが、本番環境でダッシュボードを見ようとしたらHost not permittedと言われて表示できなくなっていました。
なんの味気もない無慈悲な画面。
もちろんresque以外の通常ページはアクセスできますし、ローカル環境ではresqueダッシュボードも表示できます。
設定はよくあるこういう感じです。
# config/routes.rb
Rails.application.routes.draw do
mount Resque::Server.new, at: '/resque'
end
もちろん production.rb
で config.hosts
は正しく設定しています。
アップデートが原因
特にResque周りは変えていないのになんでだろう?と思ったら、最近依存関係にあるSinatraをアップデートしていました(このプロジェクトではrenovateで定期的に依存関係をアップデートしています)。
resqueの依存関係的には >= 0.9.2
ですが
s.add_dependency "sinatra", ">= 0.9.2"
このプロジェクトでは最近sinatraが 4.1.1 にアップデートされていました。
CHANGELOGを見ると4.1.0に以下の記述があり、これが原因の可能性が高そうです。
- New: Add host_authorization setting (#2053)
- Defaults to .localhost, .test and any IP address in development mode.
- Security: addresses CVE-2024-21510.
詳細調査
ローカルで再現しない
ローカルで RAILS_ENV=production
を指定して動かしたのですが、本番と同じ状況が再現しません。
config.hosts
に書いたのと同じホスト名(192.168.10.10
)にすると、resqueダッシュボード画面が表示される。config.hosts
に書いたのと違うホスト名として、適当なドメイン(hogehoge.com
などを適当に/etc/hosts
に書く)を使用すると、Rails側のBlocked hostエラーになる。
後者は良いとして、前者はなんでだろう。
developmentで動いていた
コードを読んでいて気づいたのですが、RAILS_ENV=production
を指定しても、マウントした Resque::Server
はそれを認識しないので、developmentモードで動いていました。
そのため localhost
.localhost
.test
0.0.0.0/0
::/0
が許可リストに入っていたようです。IPアドレスアクセスなら全て通過できるので、ローカル環境でアクセスできた理由はこれですね。また、本番環境ではドメイン名でアクセスしており、そのドメインが .localhost
ということはないので、弾かれていました。
本番サーバでdevelopmentモードで動いていたとは...なんてこったい(サーバ管理者しかアクセスできないページだから実害はない)
productionにしたら、設定しないでも許可された
ということで RACK_ENV=production
を指定すれば、許可ホスト未設定だからエラーになるのだろう、と思ったらアクセスできてしまいました。
許可ホストが空の場合、すべて禁止ではなくすべて許可という動作になるのですね。つまり、デフォルトではproductionはdevelopmentよりザルです。
完全に理解した
- 先にRails側の
config.hosts
によるチェックが走り、ここで弾かれたらRails側のエラーになる。- つまり、Sinatra側のチェックは無効化しても実害はないし、逆にSinatra側でチェックする前提でRailsの
config.host_authorization
でexclude
を指定しても良いはず。
- つまり、Sinatra側のチェックは無効化しても実害はないし、逆にSinatra側でチェックする前提でRailsの
- Rails側のチェックを通過すると、Sinatra側の
Rack::Protection::HostAuthorization
が走る。- developmentの場合、デフォルトではIPアドレスと
.localhost
.test
が許可。 - productionの場合、デフォルトでは素通し。
- developmentの場合、デフォルトではIPアドレスと
二重チェックが無駄とはいえ、せっかくある機能を無効化するのももったいないし、辻褄合わせて動かしたほうが気分が良さそうです。
最終的なコード
こんな雰囲気にすることで解決しました。
# config/routes.rb
require 'resque/server'
class ResqueServerWithPermittedHost < Resque::Server
set :environment, Rails.env.to_s
configure :production do
set :host_authorization, { permitted_hosts: Rails.application.config.hosts }
end
end
Rails.application.routes.draw do
mount ResqueServerWithPermittedHost.new, at: '/resque'
end