Rails
h2o

h2oの後ろにRails、アクセス制限とHTTPをHTTPSへリダイレクト

More than 1 year has passed since last update.

先人たちが色々踏んでおり、h2oとRailsの組み合わせでの問題は特に無くなっているような気がしたので使ってみた。

h2oはただのproxy.reverseでも色々よしなするようで、設定はとても少なくて済む。
ファイルがあったら〜な処理も並べるだけで、次へ次へと進んでいく。ほかはセキュリティ要件などに応じて、それ系のヘッダをつけておく位でよいかな。

h2o.conf
access-log: /dev/stdout
user: nobody

hosts:
  "*":
    listen: 80
    listen:
      port: 443
      ssl:
        certificate-file: certs/server.crt
        key-file: certs/server.key
    paths:
      "/":
        mruby.handler-file: limit_access.rb
        file.dir: /srv/app/public
        proxy.reverse.url: http://rails:8080/
        proxy.preserve-host: ON

これはDocker-Compose管理で、railsサービスにlinkしています。なのでfile.dirに指定しているpublicはVOLUMEです。

アクセス制限とHTTP接続のリダイレクト

今回これにテスト環境用のアクセス制限と、HTTP=>HTTPSへのリダイレクトをつけることにしました。だったらmrubyかなー。

アクセス制限は下記で紹介されているmruby-ipaddress_matcherでします。

で、リダイレクトの要件、ホスト名をコンフィグに書いてしまってよいならh2oのredirectでいいんですが、実はドメインがまだ決まってないとかそういうこともあるのでアクセス制限のついでにやることにしました。

IP制限部分は参考サイトにちょっと環境変数での分岐をつけて、IPを評価するまえにenv['rack.url_scheme']をチェックしてhttpなら出なおしてもらう処理を追加しました。もちろんハンドラを分けても大丈夫のはず。

limit_access.rb
case ENV['ACL_ENVIRONMENT']
when 'public'
ALLOW_CIDRS = %w(
0.0.0.0/0
)
else
ALLOW_CIDRS = %w(
x.x.x.x/32
x.x.x.x/32
x.x.x.x/28
)
end

class Acl
  def initialize(*args)
    @allow_regexps = ALLOW_CIDRS.map {|cidr| IPAddressMatcher::CIDR.new(cidr).to_regexp }
    super
  end

  def call(env)
    if env['rack.url_scheme'] == 'http'
      return [301, {'Location' => "https://#{env['HTTP_HOST']}/"}, []]
    end
    if @allow_regexps.select {|allow_regexp| env['REMOTE_ADDR'] =~ allow_regexp }.empty?
      [403, {'Content-Type' => 'text/plain;charset=utf-8'}, ['Forbidden']]
    else
      [399, {}, []]
    end
  end
end

Acl.new

なお、ここに書いた例ではenv['HTTP_HOST']を直接Locationに使ってますが、HTTP1.0などで来られてHostヘッダがないとここはSERVER_NAMEと同じ値が入ります。先のh2oコンフィグでは"*"になっちゃいますね。
HTTP_HOSTが空ならREMOTE_ADDRを使うようにすれば良いケースもあります。それもDockerの内部ネットワークを使うとコンテナのIPになってたりして微妙。まあ環境に応じて適当に変更します。

ついでにHSTSをしたい場合はheader.append: "Strict-Transport-Security: max-age=39420000"と入れたりします。