2020年11月2日月曜日

Sinatra で Rack::Attack を使ってみる

概要

rack-attack は Rack ミドルウェアを使ってアクセス制御を実現できるミドルウェアです
Sinatra は Rack を実装しているので今回は Sinatra を使って Rack::Attack を使ってみました
ただ使ってみた感じだと blocklistthrottle しか動作していないように見えました
ローカルネットワークからのアクセスだったのでローカルネットワークからのアクセスは基本的には許可しているのかもしれません

環境

  • macOS 10.15.7
  • Ruby 2.7.1p83
    • sinatra 2.0.7
    • rack-attack 6.3.1

インストール

  • vim Gemfile
gem "sinatra"
gem "rack-attack"
gem "activesupport"
  • bundle install

activesupport はスロットリングの機能に使います

有効にする

基本的には config.ru のみを編集していきます

  • vim config.ru
require './app'
require "rack/attack"

use Rack::Attack
Rack::Attack.enabled = true

run MyApp

アプリは何でも OK です

  • vim app.rb
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 オブジェクトが受け取れるのでリクエストの情報を元に制御するのが基本になります

# coding: utf-8
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 を使ってパスとヘッダ情報をチェックしましょう

  • vim config.ru
# coding: utf-8
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 回しかアクセスできないようなスロットリングをしてみます

# coding: utf-8
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 inseq 1 60; do curl localhost:9292/admin; done

=> okokokokok => Retry later

最後に

Sinatra で Rack::Attack を使ってみました
Rails で使う例はたくさん見かけたのですが Sinatra で使う方法はあまり紹介されていなかったので自分で試してみました

safelist がうまく動作していないように見えましたがもしかするとローカルネットワークからのアクセス意外であれば動作するかもしれません
スロットリングなどはパスやユーザ名などいろいろなパラメータでスロットリングを掛けることもできるので便利な機能かなと思います

参考サイト

0 件のコメント:

コメントを投稿