どうも、やまもとやまです。
仮想化技術には様々なものがありますが、今回取り上げるのは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/ へアクセスしてブラウザで表示してみます。
無事表示されましたね!
アクセス制御するには?
さて、それでは本題。
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のルール(チェイン内のルール順序等)がおかしくなることがあります。
その場合は正常に制御ができないので、ホストサーバーごと再起動させるのが手っ取り早い解消方法となります(手抜きですが)。
結果
無事外部からコンテナ内サービスへのアクセス制御を実施することができました。
サーバー起動時に自動で設定するにはどうしたらいいの?とか、コンテナ単体ではなく複数で組み合わせている場合ややこしいかも、、等もありますが、そのあたりはまた機会があれば。
それではまた!