管理用のSSHを閉じきれないまま使い続けている方は少なくないと思います。総当たり対策として Fail2ban を足したい一方で、設定を誤って自分の管理端末まで締め出し、復旧に時間を取られるのは避けたい。本記事は「強く守る」ための記事ではなく、常用環境へ入れる前に、効かない失敗と効きすぎる失敗を先に作って観測しておくための検証ログです。
この記事でやること / やらないこと
やること:使い捨てのVM内で openssh-server と Fail2ban を実際に導入し、(1)Fail2banが認証ログを読んでいるか、(2)認証失敗の注入でbanが入りファイアウォールにルールが追加されるか、(3)ignoreipで運用元が保護されるか、(4)logpathを誤ると効かないこと、(5)試行回数を絞りすぎると効きすぎること、(6)手動unbanでルールが元へ戻ること、を実コマンドと出力で確認します。
やらないこと:実際の総当たり攻撃の再現はしていません。検出は認証失敗ログを注入して再現しています。また、製品の推奨や購入誘導は行いません。
検証環境
- 使い捨ての検証用VM(Docker利用可、Debian bookworm相当)
- openssh-server / nftables / iptables は導入済み、fail2ban(1.0.2系)を新規導入
- 検証用に固定パスのログファイルと polling backend を使用
Fail2banはsystemd管理のサービスで、ホストのnftables/iptablesを直接操作します。ネットワーク導入が制限される最小LXCでは正直な確認ができないため、aptとsystemd・firewallが使える使い捨てVMで実行しました。
実行コマンドと結果(正常系)
1. パッケージ導入
# apt-get update
# 設定項目=noninteractive apt-get install -y openssh-server fail2ban nftables iptables
# fail2ban-client --version
openssh-server / nftables / iptables は既に最新で、fail2ban(1.0.2-2)が新規導入されました。
2. jail設定と起動
まず検証用ログを空で用意し、jail.local を作成します。
# systemctl enable --now ssh
# : > /var/log/f2b-test.log
# vi /etc/fail2ban/jail.local
[sshd]
enabled = true
filter = sshd
backend = polling
logpath = /var/log/f2b-test.log
maxretry = 3
findtime = 600
bantime = 600
ignoreip = 127.0.0.1/8 ::1 203.0.113.250
# systemctl restart fail2ban
# systemctl is-active ssh fail2ban
active
active
ignoreip の 203.0.113.250 は、運用元(自分の管理端末)を表すドキュメント用IPの代用です。
3. ログを読んでいるかの確認
# fail2ban-client status
# fail2ban-client status sshd
Status
|- Number of jail: 1
`- Jail list: sshd
---
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- File list: /var/log/f2b-test.log
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
File list に対象ログが出ていれば、Fail2banはそのファイルを監視しています。ここがズレていると、後述のとおり何も起きません。
4. 認証失敗を注入してbanを観測
# for i in 1 2 3 4 5; do printf '%s testvm sshd[%d]: Failed password for invalid user audit from 203.0.113.5 port %d ssh2\n' "$(date '+%b %e %H:%M:%S')" "$((1000+i))" "$((40000+i))" >> /var/log/f2b-test.log; done
# fail2ban-client status sshd
# nft list ruleset | grep -i f2b-sshd -A 8
Status for the jail: sshd
`- Actions
|- Currently banned: 1
|- Total banned: 1
`- Banned IP list: 203.0.113.5
--- firewall ---
chain f2b-sshd {
ip saddr 203.0.113.5 counter packets 0 bytes 0 reject
counter packets 1 bytes 52 return
}
maxretry=3 を超える失敗で 203.0.113.5 がbanされ、nftables に f2b-sshd チェーンと reject ルールが追加されました。statusの数字とファイアウォールのルール、両方が揃って初めて「効いている」と言えます。
5. ignoreipで運用元が守られるか
今度は ignoreip に入れた 203.0.113.250 から、同じく失敗を注入します。
# for i in 1 2 3 4 5; do printf '%s testvm sshd[%d]: Failed password for invalid user admin from 203.0.113.250 port %d ssh2\n' "$(date '+%b %e %H:%M:%S')" "$((2000+i))" "$((50000+i))" >> /var/log/f2b-test.log; done
# fail2ban-client status sshd
# nft list ruleset | grep -c 203.0.113.250
`- Actions
|- Currently banned: 1
`- Banned IP list: 203.0.113.5
0
banリストは 203.0.113.5 のまま、203.0.113.250 はファイアウォールに一切現れません(grep一致0)。自分を締め出さないための ignoreip が機能していることを、ここで確認しておきます。
失敗ケースA:logpathを誤ると効かない
logpath を存在しないパスへ変更し、再起動します。
# sed -i 's#^logpath = .*#logpath = /var/log/does-not-exist.log#' /etc/fail2ban/jail.local
# systemctl restart fail2ban
# fail2ban-client status sshd
このとき status の取得は exit 255 で失敗しました。観測された症状は「デーモンへ接続できずクライアントが応答を得られない」状態です。原因は、存在しないログを開けず Fail2ban が正常に起動できなかったこと。logpath の指定ミスは「banが入らない」だけでなく「サービス自体が立ち上がらない」形でも現れる、という点が読みどころです。statusが取れない=何も守れていない、と捉えてください。
失敗ケースB:設定が厳しすぎると効きすぎる
logpathを元に戻したうえで、試行回数と時間を極端に絞ります。
# vi /etc/fail2ban/jail.local
logpath = /var/log/f2b-test.log
maxretry = 1
findtime = 30
bantime = 60
# systemctl restart fail2ban
# printf '%s testvm sshd[7001]: Failed password for invalid user ops from 198.51.100.7 port 61000 ssh2\n' "$(date '+%b %e %H:%M:%S')" >> /var/log/f2b-test.log
# fail2ban-client status sshd
`- Actions
|- Currently banned: 2
`- Banned IP list: 203.0.113.5 198.51.100.7
maxretry=1 のため、たった1回の失敗で 198.51.100.7 が即banされました。打ち間違いや一時的な認証失敗でも締め出される設定で、常用環境では過剰防御に寄りやすい状態です。
手動unbanと復旧確認
誤banした想定で、手動解除とファイアウォールのロールバックを確認します。
# fail2ban-client set sshd unbanip 198.51.100.7
# fail2ban-client status sshd
# nft list ruleset | grep -i 198.51.100.7 || echo 'rule removed'
=== before unban ===
`- Banned IP list: 203.0.113.5 198.51.100.7
=== after unban ===
|- Currently banned: 1
`- Banned IP list: 203.0.113.5
rule removed
unban後はbanリストから外れ、nftablesのrejectルールも消えました(rule removed)。「締め出してしまっても1コマンドで戻せる」ことを、入れる前に手で確かめておくと安心です。
比較:3つの入れ方
| 運用方針 | 検出の確実性 | 誤ban時の復旧 | 過剰防御リスク | 向いている人 |
|---|---|---|---|---|
| Fail2banを入れない | 検出なし | banされないので不要 | なし | 外部公開せず、すぐ使い捨てる環境 |
| 既定値に近い設定で入れる | logpathが合えば検出 | ban時間が長めで待ちが発生しやすい | 中 | ignoreipとstatusを確認済みの常用 |
| 短めban+手動unban確認込みで入れる | 検出と復旧手順を把握 | unbanで即座に戻せる | 低〜中 | 自分を締め出す不安が強い運用者 |
常用前のチェックと初期値の目安
入れてよい条件:fail2ban-client status sshd の File list で対象ログが読まれている、ignoreip に管理端末の経路が入っている、unban コマンドを一度試した。
まだ待つべき条件:status を見ていない、ignoreip 未設定、ban時間や試行回数を感覚だけで長く・厳しくしている。
初期値は緩めから。maxretry=3〜5、findtime=600、bantime=600 程度で様子を見て、ログとstatusを確認しながら詰めるのが安全です。本記事の失敗ケースBのような極小値は、検証で挙動を見るとき限定にとどめてください。
制約・再現条件
- 検出は認証失敗ログを注入して再現したもので、実際の総当たり攻撃ではありません。
- 検証用に固定パスのログと polling backend を使用しています。実環境ではディストリのデフォルト(systemd journal等)に合わせて backend と logpath を確認してください。
- 再現するには、同等の使い捨てVM上で同じ手順を実行します。
まとめ
Fail2banは「入れた事実」より「効いているかを確認できること」と「誤っても戻せること」が要です。status でログ読込を確かめ、ignoreip で運用元を守り、unban で復旧を試す。この3点を使い捨て環境で先に通しておけば、常用SSHへ入れても自分を締め出して慌てる確率は大きく下げられます。
この検証を回している環境
この検証は、自宅の常設ラボ(使い捨てVM/LXCを回す母艦+GPU+VLAN分離ネットワーク)で動かしています。使っている機材と選定理由、全体構成は1本にまとめています。
ラボ構成のまとめを見る →