サーバーを少しずつUbuntu 24.04に変えていっているのだけれど、Fail2Banが上手く動いていないようだった。
調べてみると、色々と変わっていたことが分かったので、発生した問題と対応方法についてメモしておく。
やってみたことや調べたことも、ついでだからメモ。
環境
環境はこちら。
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 24.04.2 LTS
Release: 24.04
Codename: noble
$ dpkg -s fail2ban | grep "Version"
Version: 1.0.2-3ubuntu0.1
元々は、Ubuntu 20.04の頃にFail2Banに挑戦し、その時に実装した範囲で保護してきた。
今回もそれをベースにして、もう一度ちゃんと監視できる状態にしようと思う。
22.04→24.04の変更点
Fail2Ban設定中に気が付いた変更点を書き出してみる。
banaction = nftables
Banで使用するアクションが、nftablesに変更されている。
/etc/fail2ban/jail.d/defaults-debian.conf
[DEFAULT]
banaction = nftables
banaction_allports = nftables[type=allports]
backend = systemd
[sshd]
enabled = true
jail.confでは以下が設定されているが、読み込み順序の関係でdefaults-debian.confの値で上書きされる。
/etc/fail2ban/jail.conf
banaction = iptables-multiport
banaction_allports = iptables-allports
/etc/fail2ban/action.dを確認してみたところ、以前からnftablesは選択可能だった。
UFWなんかも選択できそうな勢いだった。
Fail2Banが生成したルールや、ルールによってBANされた状態については、以下で確認できるようだった。
だいぶ見やすい。
$ sudo nft list table inet f2b-table
table inet f2b-table {
set addr-set-ufw-port-scan {
type ipv4_addr
elements = { 15.235.224.227, 15.235.224.238,
15.235.224.239, 36.158.177.51,
59.89.171.154, 79.124.8.120,
80.82.77.144, 80.94.95.226,
83.222.190.82, 83.222.190.86,
<省略するが、かなり出ている>
...
set addr-set-sshd {
type ipv4_addr
elements = { 142.93.110.54, 171.251.21.144,
171.251.23.16 }
}
...
chain f2b-chain {
type filter hook input priority filter - 1; policy accept;
tcp dport 0-65535 ip saddr @addr-set-ufw-port-scan reject with icmp port-unreachable
tcp dport 22 ip saddr @addr-set-sshd reject with icmp port-unreachable
tcp dport 0-65535 ip6 saddr @addr6-set-ufw-port-scan reject with icmpv6 port-unreachable
}
}
ルール全体を以下のコマンドで見ることができる。
$ sudo nft list ruleset
table ip filter {
chain ufw-before-logging-input {
}
chain ufw-before-logging-output {
}
chain ufw-before-logging-forward {
}
chain ufw-before-input {
iifname "lo" counter packets 25716 bytes 1532086 accept
...
iptablesとnftablesについては長くなるので後述。
backend = systemd
デフォルトのバックエンドがsystemdに変わった。
/etc/fail2ban/jail.d/defaults-debian.conf
[DEFAULT]
banaction = nftables
banaction_allports = nftables[type=allports]
backend = systemd
[sshd]
enabled = true
Fail2Banをインストールし、最初に動作するjail sshdについていうと、こういう変化がある。
Ubuntu 22.04での実行結果
$ sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 23
| |- Total failed: 1401
| `- File list: /var/log/auth.log
`- Actions
|- Currently banned: 3
|- Total banned: 169
`- Banned IP list: 103.181.143.10 119.53.132.144 197.221.244.34
Ubuntu 24.04での実行結果
$ sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 189
| `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
|- Currently banned: 1
|- Total banned: 188
`- Banned IP list: 46.101.226.132
OpenSSHについては問題なく監視ができていたが、Apacheでは監視が上手くいかないなど、多少手直しが必要な状況と思われる。
この後で、バックエンド変更に対するいくつかの対応例を書いていく。
allowipv6
設定変更を試す度に、このログが出ている。
これは警告であって、未定義だからautoが使われているよ、といっている。
<省略> fail2ban.configreader [2945]: WARNING 'allowipv6' not defined in 'Definition'. Using default one: 'auto'
このままでも問題はないが、IPv6でサービスを公開しているので、IPv6は明示的に許可してもいいのかなと思い、以下とした。
/etc/fail2ban/fail2ban.local ※新規作成
[DEFAULT]
allowipv6 = yes
明示的に指定したことで、警告は出力されなくなった。
バックエンド変更への対応
今までファイルのログばっかり見てきたけれど、色々と補足情報付きで保管されるログは便利だから、世の中はジャーナル活用の方向に進んでいるのだろう。
具体的にジャーナルで監視をするにはどうすれば良いか、調べて試してみた。
OpenSSH(対応不要)
sshdは監視するユニット名が違っていた。
だけど、このままでもログが監視できる。
$ sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
$ journalctl -u sshd.service
-- No entries --
$ systemctl list-units ssh*
UNIT LOAD ACTIVE SUB DESCRIPTION
ssh.service loaded active running OpenBSD Secure Shell server
ssh.socket loaded active running OpenBSD Secure Shell server socket
$ journalctl _COMM=sshd --since -1hour
Jun 15 07:07:01 hoge sshd[1572147]: Accepted publickey for rohhie from 192.168.110.179 port 51735 ssh2: ED25519 SHA256:xxxxxxxxxxxxxx
Jun 15 07:07:01 hoge sshd[1572147]: pam_unix(sshd:session): session opened for user rohhie(uid=1000) by rohhie(uid=0)
_COMMはログの付加情報のようで、journalctl -o verboseとすると表示することができる。
確認してみると_COMMはsshdのままであり、このjournalmachはOR条件なので、問題なく監視できているという訳だった。
きれいにしておきたいということなら、journalmatchを以下のように設定変更すればOK。
/etc/fail2ban/jail.local
[sshd]
enabled = true
journalmatch = _SYSTEMD_UNIT=ssh.service
あるいは
journalmatch = _COMM=sshd
検索条件はできるだけ単純な方が良いだろうから、やってもいいかも。
Postfix
fail2banの監視状況はこうだった。
$ sudo fail2ban-client status postfix
Status for the jail: postfix
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- Journal matches: _SYSTEMD_UNIT=postfix.service
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
一方で、Journalの方はこんな状況。
$ journalctl -u postfix.service --since -10min
-- No entries --
$ journalctl -u postfix@-.service --since -10min
Jun 15 08:32:59 hoge postfix/smtpd[1383875]: connect from ip217.154.199-200.pbiaas.com[217.154.199.200]
Jun 15 08:33:00 hoge postfix/smtpd[1383875]: lost connection after EHLO from ip217.154.199-200.pbiaas.com[217.154.199.200]
Jun 15 08:33:00 hoge postfix/smtpd[1383875]: disconnect from ip217.154.199-200.pbiaas.com[217.154.199.200] ehlo=1 commands=1
Jun 15 08:36:20 hoge postfix/anvil[1383876]: statistics: max connection rate 1/60s for (smtp:217.154.199.200) at Jun 15 08:32:59
Jun 15 08:36:20 hoge postfix/anvil[1383876]: statistics: max connection count 1 for (smtp:217.154.199.200) at Jun 15 08:32:59
Jun 15 08:36:20 hoge postfix/anvil[1383876]: statistics: max cache size 1 at Jun 15 08:32:59
これでは監視ができないので、以下のように設定を変更。
Github / fail2ban / fail2ban / Change journalmatch postfix #3692
/etc/fail2ban/filter.d/postfix.local ※新規作成
[Init]
journalmatch = _SYSTEMD_UNIT=postfix.service _SYSTEMD_UNIT=postfix@-.service
これでログの監視ができるようになった。
$ sudo fail2ban-client status postfix
Status for the jail: postfix
|- Filter
| |- Currently failed: 1
| |- Total failed: 4
| `- Journal matches: _SYSTEMD_UNIT=postfix.service _SYSTEMD_UNIT=postfix@-.service
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
で…これまた調べ甲斐のある話になったなと。そもそも、OR条件にするには+記号が必要じゃなかったっけ?
journalmatch = _SYSTEMD_UNIT=postfix.service + _SYSTEMD_UNIT=postfix@-.service
でも+記号なしでちゃんとログが見える。
$ journalctl _SYSTEMD_UNIT=postfix.service _SYSTEMD_UNIT=postfix@-.service --since -5min
Jun 21 12:41:31 hoge postfix/smtpd[2109833]: warning: hostname unassigned.quadranet.com does not resolve to address 198.55.98.164
Jun 21 12:41:31 hoge postfix/smtpd[2109833]: connect from unknown[198.55.98.164]
Jun 21 12:41:32 hoge postfix/smtpd[2109833]: disconnect from unknown[198.55.98.164] ehlo=1 auth=0/1 quit=1 commands=2/3
Jun 21 12:44:52 hoge postfix/anvil[2109834]: statistics: max connection rate 1/60s for (smtp:198.55.98.164) at Jun 21 12:41:31
Jun 21 12:44:52 hoge postfix/anvil[2109834]: statistics: max connection count 1 for (smtp:198.55.98.164) at Jun 21 12:41:31
Jun 21 12:44:52 hoge postfix/anvil[2109834]: statistics: max cache size 1 at Jun 21 12:41:31
GeminiさんとCopilotさんに「+あり」と「+なし」についての動作の違いを聞いてみた。
journalmatch | journalctlの結果 | Geminiさん | Copilotさん |
---|---|---|---|
SYSTEMD_UNIT=postfix.service <space> _SYSTEMD_UNIT=postfix@-.service | ログが表示される | AND条件だから表示できない →訂正:OR条件だから表示できる | OR条件だから表示できる |
SYSTEMD_UNIT=postfix.service + _SYSTEMD_UNIT=postfix@-.service | ログが表示される | OR条件だから表示できる | AND条件だから表示できない →訂正:OR条件だから表示できる |
最初は意見が割れていたが、以下のマニュアルの一節を渡したところ、回答が訂正された。
If multiple matches are specified matching different fields, the log entries are filtered by both, i.e. the resulting output will show only
entries matching all the specified matches of this kind. If two matches apply to the same field, then they are automatically matched as alternatives, i.e. the resulting output will show entries matching any of the specified matches for the same field.複数のマッチが異なるフィールドに指定された場合、ログエントリは両方によってフィルタリングされます。つまり、結果の出力は、この種類の指定されたすべてのマッチに一致するエントリのみを表示します。もし2つのマッチが同じフィールドに適用される場合、それらは自動的に代替としてマッチされます。つまり、結果の出力は、同じフィールドに対して指定されたマッチのいずれかに一致するエントリを表示します。
回答の文字数制限もあるのかもしれないけれど、以下の感じ。
- Geminiさんは「以前の私の回答に重要な訂正があります」とか「ご指摘いただきありがとうございました」という言葉とともに、訂正内容と分析結果を示してくれた。Geminiさんの場合は、回答の文字数に物凄く余裕があるからなのか、こうした訂正についての説明はもちろん、口論すると時々学習になるという発言をすることがある。
- CopilotさんはORとANDの考え方を根本的に間違っていたけれど、それには触れずに再分析の結果を示してくれただけだった。Copilotさんの回答は基本的に淡泊、回答できる文字数に制限があるからだろうと思われる。
いずれにせよ、何かを相談するときには、前提となるドキュメントを最初にアップロードしておくと、より正しい結果が聞き出せそうである。
Apache(ログファイルを監視)
Apacheのアクセスログはrsyslogd経由ではなく、指定したファイルに直接出力しているようだ。
$ journalctl -u apache2.service --since -1d
Jun 15 00:00:02 hoge systemd[1]: Reloading apache2.service - The Apache HTTP Server...
Jun 15 00:00:03 hoge systemd[1]: Reloaded apache2.service - The Apache HTTP Server.
$ ll /var/log/apache2/*.log
-rw-r----- 1 root adm 0 May 5 14:18 /var/log/apache2/access.log
-rw-r----- 1 root adm 298 Jun 15 00:00 /var/log/apache2/error.log
-rw-r----- 1 root adm 0 May 25 00:00 /var/log/apache2/other_vhosts_access.log
次のような設定を、Apache関連のJailに入れていく。
/etc/fail2ban/jail.local
[apache-badbots]
enabled = true
backend = auto
logpath = %(apache_access_log)s
※autoを指定した場合、pyinotify, gamin, polling の順で試してくれる。
本番環境で設定変更して試してみたけれど、全然引っかかっていない。
Botの名前が変わっているのかもしれないし、Botが来ていないのかもしれないが、現状はこんな結果。
$ sudo fail2ban-client status
Status
|- Number of jail: 14
`- Jail list: apache-badbots, ...
$ sudo fail2ban-client status apache-badbots
Status for the jail: apache-badbots
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- File list: /var/log/apache2/access.log /var/log/apache2/other_vhosts_access.log
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
Apache(ジャーナルを監視)
Geminiさんに聞いたらやり方を教えてくれたので、試してみる。
お手軽さには欠けるが、良い選択になるかもしれない気配がある。
でも、本番環境でアクセスログをジャーナルに出力したらどうなるのか、というところは試していないので分からない。
どうやれば、今までみたいなログローテーションで保管期間をコントロール!みたいなことができるのかを調べるのは、だいぶ先になるなぁ。
ジャーナルにログを出力
有効化しているサイトのログ出力先を変更する、ということの模様。
例えばこんな風に2つのサービスを起動しているとして…
/etc/apache2/sites-available/hoge.conf
<VirtualHost *:443>
ServerName service.example.net
DocumentRoot /var/www/service
ErrorLog ${APACHE_LOG_DIR}/service.error.log
CustomLog ${APACHE_LOG_DIR}/service.access.log combined
...
<VirtualHost *:443>
ServerName maintenance.example.net
DocumentRoot /var/www/service
ErrorLog ${APACHE_LOG_DIR}/maintenance.error.log
CustomLog ${APACHE_LOG_DIR}/maintenance.access.log combined
...
systemd-catコマンドを使ってSYSLOG_IDENTIFIERを付ける。
/etc/apache2/sites-available/hoge.conf
<VirtualHost *:443>
ServerName service.example.net
DocumentRoot /var/www/service
ErrorLog "|/usr/bin/systemd-cat -t apache2-service -p err"
CustomLog "|/usr/bin/systemd-cat -t apache2-service" combined
...
<VirtualHost *:443>
ServerName maintenance.example.net
DocumentRoot /var/www/service
ErrorLog "|/usr/bin/systemd-cat -t apache2-maintenance -p err"
CustomLog "|/usr/bin/systemd-cat -t apache2-maintenance" combined
...
ジャーナルを見るときに、以下のように検索条件を絞り込むことができる。
$ journalctl -u apache2.service --since -5min
Jun 20 05:36:38 hoge systemd[1]: Reloading apache2.service - The Apache HTTP Server...
Jun 20 05:36:38 hoge systemd[1]: Reloaded apache2.service - The Apache HTTP Server.
Jun 20 05:36:58 hoge apache2-service[1897]: 192.168.110.178 - - [20/Jun/2025:05:36:58 +0900] "GET /index.html HTTP/1.1" 200 5273 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
Jun 20 05:36:58 hoge apache2-service[1897]: 192.168.110.178 - - [20/Jun/2025:05:36:58 +0900] "GET /favicon.ico HTTP/1.1" 404 531 "https://service.hogeserver.hogeddns.jp/index.html" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
Jun 20 05:37:04 hoge apache2-maintenance[1896]: 192.168.110.178 - - [20/Jun/2025:05:37:04 +0900] "GET /index.html HTTP/1.1" 200 5273 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
Jun 20 05:37:04 hoge apache2-maintenance[1896]: 192.168.110.178 - - [20/Jun/2025:05:37:04 +0900] "GET /favicon.ico HTTP/1.1" 404 535 "https://maintenance.hogeserver.hogeddns.jp/index.html" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
Jun 20 05:37:18 hoge apache2[1898]: 192.168.110.178 - - [20/Jun/2025:05:37:18 +0900] "-" 408 1791 "-" "-"
$ journalctl -u apache2.service SYSLOG_IDENTIFIER=apache2-service --since -5min
Jun 20 05:36:58 hoge apache2-service[1897]: 192.168.110.178 - - [20/Jun/2025:05:36:58 +0900] "GET /index.html HTTP/1.1" 200 5273 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
Jun 20 05:36:58 hoge apache2-service[1897]: 192.168.110.178 - - [20/Jun/2025:05:36:58 +0900] "GET /favicon.ico HTTP/1.1" 404 531 "https://service.hogeserver.hogeddns.jp/index.html" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
もしもFail2Banで監視するサービスを限定したければ、この方法で監視対象のログを絞り込むことができる。
ジャーナルを監視
ジャーナルを監視する場合、元々の設定に少し手を加えることになる。
ラボで立ち上げたばかりの、悪意のあるアクセスに晒されていない環境。
なので、お手軽に404なアクセス2回で投獄する apache-my.conf という標準配布(仮想)ファイルを作って、それに手を加えてみる。
/etc/fail2ban/filter.d/apache-my.conf ※標準配布(仮想) あくまでもテスト用!
[Definition]
failregex = ^<HOST>.*"(GET|POST|HEAD).*" 404 .*$
ジャーナルで各行に表示される「最初の部分」をカットする前処理を設定。
Apacheのログファイルと同じ部分が見られるようになる。
/etc/fail2ban/filter.d/apache-my.local ※新規作成
[INCLUDES]
before = common.conf
[Definition]
prefregex = ^<F-MLFID>%(__prefix_line)s</F-MLFID><F-CONTENT>.+</F-CONTENT>$
ジャーナルの中で監視対象になるログを定義しておく。
今回使うのはアクセスログ(INFO:PRIORITY=6)だけど、将来使うかもしれないので、エラーログ(ERR:3)とかも定義しておく。
/etc/fail2ban/paths-overrides.local ※新規作成
[DEFAULT]
journal_apache = _SYSTEMD_UNIT=apache2.service
journal_apache_acc = _SYSTEMD_UNIT=apache2.service PRIORITY=6
journal_apache_err = _SYSTEMD_UNIT=apache2.service PRIORITY=3
バックエンドをsystemdにして、Apacheのログを監視対象にする。
/etc/fail2ban/jail.local
[apache-my]
enabled = true
backend = systemd
journalmatch = %(journal_apache_acc)s
banaction = %(banaction_allports)s
maxretry = 2
findtime = 30
bantime = 300
テスト。
$ sudo fail2ban-regex systemd-journal --journalmatch "_SYSTEMD_UNIT=apache2.service PRIORITY=6" /etc/fail2ban/filter.d/apache-my.local
Running tests
=============
Use failregex filter file : apache-my, basedir: /etc/fail2ban
Use datepattern : {^LN-BEG} : Default Detectors
Use systemd journal
Use encoding : UTF-8
Use journal match : _SYSTEMD_UNIT=apache2.service PRIORITY=6
Results
=======
Prefregex: 101 total
| ^(?P<mlfid>\s*(?:\S+\s+)?(?:\S*(?:\[\d+\])?:?\s+)?(?:kernel:\s?\[ *\d+\.\d+\]:?\s+)?)(?P<content>.+)$
`-
Failregex: 19 total
|- #) [# of hits] regular expression
| 1) [19] ^<HOST>.*"(GET|POST|HEAD).*" 404 .*$
`-
Ignoreregex: 0 total
Date template hits:
Lines: 101 lines, 0 ignored, 19 matched, 82 missed
[processed in 0.01 sec]
Missed line(s): too many to print. Use --print-all-missed to print all 82 lines
設定を読み込む。
そして、実際にブラウザから存在しないページを要求して、404を起こさせてみたところ、2回目で獄中の人となった。
$ sudo systemctl reload fail2ban
$ sudo fail2ban-client status apache-my
Status for the jail: apache-my
|- Filter
| |- Currently failed: 0
| |- Total failed: 2
| `- Journal matches: _SYSTEMD_UNIT=apache2.service PRIORITY=6
`- Actions
|- Currently banned: 1
|- Total banned: 1
`- Banned IP list: 192.168.110.178
Apacheはジャーナルにログを出力し、それをFail2Banで監視する、という方式で実装できた。
syslogへの出力を抑止
ジャーナルにログを出力すると、それはrsyslogに渡されてsyslogに出力される。
仕組み的にそういうことになるのだけれど、これはいらないかなと思ったので、抑止する方法を探ってみた。
/etc/rsyslog.d/49-my.conf ※新規作成
# Supress apache access logs.
:programname, isequal, "apache2-service" stop
:programname, isequal, "apache2-maintenance" stop
ファイル名の先頭の番号は適当なのだけれど、50-default.conf より先に読み込まれないと効果を発揮できないことに注意。
これで、Apacheのログについてはだいたいコントロールできたのではないだろうか。
Fail2Ban
Fail2Ban自体のログは、/var/log/fail2ban.logに出力されている。
現状、
[インターネット] --- [ルーター] --- [サーバー]
という構成で、ルーターへの攻撃は全てのアクセスを遮断しているのだけれど、サーバーへの攻撃を受けた場合にはサーバーの遮断しかできない。
ということで、将来的にはサーバーでBANしたら、そのログをルーターに送り、ルーター側でもBANする構成にしようかなと考えている。
そうすると、Fail2Banのログもジャーナルに出ていた方が都合が良い。
/etc/fail2ban/fail2ban.conf
logtarget = /var/log/fail2ban.log
これをジャーナルに出力するように変更。
/etc/fail2ban/fail2ban.local
[DEFAULT]
allowipv6 = yes
logtarget = SYSTEMD-JOURNAL
設定を読み直して、ログの出力具合を見てみると、INFOとNOTICEの文字がない。
ないけれども、INFOは白、NOTICEは色付きで表示される。
$ sudo systemctl reload fail2ban
$ journalctl -eu fail2ban.service
...<省略>
Jun 21 15:44:54 hoge fail2ban[3130]: Jail 'apache-my' reloaded
Jun 21 15:44:54 hoge fail2ban[3130]: Reload finished.
Jun 21 15:44:54 hoge fail2ban-client[3762]: OK
Jun 21 15:44:54 hoge systemd[1]: Reloaded fail2ban.service - Fail2Ban Service.
Jun 21 16:03:22 hoge fail2ban[3130]: [apache-my] Ban 192.168.110.178
Jun 21 16:08:22 hoge fail2ban[3130]: [apache-my] Unban 192.168.110.178
文字はあった方がよりわかりやすいけれど、同じ形式で表示させる方法は見当たらなかった。
補足
設定ファイルの構成
/etc/fail2banの設定ファイルは、こんな風に配置されている(概ねman jail.confに沿って並べ替え)。
/etc/fail2ban
|-- fail2ban.conf ............. Fail2Ban のグローバル構成(ログ記録など)
|-- fail2ban.d
|-- fail2ban.local ............ グローバル構成allowipv6 = yesを追加するために作成した
|-- filter.d .................. 認証失敗の検出方法を指定するフィルター
| |-- <省略>
| |-- ignorecommands ........ 現在はapache-fakegooglebotのignorecommandが1つ入っている
| | `-- apache-fakegooglebot
| |-- <省略>
| `-- zoneminder.conf
|-- action.d .................. IPアドレスの禁止と禁止解除のコマンドを定義する
| |-- <省略>
| |-- ufw.conf
| `-- xarf-login-attack.conf
|-- jail.conf ................. 牢獄のフィルターとアクションの組み合わせを定義する
|-- jail.d
| `-- defaults-debian.conf .. Debian系のデフォルト設定が入っている
|-- jail.local ................ このホストで運用する牢獄を定義するために作成した
|-- paths-arch.conf ........... <この環境では未使用>
|-- paths-common.conf ......... paths-debian.confからbeforeでインクルードされる
|-- paths-debian.conf ......... jail.confからインクルードされる(Debian系のログファイルの位置とか)
|-- paths-opensuse.conf ....... <この環境では未使用>
`-- paths-overrides.local ..... paths-debian.confからafterでインクルードされる。journalmatchを定義するために作成した
OSをアップグレードし、設定ファイルやマニュアルとにらめっこしながら、なんとなく感じた雰囲気は、
- 絶対にconfファイルを作らない、触らない。何かするときはhoge.localを作る。
- マニュアルで言及されていないように思うけれど、fail2ban.localもちゃんと読み込まれる。
- 自作のフィルターを作る時には、filter.d/hoge.local という名前で作る。
- fail2ban.dとjail.dには、defaults-debian.confのような塊、例えば会社で管理するサーバー群とか、一連のサービスで連携するサーバー群とかいったような塊で一律に設定したいことを、fail2ban.d/hoge.local、jail.d/hoge.localとして書く。
- サーバー固有の設定は、fail2ban.local、jail.localに書いておく。
- 塊で設定するものも含めて、pathを個別に書き換えるところだけは別扱いで、paths-overrides.localで設定する。
- journalmatchで指定するユニット名+アルファの絞り込み条件は、現時点では、各フィルターで定義 or 各牢獄で定義 のいずれかになっている。
- Apacheみたいにサービス名がディストリビューションごとに異なっていて、多数のフィルターから参照されるようなシステムが、もしもログをジャーナルに出力し始めたら、paths-debian.confとかに定義される日が来るかもしれない。
といったところか。
これらを考慮しておけば、*.localファイルだけをバックアップ → いつでも設定を戻し可能 and 他の環境への適用可能、という状態にできる。
各種設定の読み込み順序
サーバーの設定見直しを始めたとき(=このメモを書き始めたとき)、読み込み順序についてあやふやな状態で作業していた。
そんなときに、Postfixのjournalmatchの設定で?な動作を見つけて、一度ちゃんと整理しなければと考え、man jail.confに書かれていることを、Geminiさんに翻訳してもらった。
jail.d/ および fail2ban.d/
.local に加えて、jail.conf または fail2ban.conf ファイルには、追加の .conf ファイルを含む対応する .d/ ディレクトリが存在しえます。例えば、Jail の設定の順序は以下のようになります:
jail.conf
jail.d/*.conf(アルファベット順)
jail.local
jail.d/*.local(アルファベット順)つまり、すべての .local ファイルは、元の設定ファイルや .d ディレクトリ配下の .conf ファイルがパースされた後にパースされます。後にパースされたファイルの設定は、以前パースされたファイルの同一エントリよりも優先されます。ファイルはアルファベット順に並べられます。
※パースという言葉は、日常会話で言うと「解析する」「読み解く」「構造化する」といったニュアンスに近いそうな。
Ubuntuの場合、/etc/fail2ban/jail.d/defaults-debian.conf というファイルが置かれている。
これは、Fail2Banのデフォルト値はそのままにしつつも、
- Debian系の場合の初期値をこれにしますよ。
- ユーザーの皆さんは、各種hoge.localで設定を上書きしたり追加したりして、自分用に設定してくださいね。
と、宣言しているのだと理解して良さそうだ。
セクション
Postfixのログを監視できるようにしたとき、どのセッションに設定を入れると、どんな効果があるのかをチラ見した。
しかし、マニュアルを見てもセクションのことを上手く理解できなかったので、GeminiさんとCopilotさんに相談しながら整理してみた。
セクション | 目的/概要 | 出現箇所 |
---|---|---|
[INCLUDES] | 設定をモジュール化し、それを複数のファイルで利用する。 before、ないしは、afterで設定ファイルを読み込む。 beforeは、設定を読み込んで利用+上書きする時に使う。 afterは、読み込んだ設定で上書きしてもらう時に使う。 (e.g. paths-overrides.localで設定を上書きしている) | filter.d/*.conf action.d/*.conf jail.conf paths-common.conf paths-debian.conf |
[DEFAULT] | 全牢獄に対する共通設定の一元管理、冗長な記述の回避。 全牢獄で使われるデフォルト値を指定する。 個別の牢獄で指定されない限り、この値が使われる。 (bantime, findtime, maxretry等) 実際にはこれだけでなく、 * fail2ban.confではデーモンの動作設定 * paths-common.confではログのフルパス が設定されていたりする。 | fail2ban.conf filter.d/*.conf action.d/*.conf jail.conf jail.d/defaults-debian.conf paths-common.conf paths-debian.conf |
[Init] | 変数の定義。 フィルターやアクション内で使用する変数を定義する。 具体的には、正規表現や動作設定を変数に設定している。 | filter.d/*.conf action.d/*.conf |
[Definition] | BAN対象となるログの定義、BANアクションの定義、他。 前処理として定型部分をカットする正規表現や、 ログを失敗と判定するための正規表現を定義。 失敗を無視するための正規表現を定義したりもする。 | filter.d/*.conf action.d/*.conf |
[Thread] | 複数行にまたがるログや、時間的に連続するログの正確な関連付け。 | <未使用> |
[<jailname>] | 牢獄の定義。 特定のサービスに対する牢獄の名前を付ける。 監視ルール(フィルター、ログファイル等)を設定し、 bantime, findtime, maxretry等のBAN条件も設定する。 未指定の変数には、[DEFAULT]セクションの値が使われる。 そして、enabled = true と設定すると、牢獄が稼働する。 | jail.conf |
大文字だけのセクションは全体で使われ、先頭だけ大文字のセクションはfilterやactionで使われる、という傾向が見える。
設定ファイルはとても柔軟で、fail2ban.localに[Definition]セクションを作り、allowipv6の設定をしたところ、それはそれで動いた。
本来は[DEFAULT]セクションに入れておくべき設定だと思う。
また、[Init]セクションで設定されたjournalmatchは、[postfix]のjournalmatchとマージされ、
_SYSTEMD_UNIT=postfix.service + _SYSTEMD_UNIT=postfix@-.service
となっていた。
これは何か特別な処理が行われているように思うけれど、その根拠は見つけられていない。
設定の確認
この設定でどう?と思っても、どこかが間違っていてなかなか上手く動いてくれなかったりする。
マニュアルを見れば分かりそうなことだけれど、よく使いそうなものをメモ。
テスト用の牢獄を題材とする。※あくまでもテスト用!
/etc/fail2ban/filter.d/apache-my.local
[INCLUDES]
before = common.conf
[Definition]
prefregex = ^<F-MLFID>%(__prefix_line)s</F-MLFID><F-CONTENT>.+</F-CONTENT>$
failregex = ^%(__prefix_line)s: <HOST>.*"(GET|POST).*" 404 .*$
/etc/fail2ban/jail.local
[DEFAULT]
journal_apache = _SYSTEMD_UNIT=apache2.service
[apache-my]
enabled = true
backend = systemd
journalmatch = %(journal_apache)s
banaction = %(banaction_allports)s
maxretry = 2
findtime = 30
bantime = 360
フィルターのテスト
Geminiさんに、grepでフィルターをテストできないかと相談した。
- Apacheのログを監視しようと思うとき、先頭の日付からプロセスIDのところまでを無視したいと思った。
標準で配布されているフィルターは、/var/log/apache2/*.logを見るように作られているため。 - 色々と調べてみると、__prefix_lineというのが定義されていて、Geminiさんはこれで先頭部分にマッチするはずだ、という。
prefregexやfailregexにセットしてみて、__prefix_lineがどう展開されるのかを確認、grep -Pで試したりなんかしたけれども、マッチしない。 - でも、fail2ban-regexで試すと、ヒットする。
どうやら、正規表現を処理するロジックが違っていて、かつ前処理の場合には特別な処理が走るみたい。
以上のことから、テストは fail2ban-regex で実施、という結論にたどり着いた。
fail2ban-regexのメリットは以下。
- フィルターがFail2Banに読み込まれる前の段階(ファイルを編集した直後)でもテストができる。
- ヒットすれば、ヒットした正規表現が表示される。ヒットしないときは何も表示されない…
- <HOST>とかがなくてもテストができる。← これは、ちょっとずつ正規表現を完成させたいときにいいかも!
デメリットは以下。
- 起動するためのパラメーターが面倒。
ここでメモしたからOK!という割り切り案件である。
ログファイルを指定して試すときは簡単。
$ fail2ban-regex apache.log /etc/fail2ban/filter.d/apache-my.local
ジャーナルを指定して試すときは、journalmatchに設定した値をそのままここにセットする。
例えば-uオプションは使えないため、以下のような長い起動パラメーターが必要となる。
$ sudo fail2ban-regex systemd-journal --journalmatch "_SYSTEMD_UNIT=apache2.service PRIORITY=6" /etc/fail2ban/filter.d/apache-my.local
フィルターに掛からなかったログを見たいとき
$ sudo fail2ban-regex systemd-journal --journalmatch "_SYSTEMD_UNIT=apache2.service PRIORITY=6" /etc/fail2ban/filter.d/apache-my.local \
--print-all-missed
フィルターに掛かったログが見たいとき
$ sudo fail2ban-regex systemd-journal --journalmatch "_SYSTEMD_UNIT=apache2.service PRIORITY=6" /etc/fail2ban/filter.d/apache-my.local \
--print-all-matched
除外対象のログが見たいとき
$ sudo fail2ban-regex systemd-journal --journalmatch "_SYSTEMD_UNIT=apache2.service PRIORITY=6" /etc/fail2ban/filter.d/apache-my.local \
--print-all-ignored
※オプションの順序がいい加減だけれど、動作してくれた。
設定のテスト
フィルターのテストはできたとして、Fail2Banで正しく読み込めるかどうかは分からない。
例えばfailregexに<HOST>指定がないと、エラーが発生する。
そこで、設定が問題なく起動できるものになっているのかどうかを確認する。
$ sudo fail2ban-client -t
OK: configuration test is successful
$ sudo fail2ban-client -t
2025-06-18 08:18:46,402 fail2ban [2502]: ERROR Failed during configuration: Bad value substitution: option 'failregex' in section 'Definition' contains an interpolation key 'hoge-prefix' which ...
何か問題があると、後者のようにどこに問題があるのか教えてくれる。
読み込まれた設定の確認
最終的に、設定がどう読み込まれたのかを確認したくなることがある。
たとえばfailregexを書いたときに、変数が意図通りに展開されたか確認したくなることがある。
$ sudo fail2ban-client get apache-my failregex
$ sudo fail2ban-client get apache-my ignoreregex
この get <jail> <hoge>という設定値の取得について、取得可能なhogeは
man fail2ban-client
で確認することができる。
取得しているのは読み込まれて動作しているものであって、各種conf/localファイルに設定した値ではない。
reloadとかで失敗したものは取得できない。
iptablesとnftablesの違い
Copilotさんに、いつからnftablesに変わったの?と聞いたら、以下の回答だった。
Ubuntuが本格的にiptablesからnftablesへ移行し始めたのは、Ubuntu 20.04 LTS(Focal Fossa)からです。
このバージョンでは、iptablesコマンドの裏側でnftablesを使う「互換レイヤ(iptables-nft)」がデフォルトになりました。つまり、見た目は従来のiptablesコマンドでも、実際にはnftablesのルールセットが使われているという仕組みです。
Ubuntu 22.04 LTSではさらに進んで、nftablesがUFW(Uncomplicated Firewall)のバックエンドとしても使われており、iptables-legacyは明示的に選択しない限り使われません。
ちなみに、iptables --versionを実行すると (nf_tables) と表示されていれば、nftablesベースで動作していることがわかります。
先日、Ubuntu 20.04のUFWで作ったルーターをUbuntu 24.04にアップグレードしたばっかり。
それどころか、ルーティングを変えたりして、IPv4でも内側からの要求はppp0を通さずに、v6プラス側を通るように変更したばかり。
その時もiptablesで色々と書いたけれど、本当はアレも変わってたりするのかな…というのは別の課題。
Geminiさんに、iptablesとnftablesの違いについて尋ねてみた。
例によってかなり詳細な内容を答えてくれるが、抜粋してみる。
- iptablesとnftablesの関係性:
Linuxカーネルには、パケットフィルタリングを行うためのフレームワークである「netfilter」があります。iptablesとnftablesは、どちらもこのnetfilterを操作するためのユーザー空間ツールです。
- iptables (レガシー): 従来のnetfilterを操作するためのツール群(iptables、ip6tables、arptables、ebtablesなど)です。プロトコル(IPv4、IPv6、ARP、Ethernetブリッジ)ごとに異なるコマンドとルールセットを持っていました。
- nftables (新しいフレームワーク): iptablesの後継として開発された、より新しく、効率的で柔軟なnetfilter操作フレームワークです。nftコマンドで操作します。
- Ubuntu 22.04における関係性:
Ubuntu 22.04では、デフォルトでiptablesコマンドを実行すると、実際にはnftablesのバックエンドと連携する互換レイヤーであるiptables-nftが使用されます。これは、従来のiptablesの構文をそのまま使っても、内部的にはnftablesの仕組みが使われることを意味します。
- iptablesコマンドはまだ存在し、使用できます。
- しかし、その裏側ではnftablesが動いています。
- nftコマンドを使えば、nftablesのネイティブな機能やより高度な構文を利用できます。
- ufw (Uncomplicated Firewall) もUbuntu 22.04ではnftablesをバックエンドとして使用しています。
- nftablesの利点
nftablesがiptablesよりも優れている点は多岐にわたりますが、特に以下の点が挙げられます。
- 単一のフレームワークで全てを管理: IPv4、IPv6、ブリッジングなど、あらゆる種類のパケットフィルタリングをnftコマンド一つで設定できるため、管理が大幅に簡素化されます。
- パフォーマンスの向上: ルールセットがバイトコードにコンパイルされ、より効率的な処理が可能になります。特に多数のルールを持つ環境や高負荷な環境でその恩恵が大きいです。
- アトミックなルール更新: ルールセット全体を削除して再ロードするのではなく、個々のルールをアトミックに(中断なく一貫した状態を保ったまま)追加・削除できるため、大規模なルールセットの更新が高速かつ安全に行えます。
- 柔軟なルール定義: より表現力豊かな構文により、複雑なフィルタリング条件やアクションを簡潔に記述できます。例えば、複数のIPアドレスやポートをまとめて扱う「セット」のようなデータ構造を直接利用できます。
- リソース効率: 従来のiptablesに比べて、カーネルメモリの使用量が少ない傾向にあります。
Postfixのユニットが作られる過程
Postfixのログを見ようと思ったら、ログが見られない。
厳密には、起動と終了のログしか見えない。
postfix…迄入力し、タブキーを押したら@-.が補完され、ログが表示された。
これってなんだろう?
$ journalctl -u postfix --since "2025-06-14"
-- No entries --
$ journalctl -u postfix@-.service --since "2025-06-14"
Jun 14 00:04:48 r3content postfix/smtpd[1250298]: connect from ip217.154.199-200.pbiaas.com[217.154.199.200]
Jun 14 00:04:49 r3content postfix/smtpd[1250298]: lost connection after EHLO from ip217.154.199-200.pbiaas.com[217.154.199.200]
Jun 14 00:04:49 r3content postfix/smtpd[1250298]: disconnect from ip217.154.199-200.pbiaas.com[217.154.199.200] ehlo=1 commands=1
Jun 14 00:08:09 r3content postfix/anvil[1250299]: statistics: max connection rate 1/60s for (smtp:217.154.199.200) at Jun 14 00:04:48
Jun 14 00:08:09 r3content postfix/anvil[1250299]: statistics: max connection count 1 for (smtp:217.154.199.200) at Jun 14 00:04:48
Jun 14 00:08:09 r3content postfix/anvil[1250299]: statistics: max cache size 1 at Jun 14 00:04:48
Jun 14 00:13:08 r3content postfix/smtpd[1250864]: connect from unknown[196.251.92.207]
Jun 14 00:13:09 r3content postfix/smtpd[1250864]: disconnect from unknown[196.251.92.207] ehlo=1 quit=1 commands=2
...
※何かを探られているようだ。3~4秒ごとに何かを探っているログも見つかった。
確認してみたところ、postfixユニット自体は/bin/trueを実行しているだけだった。
知らなかった…
$ systemctl cat postfix.service
# /usr/lib/systemd/system/postfix.service
[Unit]
Description=Postfix Mail Transport Agent
Documentation=man:postfix(1)
Conflicts=sendmail.service exim4.service
ConditionPathExists=/etc/postfix/main.cf
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/true
ExecReload=/bin/true
[Install]
WantedBy=multi-user.target
有効なpostfixと名前のついたユニットを確認すると、以下の2つだった。
$ systemctl list-units postfix*
UNIT LOAD ACTIVE SUB DESCRIPTION
postfix.service loaded active exited Postfix Mail Transport Agent
postfix@-.service loaded active running Postfix Mail Transport Agent (instance -)
Legend: LOAD → Reflects whether the unit definition was properly loaded.
ACTIVE → The high-level unit activation state, i.e. generalization of SUB.
SUB → The low-level unit activation state, values depend on unit type.
2 loaded units listed. Pass --all to see loaded but inactive units, too.
To show all installed unit files use 'systemctl list-unit-files'.
postfix@-.serviceの中身を見てみたところ、postfix@.serviceがインスタンス名[-]で起動しているということのようだ。
$ systemctl cat postfix@-.service
# /usr/lib/systemd/system/postfix@.service
[Unit]
Description=Postfix Mail Transport Agent (instance %i)
Documentation=man:postfix(1)
PartOf=postfix.service
Before=postfix.service
ReloadPropagatedFrom=postfix.service
After=network-online.target nss-lookup.target
Wants=network-online.target
[Service]
Type=forking
GuessMainPID=no
ExecStartPre=/usr/lib/postfix/configure-instance.sh %i
ExecStart=/usr/sbin/postmulti -i %i -p start
ExecStop=/usr/sbin/postmulti -i %i -p stop
ExecReload=/usr/sbin/postmulti -i %i -p reload
[Install]
WantedBy=multi-user.target
ん?[-]がついているユニットなのに、[-]なしのユニットの中身が表示されている。
色々と探してみると、
/lib/systemd/system-generators/postfix-instance-generator
というスクリプトが[-]付きの
/run/systemd/generator/postfix.service.wants/postfix@-.service
を生成していた。
そして、生成する元となるものが /usr/lib/systemd/system/postfix@.service だった。
この /lib/systemd/system-generators にあるファイルは、システム起動時にsystemdがユニットファイルを読み込む前に実行される。
postfix-instance-generatorがpostfix@-.serviceをシステム起動の都度、動的に生成しているということだった。
詳細は、man systemd.generatorで読むとだいたい分かってくると思う。
[Init]と[postfix]とjournalmatch
※本編で本家の設定をまねた設定をしている。ここでは、調査の過程で発見した動きについて記す。
Postfixのログを監視できなかったので、以下の設定を加えた。
/etc/fail2ban/jail.local
...
[postfix]
enabled = true
mode = aggressive
journalmatch = _SYSTEMD_UNIT=postfix@-.service
...
これでログの監視ができるようになった。
$ sudo fail2ban-client status postfix
Status for the jail: postfix
|- Filter
| |- Currently failed: 1
| |- Total failed: 4
| `- Journal matches: _SYSTEMD_UNIT=postfix.service + _SYSTEMD_UNIT=postfix@-.service
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
これで全く問題ないのだけれど、何故「追加」された状態になっているのだろう?と思って調べてみると、postfix.confでは[Init]セクションでjournalmatchが指定されていた。
/etc/fail2ban/filter.d/postfix.conf
...
[Init]
journalmatch = _SYSTEMD_UNIT=postfix.service
# Author: Cyril Jaquier
man jail.confの最後の方にこんな記載があったので、Geminiさんに翻訳してもらってみた。
Similar to actions, filters may have an [Init] section also (optional since v.0.10). All parameters of both sections [Definition] and [Init] can be overridden (redefined or extended) in jail.conf or jail.local (or in related filter.d/filter-name.local).
Every option supplied in the jail to the filter overwrites the value specified in [Init] section, which in turm would overwrite the value in [Definition] section.
Besides the standard settings of filter both sections can be used to initialize filter-specific options.アクションと同様に、フィルターにも [Init] セクションを含めることができます(バージョン0.10以降は任意)。[Definition] セクションと [Init] セクションの両方の全パラメーターは、jail.conf または jail.local(あるいは関連する filter.d/filter-name.local)で上書き(再定義または拡張)できます。
ジェイルでフィルターに提供されるすべてのオプションは、[Init] セクションで指定された値を上書きし、その値が [Definition] セクションの値を上書きします。
フィルターの標準設定に加えて、両方のセクションはフィルター固有のオプションを初期化するために使用できます。
ここに書いてあることとは違う何か特別な処理が走っているように思う。
つまり、フィルターに[Init]セクションを作ってjournalmatchを設定した場合、牢獄に設定したjournalmatchと+で結合されている。
じゃあってなもんで、[Init]で書き換えを試みたところ、マニュアル通りに上書きができる。
/etc/fail2ban/filter.d/postfix.local ※新規作成
[Init]
journalmatch = _SYSTEMD_UNIT=postfix@-.service
$ sudo fail2ban-client status postfix
Status for the jail: postfix
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- Journal matches: _SYSTEMD_UNIT=postfix@-.service
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
なかなかに難しい動きだなと思っている。
さいごに
今回もGeminiさんに助けてもらいながら、だいぶ時間を掛けて、ウチで使うFail2Banを整理した。
Fail2Banの設定ファイルはとても柔軟に設定できるのだけれど、設定ファイルの構成が理解できておらず、そこに読み込み順序、セクションの役割、個人的には不思議だなと思う動作も一部あったりして、思い通りに設定すること自体がなかなかに難しかった。
そして、Apacheのログをジャーナルに出力し、rsyslogではログ出力を抑止、journalmatchを正確(+の有無やログレベル)に書いて監視、それをテスト、と、全行程が調査と理解、実行の繰り返し。
さらに、解決した!と思っても、調べていくうちに「さっきのアレって…違ってない?」が連発し、手戻り多数。
とかなんとか紆余曲折してどうにか整理したけれど、これでも完全とは言えないなぁ。
そもそも「保護」が目的なのだから、不正アクセスをゆっくり眺め、しっかりと投獄できるように設定をいじる作業はこれからだし。
最終的にはFail2Banで見ているログは、LogCheckの対象から外していって…
まだまだやることがある。それはそれで、ある意味幸せ、かな。
コメントはこちらから お気軽にどうぞ ~ 投稿に関するご意見・感想・他