Dockerの特定コンテナにアクセスできるPCを制限する


はじめに

職場で 遊んで 仕事している最中に、
Dockerで立ち上げた特定のコンテナへのアクセスを特定のPCに制限したい
という状況が発生しました。
最初はコンテナ起動のオプションでいい感じにできるだろうと思っていたのですが、
どうやらDockerのオプションだけでは細かいアクセス制御はできないようでした。

最終的にはパケットフィルタ(iptables)の設定をいい感じにすることで当初の目的を達成できたので、備忘録として残しておこうと思います。
想定環境は Linux OS です(RaspberryPi を含む)。

結論(TL;DR)

以下の設定で、指定したIPからのみ特定のコンテナ(ポート)へのアクセスを許可することができます。当たり前ですが、ルールを複数使う場合は RETURN の後に REJECT が来るようにしてください。

sudo iptables -I DOCKER-USER -p tcp --dport コンテナが開放しているポート番号 -m conntrack --ctorigdstport ホストPCの開放ポート番号 -j REJECT
sudo iptables -I DOCKER-USER -s 接続を許可するIPアドレス -p tcp --dport コンテナが開放しているポート番号 -m conntrack --ctorigdstport ホストPCの開放ポート番号 -j RETURN

詳細説明

iptables とは

Wikiより

iptablesは、Linuxに実装されたパケットフィルタリングおよびネットワークアドレス変換 (NAT) 機能であるNetfilter(複数のNetfilterモジュールとして実装されている)の設定を操作するコマンドのこと。Netfilterは、いわゆるファイアウォールやルータとしての役割を果たす。

誤解を恐れずに言えば、Linux のファイアウォールです。
iptables に設定したルールに基づいてパケットの受信や破棄が行われます。
フィルタルールは上から順に適用され、最初に適用されたルールが最優先となります。

Docker と iptables

Docker は iptables に DOCKERDOCKER-USER という独自の チェイン(Chain)を持っています。チェインとは通信フィルタルールのグループだと思ってください。
publish オプション(-p)でコンテナを外部公開した場合、Docker は DOCKER チェインへ自動的にフィルタルールとアドレス変換(NAT)ルールを追加します。
これが良いかどうかは各方面で議論されているようですが、この機能のおかげで何も考えずとも公開されたポートにアクセスすれば、コンテナで立ち上げたサービスを利用することができるようになっています。

しかし、publish オプション(-p)だけでは、
「パソコンAはアクセスしていいけど、パソコンBからはアクセスできないようにする」
といったコンテナへのアクセス制限(IPアドレス制限)をすることはできません。
このような複雑なパケット処理のルールを追加するためには、DOCKER-USER チェインに独自ルールを追加する必要があります。

基本となるフィルタルールとNAT

特定PCのみアクセス許可するためには、次の2つのルールを追加する必要があります。

  1. パソコンAからのアクセスだったら、パケットを通すルール
  2. パソコンA以外からのアクセスだったら、パケットを破棄するルール
1. パソコンAからのアクセスだったら、パケットを通すルール(不完全)

特定のポート(コンテナ)へパソコンAからアクセスがあった場合は、パケットを通すルールです。プロトコルがUDPの場合は、-p udp としてください。

sudo iptables -I DOCKER-USER -s パソコンAのIPアドレス -p tcp --dport ポート番号 -j RETURN
2. パソコンA以外からのアクセスだったら、パケットを破棄するルール(不完全)

特定のポート(コンテナ)へのアクセスを全て拒否(パケットを破棄)するルールです。
このルールは上のルールよりも下に置く必要があります。

sudo iptables -I DOCKER-USER -p tcp --dport ポート番号 -j REJECT

しかしこのままでは問題があります。
実は --dport で指定するポート番号はコンテナ側で受け付けるポート番号になります。
例えば、nginx を以下のようなコマンドで起動したとします。

docker run -it -d -p 8080:80 --name my_nginx nginx

この場合、--dport には ポート8080 ではなく ポート80 を指定しなければなりません。
これはフィルタルールを適用する前に、アドレス変換(NAT)の処理が入っているためです。
このままでは下記のような構成だとうまくいかなくなります。

