fail2banをうまく動かすためのTips。正規表現はシンプルに見やすく
この前から使っているコントロールパネル「Hestia」で「fail2ban」を使っているのですが、うまく動かすのが大変だったので情報をシェアしておきます。
「fail2ban」というのはLinux OSで使える、アタックを検出して自動的にIPをBANするソフトウェアです。GitHubのページはこちら。

目次
fail2ban の最大の障壁、ログの読み取り
ほとんどの場合、fail2ban はインストールしただけではうまく動かないのではないでしょうか。
コントロールパネルの「Hestia」では最初からインストールされていて、何もしなくてもある程度動きます。何もしないと 10% ぐらいしかアタックを検出してくれませんが。
そのうえ、メールソフトウェア(exim、dovecot)のログ出力を syslog にしたらアタックを全く検出しなくなり、BANをしてくれなくなりました。rsyslog があるからログは syslog に出したいんですよね。
「アタックの検出」というとカッコいいですが、実際には「エラーログの読み取り」です。fail2ban 自体がアタックの検出をするわけではありません。
fail2ban にうまくログファイルを読み取らせる必要があります。
あまり細かな設定を弄りたくないため、簡単に何とかならないかなと検索しまくりました。「fail2ban がうまく動かない」という質問がネット上に大量にあります。
うまく動かすのが大変なソフトウェアのようです。
ただ結論を言うと、うまく動かすにはやはり細かな設定というか、正規表現を弄る必要が出てきます。
うまく動かすための Tips を書いておきます。
ログファイルのパスは合っている?
そもそもの話ですが、jail.local(jail.conf)の "logpath" が間違っているという事態は避けましょう。
"logpath" に存在しないファイルを指定している場合はログにしっかりエラーが出力されています。
チェックしましょう。
backend は polling でOK
"logpath" は合っているのにログを読み取らない問題として、backend 問題があります。GitHub でもたくさん言及されている問題です。
fail2ban not banning and just does nothing · Issue #1986 · fail2ban/fail2ban
これは私も経験済みです。jail.local(jail.conf)の設定は「backend = auto」で良いよとされてはいるのですが、起動時のログを見ると backend が "pyinotify" に設定されてしまいます。
pyinotify をインストールしてある人にとってはこれで期待通りの動作なのですが、私はインストールしていないので "pyinotify" になってしまうのが不思議です。
"pyinotify" になっているとログファイルの読み取りがうまくいかなかったのですが、これを「backend = polling」としたら読み取りがうまくいきました。
「backend = systemd」を推奨している人もいますが、これにするとログファイルを読み取らず、journal から読み取るようになります。
"logpath" を指定しても無効です。これが望みの動作なら問題ありません。
ログは読み取っているが fail2ban が何もしないなら正規表現がおかしい
ログファイルを読み取るようになったのは良いのですが、メールのソフトウェアのログでアタックのエラーが出力されているのに、fail2ban が何もしてくれません。
これを確認するためには "fail2ban-regex" でチェックしてみましょう。
# fail2ban-regex /var/log/mail.log /etc/fail2ban/filter.d/dovecot.conf --print-all-matched
そうすると match した文の数が表示されます。
ここで注意するのは日付がマッチしているかどうかです。
Lines: 60 lines, 0 ignored, 14 matched, 46 missed
上のように検出して欲しいIPアドレスの数だけ match しているなら問題ないです。
しかし読み取っているログファイルを自分で見て、検出して欲しいアタックがあったのに何もしていない場合は正規表現が間違っています。
Lines: 60 lines, 0 ignored, 0 matched, 60 missed
こういう場合は正規表現の文をチェックする必要があります。
日付はマッチしている?
上のコマンド、
# fail2ban-regex /var/log/mail.log /etc/fail2ban/filter.d/dovecot.conf --print-all-matched
を行ったときに下のような出力があります。
Date template hits:
|- [# of hits] date format
| [60] {^LN-BEG}(?:DAY )?MON Day %k:Minute:Second(?:\.Microseconds)?(?: ExYear)?
fail2ban のマニュアルを読むと、まず文の始めにある日付が見つからないとその後の正規表現のチェックをしないですぐに次の文へスキップすると書かれています。
なので "failregex" で指定した正規表現での match を考える前に、Date template hits があるか確かめなくてはなりません。
上の場合は [60] とありますので、文の始めにある日付にヒットしています。この右側の文字列は日付の検出に使われた検索パターンです。
ヒットしていない場合は日付の検出がおかしいです。
その場合は datepattern を確認しましょう。
文のチェックの流れ
fail2ban の文のチェックの流れは、"datepattern" → "prefregex" → "failregex" です。
これを理解しておきましょう。マニュアルを読むと書いてあるのですが、HowTo系の記事ではほとんど書かれていません。
目的は "failregex" で文の中にあるIPアドレスを match させることです。
実際の正規表現のパターンを見てみましょう。
# vi /etc/fail2ban/filter.d/dovecot.conf
[INCLUDES]
before = common.conf
[Definition]
_auth_worker = (?:dovecot: )?auth(?:-worker)?
_daemon = (?:dovecot(?:-auth)?|auth)prefregex = ^%(__prefix_line)s(?:%(_auth_worker)s(?:\([^\)]+\))?: )?(?:%(__pam_auth)s(?:\(dovecot:auth\))?: |(?:pop3|imap)-login: )?(?:Info: )?<F-CONTENT>.+</F-CONTENT>$
failregex = ^authentication failure; logname=<F-ALT_USER1>\S*</F-ALT_USER1> uid=\S* euid=\S* tty=dovecot ruser=<F-USER>\S*</F-USER> rhost=<HOST>(?:\s+user=<F-ALT_USER>\S*</F-ALT_USER>)?\s*$
^(?:Aborted login|Disconnected)(?::(?: [^ \(]+)+)? \((?:auth failed, \d+ attempts(?: in \d+ secs)?|tried to use (?:disabled|disallowed) \S+ auth|proxy dest auth failed)\):(?: user=<<F-USER>[^>]*</F-USER>>,)?(?: method=\S+,)? rip=<HOST>(?:[^>]*(?:, session=<\S+>)?)\s*$
^pam\(\S+,<HOST>(?:,\S*)?\): pam_authenticate\(\) failed: (?:User not known to the underlying authentication module: \d+ Time\(s\)|Authentication failure \(password mismatch\?\)|Permission denied)\s*$
^[a-z\-]{3,15}\(\S*,<HOST>(?:,\S*)?\): (?:unknown user|invalid credentials|Password mismatch)\s*$
この正規表現のパターンは通常の正規表現で見たことがないものが登場しています。マニュアルにある程度は書いてあるのですが、そんなに時間を掛けて読みたくない。
私がこれを弄るときに必要だった知識を書いておきます。
- 正規表現は python regular expression
- Definition にある "_auth_worker" などの変数は後から検索パターンの中で "%(_auth_worker)s" で参照できる
- この設定ファイルの読み取り前に "before" で指定している設定が読み込まれる("after" もある)。見ている設定ファイルにない変数を使っている場合は before の設定ファイルを見る
- "failregex" は OR で繋ぐのではなく、複数行で指定するのが楽。複数行の場合はインデントを揃える必要がある(上の例ではスペースが取り除かれるのでインデントがずれています)。
- "datepattern" にヒットした残りの文字列が "prefregex" での検索対象になり、その残りが "failregex" の検索対象になる
- "prefregex" にある <F-CONTENT>.+</F-CONTENT> で囲まれたものは "failregex" の検索での検索対象となる
- IPアドレスの場所に <HOST> を入れてマッチさせる
この知識を元にログファイルの文と正規表現を照らし合わせていきます。おそらくマッチしていないでしょう。
aggressive モードで検出できるものもある
下の dovecot のログにある "no auth attempts in 0 secs" は、aggressive モードでヒットさせることができます。
Apr 16 16:44:51 instance dovecot: imap-login: Disconnected (no auth attempts in 0 secs): user=<>, rip=111.7.96.136, lip=10.0.0.246, TLS handshaking: SSL_accept() failed: error:1408F10B:SSL routines:ssl3_get_record:wrong version number, session=<L8O2JxLAnChvB2CI>
aggressive モードでの検索パターンは "mdre-aggressive" で定義されています。
mdre-aggressive = ^(?:Aborted login|Disconnected)(?::(?: [^ \(]+)+)? \((?:no auth attempts|disconnected before auth was ready,|client didn't finish \S+ auth,)(?: (?:in|waited) \d+ secs)?\):(?: user=<[^>]*>,)?(?: method=\S+,)? rip=<HOST>(?:[^>]*(?:, session=<\S+>)?)\s*$
aggressive モードで使いたい場合、フィルターの指定でファイル名の後ろに [mode=aggressive] を付けます。
# fail2ban-regex /var/log/mail.log /etc/fail2ban/filter.d/dovecot.conf[mode=aggressive] --print-all-matched
"jail.local" では "mode = aggressive" と書く手もありますが、これは filter を指定していないデフォルトの状態の時でないと機能しないので注意。
例えば dovecot の場合、filter を指定して aggressive モードにするときはこんな感じに書きます。
[dovecot]
filter = dovecot[mode=aggressive]
先ほど書いた注意点の悪例として、下が設定の効果が無い例です。
[dovecot]
filter = dovecot
mode = aggressive # ← filter を指定しているので効きません
正規表現を自分で作る
fail2ban が何もしてくれない場合はおそらく正規表現が間違っています。
対象のソフトウェアのログ出力がOSやバージョンによって違ったりするので、fail2ban に付属する検索パターンはそういうのを考慮したものになっています。
ですが、syslog 向けの検索パターンか、ソフトウェアがファイルに出力するログを対象とした検索パターンしかないものがあり、これがマッチしない原因になっていることが多いです。
私の場合もこれでした。exim 向けに fail2ban に最初から含まれている検索パターンでは syslog のログにマッチしません。
fail2ban に含まれる検索パターンは広くヒットをしないように作られていて、文の最初から最後までを細かく指定されています。
もう少し柔軟な検索パターンにすれば良いと思うのですが、おそらく誤BANを避けるためにこうしているのでしょう。
しかし、メールサーバーなどはアタックを許すよりも誤BANした方が良いです。
もっと柔軟な正規表現にしてしまいましょう。
common.conf の "__prefix_line" の使用を避ける
"common.conf" に含まれる検索 "__prefix_line" ですが、これは使わない方が良さそうです。
これで syslog の hostname あたりまでをマッチできるのですが、様々な環境に配慮されていてもの凄く長ったらしい検索パターンです。これを使うと検索がかなり遅くなります。
自分のシステムだけに使う検索パターンの場合は、これを使わずに作ると軽量です。
マシンの hostname にヒットさせる必要はなく、プロセス名へのヒットだけで十分だと思います。
正規表現のチェック
正規表現を書いたらログファイルに対してうまくマッチできるかチェックしてみましょう。上で登場したコマンドを使います。
# fail2ban-regex /var/log/mail.log /etc/fail2ban/filter.d/dovecot.conf --print-all-matched
"dovecot.conf" などインストールされるコンフィグファイルを直接弄るのではなく、copy して編集するのが楽です。
正規表現の検索パターンの例
実際に作ったものを例にして説明します。
ログファイルを見て、ヒットして欲しいアタックのログを確認します。
Apr 18 09:02:07 instance-2 dovecot: auth: passwd-file(aaa@example.com,149.255.204.12): Password mismatch (given password: aaa)
Apr 18 09:02:13 instance-2 dovecot: auth: login(?,149.255.204.12): Username character disallowed by auth_username_chars: 0x13 (username: ?)
これにヒットさせてみます。
"dovecot-my.conf" を作ります。
[INCLUDES]
[Definition]
prefix = ^.*?dovecot:
failregex = %(prefix)s auth: .*?\(\S*?,<HOST>\): (?:Password mismatch|Username character disallowed)
"before" は何も指定しません。自分で "prefix" を作ります。上で書きましたが "__prefix_line" で hostname にヒットさせる必要がありません。
ということでプロセス名がヒットすれば十分です。"prefix = ^.*?dovecot:" で "devoct:" までたどり着きます。こちらの方が処理が軽いです。
ちなみに "^" がヒットする位置ですが、上で説明した通り "datepattern" の検索の後ですから、日付+空白の後ろが文頭になります。上の例では "instance-2" の "i" の手前です。
"failregex" は "%(prefix)s " を先頭に置いて参照させます。後で追加する行でも同じ様にして見やすくします。
"<F-CONTENT>" など特殊なものを使わず、複雑なことをしないようにして見やすくしています。
そしてIPアドレスの位置に "<HOST>" を置き、後ろの文字列を指定してエラーの文にしかヒットしないようにします。
これで上のログの2つともヒットさせられます。
最初にインストールされるコンフィグの正規表現パターンよりもかなりシンプルで見やすく、後からメンテナンスしやすいはずです。
何か抜けがあった場合や、ヒットして欲しくない文が登場したときはその時に修正しましょう。
読み取るログはそのソフトウェアだけのログにすると動作が軽く、メンテナンスも楽になります。
うまく動くと気持ちいい
自分で作った正規表現パターンにヒットしてIPアドレスが次々と検出されると気持ちいいです。
厳しいシステムにする場合は "maxretry" の値を減らし、"findtime" と "bantime" を増やし、"recidive" を設定しましょう。
fail2ban は設定がちょっと大変ですが、設定を完了させてしまえば自動的にIPをBANしてくれるのでとても楽です。
しつこいアタックも "recidive" で厳しいBANを与えられます。