結論
クエリログのマスクに関してはこちらのコードで 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
の前段にマスクの処理を追加します。
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のログにパラメータが埋め込まれているため、該当箇所を正規表現で抜いて置換します。
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
良いマスクライフを!