Nginxのアクセスログに機密情報を残したくない方のためのlua-resty-querymask

最近Facebookがパスワードを平文で保存しているというニュースがありましたね。

jp.techcrunch.com

またシステム的に保持していなくても、意図せずアクセスログに情報が残ってしまうことも考えられます。
その対策として安全にアクセスログのマスク処理を行う lua-resty-querymaskというモジュールをご紹介いたします。


使い方

OpenResty のモジュールとして動作します。 以下、README のSYNOPSIS のママです。

local querymask = require "resty.querymask"
local q = querymask:new({
    mode = "writelist",
    mask_part_string = "*",
    mask_part_length = 3,
    mask_fill_string = "*MASK*",
    mask_hash_seed   = "hogefugapiyo",
    max_field_length = 512,
    fields = {
      origin = {"attr1", "attr2"},
      part   = {"attr3"},
      fill   = {"attr4"},
      hash   = {"attr5"},
    }
})
print(q:mask_query_string())

-- (example)
--   curl 'http://localhost/mask?attr1=hogeeee&attr2=fugaaaa&attr3=piyoooo&attr4=fooooo&attr5=barrrrr'
-- 
--   attr1=hogeeee&attr2=fugaaaa&attr3=piy****&attr4=*MASK*&attr5=eoroiaweuroajejrfalwjreoaijrejwaerwaer'


モード

whitelistモード と blacklistモードがあります。
そのままなのですがホワイトリストモードは「fieldsに指定したパラメータのみを対象とし、マスク処理を行う」モードで、ブラックリストモードは「基本的には全パラメータを出力対象とし、fieldsに指定された項目のみマスク処理を行う」モードです。

ターゲット・マスク処理

以下の4種類があります。

  • origin
  • part
    • 項目の一部のみをマスクします。 mask_part_stringmask_part_length の値に準じ処理を行います。
    • ex) 設定が mask_part_string=%, mask_part_length=5 で、指定パラメータ値が abcdefghijklmn の時
      • 出力値=abcde%%%%%%%%%
  • fill
    • 項目の値が設定した固定値でマスクされます。 mask_fill_string の値に準じ処理を行います
  • hash
    • 項目の値をhashingします。 mask_hash_seed の値に準じ処理を行います。

hashingされた値の表示

メールアドレスをハッシュ化したいことはよくあることでしょう。
たとえばメールアドレスをハッシュ化していて、 hogehoge@gmail.com のメールアドレスを検索したい時のハッシュ値を知りたいときは、以下のようなエンドポイントを用意し

curl 'http://xxxxxxxxx/get_hash?data=hogehoge@gmail.com'

といったリクエストを実行するとお手軽にハッシュ値を得ることが出来ます。
(こちらも同様にREADMEに例が載っています)

location /get_hash {
    # なんらかの制限をしておくと良いですね
    allow xxx.xxx.xxx.xxx;
    deny all;

    content_by_lua_block {
        local data = ngx.req.get_uri_args().data

        local querymask = require "resty.querymask"
        local q = querymask:new({
            mode = "writelist",
            mask_part_string = "*",
            mask_part_length = 3,
            mask_fill_string = "*MASK*",
            mask_hash_seed   = "hogefugapiyo",
            max_field_length = 512,
            fields = {
              origin = {"attr1", "attr2"},
              part   = {"attr3"},
              fill   = {"attr4"},
              hash   = {"attr5"},
            }
        })
        -- get hash
        local data_hash = q:get_hash(data)

        ngx.header.content_type = "text/plain"

        ngx.say(data_hash)
    }
}


設定インタフェースについて

自分でもイマイチだと思っているのですが、モジュールの設定インタフェースどうにかしたい気持ち…
ホワイトリストブラックリストを両方包括しているので仕方なくこうしているのもあるのですが、もっとイイ感じの設定方法があればPRくださいませ!


その他便利なところ

クエリパラメータもbodyもまとめて処理しているので、同じインタフェースで同じ変数に簡単に突っ込めるのが割と便利!


まとめ

昨今、セキュリティ関連の話題が尽きません。お手軽かつハイパフォーマンスにマスク処理を行いたいときには検討してみてください。