任意の文字列をマスクするmaskedioパッケージを作ったが実装に不満がある

runnトークンやパスワードといった機密データをマスクする機能を作りたかったので、そのためのパッケージを作りました。

github.com

機能は単純で、maskedio.NewWriter(w io.Writer) で、任意の io.Writer を内包したインスタンス *maskedio.Writer を作成します。*maskedio.Writerio.Writer を満たすのでそれを代わりに使うというものです。 そして、任意のキーワードを登録すると、そのキーワードが指定の文字列(デフォルトは ***** )で置換されて、元の io.Writer に渡されます。

一見するとシンプルなのですが、1つだけ実装に不満があります。

厳密にキーワードをマスクしようとすると、どうしてもフラッシュ処理(末尾処理)が必要になる

io.Writer は Writeメソッドだけを持つインターフェイスです。

type Writer interface {
    Write(p []byte) (n int, err error)
}

例えば、passw0rd というキーワードをマスクしようとした時、単純な実装だと

Write([]byte("xxxxxpassw0rdxxxx")

ならマスクできるのですが、

Write([]byte("xxxxxpass"))
Write([]byte("w0rdxxxx"))

となると、マスクに失敗してしまいます。

そこで、マスク失敗を回避するために maskedio では内部にバッファを持って、入力の末尾がキーワードに合致しそうだったら、バッファに溜め込んで次のWriteを待つようにしました。

しかし、そのようなバッファを持ってしまうと、次に問題となるのが次のような末尾が中途半端にキーワードに合致してしまうケースです。

Write([]byte("xxxxxpass"))
// 終わり

バッファに溜まっている値(今回のケースだと []byte("xxxxxpass") を何かをトリガーに内部のio.Writer に書き出す必要があります。

ここまでで、気づいた方はいるかと思いますが、これは bufio.Writer と同じです。

bufio.WriterFlush() メソッドで末尾の書き出しをサポートしています。

maskedio.Writer も同様に Flash() メソッドを持たせていますが。maskedioのユースケースの性質上、どうにかして io.Writer の interface のメソッドだけで機能を実現したいと考えました。

時間差でフラッシュを自動実行

そこで苦肉の策として実装したのが、時間差での自動Flushです。

maskedio/maskedio.go at e9228162f440ff3c778fe2565fab4aa2534dea1f · k1LoW/maskedio · GitHub

// Auto flush
go func() {
    time.Sleep(100 * time.Microsecond)
    _ = w.Flush()
}()

この実装に不満があります

100 * time.Microsecond に強い根拠はないし、全てケースをカバーできるわけではない実装です。

かと言って、io.Writer + Flush() をライブラリとして強いるもの違う気がしています。

結局

解決できずに今に至ります。

厳しい。。