概要
rack-attack は Rack ミドルウェアを使ってアクセス制御を実現できるミドルウェアです
Sinatra は Rack を実装しているので今回は Sinatra を使って Rack::Attack を使ってみました
ただ使ってみた感じだと blocklist
と throttle
しか動作していないように見えました
ローカルネットワークからのアクセスだったのでローカルネットワークからのアクセスは基本的には許可しているのかもしれません
環境
- macOS 10.15.7
- Ruby 2.7.1p83
- sinatra 2.0.7
- rack-attack 6.3.1
インストール
gem "sinatra"
gem "rack-attack"
gem "activesupport"
activesupport はスロットリングの機能に使います
有効にする
基本的には config.ru のみを編集していきます
require './app'
require "rack/attack"
use Rack::Attack
Rack::Attack.enabled = true
run MyApp
アプリは何でも OK です
require 'sinatra/base'
class MyApp < Sinatra::Base
get '/admin' do
'ok'
end
end
起動して問題なくアクセスできるか確認します
bundle exec rackup config.ru -o 0.0.0.0
curl localhost:9292/admin
アクセス可能な IP で制御する
アクセス可能な IP で制御してみます
blocklist というメソッドにブロックでアクセス拒否する条件を記載できます
request オブジェクトが受け取れるのでリクエストの情報を元に制御するのが基本になります
require './app'
require "rack/attack"
use Rack::Attack
Rack::Attack.enabled = true
Rack::Attack.blocklist("特定の IP のみ admin パスにアクセスできる") do |request|
!(request.ip == "127.0.0.1" && request.path == "/admin")
end
run MyApp
本来であれば safelist を使って否定構文を削除したほうが良いです
なぜか safelist がうまく動作せず blocklist を使っているためこのような書き方になっています
起動して動作確認してみましょう
ローカルからのアクセスは拒否されないことを確認しましょう
bundle exec rackup config.ru -o 0.0.0.0
curl localhost:9292/admin
=> ok
アクセス不可なパスを制御する
例えば特定のヘッダが付与されていないとアクセスできないパスを作ってみます
blocklist
を使ってパスとヘッダ情報をチェックしましょう
require './app'
require "rack/attack"
use Rack::Attack
Rack::Attack.enabled = true
Rack::Attack.blocklist("API キーが設定されているユーザのみ admin パスにアクセスできる") do |request|
!(request.env["HTTP_APIKEY"] == "secret-string" && request.path == "/admin")
end
run MyApp
起動して動作確認してみましょう
ちゃんとヘッダがないと 403 になることが確認できます
bundle exec rackup config.ru -o 0.0.0.0
curl localhost:9292/admin
=> Forbidden
curl -H "APIKEY: secret-string" localhost:9292/admin
=> ok
スロットリングしてみる
スロットリングとは時間と回数によるアクセス制御のことです
例えば 2 秒間に 5 回しかアクセスできないようなスロットリングをしてみます
require './app'
require "rack/attack"
require "active_support"
use Rack::Attack
Rack::Attack.enabled = true
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
Rack::Attack.throttle("2 秒間に 5 回しかアクセスできない", limit: 5, period: 2) do |request|
request.ip
end
run MyApp
起動して動作確認してみましょう
連続でアクセスすると ok が 5 回続いた後はすべて「Retry later (429)」になることが確認できます
そして 2 秒後に再度 5 回アクセスできることが確認できると思います
bundle exec rackup config.ru -o 0.0.0.0
for i in
seq 1 60; do curl localhost:9292/admin; done
=> okokokokok => Retry later
最後に
Sinatra で Rack::Attack を使ってみました
Rails で使う例はたくさん見かけたのですが Sinatra で使う方法はあまり紹介されていなかったので自分で試してみました
safelist がうまく動作していないように見えましたがもしかするとローカルネットワークからのアクセス意外であれば動作するかもしれません
スロットリングなどはパスやユーザ名などいろいろなパラメータでスロットリングを掛けることもできるので便利な機能かなと思います
参考サイト