どういうわけだか、2019年になってもなお、自分でサーバを立て、自分でソフトをインストールし、自分でメンテをしながらブログを書くのを止められない。本当だったら最低でも、レンタルサーバ・サービスを使って運用を任せるとか、少なくともドッグフーディングっぽいことをした方がいいようにも思えるが、一方でクラウドサービスも自分が関わったサービスだし立派なドッグフーディングである。ということで、周囲には一定の量のため息をつかれつつも自分でメンテを続けてしまうのであった。そう、このサイトもそうだし、研究所ブログすら自分でメンテしているのだ。うーむ、やっぱりなんかダメかも?
サーバを立てるとポートスキャンがすごい
自分でサーバを立てた人ならご理解いただけると思うが、グローバルIPアドレスを振ると、すごい勢いでポートスキャンされる。22番にはありとあらゆる有りそうな名前でログインを試行されるし、80番にはいろいろな脆弱性をつこうと様々なリクエストが飛んでくる。特に多いのはWordPressの脆弱性を確かめるようなパターンだ。それも同じIPアドレスから何度もしつこく試されるので、ほっとくとDDOSみたいになってしまう。特に認証系やAPIに対して試行されると、破れなくてもCPUパワーを食わされるし、場合によってはサイトダウンしてしまうので困る。そこでパターンを書いて403でブロックするとか、fail2banのようなミドルウェアでフィルタするなどの対策をするわけだ。
fail2banはすごい、が動かない
fail2banは、さくらのクラウドではデフォルトで導入済みなので、sshを短時間のうちにN回失敗するとiptablesに登録され、フィルタされるようになっている。フィルタ時間は比較的短いのでちょっとしたパスワード間違いぐらいなら戻れるし、DDOSをするには「不便」なようにできている。非常によくできたミドルウェアだ。で、fail2banはルールを書けばいろいろな用途に利用できる。たとえばnginxのログを監視して、WordPressの脆弱性を突くような試行を繰り返すIPアドレスをbanするぐらいのことは簡単にできる。具体的にはこんな手順になる。
nginxにフィルタルールを入れる
まずnginxでフィルタルールを入れる。WordPressの脆弱性にはいろいろあるが、よくある攻撃パターンはだいたい以下の通りだ。
- xmlrpc.phpへのリクエスト
- wp-login.phpへの試行(脆弱なパスワードへの攻撃)
- wp-admin以下への試行(過去の様々な脆弱性のチェック)
で、簡単にいうならこれらへのアクセスを403で弾いてしまいたい、ただ特定IPアドレス(たとえば自宅)は除外して、というルールなら、こんな感じになる。
location ~* /xmlrpc\.php|/wp-login\.php|/wp-admin/((?!admin-ajax\.php).)*$ { satisfy any; allow xxx.xxx.xxx.xxx; deny all; : }
こうして弾いてしまうと、許可したIPアドレス以外のアクセスは403エラーになる。で、それがaccess.logに残るようになる。
fail2banを設定する
次にfail2banでフィルタを定義する。/etc/fail2ban/filter.d というディレクトリに nginx-403.conf という名前でファイルを作成する。
[Definition] failregex = ^<HOST>.*"(GET|POST).*" 403 .*$ ignoreregex =
最後にfail2banの定義ファイルを書く。fail2banを初めてカスタマイズするならば、/etc/fail2ban/jail.local というファイルを作成する。サイト固有のローカルな設定は、すべてこのファイルに記述するというのが作法だそうだ。内容はこんな感じに。
[nginx-403] enabled = true backend = auto filter = nginx-403 logpath = /home/northpage/logs/access.log /var/log/nginx/access.log action = iptables-multiport[name="403", port="http,https", protocol="tcp"] maxretry = 5 findtime = 30 bantime = 1800
この設定で重要なポイントは、backendで指定する「auto」という部分だ。これを指定しないと、systemdが選択されてしまう。試しに、backendを指定しないで起動するとfail2banがどのように認識するかログを確認してみよう。こんな風になるはずだ。
2019-05-01 21:59:19,621 fail2ban.jail [3162]: INFO Creating new jail 'nginx-403' 2019-05-01 21:59:19,621 fail2ban.jail [3162]: INFO Jail 'nginx-403' uses systemd {} 2019-05-01 21:59:19,623 fail2ban.jail [3162]: INFO Initiated 'systemd' backend 2019-05-01 21:59:19,625 fail2ban.filter [3162]: INFO Set maxRetry = 5 2019-05-01 21:59:19,626 fail2ban.filter [3162]: INFO Set jail log file encoding to UTF-8 2019-05-01 21:59:19,627 fail2ban.actions [3162]: INFO Set banTime = 1800 2019-05-01 21:59:19,628 fail2ban.filter [3162]: INFO Set findtime = 30 2019-05-01 21:59:19,642 fail2ban.filtersystemd [3162]: NOTICE Jail started without 'journalmatch' set. Jail regexs will be checked against all journal entries, which is not advised for performance reasons. 2019-05-01 21:59:19,647 fail2ban.jail [3162]: INFO Jail 'nginx-403' started
御覧の通り、systemdが選択されていることが分かる。で、それで何が問題かというと、実はこんな副作用があるのだ。jail.confのmanpageを読むとこんな記述がある。
Backends systemd uses systemd python library to access the systemd journal. Spec‐ ifying logpath is not valid for this backend and instead utilises journalmatch from the jails associated filter config.
つまり、systemdが選択されるとlogpathは無視してjournalmatchを使ってどのログファイルを監視するかを決める、というのだ。logpathを無視する? これは大変だ。試しにlogpathがどのように扱われているかを確認してみると、結果はこうなる。
[root@north fail2ban]# fail2ban-client get nginx-403 logpath No file is currently monitored
モニタしてないよ、といっている! これはまずい。
backendをsystemdにしないようにするためには、backend=autoと指定する回避策がある。autoにすると、systemdを避けて他の手段を選んでくれるようになる。おすすめなのはpython-inotifyをインストールして、backendがpyinotifyになるよう仕向けるのがいい。
yum install python-inotify
こうしておいて、backend=auto とするか、明示的に backend=pyinotify を指定すると、起動メッセージはこう変わる。
2019-05-01 22:08:32,538 fail2ban.jail [3162]: INFO Creating new jail 'nginx-403' 2019-05-01 22:08:32,538 fail2ban.jail [3162]: INFO Jail 'nginx-403' uses pyinotify {} 2019-05-01 22:08:32,548 fail2ban.jail [3162]: INFO Initiated 'pyinotify' backend 2019-05-01 22:08:32,551 fail2ban.filter [3162]: INFO Added logfile = /home/northpage/logs/access.log 2019-05-01 22:08:32,553 fail2ban.filter [3162]: INFO Added logfile = /var/log/nginx/access.log 2019-05-01 22:08:32,554 fail2ban.filter [3162]: INFO Set maxRetry = 5 2019-05-01 22:08:32,555 fail2ban.filter [3162]: INFO Set jail log file encoding to UTF-8 2019-05-01 22:08:32,556 fail2ban.actions [3162]: INFO Set banTime = 1800 2019-05-01 22:08:32,557 fail2ban.filter [3162]: INFO Set findtime = 30 2019-05-01 22:08:32,574 fail2ban.jail [3162]: INFO Jail 'nginx-403' started
logpathを確認してみると、正しくログファイルを監視しているのが分かる。
[root@north fail2ban]# fail2ban-client get nginx-403 logpath Current monitored log file(s): |- /home/northpage/logs/access.log `- /var/log/nginx/access.log
なぜこんなことになるのか
実をいうと、jail.confのDEFAULTセクションには backend=auto と書いてあって、systemdが優先される理由がよく分からない。というかそもそも、個別のルールにautoと書いてsystemdが無視されたりpollingが優先される理由が分からない。ソースを追いかけるのも面倒なのでworkaroundだけ記事にまとめておしまいにするが、もし同じ問題にはまっている人がいたら、こんなワケなのでそういう感じで回避してみてほしい。