Dockerで接続元によるアクセス制御を実施する

やまもとやま
2025-02-03
2025-02-03

どうも、やまもとやまです。
仮想化技術には様々なものがありますが、今回取り上げるのはDockerになります。
さて、Dockerコンテナによる機能の軽量な分離、便利ですよね。
ところで外部からのアクセスを制御しようとした場合、どこで制御すれば良いのでしょうか。

環境

Dockerのホストとして、AlmaLinux9を(ほぼ)最小インストールした環境を想定しています。
ホストのIPアドレスは198.51.100.10とします。
ついでに、firewalldで以下のみ外部からの接続を許可しているとします。

○外部からの接続許可サービス
HTTP/HTTPS

○特定ネットワークから全許可
198.51.100.0/24

とりあえずDockerを入れてみる

何はともあれDockerを導入しましょう。

# dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# dnf install docker-ce

Dockerを起動します。

# systemctl start docker

Nginxイメージをpullして起動します。
起動の際、ポート指定でホストの80ポートをコンテナの80ポートに転送します。

# docker pull nginx:latest
# docker run --name webtest -d -p 80:80 nginx

docker psで状態を確認しましょう。

# docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED              STATUS              PORTS                               NAMES
35a80cd2db34   nginx     "/docker-entrypoint.…"   About a minute ago   Up About a minute   0.0.0.0:80->80/tcp, :::80->80/tcp   webtest

正常に起動しているようなので、http://198.51.100.10/ へアクセスしてブラウザで表示してみます。

nginx

無事表示されましたね!

アクセス制御するには?

さて、それでは本題。
Webアクセスを制御(接続元により許可や拒否)したい場合どうすれば良いでしょう。
ぱっと思い浮かぶのはホスト機のfirewalldですね。

ということで、firewalldでHTTP/HTTPSの許可ルールを削除してみます。

# firewall-cmd --zone=public --remove-service=http
# firewall-cmd --zone=public --remove-service=https

ブラウザで確認確認、、、あれー、、まだ表示されますね。。
どうなってるのでしょう??

Dockerとiptables

実は、Dockerを起動するとiptablesが有効になり、Docker関連のチェインやルールが追加されます。
ルールを見てみましょう。

# iptables -nL
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy DROP)
target     prot opt source               destination
DOCKER-USER  0    --  0.0.0.0/0            0.0.0.0/0
DOCKER-ISOLATION-STAGE-1  0    --  0.0.0.0/0            0.0.0.0/0
ACCEPT     0    --  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
DOCKER     0    --  0.0.0.0/0            0.0.0.0/0
ACCEPT     0    --  0.0.0.0/0            0.0.0.0/0
ACCEPT     0    --  0.0.0.0/0            0.0.0.0/0

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain DOCKER (1 references)
target     prot opt source               destination
ACCEPT     6    --  0.0.0.0/0            172.17.0.2           tcp dpt:80

Chain DOCKER-ISOLATION-STAGE-1 (1 references)
target     prot opt source               destination
DOCKER-ISOLATION-STAGE-2  0    --  0.0.0.0/0            0.0.0.0/0
RETURN     0    --  0.0.0.0/0            0.0.0.0/0

Chain DOCKER-ISOLATION-STAGE-2 (1 references)
target     prot opt source               destination
DROP       0    --  0.0.0.0/0            0.0.0.0/0
RETURN     0    --  0.0.0.0/0            0.0.0.0/0

Chain DOCKER-USER (1 references)
target     prot opt source               destination
RETURN     0    --  0.0.0.0/0            0.0.0.0/0

ちょっと長いですが、よく見るとDocker関連のルールも表示されていますね。
DOCKERチェインで、172.17.0.2のTCP80へのアクセスが許可されていますが、172.17.0.2が起動したNignxコンテナのIPアドレスのようです。
また、FORWARDチェインでDOCKERチェインを呼び出しています。
そのため、ホストのfirewalldでHTTPが許可されていないのにアクセスができているという訳です。

ちなみに、natのルールを見るとこうなっています。

# iptables -nL -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DOCKER     0    --  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
DOCKER     0    --  0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  0    --  172.17.0.0/16        0.0.0.0/0
MASQUERADE  6    --  172.17.0.2           172.17.0.2           tcp dpt:80

Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     0    --  0.0.0.0/0            0.0.0.0/0
DNAT       6    --  0.0.0.0/0            0.0.0.0/0            tcp dpt:80 to:172.17.0.2:80

動作としては、ざっくりこんな感じになっています(のはずです)。
・外部からホストのTCP80ポートへアクセス
・Docker-proxyがホストのTCP80ポートで接続を受け、172.17.0.2のTCP80ポートへ転送
・iptablesで許可されているのでコンテナのNginxへアクセスされ、表示

では、制御するにはどこですれば良いでしょうか?
FORWARDチェインの中で、DOCKERチェインより前に呼び出されているDOCKER-USERチェインがありますね。
このユーザ用チェインの中で制御してやればOKです。

試しに、特定ホスト(203.0.113.123)からのみ接続を許可してみましょう。
公式にも記載がありますが、ホストのインタフェース指定が必要なようなのでens3を指定しています。

# iptables -I DOCKER-USER -i ens3 -p tcp --dport 80 -j REJECT
# iptables -I DOCKER-USER -i ens3 -p tcp --dport 80 -s 203.0.113.123 -j RETURN

# iptables -nL DOCKER-USER
Chain DOCKER-USER (1 references)
target     prot opt source               destination
RETURN     6    --  203.0.113.123        0.0.0.0/0            tcp dpt:80
REJECT     6    --  0.0.0.0/0            0.0.0.0/0            tcp dpt:80 reject-with icmp-port-unreachable
RETURN     0    --  0.0.0.0/0            0.0.0.0/0

こうなりました。
さて、表示はどうでしょうか?どきどき。。

203.0.113.123からは表示ができました。
他のホストからは表示ができません。成功ですね!

なお、注意点として、firewalldのリロードやDocker再起動等を行うと、iptablesのルール(チェイン内のルール順序等)がおかしくなることがあります。
その場合は正常に制御ができないので、ホストサーバーごと再起動させるのが手っ取り早い解消方法となります(手抜きですが)。

結果

無事外部からコンテナ内サービスへのアクセス制御を実施することができました。
サーバー起動時に自動で設定するにはどうしたらいいの?とか、コンテナ単体ではなく複数で組み合わせている場合ややこしいかも、、等もありますが、そのあたりはまた機会があれば。

それではまた!