Logwatchの結果をチェックして「見慣れない」ログがあったらALERTするスクリプトのメモ
自分が使ってるサーバ(FC5, CentOS4)では、logwatchのバージョンがぜんぜん違う
- FC5だと logwatch-7.x
- CentOS4だと logwatch-5.x
そして、ログの種類によって、出力されるメッセージのスタイル、段落の切れ目などもけっこうバラバラなので、うまくparseできる文法をyacc(bison)のスタイルで書くのはかなりしんどそう。
せめて空行でパラグラフを区切るぐらいはできるかと思ったら、ところどころに /^\s$/ みたいな「空白のみを含む行」まで存在する始末。
そこで、考えを改めて、既知の「ALERTを出すほどではない」情報のパラグラフのスタイルを定義してみようと考えた。
たとえば、ssh関係のログのsummaryはこんなパラグラフになることが多い。空行の数が微妙なところにご注目(笑
--------------------- SSHD Begin ------------------------ Failed logins from: 111.22.33.44 (foo.servername): 3 times Users logging in through sshd: me: 11.55.66.77 (ok.servername): 5 times Received disconnect: 2: disconnected by server request : 2 Time(s) **Unmatched Entries** error: channel_setup_fwd_listener: cannot listen to port: 3000 : 2 time(s) error: bind: Address already in use : 2 time(s) ---------------------- SSHD End -------------------------
初めが ----- SSHD Begin ---- で、
終わりが ---- SSHD End ---- にマッチすればいい。ぐらいのことはすぐできそうだ。
このパターンを一般化して、 ---- <サービスのタイトル> (Begin|End) ---- というのをマーカーにできそうだと考える。あとは、その中身をどう分類するか、なのだが、これがぜんぜん統一感が無い。
いくつかの出力結果を見比べて、分かったことは
- ログのグループ分けは空行で行われるらしい
- 各グループの最初の行は、ログのグループの説明か、そのグループで典型的なログでいきなり始まる
- パラグラフの最初のマーカーの直後に空行があるとは限らない
- パラグラフの最後のマーカーの前に空行があるとは限らない
実際、頼れるのはこれだけのようだった。
そこで、結局次のようなストラテジー(pseudo codeをRubyで書いてみた)で処理することになった
while thisline = gets do next if blank_line?(thisline) regexpsForServices.each do |servicename, regexps| found = false if regexps["start_marker"] =~ thisline then if (skipuntil(regexps["end_marker"], regexps["subparagraph_titles_array"])) then found = true break end end end raise "#{thisline} is illegal" unless found end
とりあえず、このスタイルで「見知ったログのパラグラフとそのサブパラグラフ」はだいたい認識できるようになった。
あとはregepsのHashをどうやって構成するかだが、JSONで設定ファイルを作って、読み込んでみることにしてみた。
たとえば、上記のSSHDの場合は、こんな感じ↓ところで、マーカーの構造が常に同じなら、start_markerとend_markerの項目は冗長なのだが、実はそうではなかったりするところがミソである。たとえば、ディスク容量の項目は、end_markerが存在しなくて、Logwatch全体のエンドマーカーである"### LogWatch End #######"を使うことになったりした。
{ "SSHD" : { "start_marker" : "---------- SSHD Begin --------", "subparagraph_titles_array" : [ "Illegal users from these:", "Postponed authentication:", "Users logging in through sshd:" ], "end_marker" : "---------- SSHD End --------" }, "httpd" : { "start_marker" : "---------- httpd Begin --------", (以下略) }
これを読み込むコードは、Rubyだとこんな感じか。
require 'jsonp.rb' fd = open(config_by_json_file).read regexpsForServices = JsonParser.new.parse(fd) p regexpsForServices["SSHD"]["start_marker"] => ---------- SSHD Begin --------
JSONのparserは、Ruby で JSON パーサーを書いてみました - WebOS Goodiesで紹介されていたものを利用させていただきました。
で、ここまでが自分としては前フリである(笑)本当にやりたいのは、上記のJSONのルール(white list)を、OKだったlog summary(メールで送られてくる)を入力として、自動生成することなのだ。
すでに、HashからJSONのファイルを作成するJsonBuilderクラスはjsonp.rbに用意されているので、あとは
- start/end_markerの例外処理の検出
- subparagraph_titles_arrayの配列の採取
ができれば良い。
特に後者は、SElinuxのlog summaryのように
/\*\*Unmatched Entries\*\* \(Only first \\d+ out of \\d+ are printed\)/
とかいうパラメトリックな形式のものがあるので、自動判別はかなり難しい。たくさんの入力データから得られたパターンを見比べて、最長一致をとるとかするしかないかもしれない。
そんな苦労をするくらいなら、ベイジアンフィルタを考えようよ、というのが、数日前の呻きだったのだ。