7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Railsのログでもちゃんとマスクする

Last updated at Posted at 2020-09-03

結論

クエリログのマスクに関してはこちらのコードActiveRecord::LogSubscriber にパッチをあてる

ログのマスクとは

ログ上の個人情報等の機密情報を特定の文字列に置換して隠すことを指しています。
マスクすることで機密情報の漏洩のリスクを低減させることができるため、マスクをすることは重要なことです。

Railsでのログのマスク

Railsでのログのマスクというと config.filter_parameters があります。
マスクしたいパラメータを指定することで、リクエストのログ上のパラメータをマスクしてくれる便利な機能です。

ただし、こちらのissueでも挙げられているように、クエリログのマスクは対象外であり、
また、Railsはデフォルトでproductionでもログレベルがdebugであるため、
productionでもクエリログが流れて機密情報が露出してしまいます。

Rails 6では filter_attribute がリリースされ、inspectされてログに機密情報が露出する問題も防げるようになりました。
ただ、こちらでもクエリログをマスクすることはできませんでした。
(Active Recordの実装の仕方によってはマスクできるようになる可能性はあるのかもしれません)

解決策

クエリログのマスクについていくつか解決策を提示します。

クエリログを出さない

そもそも config.log_level = :info にしてクエリログを出さないようにしてしまえば解決します。
ただ、全てのクエリログが見られなくなるため、できればこの方法を取りたくない場合もあると思います。

blousonを使う

blouson というgemがあり、特定のテーブルのクエリログをマスクする機能がある素晴らしいgemです。
ただ、マスクしたいテーブルの名前のprefixが secure_ である必要があるのと、条件に引っかかったクエリログ全体をマスクしてしまいます。
そのため、筆者の環境では少し使いづらく、欲を言うと filter_parameters のように特定のカラムのみマスクしたいという思いがありました。

ActiveRecord::LogSubscriberにパッチをあてる

提示する解決策の中で一番 filter_attribute の挙動に近いのがこちらではないかと思います。

以下のように、 ActiveRecord::LogSubscriber のログを出力する部分で、
特定の文字列のカラムをbindしているクエリログの場合は、bindしている値をマスクするようにします。

MySQLの場合

こちらで提示されている方法で、 render_bind の前段にマスクの処理を追加します。

config/initializers/sql_log_filter.rb
module SqlLogFilter
  FILTERS = Set.new(%w(email password))

  def render_bind(attr, value)
    value = '[FILTERED]' if FILTERS.include?(attr.name)
    super(attr, value)
  end
end

ActiveRecord::LogSubscriber.prepend SqlLogFilter

SQL Server(activerecord-sqlserver-adapter)の場合

直接sqlのログにパラメータが埋め込まれているため、該当箇所を正規表現で抜いて置換します。

config/initializers/sql_log_filter.rb
module SqlLogFilter
  FILTER_REGEX = /email|password/i

  def sql(event)
    binds = event.payload[:binds]
    return super(event) if binds.blank?

    binds.each_with_index do |bind, index|
      next unless bind.name.match?(FILTER_REGEX)

      event.payload[:sql] = event.payload[:sql].gsub(/@#{index} = (N?'(.*?)'|\d+)/) do |_|
        "@#{index} = [FILTERED]"
      end
    end

    super(event)
  end
end

ActiveRecord::LogSubscriber.prepend SqlLogFilter

良いマスクライフを!

7
2
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
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?