課題
rack-contribというgemがあるのですが、ここ数年メンテナンスされておらず、プロジェクトをRails5にアップデートしたタイミングでrackのバージョンの依存性解決ができなくなっていました。他の方がrack 2系に対応したものをPull Requestしていたので、それを使えばひとまず延命はできたのですが、Circle CIでのテストがうまく動かなくなったので、捨て去る決意をしました。
ちなみに、rack-contribはIP制限の用途で使っていました。
調査
IP制限を実現する方法
IP制限を実現する方法は、
- Railsアプリのbefore_actionなどで実装する
- 他のRack Middlewareを使う(他のgemを探す)
という2つが思い浮かびました。
今回は2の、他のRack Middlewareを使う方を選びました。まー車輪の再発明するとテストも大変だし。
rack-attackの採用
IP制限できるgemは、
の2つを見つけたのですが、alpacaはrack-contribと同様あんまり活発ではないようなので、rack-attackを試してみることにしました。
ちなみに、rack-attackは、名前の通りですが、本来はDOS攻撃を防ぐためのgemです。
ただ、IP制限をすることもできます。ホワイトリスト、ブラックリストが使えます。
使い方(ブラックリスト方式)
導入
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.middleware.use Rack::Attack
次に、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はこんな感じです。
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
を修正します。
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を使おう!