サーバにグローバルIPアドレスを付けるとportscanが激しいので、対策が欠かせない。fail2banは手軽な対策手段なのでオススメなのだが、その原理はscanを検知するとiptablesやfirewall-cmdを使ってフィルタをかけるというものだ。CentOS8になってiptablesからnftablesに移行したのだが、fail2banがきちんと動作するのかが心配になる。これを確かめてみよう。
インストール手順
CentOS8では、まだfail2banは標準ではない。ソースからコンパイルするのもよいが、いろいろ面倒なのでパッケージですませよう。 まず最初にEPELを使えるようにする。
dnf -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
次にfail2banを入れる。
dnf -y install fail2ban python3-inotify
sshの解析だけならinotifyは不要だが、テキストのログ解析をするつもりなら入れておいた方がよいので、ついでに指定しておく。
次にsshdチェックを有効にするために、/etc/fail2ban/jail.local
を書く。
[DEFAULT]
banaction = firewallcmd-ipset
banaction_allports = firewallcmd-allports
[sshd]
enabled = true
最後に起動する。
systemctl enable fail2ban
systemctl start fail2ban
動作チェック
CentOS8において、iptablesは廃止されたわけではない。コマンドは残っていて、フィルタを作ったり状態を確認することはできる。iptablesはwrapperになっていて、フィルタリングルールはnftablesのルールセットに変換されるようになっている。iptablesがwrapperか否かは、次のようにすれば確認できる。
[root@c8 ~]# iptables --version
iptables v1.8.2 (nf_tables)
さて上述のようにインストールしたfail2banにsshでアタックするとどうなるだろうか。iptablesのルールを見てみよう。
[root@c8 ~]# iptables -nL
Chain INPUT (policy ACCEPT)
target prot opt source destination
f2b-sshd tcp -- 0.0.0.0/0 0.0.0.0/0 ctstate NEW multiport dports 22
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain f2b-sshd (1 references)
target prot opt source destination
REJECT all -- 116.228.88.115 0.0.0.0/0 reject-with icmp-port-unreachable
REJECT all -- 164.132.205.21 0.0.0.0/0 reject-with icmp-port-unreachable
REJECT all -- 177.68.148.10 0.0.0.0/0 reject-with icmp-port-unreachable
RETURN all -- 0.0.0.0/0 0.0.0.0/0
nftablesのルールセットはこんな感じ。
table ip filter {
chain INPUT {
type filter hook input priority 0; policy accept;
meta l4proto tcp ct state new tcp dport 22 counter packets 12 bytes 720 jump f2b-sshd
}
chain FORWARD {
type filter hook forward priority 0; policy accept;
}
chain OUTPUT {
type filter hook output priority 0; policy accept;
}
chain f2b-sshd {
ip saddr 116.228.88.115 counter packets 0 bytes 0 reject
ip saddr 164.132.205.21 counter packets 1 bytes 60 reject
ip saddr 177.68.148.10 counter packets 0 bytes 0 reject
counter packets 10 bytes 600 return
}
}
iptablesのルールを、そのまま変換したような感じになっているのが分かる。というわけで互換性を保ちつつ、きちんと動作している。
fail2banのnftablesネイティブルール
/etc/fail2ban/action.d
を見てみると、nftablesで始まるネイティブなルールらしきものがある。これをbancactionに指定してみると、なぜかエラーになって動かない。
[sshd]
enabled = yes
banaction = nftables-multiport
banaction_allports = nftables-allports
/var/log/fail2ban.log
を確認してみると、こんなエラーになっている(一部抜粋)。
#39-Lev. 7fd6603dc258 -- exec: nft add set inet filter f2b-sshd \{ type ipv4_addr\; \}
ERROR 7fd6603dc258 -- stderr: 'Error: Could not process rule: No such file or directory'
ERROR 7fd6603dc258 -- stderr: 'add set inet filter f2b-sshd { type ipv4_addr; }'
ERROR 7fd6603dc258 -- stderr: ' ^^^^^^'
ERROR 7fd6603dc258 -- stderr: 'Error: Could not process rule: No such file or directory'
ERROR 7fd6603dc258 -- stderr: 'insert rule inet filter input tcp dport { ssh } ip saddr @f2b-sshd reject'
ERROR 7fd6603dc258 -- stderr: ' ^^^^^^'
ERROR 7fd6603dc258 -- returned 1
ごちゃごちゃして分かりづらいが、inet filter
というルールが見つからないので追加に失敗した、と主張しているようだ。nftablesについては完全に理解していないのだが、inet
というのは標準で実装されているはずのアドレスファミリだ。だがCentOS8のnftablesではルールセットに定義がない。
ここで2つの選択肢がある。
inet filter
を追加して、fail2banが動くようにする- fail2banの方を変更して、既存のルールに追加できるようにする
2の方法については、このページに方法が示されている。
CentOS8の実装では、ip
というアドレスファミリは存在しているのでそれにルールを書くようにし、チェイン名を大文字のINPUT
に変えればよいようだ。
# オススメしない例 - iptablesが動かなくなる
[sshd]
enabled = true
chain=INPUT
banaction = nftables-multiport[nftables_family=ip]
banaction_allports = nftables-allports[nftables_family=ip]
確かにこの設定ならfail2banは動作する。だが今度はiptablesが文句を言うようになる。
[root@c8 ~]# iptables -nL
iptables v1.8.2 (nf_tables): table `filter' is incompatible, use 'nft' tool.
fail2banが入れるnftablesのルールには後方互換性がなく、iptablesが使えなくなってしまうのだ。これは困る。
そこで1の手法である、fail2banのルールをinet filter
の方に入れる手順を試してみる。
nftablesの有効化とinet filterの投入
そもそも、CentOS8にはなぜinet filter
のルールセットがないのだろうか。理由はサービスとしてのnftablesはdisabledになっていて、初期化が行われていないから、である。ではCentOS8の初期状態のnftablesのルールセットはどうやって初期化されているのかというと、実はfirewalldが行っている。このため、/etc/nftables
も/etc/sysconfig/nftables.conf
も読み込まれない。このままではまずいので、まず最初にnftablesを有効化する。
[root@c8 ~]# systemctl enable nftables
Created symlink /etc/systemd/system/multi-user.target.wants/nftables.service → /usr/lib/systemd/system/nftables.service.
次に/etc/sysconfig/nftables.conf
を編集する。inet filter
を有効化するのが目的なので、ファイル中のコメントアウトされた1行を外すだけでよい。
# include "/etc/nftables/inet-filter.nft"
↓
include "/etc/nftables/inet-filter.nft"
ちなみに該当ファイルの内容はこんな感じ。
#!/usr/sbin/nft -f
table inet filter {
chain input { type filter hook input priority 0; }
chain forward { type filter hook forward priority 0; }
chain output { type filter hook output priority 0; }
}
こうしておいてリブート、もしくは次のようにしてルールを読み込む。
nft -f /etc/nftables/inet-filter.nft
これで準備完了だ。これで、エラーになっていたシンプルなルールでfail2banが動作するようになる。再掲載しよう。
[sshd]
enabled = yes
banaction = nftables-multiport
banaction_allports = nftables-allports
今度はエラーにならずに動作するはずだ。nftablesのルールセットの方を確認してみると、このようになる。
table inet filter {
set f2b-sshd {
type ipv4_addr
elements = { 116.228.88.115, 164.132.205.21,
177.68.148.10 }
}
chain input {
type filter hook input priority 0; policy accept;
tcp dport { ssh } ip saddr @f2b-sshd reject
}
chain forward {
type filter hook forward priority 0; policy accept;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
nftablesネイティブの方が、シンプルでよさそうに見える。
この方法でfail2banを運用すると、iptablesは完全にバイパスすることになる(ルールも入らない)が、別のルールを適用して運用することは全く問題ないので、互換性問題も生じない。