RailsでIP制限をPunditで実装してみる

前回に引きつづきRailsのアプリケーションレイヤーでなんとかIPによるアクセス制限をかける方法の模索。今回はAuthrization(認可)用のGemであるPunditを使ったアプローチです。

概要

  • いろいろあってアプリケーションレイヤーでIP制限したくなった
  • 前回はRailsでやる方法としてrack-attackでやる方法を考えてみた
  • 今回はPunditで認可の流れの一環として制限するアプローチをしてみた
  • どっちかというとrack-attackのほうが良いと思うけど、punditでも悪くない。でもCloudFlareとかと相性悪い

Pundit is何?

Punditは認可用のモジュールとして広くRailsで使われてるライブラリです。Railsのコントローラとアクションごとにポリシーを定義して制御します。シンプルなのが特徴で読み解くのも使い方に工夫するもの比較的楽にできます。

なぜやったか

  • こんなこと(あった | 思った | 必要だった | 困っている)
  • 興味をもったきっかけ
  • 選択肢が他にもありそうな場合は選定理由も

前回、Rack::Attackで実装して十分に制限はできています。とはいえやはり依存するものは少ないにこしたことはないのでちょっと制限するためだけにRack::Attackをいれるのに抵抗があるかもしれません。PunditならWebアプリケーションとして認可のために入れていることが多いので、どうせならこれでやってみる方法を考えてみました。

前準備

前回の前準備として下記のようにnamespaceをrouting内できっていました。

Rails.application.routes.draw do
  root "home#index"
  
  namespace :admin do # admin配下には特定のIPでないとアクセスできないようにしたい
    get "/", to: 'admin#index' 
  end
end

となるとadminで切られてるので、そこにBaseControllerをきってみます。

class Admin::BaseController < ApplicationController
	include Pundit
  before_action :autorize
end

こうしておけば/admin以下でpunditの認可メソッドによる検証がおこなわれますね。もちろんAdmin以外でもPunditを使うならApplicationControllerに入れてることもあると思いますので、その場合は不要です。

PunditでIP制限をする

PunditでIP制限をかけてみます。Punditにはもともとcontroller内でuserメソッドが生えていて、これはpundit_userというメソッドをオーバーライドすることで変更できます。

class Admin::BaseController < ApplicationController
  include Pundit
  before_action :autorize

  private

    # これを追加
    def pundit_user
      request.ip
    end
end

のようにしておきます。

そうするとリクエスト送信元のユーザのipが入ってきますので、あとはPunditのpolicyで

class Admin::BasePolicy < ApplicationPolicy
  def index?
    admin?
  end

  def show?
    admin?
  end

  def create?
    admin?
  end

  def new?
    create?
  end

  def update?
    admin?
  end

  def edit?
    update?
  end

  def destroy?
    admin?
  end

  # これで判断
  def admin?
    [
      "127.0.0.1",
      "123.456.7.8",
    ].include?(user)
  end
end

みたいにしてやればいいですね。
Policyの詳しい使い方にPunditのドキュメントを参照すると良いでしょう。

やってみた結果

今回はPunditを使って認可によるでIP制限をしてみました。

これを本当に本番で使うか、というとちょっと疑問が残ります。というのもIP制限というログインユーザではなくアクセスしてきた情報を元に認可するのはPunditの責務としてはイマイチ適切ではない気がします。

前回のRack層でやるほうがアプローチとしては良いとは思います。とはいえ、冒頭で書いたようにいろんな理由でPunditでやれなくないですね。もしくはログインしたユーザの情報と併用してみるのも良いかもしれません。

また、こちらももちろんCDNなどでIP元が変われば同じく対応できませんね。

前回と合わせて2アプローチでRails内でIP制限を試してみました。Punditを使ったことがある人も多いと思うので、仕組みを理解していればすぐにできることでしょう。

先月、引越したので、その振り返りをします。RailsでRack::AttackによるIP制限を実装する