サーバ名 ホスト側のポート コンテナ側のポート ルール
nginx1 8080 80 パソコンAからのみアクセス可
nginx2 8081 80 すべてのPCからアクセス可

したがって、ホスト側のポート番号も考慮したフィルタを構成する必要があります。

ホスト側のポートも考慮したフィルタルール

iptables の conntrack というオプションを利用します。

1. パソコンAから特定のホストポートへのアクセスだったら、パケットを通すルール

特定のポート(コンテナ)へパソコンAからアクセスがあった場合は、パケットを通すルールです。プロトコルがUDPの場合は、-p udp としてください。

sudo iptables -I DOCKER-USER -s パソコンAのIPアドレス -p tcp --dport コンテナ側のポート番号 -m conntrack --ctorigdstport ホスト側のポート番号 -j RETURN
2. パソコンA以外から特定のホストポートへのアクセスだったら、パケットを破棄するルール

特定のポート(コンテナ)へのアクセスを全て拒否(パケットを破棄)するルールです。
このルールは上のルールよりも下に置く必要があります。

sudo iptables -I DOCKER-USER -p tcp --dport コンテナ側のポート番号 -m conntrack --ctorigdstport ホスト側のポート番号 -j REJECT

これで当初目的としていた動作が実現できました。

設定例

nginx サーバを2つ立ち上げて、片方はすべてのPCからアクセスを許可し、もう一方は特定のPCからのみアクセスを許可するように設定してみます。検証環境は以下の通りです。

項目 バージョンなど
マシン RaspberryPi4 4GB
OS Linux raspberrypi 5.10.17-v8+ #1421 SMP PREEMPT Thu May 27 14:01:37 BST 2021 aarch64 GNU/Linux
Docker Docker version 20.10.7, build f0df350
サーバ名 ホスト側のポート コンテナ側のポート ルール
nginx1 8080 80 192.168.1.24 からのみ
アクセス可
nginx2 8081 80 すべてのPCからアクセス可

初期の iptables 設定状態

$> iptables -L
(中略)
Chain DOCKER-USER (1 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere

Chain DOCKER (2 references)
target     prot opt source               destination

1. iptables へのルール追加

sudo iptables -I DOCKER-USER -p tcp --dport 80 -m conntrack --ctorigdstport 8080 -j REJECT
sudo iptables -I DOCKER-USER -s 192.168.1.24 -p tcp --dport 80 -m conntrack --ctorigdstport 8080 -j RETURN

2. コンテナの立ち上げ

$> docker run -it -d -p 8080:80 --name nginx1 nginx
$> docker run -it -d -p 8081:80 --name nginx2 nginx
$> docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                                   NAMES
73fed61ceb4f   nginx     "/docker-entrypoint.…"   5 seconds ago    Up 5 seconds    0.0.0.0:8081->80/tcp, :::8081->80/tcp   nginx2
c987876549e0   nginx     "/docker-entrypoint.…"   12 seconds ago   Up 11 seconds   0.0.0.0:8080->80/tcp, :::8080->80/tcp   nginx1
$> iptables -L
(中略)
Chain DOCKER-USER (1 references)
target     prot opt source               destination
RETURN     tcp  --  192.168.1.24         anywhere             tcp dpt:http ctorigdstport 8080
REJECT     tcp  --  anywhere             anywhere             tcp dpt:http ctorigdstport 8080 reject-with icmp-port-unreachable
RETURN     all  --  anywhere             anywhere

Chain DOCKER (2 references)
target     prot opt source               destination
ACCEPT     tcp  --  anywhere             172.17.0.2           tcp dpt:http
ACCEPT     tcp  --  anywhere             172.17.0.3           tcp dpt:http

3. アクセス検証

192.168.1.24 からのアクセス結果

192.168.1.26 からのアクセス結果

狙い通り、特定のコンテナへのアクセスが制限されています。

おわりに

今回は Docker で立ち上げた特定のコンテナへのアクセスを特定のPCに制限する方法についてまとめました。
注意点として、今回の設定は PCを再起動すると全て消えます。設定の永続化をする場合は、iptables-persistent などを使って永続化設定を行ってください。やり方は参考資料のURLを参照してください。

参考資料