Fail2BanでBAN発生時に警告灯を点灯させる

警告灯をブラウザで制御

Fail2Banは、サービスのログを監視して、ネットから攻撃された際にファイアウォールでそのIPを指定時間弾く(BANする)というようなことに使うツール。BANが発生したときには、それを何かに通知するだろうが、多くはメールで通知する程度?まぁ、メールで通知されて何か役に立つわけではないが、届いたメールの数で「昨日は午後多かったね」くらいは判る。通知先を監視システムにしていれば、それで統計にしたりリアルタイムで監視したりというのも。
で、「がとらぼ」では警告灯を作ったのでそれに通知させることにした。つまり、BANが発生したら警告灯が光るようにした。通知方法はシンプルにWebhookを使う(またかよ)。

Fail2Banの設定

以下、設定ファイルのPathはFreeBSDのpkg/portsでFail2Ban(security/py-fail2ban)をインストールした場合に倣ったもの。Linuxなどでは/usr/local/etc下ではないと思うので適当に読み替えて欲しい。

/usr/local/etc/fail2ban/action.d/webhook.conf (新規)
1
2
3
4
[Definition]
actionban = /usr/local/bin/curl -X POST -H "Content-Type: application/json" \
            -d '{"data":{"name":"Fail2Ban", "status":"warning"}}' \
            http://hoge.example.com/path/webhook.php

アクション用のファイルをaction.d下に作製する。
今回はBANが発生したときにWebhookするというものなのでactionbanを書いた。
curlでJSONをPOSTするだけ。今回は警告灯を光らせるだけが目的なのでJSONの内容はdataの中でnameとstatusを内容固定にした。2〜4行目は本来は1行で書くところを行末の \ で改行している。4行目はwebhookの通知先。

/usr/local/etc/fail2ban/jail.local (1行追加)
1
2
3
4
[DEFAULT]
action = pf[name=%(__name__)s, bantime="%(bantime)s", actiontype=<allports>]
         %(mta)s-whois[name=%(__name__)s, sender="%(sender)s", dest="%(destemail)s"]
         webhook[]
4行目が今回追加した行。
2〜4行目がアクションの指定。それぞれの行で、FreeBSDのパケットフィルタであるpfのテーブルに該当IPアドレスを追加、メールで通知、今回追加したアクションであるWebhookを指定している。上の例ではDEFAULTセクション(全てのフィルタのアクション)で指定しているが、サービス毎のセクションで指定するのももちろんあり。 今回は単に警告灯のランプを点灯させるだけの目的で、可変で何か情報を通知させたいわけではないのでwebhookの[ ]の中は何も無し。
# service fail2ban reload
OK

Fail2Banの設定をリロードさせる。OKが出れば設定に致命的なエラーもなく設定がリロードされた筈。
設定のリロードだけでなく、Fail2Banにサービスのログをリロードさせたい場合はFail2Banを再起動 (service fail2ban restart)。
/var/log/fail2ban.logを見てエラーが出ていないことを確認。
これで、Fail2Ban側が完了。

Webhookの受信側

上で設定したFail2Banのホストからネットワーク的に疎通できるウェブサーバにphpスクリプトを置く。上のFail2Banの設定では http://hoge.example.com/path/webhook.php になる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
if ($_SERVER["REQUEST_METHOD"] == "POST") {

    //ここはfail2banのwebhookを単純に取得
    $body = file_get_contents('php://input');
    $json = json_decode($body, true);   // <- 2つめの引数にtrueを付けないと連想配列にならない
    //echo print_r($json);

    foreach($json as $ealert) {
        if ((array_key_exists('name', $ealert)) && ($ealert['name'] == 'Fail2Ban')) {
            light(2);       //黄色点灯
            usleep(500000); //0.5秒停止
            light(0);       //消灯
            exit;
        }
    }
}

function light($c) {
    switch ($c) {
        case 0:
            //警告灯 消灯
            $code = "0f";
            break;
        case 1:
            //警告灯 緑
            $code = "0b";
            break;
        case 2:
            //警告灯 黄
            $code = "0d";
            break;
        case 3:
            //警告灯 赤
            $code = "0e";
            break;
    }

    $cmd = 'echo "' .  $code . '" | xxd -r -p > /dev/cuaU0';
    exec($cmd , $error);
    //echo $error;
}
?>

このコードではdata内にnameが存在し、且つそのnameが「Fail2Ban」なWebhookを受けると0.5秒間だけ黄色点灯するというシンプルなもの。読みやすくするためFail2Ban側で送信したstatus内容の判断はこの例では入れていない。
ただし、これだと深夜0時からのFail2Banの再起動(日跨ぎ処理)の再BAN発生により暫く警告灯が激しくチカチカしまくることになる筈。点灯条件は必要に応じて増やしておいた方が良いと思う。

関連記事: