runnでトークンやパスワードといった機密データをマスクする機能を作りたかったので、そのためのパッケージを作りました。
機能は単純で、maskedio.NewWriter(w io.Writer)
で、任意の io.Writer
を内包したインスタンス *maskedio.Writer
を作成します。*maskedio.Writer
は io.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.Writer
は Flush()
メソッドで末尾の書き出しをサポートしています。
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() をライブラリとして強いるもの違う気がしています。
結局
解決できずに今に至ります。
厳しい。。