GORM を使って監査ログを出す!
こんにちは!矢ヶ崎です!
さて、今日は監査ログのお話です。
Web アプリケーションのバックエンドを作成していると、センシティブな情報を持っているときには、アプリケーションからも監査ログを出す必要がでてきたりしますよね。
そんなとき、Go言語 ( Golang ) で 実装していた場合にはどうすればよいのか?GORM と各種 Web Framework (今回はecho) の Middleware を使ってやってみました!
今回の監査ログの目的
・いつ
・誰が
・どんなデータに
・何をしたか
を追えるようにする。つまり機密情報アクセスのトレーサビリティを確保するということだとします。
そして、今回は個人情報は全て RDB に入っているという設定で、 SQL のレベルでの全ログを取得し、その SQL 操作をアプリケーションのどのユーザが行ったか?( DB のユーザではなく、アプリケーションのユーザ)を記録していきたいと思います。
DB 側やインフラ側での監査ログでは、アプリケーションのどのユーザが利用したのかを記録するのはなかなか難しいです。 DB 自体のユーザを出力することはできますが、アプリケーションユーザを出すには DB のユーザとアプリケーションユーザを1対1で作成する必要があります。これはマルチテナントで提供する SaaS などの実装ではなかなか現実的ではありません。テナント単位での DB ユーザは作成できたとしても、そのテナント内でのユーザ単位まで作成するのは、規模にもよりますがなかなか厳しいかと思います。
では、早速ですが実装サンプルに行きたいと思います。
全体的なサンプルコードはこちらです!
https://github.com/yaggytter/auditlog-sample-gorm-echo
まず、バックエンドでは何かしらの認可(認証もある場合もあるかと思います)の処理が入るはずです。その認可の時点でアプリケーションのユーザを記録していきます。
echo などではこの認可(や認証)の部分は、Middleware で実装する場合が多いかと思います。ここで、アプリケーションのユーザを一意に識別するユーザ名や UUID などが取得されていると思うので、それをコンテキストに覚えておきます。
main.go
ctx := context.WithValue(c.Request().Context(), "UserName", username)
r := c.Request().WithContext(ctx)
c.SetRequest(r)
return next(c)
このコンテキストを利用し、 GORM のカスタムロガーを書き実装します。
auditlog/auditlog.go
pri := fmt.Sprintf(logPrefix, ctx.Value("UserName").(string))
GORM の Open のときに、このカスタムロガーを使うように指定します。
main.go
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: auditlog.Default.LogMode(logger.Info), // for Audit logs
})
そして、コンテキスト付きで GORM を利用するようにします。
main.go
ctxdb := db.WithContext(c.Request().Context())
これで GORM を経由したすべての SQL が、そのデータを利用したアプリケーションユーザ情報とともに出力できます。
2021/11/09 05:08:08 [AUDIT] user='ee04243e-a635-4bbd-9788-e2d5483efa96' /app/infrastructure/postgres/user.go:416 [4.140ms] [rows:6] SELECT * FROM "skills" WHERE "skills"."user_id" = 182 AND "skills"."deleted_at" IS NULL
こんな感じでトレーサビリティを確保できますね!
もちろん、クラウドなどのインフラ側でのログも合わせて取得するのが大事ですね。 DB 側の監査ログやアクセスログなども合わせて利用するのが良いと思います。そして場合によっては、分散トレーシングなども合わせて利用するために、トレースIDのようなものもログに出力しておくとさらにトレーサビリティがあがりますね。監査ログのついでにオブザーバビリティの観点も考慮しておくといいですね!
こちらからは以上です。