2014年10月8日水曜日

Linuxの操作ログ、操作結果をsyslogに自動的に、強制的に渡す

操作ログ保存しようと考えた場合、いくつかの方法が紹介されていますが、操作結果(操作端末にエコーされた全情報)をログに保存しようと考えた場合、満足のいく解答がグーグル先生から得られませんでした。
.bash_historysudo のログ、psacct サービスの出力等では足りません。bash が操作ログを syslog に渡すコンパイルオプションを追加したようですが、試していません。ビルドし直しは、商用環境ではなかなか受け入れられないように思われます。私としては、script コマンドをうまく使うしかないと考えています。
script コマンドの出力を普通にファイルに書き出したのでは、ファイルの管理が大変になるので、syslog へ渡したいところです。script コマンドが直接ファイルを作成すると本人の権限でログファイルが作成されてしまう(簡単に改竄できるようになる)という問題もあります。リアルタイムで syslog へ渡しさえすれば、syslog 転送したり、分類したり、再構成したり、ローテーションしたり、何とでもなります。ニーズは多いと思うのですが、syslog への渡し方がなかなか見つけられなくて困りました。

そこで、以下のスクリプトを作りました。


cat << 'EOF' | sudo tee /usr/local/bin/operation_logger
#!/bin/bash
export PROMPT_COMMAND='printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME}" "${PWD/#$HOME/~}";alias l.="ls -d .* --color=auto";alias ll="ls -l --color=auto";alias ls="ls --color=auto";alias vi="vim";alias which="alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde";[ "$PS1" ] && PS1='\''[\u@\[\e[1;41m\]\H\[\e[0m\] \t \w] \n\$ '\'';PROMPT_COMMAND='\''printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME}" "${PWD/#$HOME/~}"'\'
/usr/bin/mkfifo -m 0600 /dev/shm/$USER.$$
(/bin/sed -u -e 's/[^[:graph:]]/ /g' /dev/shm/$USER.$$ | /bin/logger -i -p local0.info -t $USER.$$; /bin/rm -f /dev/shm/$USER.$$) &
exec /usr/bin/script -fq /dev/shm/$USER.$$
EOF
sudo chmod 755 /usr/local/bin/operation_logger


このスクリプトを実行すると、操作だけではなく、操作結果も syslog に保存されるようになります。エコーバックされないパスワードは記録されません。
この最終形になるまでに、結構な時間を使ってしまいました。
このほど、やっと満足がいく結果を得られたので、公開します。すこし解説をしておきます。

まず最初の export ですが、bash の機能として、プロンプト表示の際に毎回実行したいコマンドを PROMPT_COMMAND 変数に定義しておけばよい、という仕組みを利用しています。
本来は export する必要は全くないのですが、export しても害はないと思われるので、script コマンドが実行するシェルの中で最初に実行したいコマンドを埋め込んでいます。
script コマンドが呼び出したシェルで alias を利用したり、プロンプトを変更したりということを自動でできるようにしています。
PS1 変数は export してはいけません。既存のいろいろなスクリプトがこの変数の定義を見て、インタラクティブかどうか判断しているので、export してしまうと誤作動を起こす可能性があります。
ここで行っている PS1 の定義内容は、ホスト名を赤地に黄色い文字で FQDN 表示し、現在時刻を表示し、カレントディレクトリをフルパスで表示した後、プロンプト記号を表示するように定義しています。
ステージング環境や開発環境では色を変えることで分かりやすくしたり等、適当に変更してください。色盲の方もプロジェクトにいらっしゃる場合を考慮し、ドメイン名まで表示しています。当然、環境ごとにドメイン名が異なる前提です。

次に、fifo を作成しています。名前付きパイプというものです。このパイプを利用して、script コマンドの出力を syslog に渡しています。名前付きパイプの使い方の詳細は各自調べてください。
サブシェル内でパイプから入力を受けた sed コマンドが logger コマンドに出力を渡しています。ここの sed コマンドは、制御文字を半角空白に置き換える目的で使用しています。
script コマンドでは、vi の操作などがごみとして出力されてしまいます。script コマンドの man を見るとバグがある旨の記載があり、その対策となります。
この sed コマンドの使い方を思いつくまでに時間をかけてしまいました。当初は、logger コマンドを perl で書き直して置き換えようとしていたのですが、どうしてもゴミを取りきれず困り果てていました。このゴミが悪さをしていて、local0 で渡しているにもかかわらず user としてゴミが出力されていました。
よくよく考えたら、logger に渡る前から対処しないと対応できないことに思い当たりました。たったこれだけのことに1年悩んでしまいました(1年前にすぐあきらめ、寝かしておいたわけですが)。

使い方は、~/.bash_profile の末尾に以下のように追加します。


[ "$PS1" ] && exec /usr/local/bin/operation_logger


このようにしておけば、強制的にすべての操作が記録でき、ログ記録 (script コマンド) を終了させるとログアウトしてしまうので、監査ログになるのではないかと思います。
rbash でも動作確認しましたので、~/.bash_profile の書き換えを防げば、踏み台用のログ機構としては、一応役立つのではないかと思っています。
抜け道は多分あるのだろうとは思っています。

※ 2014/10/13
ln -s /bin/bash /bin/rbash している環境で、ssh -t foo rbash にてログインすると、ログが記録されませんでした。やっぱり抜け道はあります。監査目的の場合、他の手段と併用する必要があります。
rbash のシンボリックリンクは、パスが通っているところに作成すると、セキュリティが弱くなる場合があることがわかりましたデフォルトシェルが rbash となっているユーザでも、そのユーザの PATH に含めていないコマンドを外から、ssh -t foobar vi a のように実行でき、vi では /usr/local/bin/bin/usr/bin に含まれるコマンドが実行できる (:!ls 等) ので、抜け道だらけでした。ssh デーモンでデフォルトの環境変数を変更しておく必要がありました。環境変数を調整するよりも、ssh で chroot する等の対策が併せて必要だと思います。ssh コマンドを使えるセキュアな踏み台を目指すのは大変です。たぶん他にもありそうな気がしています。「ssh -t foo ssh -t bar」 等とされないような対策を考えると頭が痛くなります。公開鍵でコマンド制限をかけるしかないのかもしれません。1段目の踏み台は2段目の踏み台にログインできるだけのものとします。実際に作ってみたら抜け道が見つかるかもです。ログを完全に取得する道はまだまだ遠そうです。他のログと相互参照して欠けていたら何らかの脱法行為があった、といった運用に逃げるしかないのかもしれません。


970文字で切れてしまう現象にぶつかった場合は、logger 呼び出しの部分 (/bin/logger -i -p local0.info -t $USER.$$) を以下のスクリプト呼び出し (/usr/local/bin/logger_ex $USER.$$ local0 info) に書き換える必要があります。


cat << 'EOF' | sudo tee /usr/local/bin/logger_ex
#!/usr/bin/perl
my $ident = $ARGV[0];
my $facility = $ARGV[1];
my $level = $ARGV[2];
use Sys::Syslog qw( :DEFAULT setlogsock );
setlogsock('unix');
openlog($ident, 'pid', $facility);
while ($log = <STDIN>) {
  syslog($level, $log);
}
closelog
EOF
sudo chmod 755 /usr/local/bin/logger_ex


rsyslog の場合、「$MaxMessageSize 1048576」などといった設定追加が必要と思われます。



0 件のコメント:

コメントを投稿