RailsのActiveJobログで秘匿情報をマスクする方法
背景
私が開発に携わっているプロダクトでは、Rails アプリケーションのログを Datadog に収集していました。また、ログとして収集する際に、ユーザーに関する情報(ユーザー名、メールアドレス等)にはマスキングを行っていました。
マスキングの処理としては、以下のコードのように一般的な手法で対応していました。
Rails.application.config.filter_parameters += %i[email]
発生した問題
Datadog のログにメールアドレスが表示されていることに気づきました。具体的には、ActionMailer を実行するための ActiveJob のログにメールアドレスが表示されていました。
これは ActiionMailer の with メソッドにメールアドレスをそのまま渡していることが原因でした。
# 例
HogeMailer
.with(email: 'hoge@example.com')
.account_invitation
.deliver_later
この問題を解決するためのアプローチとして、メールアドレスをそのまま渡すのではなく、メールアドレスを保持するモデルのインスタンスを渡せば、ログには Global ID の情報のみが表示されます。
# 例
HogeMailer
.with(user: User.first)
.account_invitation
.deliver_later
しかし、多くの ActionMailer がメールアドレスをパラメータとして受け取るよう実装されており、変更が大変でしたので、ActiveJob のログをマスクする方針で対応しました。
やったこと
ActionJob のログが定義されている箇所を調べた結果、ActiveJob::LogSubscriber#format で定義されていることがわかりました。
そこで、このメソッドにモンキーパッチを適用することにしました。
具体的には、arg が Hash の場合に実行される arg.transform_values { |value| format(value) }
の前に、arg にマスク処理を実行するようにしました。
また、マスク処理にはログをマスキングする際に呼び出される ActiveSupport::ParameterFilterクラスを使用しました。
ActiveSupport::ParameterFilter を使用した例は以下のようになります。
f = ActiveSupport::ParameterFilter.new(%i[email])
f.filter({
email: 'hoge@example.com',
foo_email: 'foo@example.com',
bar: {
email_baz: 'baz@example.com'
}
})
# => {:email=>"[FILTERED]",
# :foo_email=>"[FILTERED]",
# :bar=>{:email_baz=>"[FILTERED]"}}
そして、実装したコードは以下の通りです。
この実装により、ActiveJob のログも期待通りマスクされるようになりました。
Rails.application.config.filter_parameters += %i[email]
+ ActiveSupport.on_load(:active_job) do
+ module ActiveJob
+ class LogSubscriber
+ private
+ def format(arg)
+ case arg
+ when Hash
+ # arg にマスクする処理を実行するようにした。
+ mask_parameter(arg).transform_values { |value| format(value) }
+ when Array
+ arg.map { |value| format(value) }
+ when GlobalID::Identification
+ begin
+ arg.to_global_id
+ rescue StandardError
+ arg
+ end
+ else
+ arg
+ end
+ end
+ def mask_parameter(args)
+ parameter_filter = ActiveSupport::ParameterFilter.new(%i[email])
+ parameter_filter.filter(args)
+ end
+ end
+ end
+ end
参考
Discussion