auditdのログをlogrotateでローテートする運用にしたかったがうまくいかなかったのでcronで直接ローテートするようにした

紆余曲折の記録です。

Auditとは何か

Linux AuditはLinuxのシステムで発生しているイベントを記録するための仕組みです。詳しくは以下のページ

第5章 システム監査 - Red Hat Customer Portal

具体的には、(デフォルトで) /var/log/audit/audit.logシステムコールやユーザが実行したコマンドなどの様々なシステムイベントを記録するサービス auditd が起動することになります。

auditdのログ (audit.log) を長期保存したい

audit.logはそのまま監査ログとして利用できるため、一定期間保存しようと考えます。

しかし、前述のようにauditdは様々なシステムイベントを記録するために単純にログの保存を続けるとかなりのディスク容量を消費していきます。

そのためログローテーションの設定を組むのが一般的であり、それ以前にauditd自体にはログローテーションの機構が備わっており、デフォルトで有効になっています。

しかし、そのままローテートをしていると端から消えていくことになるので、ローテートされた古いログを保存する仕組みが必要になります。

あわせて、以下の3つの要件を実現しようと考えました。

  1. 時間ベースでローテートをしたい
    • あとでログを見つけやすくするため。ログローテートされたaudit.logに時刻のプレフィックス (ex. audit.log-2018111115 ) をつけたい
  2. ローテーション時に圧縮して保存したい
    • ログファイルが大きくなりがちのため
  3. 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 さんの以下のエントリがとてもわかりやすかったです。

medium.com

ここまでで、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/'

仕組みとしては以下のページの方法と同じです。

How to implement audit log rotation with compression based on time instead of size - Red Hat Customer Portal

  1. まずUSR1シグナルをauditdプロセスに渡すことで、auditdのログローテーション機能を実行します。
  2. 生成された audit.log.1 を時刻つきファイル名にリネームします
  3. 2でリネームしたファイルをgzipで圧縮します
  4. 古いログファイルから移動します

上記スクリプトをcronで1時間ごとに実行することで、なんとかやりたいことを実現することができました。

結局

audit.logを時間でローテートするベストな方法は何だったのか。。是非どなたか教えて欲しいです。

ここまでくるのに時間がかかってしまった。。