LoginSignup
25
24

More than 5 years have passed since last update.

rack-contribをやめてrack-attackに乗り換えた

Posted at

課題

rack-contribというgemがあるのですが、ここ数年メンテナンスされておらず、プロジェクトをRails5にアップデートしたタイミングでrackのバージョンの依存性解決ができなくなっていました。他の方がrack 2系に対応したものをPull Requestしていたので、それを使えばひとまず延命はできたのですが、Circle CIでのテストがうまく動かなくなったので、捨て去る決意をしました。

ちなみに、rack-contribはIP制限の用途で使っていました。

調査

IP制限を実現する方法

IP制限を実現する方法は、

  1. Railsアプリのbefore_actionなどで実装する
  2. 他のRack Middlewareを使う(他のgemを探す)

という2つが思い浮かびました。

今回は2の、他のRack Middlewareを使う方を選びました。まー車輪の再発明するとテストも大変だし。

rack-attackの採用

IP制限できるgemは、

の2つを見つけたのですが、alpacaはrack-contribと同様あんまり活発ではないようなので、rack-attackを試してみることにしました。

ちなみに、rack-attackは、名前の通りですが、本来はDOS攻撃を防ぐためのgemです。
ただ、IP制限をすることもできます。ホワイトリスト、ブラックリストが使えます。

使い方(ブラックリスト方式)

導入

Gemfileに記述。

Gemfile
# rack-contribは削除
# gem 'rack-contrib', require: 'rack/contrib', git: 'https://github.com/pabse/rack-contrib.git', branch: 'rack_ruby_2+'
gem 'rack-attack', '~> 5.0.1'

そして、bundle installを実行します。

基本

まず、config/application.rbでrack-attackを有効化します。

config/application.rb
config.middleware.use Rack::Attack

次に、config/initializers/rack-attack.rbを作ります。
そこに、ブラックリストを作ります。

config/initializers/rack-attack.rb
class Rack::Attack
  OFFICE_IP_ADDRESS = 'XXX.XXX.XXX.XXX'

  blocklist('only allow from office') do |req|
    req.path.match(%r{^/admin}) && (OFFICE_IP_ADDRESS != req.ip)
  end
end

ちなみに、blacklistからblocklistになった模様です(blacklistって書くとwarningが出る)。

応用

基本の通りでもいいのですが、コード側はあまりいじりたくありません。
rack-contribの際に使っていたアクセスリスト(access.yml)を使い回したいなーという欲求がでてきました。

ちなみに元々のconfig/access.ymlはこんな感じです。

config/access.yml
localhost: &localhost
  /admin:
    - 127.0.0.1
    - 192.168.0.0/24

development:
  <<: *localhost

test:
  <<: *localhost

production: &production
  /admin:
    - XXX.XXX.XXX.XXX(拠点AのIP)
    - XXX.XXX.XXX.XXX(拠点BのIP)
    - XXX.XXX.XXX.XXX(拠点CのIP)

staging:
  <<: *production

これを使い回すために、config/initializers/rack-attack.rbを修正します。

config/initializers/rack-attack.rb
class Rack::Attack
  OFFICE_IP_ADDRESSES = YAML.load(File.read("#{Rails.root.join('config')}/access.yml"))[Rails.env]

  blocklist('only allow from office') do |req|
    OFFICE_IP_ADDRESSES.any? do |path, ip_addresses|
      ip_addrs = ip_addresses.map { |ip_address| IPAddr.new(ip_address) }
      req.path.match(%r{^#{path}}) && ip_addrs.none? {|ip_addr| ip_addr.include?(req.ip) }
    end
  end
end

ミソなのは、IPAddrクラスを使って、サブネットマスクを使ったIP範囲(192.168.0.0/24とか)での検証を行えるようにしていることです。
また、Array#none?を使って、アクセスしてきたIPアドレスが許可されたIPアドレスの範囲にない場合、trueを返すようにしています。

上記のaccess.ymlだと、/adminから始まるpathで、かつ、許可されていないIPアドレスの場合に403 forbiddenを返すようになっています。

これで、もし許可IPを変えたい場合でもconfig/access.ymlを編集するだけでよくなりました。

まとめ

IP制限は今のところ、rack-contribよりはrack-attackを使おう!

25
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
24