はじめに
外部API への通信内容を yaml で記録し、モックを作りやすくする vcr が便利でよく使っています。
その中で、機密情報を伴うリクエストを vcr で取り扱うとき、機密情報をマスクする方法について調べました。
機密情報をマスクしたい
POST でユーザー名・パスワードをパラメータで送信することを例とします。
いずれもダミーの例として、以下とします。
- username: yamadataro
- password: test12345678
vcr を使うと、リクエストの内容が以下のような yaml ファイルで保存されるのですが、その際リクエストパラメータに username=yamadataro&password=test12345678
というように、機密情報が平文で残ってしまう課題がありました。
- request:
method: post
uri: https://test.api.com/auth
body:
encoding: US-ASCII
string: grant_type=password&username=yamadataro&password=test12345678
headers:
User-Agent:
- Faraday v0.12.2
Content-Type:
- application/x-www-form-urlencoded
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
外部に漏れても問題ない情報であればいいのですが、そうでない場合もあると思うので、なるべくマスクできればいいなと考えていました。
filter_sensitive_data を使えばできる
調べたところ、filter_sensitive_data というメソッドがあるようでした。
vcr の config に設定を記載することで、マスクしたい文字列にマスクを掛けることができます。
以下の例ですと、
VCR.config do |c|
c.stub_with :fakeweb
c.cassette_library_dir = 'cassettes'
c.filter_sensitive_data('<USERNAME>') { 'yamadataro' }
c.filter_sensitive_data('<PASSWORD>') { 'test12345678' }
end
先ほどの yaml は yamadataro
と test12345678
の部分がそれぞれマスクされます。yamadataro
と test12345678
は、ENV['USERNAME']
や、Rails.application.secrets.username
などの定数を割り当てて使うことが多いかなと思います。
- request:
method: post
uri: https://test.api.com/auth
body:
encoding: US-ASCII
string: grant_type=password&username=<USERNAME>&password=<PASSWORD>
headers:
User-Agent:
- Faraday v0.12.2
Content-Type:
- application/x-www-form-urlencoded
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
filter_sensitive_data は文字列完全一致が必要そう
しかし、filter_sensitive_data
では文字列完全一致をさせる必要があるというところで少しハマりました。
例えば、URL の中にリクエストパラメータがある場合、パラメータ内の文字列がエスケープされている場合があります。example@example.com
をマスクしたい場合を考えます。
VCR.config do |c|
c.stub_with :fakeweb
c.cassette_library_dir = 'cassettes'
c.filter_sensitive_data('<USERNAME>') { 'example@example.com' }
end
しかし、URL 内のユーザー名にあたる文字列は、example%40example.com
とエスケープされているため、filter_sensitive_data
とマッチせず使えませんでした。
# 中略
- request:
method: post
uri: https://test.api.com/auth
body:
encoding: US-ASCII
string: grant_type=password&username=example%40example.com
# 中略
このように、 URL 内でエスケープされている文字列は、エスケープしてあげる必要がありました。例としては以下のようになります。
VCR.config do |c|
c.stub_with :fakeweb
c.cassette_library_dir = 'cassettes'
c.filter_sensitive_data('<USERNAME>') { CGI.escape('example@example.com') }
end
ソースコード確認
上記の挙動について、念の為該当のソースコードも確認しました。
filter_sensitive_data について
-
filter_sensitive_data
はdefine_cassette_placeholder
に alias で登録されている -
define_cassette_placeholder
が実行され、interaction.filter!(orig_text, placeholder)
でオリジナルテキストと置き換え後テキストのフィルタリングが行われる
filter! について
-
text
,replacement_text
がどちらもto_s
されている -
filter_object!
呼ばれる -
object.gsub!(text, replacement_text) if object.include?(text)
で、置き換え前のテキストが含まれていれば置換処理する
上記から、実際の置換処理は gsub!
で実行されていますが、置換前・置換後の対象はどちらも文字列になってるので、完全一致させる必要がありそうでした。
おわりに
使い方をドキュメントやソースコードから追って確認するのは勉強になりますね。継続して使い方を確認しようと思います。