Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: resque管理画面がHost not permittedになった

resqueを使っているのですが、本番環境でダッシュボードを見ようとしたらHost not permittedと言われて表示できなくなっていました。

resque/resque - GitHub

なんの味気もない無慈悲な画面。

もちろんresque以外の通常ページはアクセスできますし、ローカル環境ではresqueダッシュボードも表示できます。

設定はよくあるこういう感じです。

# config/routes.rb
Rails.application.routes.draw do
  mount Resque::Server.new, at: '/resque'
end

もちろん production.rbconfig.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_authorizationexclude を指定しても良いはず。
  • Rails側のチェックを通過すると、Sinatra側の Rack::Protection::HostAuthorization が走る。
    • developmentの場合、デフォルトではIPアドレスと .localhost .test が許可。
    • productionの場合、デフォルトでは素通し。

二重チェックが無駄とはいえ、せっかくある機能を無効化するのももったいないし、辻褄合わせて動かしたほうが気分が良さそうです。

最終的なコード

こんな雰囲気にすることで解決しました。

# 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

関連記事

secret_key_baseが漏れると何が起きるのか実際に試してみた


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。