docker対応ufw(iptabels)設定の件

ここでdockerについての解説をするのはおこがましいので、下記に覚え書きリンクを貼付ける

ローカルLAN環境でWEBサーバーを運用するだけなら良いが、当サイトにdockerを導入するに当たって一番の懸念事項がファイアウォールとの相性問題があった。

当サイトと同じUbuntu20.04LTSで下記のようなファイアウォール設定であったとして

xxxx@user$ sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
80/tcp                     ALLOW       Anywhere
443/tcp                    ALLOW       Anywhere
80/tcp (v6)                ALLOW       Anywhere (v6)
443/tcp (v6)               ALLOW       Anywhere (v6)

dockerコンテナで8080ポートでwebサイトを立ち上げると、ファイアウォールをするりと抜けて外部に公開できてしまう(下記テキトウdocker-compose.yml)

web:
  image: nginx
  volumes:
   - ./templates:/etc/nginx/templates
  ports:
   - "8080:80"
  environment:
   - NGINX_HOST=foobar.com
   - NGINX_PORT=80

当サイトではufwファイアウォールの運用で特定IPアドレスからのアクセスを遮断している(メールサーバへの不正アクセスやwordpressへの不正ログイン試行を手動・自動で監視して遮断対象アドレスをリストアップ)。

ufwコマンドで設定するポートアクセス許可よりも前で設定するために、/etc/ufw/before.rules に記載のあるチェインufw-before-inputにログ出力と遮断コマンドを追加している。

# blacklist-ipset-S ここにブロック対象のアドレスを追加
-A ufw-before-input -s xxx.xxx.xxx.xxx -j auto-blocklist
# blacklist-ipset-S

# タグをつけたログを残してドロップさせるチェイン
# ファイルの先頭の*filter節に追加する必要あり?
-A auto-blocklist -j LOG --log-prefix "[BLOCKLIST]"
-A auto-blocklist -j DROP

こうすることでufw起動時にiptableの遮断リストが設定される。

しかし、上記のdockerコンテナはこの設定も効かず、遮断対象アドレスも通過してしまう。

そこで、まずdockerコンテナが何故ufwの設定を無視できるのかを調べてみる。(iptableコマンド)

user@server:~$ sudo iptables -nvL
Chain INPUT (policy DROP 2979 packets, 152K bytes)
 pkts bytes target     prot opt in     out     source               destination
3687K 3750M ufw-before-logging-input  all  --  *      *       0.0.0.0/0            0.0.0.0/0
3687K 3750M ufw-before-input  all  --  *      *       0.0.0.0/0            0.0.0.0/0
83453 4149K ufw-after-input  all  --  *      *       0.0.0.0/0            0.0.0.0/0
77337 3836K ufw-after-logging-input  all  --  *      *       0.0.0.0/0            0.0.0.0/0
77337 3836K ufw-reject-input  all  --  *      *       0.0.0.0/0            0.0.0.0/0
77337 3836K ufw-track-input  all  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
4406K 5775M DOCKER-USER  all  --  *      *       0.0.0.0/0            0.0.0.0/0
4406K 5775M DOCKER-ISOLATION-STAGE-1  all  --  *      *       0.0.0.0/0            0.0.0.0/0
3634K 4639M ACCEPT     all  --  *      br-a52db8fb2804  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
80854 4814K DOCKER     all  --  *      br-a52db8fb2804  0.0.0.0/0            0.0.0.0/0
 675K 1102M ACCEPT     all  --  br-a52db8fb2804 !br-a52db8fb2804  0.0.0.0/0            0.0.0.0/0
70296 4218K ACCEPT     all  --  br-a52db8fb2804 br-a52db8fb2804  0.0.0.0/0            0.0.0.0/0
71973  318M ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
54110 3031K ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0
    0     0 ufw-before-logging-forward  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 ufw-before-forward  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 ufw-after-forward  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 ufw-after-logging-forward  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 ufw-reject-forward  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    0     0 ufw-track-forward  all  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain OUTPUT (policy ACCEPT 4 packets, 196 bytes)
 pkts bytes target     prot opt in     out     source               destination
