iptablesでfirewall設定~Dockerのポートフォワードを後から変える方法~

はじめに

Docker上で開発や検証を行っている時に、後からこのポート使いたかったのに既にコンテナをrunしちゃったから削除して新規に立ち上げなきゃみたいな経験がおありでしょうか。自分は何度もありまして、Dockerは裏側ではポートフォワーディングしているだけだから、後から追加で設定が出来てもおかしく無いのにと思いながらも、恥ずかしい話、毎回毎回コンテナを作り直しておりました。結論から言うとポートフォワード を後から追加設定する方法はあります。

※ホスト8001をコンテナ8888に転送する場合
# docker inspect --format '{{ .NetworkSettings.IPAddress }}' 【コンテナ名】
172.17.0.2
# NAT設定
# iptables -t nat -A DOCKER ! -i docker0 -p tcp -m tcp --dport 8001 -j DNAT --to-destination 172.17.0.2:8888
# Filtering設定
# iptables -A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 8001 -j ACCEPT
# 確認
# iptables-save > iptables.txt

最近改めて調べてみたところiptablesを使うことで可能でした。しかし恥の上塗りなことにiptablesについての理解も浅かったので、上を解説する前に今回はfirewallの基本的な話からまとめてみたいと思います。

iptables

iptablesとはCentOSに標準で備わっているFirewallのこと。パケットをフィルタリングしたりNATしたり出来ます。普段触っているfirewalldはiptablesのフロントエンドの事です。

チェイン

通過するパケットの許可や拒否を行うわけですが、その経路のことをチェインと呼びます。

  • INPUT
    • 入ってくるパケット
  • OUTPUT
    • 出て行くパケット
  • FORWARD
    • 転送されるパケット
  • PREROUTING
    • 受信時に宛先アドレスを変換
    • filter前
  • POSTROUTING
    • 送信時に送信元アドレスを変換
    • filter後、送信前
テーブル

iptablesは該当するチェイン、タイミング別に4つのテーブルに分けて設定を行います。

  • filter
    • パケットの通過/遮断
    • 省略時のデフォルトテーブル
    • INPUT, OUTPUT, FORWARD
  • nat
    • NAT変換
    • POSTROUTING, PREROUTING, OUTPUT
  • mangle
    • TOS(Type Of Service)フィールド値変更
    • 優先度付け
    • POSTROUTING, PREROUTING, INPUT, OUTPUT, FORWARD
  • raw
    • firewall以外への装置転送用のパケットマーク
    • PREROUTING, OUTPUT

設定確認方法は以下の通りです。

$ iptables -L -t filter
target     prot opt source               destination         

$ iptables -L -v -n -t filter

今回の試験はAWS環境で行っており、セキュリティグループで管理しているためINPUTはsshを許可、OUTPUTはフルオープンとなっていますがiptablesには何も入っていません。とはいえAWSのセキュリティグループには上限がありますので、細かい設定はiptablesで行うという人も多いようです。また本来何も入っていなければ何も評価しないので、全てオープンとなるのですが、セキュリティグループも影響するので両方を考える必要があります。

  • -t
    • 参照テーブル指定
    • 省略時はfilterテーブルを参照する
  • -v
    • 詳細情報表示
  • -n
    • 名前解決をしない
  • -A
    • ルール追加
  • -j
    • ターゲット指定、ACCEPT/DROPなど

書式は次の通りです。

# iptables [-t テーブル] コマンド [マッチ] [ターゲット/ジャンプ]
# iptables -t filter -A INPUT -p tcp -j ACCEPT

上の例を実行するとtcpプロトコルの通信を全て許可します。

Dockerを起動したiptables

dockerデーモンを起動すると、iptablesが少し変化します。まずはfilterテーブルを見ていきます。

# iptables -L -t filter -nv --line-number
Chain INPUT (policy ACCEPT 24383 packets, 78M bytes)
num   pkts bytes target     prot opt in     out     source               destination         

