サービス自体をDockerでパッケージングするのはよくある利用形態だと思います。小規模なものであればそのままDockerで提供することもあるでしょう。
そして、最近は世の中も物騒です。不要なポートを閉じておくことはもちろんですがサービス提供ポートであっても不要なトラフィックは遮断する必要があります。そういう時に役立つツールがみんな大好きfail2ban
です。
fail2banとは?
fail2banはログを監視して不正(と思われる)アクセスを発見するパケットフィルタを操作して当該アドレスからのアクセスを一定時間停止するツールです。
fail2ban自体の使い方は検索すれば沢山でてくるのでそちらをご参照ください。
iptables chain
Dockerを使う時点でおのずとターゲットはlinuxそして、iptables(もしくはその制御コマンド類)が対象になります。
パケット ---------> routing ----->FORWARD chain ----> 他のノードへ
|
|
INPUT chain <-- fail2banはここで拒否する
|
|
自身宛て
iptablesではパケットが飛んでくると自身宛てのものなのか、あるいは他のノードに転送するかでINPUT, FORWARDいずれかのchainへと送られます。このchainが通過させたり拒否したりという処理を加えるタイミングとなります。
fail2banはそのサーバーで運営しているサービスを守るものなので基本的にはINPUT chainに拒否ルールを生成します。
ところが、Dockerは隔離された仮想環境です。ネットワークも例外ではなく独立した仮想ネットワークが作られます。ということは?仮想ネットワークの先で処理するのでINPUT chainは通らず別のノード(サーバー内にある仮想ノードですが)に送られます。
従って、サーバー自身でfail2banを動作させると、、、拒否ルールが適用されません!!
fail2ban chain
でも、安心してください。fail2banはこういうケースに適用できるようになっています。
通常以下のような設定を書くと思いますが
[sshd]
enabled = true
ここにさらに
chain=FORWARD
と追記すれば、INPUT chainではなくFORWARD chainにルールが適用されます。
なんだ、1行追加するだけでいいんじゃん。
まだおわらない
設定を作ってfail2banを再起動すると一見解決した気になれます。しかし、サーバー自身を再起動すると拒否するはずのトラフィックがすり抜けてくることに気づくと思います。
サーバー再起動後にiptablesを確認すると、Dockerで生成するルールの後にfail2banのルールが配置されています。
これは、Dockerもfail2banもFORWARD chainの先頭にルールを挿入するからです。fail2banがルールをセットし終わった後でDockerがルールをセットすると、fail2banルールの前に割り込んでしまうのでこうなります。そして、DockerはFORWARD chainでコンテナネットワークにトラフィックを割り振るので、割り振りが終わった後で拒否ルールを書いても手遅れです。
Chain FORWARD (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
384K 468M DOCKER-USER all -- any any anywhere anywhere
379K 468M DOCKER-ISOLATION-STAGE-1 all -- any any anywhere anywhere
0 0 ACCEPT all -- any docker0 anywhere anywhere ctstate RELATED,ESTABLISHED
0 0 DOCKER all -- any docker0 anywhere anywhere
0 0 ACCEPT all -- docker0 !docker0 anywhere anywhere
0 0 ACCEPT all -- docker0 docker0 anywhere anywhere
0 0 f2b-sshd tcp -- * * 0.0.0.0/0 0.0.0.0/0
fail2banを再起動すると、一旦最後尾のルールが削除されて先頭に配置されるのでbanが再開されます。
この課題に対する解決方法は二つです。
- fail2banをDockerよりも後で起動させる
- fail2banのルールをFORWARD chain ではなく(Dockerによって生成される)DOCKER-USER chainに適用する
まず2の方法ですが、先ほど chain=FORWARDと書いた部分をchain=DOCKER-USER
と書けば完了です。
ただし、再起動してみるとわかるのですがこれだけではうまくいきません。
fail2banのルール作成時にDOCKER-USER chainが定義済みである必要があります。つまり、Dockerが先に起動を完了しておく必要があります。
となると2つの解決方法ではなく、1は必須。2は、、、重要ではないかもしれません。
起動順序を操る
最近のLinuxはsystemdによって起動プロセスが制御されています。
systemdではBefore, Afterという記述によって自身を他のサービスよりも前に、あるいは後で起動するように指定ができます。
つまり、以下のような順序を持つサービスを登録しておけばDocker, fail2banの起動順序をコントロールできます。
After=docker.service
Before=fail2ban.service
oneshotのダミースクリプトでも用意しておけばよいでしょう。
systemdへのサービスの登録方法も世の中に沢山ドキュメントがあるのでそちらを参照してください。簡単なサンプルに先のAfter,Beforeだけ足しておけばよいでしょう。
結局ベストプラクティスはどこに?
ホストのiptablesにコンテナにも作用するフィルターを適用する解決方法を見てきましたが、コンテナ内にも独立したiptablesが用意されています。場合によってはこちらで対応する方が良いケースもあるかもしれません。目的や用途によってどの段階で処理した方がよいかは変わると思うのでそれぞれにあった手法を見つけることになると思います。