Docker版Jitsi Meetの、nftablesを使用したアクセス制限の設定方法について記載します。
サーバー環境は、以前構築した「Docker版Jitsi Meet」を前提としています。
はじめに
オンライン会議ソフトウェアJitsi Meetサーバーを自前で用意するのは、オープンなサービスというよりは、企業・団体や会員制サービスなど、クローズドなサービスが多いと思います。
ここでは、外部の第三者からのアクセスをできるだけ防ぐ、セキュリティ対策ための工夫のひとつとして、nftablesによるアクセス制限の導入方法をまとめます。
なお、nftablesの設定をミスると、サーバーにSSH接続できなくなることもあり得るので、クラウド・VPSサービスのコンソール機能の利用方法を確認する、あるいは、数分後にnftablesルールをクリアするようatコマンドで仕込んでおく、など、工夫しておくと安心です。
# echo "nft flush ruleset" | at now + 5min
nftablesについて
nftablesは、Linux上で動作するパケットフィルタリング・フロントエンドツールで、広い意味ではファイアウォールといえます。
クラウド、VPSサービスを利用すれば、サーバーの前段にある、各サービスのファイアウォール(AWSのセキュリティグループなど)やパケットフィルタ機能を設定すると思いますが、以下のような、ポート制限以外の細かい設定を行えるので重宝しています。
- サーバー内部ネットワークのパケット制御
- アクセス元の国を指定した許可・拒否ルールの追加
同じようなツールとしては、firewalldやiptablesなどがあり、各自、お好みのものを使えばよいと思います
僕は、iptablesの後継で直感的に扱えるので、自分で選択できる場合は、nftablesを使用しています。
今回は、Docker版Jitsi Meetサーバー環境における、nftablesの設定を考えてみます。
Dockerとnftables
Dockerのデフォルト設定、およびDocker版Jitsi Meetのデフォルト設定では、コンテナを起動する際に、Dockerデーモンがコンテナのポート設定を基に、自動的にiptablesルールを追加して、TCP/80,443、UDP/10000 といったポートがインターネットを含む任意のアドレス(0.0.0.0)に公開されます。
※iptablesサービスを起動していなくても、ufw, firewalld, nftablesサービスを起動していても、Dockerデーモンは自動的にiptablesルール(実体はどれもnetfilterのルール)を追加します。
(参考)
・ホスト上にコンテナのポートを割り当て – Docker-docs-ja
https://docs.docker.jp/v20.10/engine/userguide/networking/default_network/binding.html
・iptablesでDockerコンテナへのアクセス制限をする
https://qiita.com/irotoris/items/f3b98feb69807f450984
Dockerデーモンを起動した状態で、iptablesのfilterテーブルとnatテーブルのルールを確認してみます。
# 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-FORWARD 0 -- 0.0.0.0/0 0.0.0.0/0 Chain OUTPUT (policy ACCEPT) target prot opt source destination Chain DOCKER (2 references) target prot opt source destination ACCEPT 17 -- 0.0.0.0/0 172.18.0.5 udp dpt:10000 ACCEPT 6 -- 0.0.0.0/0 172.18.0.5 tcp dpt:8080 ACCEPT 6 -- 0.0.0.0/0 172.18.0.4 tcp dpt:443 ACCEPT 6 -- 0.0.0.0/0 172.18.0.4 tcp dpt:80 ACCEPT 6 -- 0.0.0.0/0 172.18.0.2 tcp dpt:8888 DROP 0 -- 0.0.0.0/0 0.0.0.0/0 DROP 0 -- 0.0.0.0/0 0.0.0.0/0 Chain DOCKER-BRIDGE (1 references) target prot opt source destination DOCKER 0 -- 0.0.0.0/0 0.0.0.0/0 DOCKER 0 -- 0.0.0.0/0 0.0.0.0/0 Chain DOCKER-CT (1 references) target prot opt source destination ACCEPT 0 -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED ACCEPT 0 -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED Chain DOCKER-FORWARD (1 references) target prot opt source destination DOCKER-CT 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 DOCKER-BRIDGE 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 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 DOCKER-ISOLATION-STAGE-2 0 -- 0.0.0.0/0 0.0.0.0/0 Chain DOCKER-ISOLATION-STAGE-2 (2 references) target prot opt source destination DROP 0 -- 0.0.0.0/0 0.0.0.0/0 DROP 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 # iptables -t nat -nL 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 0 -- 172.18.0.0/16 0.0.0.0/0 Chain DOCKER (2 references) target prot opt source destination RETURN 0 -- 0.0.0.0/0 0.0.0.0/0 RETURN 0 -- 0.0.0.0/0 0.0.0.0/0 DNAT 6 -- 0.0.0.0/0 127.0.0.1 tcp dpt:8888 to:172.18.0.2:8888 DNAT 6 -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.18.0.4:80 DNAT 6 -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:443 to:172.18.0.4:443 DNAT 6 -- 0.0.0.0/0 127.0.0.1 tcp dpt:8080 to:172.18.0.5:8080 DNAT 17 -- 0.0.0.0/0 0.0.0.0/0 udp dpt:10000 to:1
DOCKER, DOCKER-xxxチェインや、MASQUERADEの設定などが存在することがわかります。
ここで例えば、TCP/80,443(=HTTP(S)ポート)へのアクセスを特定のIPアドレスに限定したい場合は、どのように設定すればよいでしょうか?
Dockerオフィシャルドキュメントでは、
「ユーザーカスタム定義は DOCKER-USERチェインに、Sourceを限定したiptablesルールを追加する」
という方法が記載されています。
(参考)
・Docker と iptables – Docker-docs-ja
https://docs.docker.jp/network/iptables.html
ただし、iptablesルールの永続化は、以下の理由から、なかなか難しそうです。
- Dockerコンテナの通信に必要なルールは、Dockerデーモンの起動時に追加する。
- iptablesが起動する時点では、Dockerデーモンが起動していないので、DOCKER-USERチェインが存在しない。(DOCKER-USERチェインへのルール追加がエラーとなる。)
その解決法としては、
「DockerデーモンのSystemd起動設定で、DOCKER-USERチェインへのルール追加を設定する」
という方法があります。
(参考)
・パブリックIPを持つサーバでDockerを起動するとportが全開放される問題の対処法 – grep Tips *
https://www.greptips.com/posts/1268/
ここでは思い切って、Dockerが自動的にiptablesルールを追加しないようにして、コンテナ通信に必要なルールは自分でnftablesに追加する方法を考えてみたいと思います。
※この方法は、Docker公式では非推奨のようですので、設定する際は自己責任でお願いします。
Docker エンジンの /etc/docker/daemon.json 設定ファイルで、 iptables キーを false に設定できます。ですが、このオプションは大部分のユーザにとって適切ではありません。Docker によって作成された iptables ルールを完全に回避できないだけでなく、作成後のルールは、極めて複雑かつ命令範囲が広まってしまう懸念があります。 iptables を false にする設定とは、Docker Engine のコンテナネットワークへ通信を遮断するような場合に使います。
https://docs.docker.jp/network/iptables.html#id3 より
nftablesとDockerデーモンの設定
Dockerがiptablesルールを変更しないようにする
Dockerデーモンの設定ファイルで指定します。
# vim /etc/docker/daemon.json
{
"iptables": false
}
Dockerデーモンを再起動して反映します。
# systemctl restart docker
nftables以外のパケットフィルターサービスの停止
Ubuntuは、デフォルトでは ufw サービスが起動していますので、停止します。
# systemctl disable --now ufw
Synchronizing state of ufw.service with SysV service script with /usr/lib/systemd/systemd-sysv-install.
Executing: /usr/lib/systemd/systemd-sysv-install disable ufw
Removed "/etc/systemd/system/multi-user.target.wants/ufw.service".
# systemctl status ufw
○ ufw.service - Uncomplicated firewall
Loaded: loaded (/usr/lib/systemd/system/ufw.service; disabled; preset: ena>
Active: inactive (dead)
Docs: man:ufw(8)
firewalld サービスや iptables サービスが起動している場合も、同じように停止します。
nftablesの設定
Dockerによるiptablesルールを無効にした場合に、自分で追加すべきコンテナ通信に必要なルールについては、以下の記事を参考にしました。
(参考)
・Docker と nftables – eagletmt’s blog
https://eagletmt.hateblo.jp/entry/2017/05/09/025510
まず、nftablesのSystemd Unitファイルを確認すると、、、
# systemctl cat nftables ... [Service] Type=oneshot RemainAfterExit=yes StandardInput=null ProtectSystem=full ProtectHome=true ExecStart=/usr/sbin/nft -f /etc/nftables.conf ExecReload=/usr/sbin/nft -f /etc/nftables.conf ExecStop=/usr/sbin/nft flush ruleset ...
↑サービス起動時に /etc/nftables.conf を参照しているので、/etc/nftables.conf にルールセットを記述します。
# vim /etc/nftables.conf
#!/usr/sbin/nft -f
flush ruleset
define docker_user_bridge = {"br-xxxxxxxxxxxx"}
define docker_bridges = {"docker0", "br-xxxxxxxxxxxx"}
define docker_bridges_and_lo = {"docker0", "br-xxxxxxxxxxxx", "lo"}
table ip filter {
chain INPUT {
type filter hook input priority 0; policy drop;
ct state established,related counter accept
ct state invalid counter drop
iifname "lo" counter accept
ct state new tcp dport 80 counter accept
ct state new tcp dport 443 counter accept
ct state new udp dport 10000 counter accept
ct state new tcp dport 22 counter accept
icmp type echo-reply counter accept
icmp type destination-unreachable counter accept
icmp type time-exceeded counter accept
}
chain FORWARD {
type filter hook forward priority 0; policy drop;
ct state established, related counter accept
ct state invalid counter drop
iifname $docker_bridges accept
}
chain OUTPUT {
type filter hook output priority 0; policy accept
}
}
table ip nat {
chain PREROUTING {
type nat hook prerouting priority 0
}
chain POSTROUTING {
type nat hook postrouting priority 0
oifname != $docker_bridges_and_lo masquerade
}
}
# EOF
補足します。
冒頭の flush ruleset で、既存のルールをクリアして、このファイルのルールセットに置換します。
次の変数で、Dockerのインタフェースを定義しています。
ルールによって、設定対象のインタフェースが異なるため、微妙に異なる変数を3つ用意しました。
define docker_user_bridge = {"br-xxxxxxxxxxxx"}
define docker_bridges = {"docker0", "br-xxxxxxxxxxxx"}
define docker_bridges_and_lo = {"docker0", "br-xxxxxxxxxxxx", "lo"}
docker0 は、Dockerがデフォルトで作成するブリッジネットワークのインタフェース。
br-xxxxxxxxxxxx は、Dockerのユーザー(ここではJitsi Meet)定義ブリッジネットワークのインタフェースです。
br-xxxxxxxxxxxx の部分は、ipコマンドや、docker networkコマンドで確認できますので、調べて置き換えましょう。
$ ip addr show
...
3: br-xxxxxxxxxxxx: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether d6:aa:23:a3:44:d4 brd ff:ff:ff:ff:ff:ff
inet 172.18.0.1/16 brd 172.18.255.255 scope global br-xxxxxxxxxxxx
valid_lft forever preferred_lft forever
inet6 fe80::d4aa:23ff:fea3:44d4/64 scope link
valid_lft forever preferred_lft forever
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 6a:22:ba:87:b7:a1 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
...
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
8774d52a0944 bridge bridge local
557f9b9bcf58 host host local
xxxxxxxxxxxx jitsi-meet_meet.jitsi bridge local
bae25ce02690 none null local
↑Name: jitsi-meet_meet.jitsi の NETWORK ID が、br-xxxxxxxxxxxx の xxxxxxxxxxxx の部分となっています。
※Dockerのユーザー(ここではJitsi Meet)定義ブリッジネットワークのインタフェース名 br-xxxxxxxxxxxx は、ブリッジネットワークが再作成されると名前が変わてしまうのでご注意ください。docker compose down/up すると、再作成されます。docker compose stop/up では、再作成されません。
filterテーブルのINPUTチェーンでは、ホストサーバーへの一般的なアクセス許可ルールを設定しています。
TCP/80,443 と UDP/10000 は、Jitsi Meetが使用するポート。
TCP/22 は、作業・メンテナンス用のSSHですね。
ct state new tcp dport 80 counter accept
ct state new tcp dport 443 counter accept
ct state new udp dport 10000 counter accept
ct state new tcp dport 22 counter accept
ICMPの制御はお好みで。
filterテーブルのFORWARDチェーンでは、Dockerの2つのブリッジネットワークのインタフェースへの通信を許可しています。
chain FORWARD {
type filter hook forward priority 0; policy drop;
...
iifname $docker_bridges accept
}
natテーブルのPOSTROUTINGチェーンが非常に重要なポイントで、
「Dockerの2つのブリッジネットワークのインタフェースとループバックインタフェース(lo)以外」への通信は、IPマスカレード(NAPT)を設定します。
table ip nat {
...
chain POSTROUTING {
type nat hook postrouting priority 0
oifname != $docker_bridges_and_lo masquerade
}
}
↑これにより、コンテナからホストサーバー外部(インターネットなど)への通信時は、Source NATで、アクセス元IPアドレスを、コンテナのIPアドレスからホストサーバーのIPアドレスに変換して通信します。
条件にループバックインタフェース(lo)を追加したのは、これを含めないと、ホストサーバーでDNSの名前解決ができなくなったからです。
これは、DNSリゾルバーとして systemd-resolved が起動していると、デフォルトでは、ループバックインタフェース(127.0.0.1)経由でのみ名前解決の問い合わせを受け付けるようになっているからなんですね。
※他にも、アクセス元をループバックインタフェース(127.0.0.1)に限定するサービスがあれば、その通信もできなくなってしまう。(ので、ループバックインタフェースもIPマスカレードしないようにする)。
nftablesのルールセットを作成したので、nftablesサービスを起動して反映します。
(すでに起動済みの場合は、nftablesサービスをreloadして反映します。)
# systemctl enable --now nftables
# systemctl status nftables
● nftables.service - nftables
Loaded: loaded (/usr/lib/systemd/system/nftables.service; enabled; preset:>
Active: active (exited) since Fri 2025-06-10 12:40:18 JST; 18s ago
Docs: man:nft(8)
http://wiki.nftables.org
Process: 47527 ExecStart=/usr/sbin/nft -f /etc/nftables.conf (code=exited, >
Main PID: 47527 (code=exited, status=0/SUCCESS)
CPU: 21ms
6月 10 12:40:18 ip-10-0-1-80 systemd[1]: Starting nftables.service - nftables.>
6月 10 12:40:18 ip-10-0-1-80 systemd[1]: Finished nftables.service - nftables.
現在のnftablesルールを確認します。
# nft list ruleset
table ip filter {
chain INPUT {
type filter hook input priority filter; policy drop;
ct state established,related counter packets 36 bytes 2273 accept
ct state invalid counter packets 0 bytes 0 drop
iifname "lo" counter packets 0 bytes 0 accept
ct state new tcp dport 80 counter packets 0 bytes 0 accept
ct state new tcp dport 443 counter packets 0 bytes 0 accept
ct state new udp dport 10000 counter packets 0 bytes 0 accept
ct state new tcp dport 22 counter packets 0 bytes 0 accept
icmp type echo-reply counter packets 0 bytes 0 accept
icmp type destination-unreachable counter packets 0 bytes 0 accept
icmp type time-exceeded counter packets 0 bytes 0 accept
}
chain FORWARD {
type filter hook forward priority filter; policy drop;
ct state established,related counter packets 0 bytes 0 accept
ct state invalid counter packets 0 bytes 0 drop
iifname { "docker0", "br-xxxxxxxxxxxx" } accept
}
chain OUTPUT {
type filter hook output priority filter; policy accept;
}
}
table ip nat {
chain PREROUTING {
type nat hook prerouting priority filter; policy accept;
}
chain POSTROUTING {
type nat hook postrouting priority filter; policy accept;
oifname != { "lo", "docker0", "br-xxxxxxxxxxxx" } masquerade
}
}
ルールが問題なければ、Jitsi Meetにアクセスして、ひととおりの機能が問題なく動作することを確認します。
また、Jitsi Meetだけではなく、ホストサーバーのSSH接続や、サーバーからインターネットへの通信(aptコマンドなど)もしっかり確認します。
- サーバーへのSSH接続
- サーバーから外部へのアクセス(DNSへの名前解決やaptコマンドなど)
など
さらに、ホストサーバーを再起動して、再起動前と同じようにnftablesルールが設定されていることの確認と、ひととおりの動作確認を実施しておくと安心です。
ここまでの設定で、Dockerが自動的にiptablesルールを追加しないようにしたうえで、コンテナ通信に必要なルールは自分でnftablesに追加する設定はひとまず完了です。
コンテナへのポートフォワードについて
nftablesのルールセットを眺めていて、ふと気づいたのですが、個別のコンテナへのポートフォワード設定がありません。
Jitsi Meetの4つのDockerコンテナのうち、Webフロントエンドの処理を行うのはwebコンテナですが、ホストサーバーへのHTTP(S)リクエストを、どうやってwebコンテナに転送するのでしょうか?
調べたところ、docker-proxy (userland-proxy) がその役割を果たしていることがわかりました。
Dockerコンテナ定義のポート設定に従って、コンテナ起動時に、docker-proxyというプロセスが起動されるんですね。
# ps aux | grep docker-proxy
root 46102 0.0 0.3 1523632 2944 ? Sl 15:11 0:00 /usr/bin/docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 8080 -container-ip 172.18.0.3 -container-port 8080 -use-listen-fd
root 46106 0.0 0.3 1523632 3072 ? Sl 15:11 0:00 /usr/bin/docker-proxy -proto udp -host-ip 0.0.0.0 -host-port 10000 -container-ip 172.18.0.3 -container-port 10000 -use-listen-fd
root 46111 0.0 0.3 1523632 3072 ? Sl 15:11 0:00 /usr/bin/docker-proxy -proto udp -host-ip :: -host-port 10000 -container-ip 172.18.0.3 -container-port 10000 -use-listen-fd
root 46117 0.0 0.3 1597364 3072 ? Sl 15:11 0:00 /usr/bin/docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 8888 -container-ip 172.18.0.4 -container-port 8888 -use-listen-fd
root 46629 0.0 0.3 1523632 2944 ? Sl 15:11 0:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 80 -container-ip 172.18.0.5 -container-port 80 -use-listen-fd
root 46633 0.0 0.3 1523632 2944 ? Sl 15:11 0:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 80 -container-ip 172.18.0.5 -container-port 80 -use-listen-fd
root 46639 0.0 0.3 1523632 3200 ? Sl 15:11 0:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 443 -container-ip 172.18.0.5 -container-port 443 -use-listen-fd
root 46646 0.0 0.3 1523632 2944 ? Sl 15:11 0:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 443 -container-ip 172.18.0.5 -container-port 443 -use-listen-fd
mps 47666 0.0 0.2 7740 2304 pts/2 S+ 15:56 0:00 grep --color=auto docker-proxy
# ss -nltp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:* users:(("docker-proxy",pid=46102,fd=7))
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=29350,fd=15))
LISTEN 0 4096 0.0.0.0:80 0.0.0.0:* users:(("docker-proxy",pid=46629,fd=7))
LISTEN 0 4096 0.0.0.0:443 0.0.0.0:* users:(("docker-proxy",pid=46639,fd=7))
LISTEN 0 4096 127.0.0.1:8888 0.0.0.0:* users:(("docker-proxy",pid=46117,fd=7))
LISTEN 0 4096 127.0.0.54:53 0.0.0.0:* users:(("systemd-resolve",pid=29350,fd=17))
LISTEN 0 4096 *:22 *:* users:(("sshd",pid=29351,fd=3),("systemd",pid=1,fd=115))
LISTEN 0 4096 [::]:80 [::]:* users:(("docker-proxy",pid=46633,fd=7))
LISTEN 0 4096 [::]:443 [::]:* users:(("docker-proxy",pid=46646,fd=7))
なるほど、これは知らなかったので、勉強になりました。
(参考)
・The docker-proxy – Adventures in a Cloud Native Landscape
https://windsock.io/the-docker-proxy/
nftablesで日本国内IPアドレス限定設定
冒頭で「外部の第三者からのアクセスをできるだけ防ぐ、セキュリティ対策ための工夫のひとつとして、nftablesによるアクセス制限の導入」と書きましたが、先ほど設定したnftablesのルールセットでは、特定のポートをフルオープンしているので、このままでは、セキュリティ対策の効果が薄いです。
ここでは、日本国内からのアクセスに限定するよう設定してみます。
日本のIPアドレスからのアクセスのみ許可する
この方法は、以前書いた以下の記事を参考にします。
・nftablesでSSHを日本国内からの接続に限定する (CentOS 8)
https://inaba-serverdesign.jp/blog/20191219/nftables-country-ipaddress-centos8.html
このときはSSHのアクセス元を限定しましたが、今回はHTTP(S)など、すべてのポートを日本国内に限定します。
Google等のクローラーからもアクセスされなくなり、SEO的には好ましくありませんが、クローズなサービスであれば、それで問題ないでしょう。
日本のIPアドレスリストファイルを作成
国別のIPアドレスリストは、今回も「世界の国別 IPv4 アドレス割り当てリスト」を使用させていただきます。
ありがとうございます。
# vim /root/bin/mk_country_allowlist.sh
#!/bin/bash
#
WORKDIR=/root/bin
ALLOWLISTSET_FILE=/etc/nftables_country_allowlist
ALLOWLIST_COUNTRY='JP'
cd ${WORKDIR}/
if [ -s cidr.txt ]; then
mv cidr.txt cidr.txt.bak
fi
wget http://nami.jp/ipv4bycc/cidr.txt.gz
gunzip cidr.txt.gz
echo "define country_allowlist = {" > ${ALLOWLISTSET_FILE}
for COUNTRY in ${ALLOWLIST_COUNTRY}
do
sed -n "s/^${COUNTRY}\t//p" cidr.txt | while read ADDRESS
do
echo " ${ADDRESS}," >> ${ALLOWLISTSET_FILE}
done
done
echo "}" >> ${ALLOWLISTSET_FILE}
# EOF
スクリプトファイルに実行権限を付与して、実行します。
# chmod 700 /root/bin/mk_country_allowlist.sh # /root/bin/mk_country_allowlist.sh
作成したIPアドレスのリストを確認します。
# less /etc/nftables_country_allowlist
define country_allowlist = {
1.0.16.0/20,
1.0.64.0/18,
...
223.252.64.0/19,
223.252.112.0/20,
}
↑IPアドレス範囲が、カンマ区切りで記載されていればOKです。
このスクリプトでは、IPアドレス範囲の最後の行の末尾にもカンマが付与されてしまいますが、nftables が include するときに削除してくれるので、問題ありません。
nftablesのルールセット定義を変更
nftablesのルールセットファイル /etc/nftables.conf を変更します。
# vim /etc/nftables.conf
#!/usr/sbin/nft -f
flush ruleset
include "/etc/nftables_country_allowlist"
define docker_user_bridge = {"br-xxxxxxxxxxxx"}
define docker_bridges = {"docker0", "br-xxxxxxxxxxxx"}
define docker_bridges_and_lo = {"docker0", "br-xxxxxxxxxxxx", "lo"}
table ip filter {
set country_accept {
type ipv4_addr; flags interval;
elements = $country_allowlist
}
chain INPUT {
type filter hook input priority 0; policy drop;
ct state established,related counter accept
ct state invalid counter drop
iifname "lo" counter accept
ct state new tcp dport 80 ip saddr @country_accept counter accept
ct state new tcp dport 443 ip saddr @country_accept counter accept
ct state new udp dport 10000 ip saddr @country_accept counter accept
ct state new tcp dport 22 ip saddr @country_accept counter accept
icmp type echo-reply counter accept
icmp type destination-unreachable counter accept
icmp type time-exceeded counter accept
}
...
補足します。
日本のIPアドレスリストファイルを Include します。
include "/etc/nftables_country_allowlist"
filterテーブルの冒頭で、名前付きセット country_accept を作成し、その値(のリスト)は、日本のIPアドレスリストファイルで定義(define)した country_allowlist とします。
set country_accept {
type ipv4_addr; flags interval;
elements = $country_allowlist
}
アクセス許可するポートのソースIPアドレスは、名前付きセット country_accept を参照するようにします。
ct state new tcp dport 80 ip saddr @country_accept counter accept
ct state new tcp dport 443 ip saddr @country_accept counter accept
ct state new udp dport 10000 ip saddr @country_accept counter accept
ct state new tcp dport 22 ip saddr @country_accept counter accept
SSHポートを管理者の固定IPアドレスに限定できれば、より安心ですね。
set develop {
type ipv4_addr; flags interval;
elements = {
xxx.xxx.xxx.xxx,
xxx.xxx.xxx.xxx
}
}
...
ct state new tcp dport 22 ip saddr @develop counter accept
nftablesを reload して新しいルールセットを反映します。
# systemctl reload nftables
ルールセットを確認します。
# nft list ruleset | less
set country_accept {
type ipv4_addr
flags interval
elements = { 1.0.16.0/20, 1.0.64.0/18,
1.1.64.0/18, 1.5.0.0/16,
...
223.252.64.0/19, 223.252.112.0/20 }
}
chain INPUT {
type filter hook input priority filter; policy drop;
ct state established,related counter packets 25 bytes 1752 accept
ct state invalid counter packets 0 bytes 0 drop
iifname "lo" counter packets 0 bytes 0 accept
ct state new tcp dport 80 ip saddr @country_accept counter packets 0 bytes 0 accept
ct state new tcp dport 443 ip saddr @country_accept counter packets 0 bytes 0 accept
ct state new udp dport 10000 ip saddr @country_accept counter packets 0 bytes 0 accept
ct state new tcp dport 22 ip saddr @country_accept counter packets 0 bytes 0 accept
icmp type echo-reply counter packets 0 bytes 0 accept
icmp type destination-unreachable counter packets 0 bytes 0 accept
icmp type time-exceeded counter packets 0 bytes 0 accept
}
...
↑set country_accept で、IPアドレスがたくさんセットされていることがわかります。
ルールセットが反映されたので、Jitsi Meetや、ホストサーバーへのSSH接続など、ひととおりの動作確認を行います。
「日本国外IPアドレスからはアクセスできない」ことの確認が非常に重要ですが、海外のサーバーを自前で建てるのは面倒なので、Webサイトをチェックする海外のサービスなどを利用するのが簡単でしょうか。
(参考)
・海外からWebページを確認する方法
https://qiita.com/natsuya-niki/items/a09e3f7d4e98d2dc4a44
あるいは、AWSマネジメントコンソールを使える環境であれば、CloudShellでcurlコマンドによるWebアクセスを実行してみてもよいでしょう。
nftablesが意図したとおり設定されていれば、タイムアウトします。
$ curl https://meet.example.jp/
※CloudShell実行環境のIPアドレスは、東京リージョンでも、(今回採用したIPアドレスリストでは)日本国外とみなされます。
これで、Jitsi Meetの各機能やSSH接続を、日本国内のIPアドレスに限定できました。
日本以外の国を指定したいとき
アクセス制限を「日本のみ」ではなく、例えば「日本と韓国のみ」としたい場合は、IPアドレスリストを作成するシェルスクリプトの ALLOWLIST_COUNTRY に許可したい国をスペース区切りで指定します。
# vim /root/bin/mk_country_allowlist.sh
#!/bin/bash # WORKDIR=/root/bin ALLOWLISTSET_FILE=/etc/nftables_country_allowlist ALLOWLIST_COUNTRY='JP KR' ...
2文字の国名コードは、例えば法務省の情報などを参考にするとよいでしょう。
・国名コード表 – 法務省
https://www.moj.go.jp/MINJI/common_igonsyo/pdf/001321964.pdf
SSL証明書の自動更新
HTTP(S)のアクセス元を日本国内に限定することのディメリットのひとつとして、
「SSL証明書の自動更新ができなくなる」
というのがあります。
Jitsi MeetのSSL証明書取得は、デフォルトではZeroSSLを使用していますが、証明書作成・更新時のドメイン認証アクセスは海外のサーバーから行うので、エラーとなってしまうんですね。
Jitsi MeetのSSL証明書取得について調べてみると、、、
webコンテナでスクリプトを実行しているようです。
$ docker compose exec web /bin/bash # crontab -l 7 20 * * * "/config/acme.sh"/acme.sh --cron --home "/config/acme.sh" > /dev/null
※時刻はランダムに設定されるようです。
これを踏まえると、以下を実現すれば、証明書の更新ができそうです。
- 一時的にホストのnftablesで、TCP/80, 443 を全IPアドレスに許可するルールを追加
- webコンテナで “/config/acme.sh”/acme.sh スクリプトを実行して更新
- webコンテナで nginx を reload して反映
- ホストのnftablesで、一時的に追加した TCP/80, 443 を全IPアドレスに許可するルールを削除
自動化するのは、少し手間がかかりますね。
この問題の解決方法としては、
「ホストサーバーにNginxを導入しリバースプロキシする」
という方法を考えてみましたが、これは別の記事にまとめます。
日本のIPアドレスリストを定期的に自動更新する
国別IPアドレスリストは随時変更があるため、スクリプトとcronでnftablesルールを自動更新できるようにしてみます。
自動更新スクリプトを作成します。
# vim /root/bin/update_country_allowlist.sh
#!/bin/bash
#
WORKDIR=/root/bin
ALLOWLISTSET_FILE=/etc/nftables_country_allowlist
ALLOWLIST_COUNTRY='JP'
echo "===== Update nftables Country AllowList ====="
echo "`date` Update nftables Country AllowList start"
cd ${WORKDIR}/
if [ -s cidr.txt ]; then
mv cidr.txt cidr.txt.bak
fi
wget http://nami.jp/ipv4bycc/cidr.txt.gz
gunzip cidr.txt.gz
if [ -f cidr.txt ]; then
echo "define country_allowlist = {" > ${ALLOWLISTSET_FILE}
for COUNTRY in ${ALLOWLIST_COUNTRY}
do
sed -n "s/^${COUNTRY}\t//p" cidr.txt | while read ADDRESS
do
echo " ${ADDRESS}," >> ${ALLOWLISTSET_FILE}
done
done
echo "}" >> ${ALLOWLISTSET_FILE}
# nftablesを reload して反映する
systemctl reload nftables
echo "`date` Update/Reload nftables rule"
fi
echo "`date` Update nftables Country AllowList end"
# EOF
実行権限を付与し、スクリプトを実行します。
# chmod 700 /root/bin/update_country_allowlist.sh # /root/bin/update_country_allowlist.sh
作成したIPアドレスのリストを確認します。
# less /etc/nftables_country_allowlist
define country_allowlist = {
1.0.16.0/20,
1.0.64.0/18,
...
223.252.64.0/19,
223.252.112.0/20,
}
IPアドレスリストファイルが更新されたことを確認します。
# ls -l /etc/nftables_country_allowlist -rw-r--r-- 1 root root 58077 6月 10 12:35 /etc/nftables_country_allowlist
nftablesのルールセットを確認します。
# nft list ruleset | less
set country_accept {
type ipv4_addr
flags interval
elements = { 1.0.16.0/20, 1.0.64.0/18,
1.1.64.0/18, 1.5.0.0/16,
...
223.252.64.0/19, 223.252.112.0/20 }
}
chain INPUT {
type filter hook input priority filter; policy drop;
ct state established,related counter packets 25 bytes 1752 accept
ct state invalid counter packets 0 bytes 0 drop
iifname "lo" counter packets 0 bytes 0 accept
ct state new tcp dport 80 ip saddr @country_accept counter packets 0 bytes 0 accept
ct state new tcp dport 443 ip saddr @country_accept counter packets 0 bytes 0 accept
ct state new udp dport 10000 ip saddr @country_accept counter packets 0 bytes 0 accept
ct state new tcp dport 22 ip saddr @country_accept counter packets 0 bytes 0 accept
icmp type echo-reply counter packets 0 bytes 0 accept
icmp type destination-unreachable counter packets 0 bytes 0 accept
icmp type time-exceeded counter packets 0 bytes 0 accept
}
...
問題なさそうですね。
続いて、cronエントリーに追加して、定期実行するよう設定します。
時刻や頻度(間隔)はお好みで。
ここでは、毎週木曜の14時05分に更新するよう設定してみます。
(平日の営業時間のほうが、何か問題があったときに対応しやすいと考えて。)
テストの際は、現在の数分後などを設定するとよいでしょう。
# crontab -e # Update nftables country allow list 5 14 * * thu /root/bin/update_country_allowlist.sh >> /var/log/update_country_allowlist.log 2>&1
実行後、ログを確認します。
(cronの実行については、標準出力・エラーをログに出力することをおすすめします。)
# less /var/log/update_country_allowlist.log ===== Update nftables Country AllowList ===== 2025年 6月 12日 木曜日 14:05:01 JST Update nftables Country AllowList start ... 2025-06-12 14:05:01 (14.6 MB/s) - ‘cidr.txt.gz’ へ保存完了 [622935/622935] 2025年 6月 12日 木曜日 14:05:01 JST Update/Reload nftables rule 2025年 6月 12日 木曜日 14:05:01 JST Update nftables Country AllowList end
IPアドレスリストファイルが更新されたことを確認。
# ls -l /etc/nftables_country_allowlist -rw-r--r-- 1 root root 58077 6月 12 14:05 /etc/nftables_country_allowlist # less /etc/nftables_country_allowlist
nftablesのルールセットを確認します。
# nft list ruleset | less
念のため、Jitsi Meetの動作も確認しましょう。
最後に、スクリプトのログローテート設定を追加します。
# vim /etc/logrotate.d/update_country_allowlist
/var/log/update_country_allowlist.log
{
monthly
rotate 12
compress
missingok
}
おわりに
Docker版Jitsi Meetサーバーにおいて、nftablesを使用したセキュリティ対策についてまとめました。
僕はどうしてもnftablesを使用したかったので、このような方法で設定しました。
Dockerが自動的にiptablesルールを追加しないようにする設定(”iptables”: false)は、Docker公式では推奨していないようですので、ご注意ください。
(参考記事)
文中に記載した参考記事のほか、以下の記事もとても参考になりました。
・docker-compose で起動させた Docker コンテナから送出するパケットを iptables で制限する
https://qiita.com/tatsurou313/items/6fef1858798742cd1150
・Dockerのbridge接続の通信フローをnftablesで追う
https://qiita.com/bashaway/items/39562850e09b5b002d7c
・Dockerがファイアウォールに穴を空けるとはどういうことか? – 新・日々録 by TRASH BOX@Eel
https://eel3.hatenablog.com/entry/2023/07/09/222557
・Dockerで接続元によるアクセス制御を実施する – フューチャースピリッツのエンジニアブログ
・Dockerのiptablesを有効にしたままSSRF対策をするには – セキュアスカイ エンジニアブログ
https://securesky-plus.com/engineerblog/1790/
・dockerとiptable – かないノート
http://hogetan.net/note/diary/20240531_docker_iptables.html
・Docker の iptables 設定
https://blog.tiqwab.com/2018/02/12/learniing-iptables.html
・nftablesでdockerを使ってみました
https://ny-a.github.io/blog2/2020-02/nftables-with-docker/
・nftables/iptables/ufwの使い分け・ベストプラクティス(+Docker連携) – turgenev’s blog
https://turgenev.hatenablog.com/entry/2024/02/27/015827
・【Docker】コンテナネットワークの基礎知識(bridgeネットワークについて)
https://qiita.com/koizumi_naoto/items/17533c425cf33ff0235d
・Docker Userland Proxy – ipSpace
https://blog.ipspace.net/kb/DockerSvc/40-userland-proxy/
Jitsi Meetについて書いた記事まとめ。