Chain FORWARD (policy DROP 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1     4534   53M DOCKER-ISOLATION  all  --  *      *       0.0.0.0/0            0.0.0.0/0           
2     2450   53M DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0           
3     2450   53M ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
4     2084  121K ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0           
5        0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0           

Chain OUTPUT (policy ACCEPT 24345 packets, 4846K bytes)
num   pkts bytes target     prot opt in     out     source               destination         

Chain DOCKER (1 references)
num   pkts bytes target     prot opt in     out     source               destination         

Chain DOCKER-ISOLATION (1 references)
num   pkts bytes target     prot opt in     out     source               destination         
1     4534   53M RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0    

主にFORWARDチェインにルールが追加されています。

  • まず全通信がDOCKER-ISOLATIONチェインで評価される
    • 何もせずRETURNで元のチェインに戻る
  • docker0 IF行のパケットはDOCKERチェインで評価される
    • 何もしない
  • docker0 IF行のctstate RELATED,ESTABLISHED状態のパケットは許可
  • docker0 IFから来てdocker0 IF以外行のパケットは全て許可
    • コンテナ→外部など
    • 別docker NW行の場合はDOCKER-ISOLATIONチェインで評価
  • docker0 IFから来てdocker0 IF行のパケットは全て許可
    • コンテナ間通信など

続いてnatテーブルを見ていきます。

# iptables -L -t nat -nv --line-number
Chain PREROUTING (policy ACCEPT 1273 packets, 75163 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1     1196 70261 DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT 1196 packets, 70261 bytes)
num   pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 1731 packets, 143K bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1        0     0 DOCKER     all  --  *      *       0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT 1731 packets, 143K bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1       77  4902 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0           

Chain DOCKER (2 references)
num   pkts bytes target     prot opt in     out     source               destination         
1        0     0 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0    
  • PREROUTING
    • ADDRTYPE match dst-type LOCALの時にDOCKERチェインで評価する
  • OUTPUT
    • ADDRTYPE match dst-type LOCALでループバックアドレス行ではない場合にDOCKERチェインで評価する
  • POSTROUTING
    • 172.17.0.0/16はDockerのブリッジネットワーク
    • このNWから外部NW行のパケットにMASQUERADEを実施する

NATには送信元アドレスを変換するSNAT宛先アドレスを変換するDNATがあるかと思いますが、MASQUERADEはSNATの事です。SNATはPREROUTING、DNATはPOSTROUTINGで定義します。

コンテナを起動したiptable

ホストの80番ポートをコンテナの8080番ポートにフォワーディングする設定でコンテナを起動してみます。

# docker run -itd -p 80:8080 centos:7 /bin/bash

iptablesのfilterテーブルを見てみます。

# iptables -L -t filter -nv --line-number
Chain INPUT (policy ACCEPT 104 packets, 7292 bytes)
num   pkts bytes target     prot opt in     out     source               destination         

Chain FORWARD (policy DROP 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1     4534   53M DOCKER-ISOLATION  all  --  *      *       0.0.0.0/0            0.0.0.0/0           
2     2450   53M DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0           
3     2450   53M ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
4     2084  121K ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0           
5        0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0           

Chain OUTPUT (policy ACCEPT 77 packets, 6544 bytes)
num   pkts bytes target     prot opt in     out     source               destination         

Chain DOCKER (1 references)
num   pkts bytes target     prot opt in     out     source               destination         
1        0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.2           tcp dpt:8080

Chain DOCKER-ISOLATION (1 references)
num   pkts bytes target     prot opt in     out     source               destination         
1     4534   53M RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0    

DOCKERチェインにルールが追加されています。docker0 IF以外からdocker0 IFへのパケットで172.17.0.2:8080宛ての場合に許可しています。

続いてnatテーブルを見てみます。

# iptables -L -t nat -nv --line-number
Chain PREROUTING (policy ACCEPT 7 packets, 384 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1     1236 72609 DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT 7 packets, 384 bytes)
num   pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 5 packets, 623 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1        0     0 DOCKER     all  --  *      *       0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT 5 packets, 623 bytes)
num   pkts bytes target     prot opt in     out     source               destination         
1       77  4902 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0           
2        0     0 MASQUERADE  tcp  --  *      *       172.17.0.2           172.17.0.2           tcp dpt:8080

Chain DOCKER (2 references)
num   pkts bytes target     prot opt in     out     source               destination         
1        0     0 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0           
2        0     0 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80 to:172.17.0.2:8080

DOCKERチェインにルールが追加されています。docker0 IF以外から来たパケットで宛先ポートが80番の場合、172.17.0.2:8080にDNAT変換を行っています。

今までiptablesを特に意識する事なくdocker runを行ってきましたが、裏では結構色々なことをしていたんすね..

最後に

ここで改めて8001→8888のポート変換を行ったコマンドを見てみます。(抜粋)

# iptables -t nat -A DOCKER ! -i docker0 -p tcp -m tcp --dport 8001 -j DNAT --to-destination 172.17.0.2:8888
# iptables -A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 8001 -j ACCEPT

一行目はnatテーブルにポートフォワーディングの設定をDOCKERチェインに入れています。docker0 IF以外から来て宛先ポート番号が8001番の時、宛先を172.17.0.2:8888にDNAT変換しています。

二行目ではfilterテーブルに通信を許可する設定をDOCKERチェインに追加しています。docker0 IF以外からdocker0 IF行で172.17.0.2:8001宛のパケットを許可しています。

これでコンテナを消さずにポートフォワーディングの設定を追加する事が出来ます。以上です。

docker用ポート設定の追加方法はこちらを参考にしました。
iptablesについてはこちらを参考にしました。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA