紆余曲折の記録です。
Auditとは何か
Linux AuditはLinuxのシステムで発生しているイベントを記録するための仕組みです。詳しくは以下のページ
第5章 システム監査 - Red Hat Customer Portal
具体的には、(デフォルトで) /var/log/audit/audit.log
にシステムコールやユーザが実行したコマンドなどの様々なシステムイベントを記録するサービス auditd
が起動することになります。
auditdのログ (audit.log) を長期保存したい
audit.logはそのまま監査ログとして利用できるため、一定期間保存しようと考えます。
しかし、前述のようにauditdは様々なシステムイベントを記録するために単純にログの保存を続けるとかなりのディスク容量を消費していきます。
そのためログローテーションの設定を組むのが一般的であり、それ以前にauditd自体にはログローテーションの機構が備わっており、デフォルトで有効になっています。
しかし、そのままローテートをしていると端から消えていくことになるので、ローテートされた古いログを保存する仕組みが必要になります。
あわせて、以下の3つの要件を実現しようと考えました。
- 時間ベースでローテートをしたい
- あとでログを見つけやすくするため。ログローテートされたaudit.logに時刻のプレフィックス (ex.
audit.log-2018111115
) をつけたい
- あとでログを見つけやすくするため。ログローテートされたaudit.logに時刻のプレフィックス (ex.
- ローテーション時に圧縮して保存したい
- ログファイルが大きくなりがちのため
- audit.logのみで保存したい
ぱっと見、logrotateに任せたほうが良さそうです(しかし、後述しますが失敗しました)
logrotateの前に検討したいくつかの方法
auditd の設定でなんとかする
auditd.confの設定パラメータを見る限りでは時間ベースのローテートの仕組みや古いログを圧縮するためのフック機構などはなさそうでした。
audisp pluginを利用してsyslogに記録する
audisp pluginを利用して、auditdのログをsyslogに記録して、syslogに設定したログローテーションにのっかるという方法があります。
- auditd.conf で
dispatcher = /sbin/audispd
を指定 /etc/audisp/plugins.d/syslog.conf
を有効化
active = yes direction = out path = builtin_syslog type = builtin args = LOG_INFO format = string
これでsyslogがlogrotateでログローテートされてれば、合わせてaudit.logの情報もローテートされるという仕掛けです。
しかし、今回はaudit.logのみで保存をしたいのとsyslogにもよく参照する情報があって混在するのを避けたかったため、この方法は見送ることにしました。
audit.logを直接logrotateの仕組みに載せる -> 失敗
これが本命 ですでした。
auditdが独自にログローテーション機能を止めてlogrotateの仕組みに載せることができれば、「時間ベースのローテーション」も「古いログの圧縮保存」も「audit.logのみでのローテーション」も実現できると考えました。
まず、/etc/audit/auditd.conf の max_log_file_action
の設定を変更して auditdのログローテーションを止めます。
max_log_file_action = IGNORE
そしてlogrotateの設定を追加します。
/var/log/audit/audit.log { rotate 24 hourly ifempty dateext missingok compress delaycompress }
これでauditdを再起動して、確認しましたが 結果、うまくいきませんでした。
ログファイルはローテートするのですが、auditdが audit.log
ではなく、ローテートしたほうの audit.log-2018111115
の方にそのままログを書き続けてしまいました。
原因は、auditdが最初に掴んだログファイルのファイルディスクリプタにそのまま書き込んでいるからだと予想できます。
ちなみに、logrotateで考えるべきことについては @catatsuy さんの以下のエントリがとてもわかりやすかったです。
ここまでで、auditdは上記エントリで示されている「ちゃんとローテートをしたい場合」の3つの方法のうちの 「1. ファイルに書き込む度にファイルをopenして書き込み後にcloseする」という挙動にも「3. ファイル名が変更されたタイミングを自力で検知して、元のファイル名でopenし直す」 という挙動にもなっていないということがわかりました。
なので、最後に残った「2. ファイル名が変更されたタイミングを教えてもらい、元のファイル名でopenし直す」方法を考えてみました。
しかし、auditdにはNGINXのUSR1
のような「元のファイル名でopenし直す」ようなシグナルは用意されておらず、これも断念することになりました。おそらくauditd自体がログローテーションの仕組みを持っているので、そのようなシグナルを受け取る処理を作る必要がなかったのではないか?と予想しました。
logrotateで、ファイルディスクリプタを掴むようなプロセスのときに使うらしい copytruncate
オプションを付与すれば一見うまくいくのですが、ログファイルをコピーして(copy)元のファイルの内容を消去する(truncate)するときにログを一部ロストしてしまう可能性があるということで(監査ログという意味でも)選択できませんでした。
cronで直接ローテートするようなスクリプトを組む
というわけでlogrotateの仕組みに載せるのはあきらめて、直接cronをトリガーに意図したログローテーションをするようにスクリプトを組みました。
#!/bin/bash set -eu DATEEXT=`date +%Y%m%d%H` KEEP=5 [ -f /var/run/auditd.pid ] && kill -USR1 `cat /var/run/auditd.pid` [ -f /var/log/audit/audit.log.1 ] && mv /var/log/audit/audit.log.1 /var/log/audit/audit.log-${DATEEXT} [ -f /var/log/audit/audit.log-${DATEEXT} ] && gzip -f /var/log/audit/audit.log-${DATEEXT} ls -t /var/app/audit.log-* | tail -n+${KEEP} | xargs --no-run-if-empty -i mv {} /backup/'
仕組みとしては以下のページの方法と同じです。
- まず
USR1
シグナルをauditdプロセスに渡すことで、auditdのログローテーション機能を実行します。 - 生成された audit.log.1 を時刻つきファイル名にリネームします
- 2でリネームしたファイルをgzipで圧縮します
- 古いログファイルから移動します
上記スクリプトをcronで1時間ごとに実行することで、なんとかやりたいことを実現することができました。
結局
audit.logを時間でローテートするベストな方法は何だったのか。。是非どなたか教えて欲しいです。
ここまでくるのに時間がかかってしまった。。