3574K 2258M ufw-before-logging-output  all  --  *      *       0.0.0.0/0            0.0.0.0/0
3574K 2258M ufw-before-output  all  --  *      *       0.0.0.0/0            0.0.0.0/0
27893 2070K ufw-after-output  all  --  *      *       0.0.0.0/0            0.0.0.0/0
27893 2070K ufw-after-logging-output  all  --  *      *       0.0.0.0/0            0.0.0.0/0
27893 2070K ufw-reject-output  all  --  *      *       0.0.0.0/0            0.0.0.0/0
27893 2070K ufw-track-output  all  --  *      *       0.0.0.0/0            0.0.0.0/0

この辺りもこのために改めて勉強したため、理解不足かも知れないが、、、

普通にufwで設定するファイアウォールはINPUTチェインが主で、5行目のufw-before-inputのチェインから繋がっている(当サイトのアクセス遮断リストもここに繋げている)。それに対してdockerのファイアウォールはFORWARDチェインであり、16,20行目のDOCKERチェインでコンテナで設定したポートの待ち受けを実現している。それはufwのFORWARD設定よりも優先しているので、アクセス制限はそれよりも前で行う必要がある。ここでアレ?と思うのがDOCKERチェインよりも前にあるDOCKER-USERチェイン。このチェインの中身は空白で何もせずに帰ってくる仕組み。

ならば、ここに遮断リストを追加してやれば良いんじゃね?という感じで、

$ sudo iptables -A DOCKER-USER -s xxx.xxx.xxx.xxx -j DROP

としてみたら、無事遮断していることを確認(とりあえずスマホのIPアドレスを遮断してみて確認)。

実は英文の方のdocker docsにはそれっぽいことが書いてあった。。

次に問題はこのリストを起動時に読み込ませるにはだけど、これは何のことはなくufwの助けが借りられることが分かった。先の遮断リストをログ出力しつつDROPするチェインを追加した要領でDOCKER-USERチェインも/etc/ufw/before.rulesに記載してしまおうという思いつきで実験、、上手く動作したので下記に示します。

$ sudo cat /etc/ufw/before.rules
# Don't delete these required lines, otherwise there will be errors
# added for docker access limits. 21/08/17 by mamiyan
*filter
:ufw-before-input - [0:0]
:ufw-before-output - [0:0]
:ufw-before-forward - [0:0]
:ufw-not-local - [0:0]
:DOCKER-USER - [0:0]           # <---- これ追加
:auto-blocklist - [0:0]        # <---- ログ出力&DROP処理用チェイン
:docker-blocklist - [0:0]      # <---- 同上
# End required lines

・・・・・
# for blocklist custon chain 追加チェイン
-A auto-blocklist -j LOG --log-prefix "[DROPLIST]"
-A auto-blocklist -j DROP

# for docker custom chain 追加チェイン
-A docker-blocklist -j LOG --log-prefix "[DROPDOCKER]"
-A docker-blocklist -j DROP

# blacklist-ipset-S ここから
-A ufw-before-input -s xxx.xxx.xxx.xxx -j auto-blocklist
-A DOCKER-USER -s xxx.xxx.xxx.xxx -j docker-blocklist
# blacklist-ipset-E ここまでにリスト追記

24行25行のように二つのチェイン向けに遮断アドレスリストを追記して、$ sudo ufw reload でdocker向けのファイアウォールが設定されます。その他のアクセス制限もこの/etc/ufw/before.rules の DOCKER-USERチェインに記載しておけば良いと思います。

この辺りは、ufwを少し触れる程度の知識からiptableの仕組みとチェインを触れる程度までじっくり勉強が出来て良かったと思います。またここでCentOSとかのRHL系でfirewalldとかになると変ってくるのだろうか。そもそもあちら側は相性問題は無いのかな。