treafikでIPアドレス制限を行う。
環境
- traefik 2.8
- nginx 1.23
ブラックリスト方式
特定のIPアドレスによるアクセスを遮断する。CIDR指定も可能。traefikのmiddlewareとアクセス可否判別用サーバとしてnginxを使う。
以下の3つを参考にした。最初のを検索エンジンで見つけ、あとはそこから辿ったりした:
これらを参考し、必要な設定を追加したものを書き留めておく。
この方式の流れ
この方式の流れの確認:
- traefikにWebサーバ宛のリクエストが着く。
- traefikが一旦アクセス可否チェック用サーバ(のURL)にリクエストを転送する。
- アクセス可否チェック用サーバで(IPアドレスに基づき)
200
か403
などを返す。 200
が返されたらtraefikは通常通りWebサーバにリクエストを渡す。403
が返されたらtraefikはそれをそのままリクエスト元に返す。
のような感じ。traefikのforwardAuth
というミドルウェアを使うので、これの設定と、アクセス可否チェック用サーバ(nginxを使った)を立てておけばいいことになる。
traefikのミドルウェアの指定はサービス単位で行うので、適応したいサービスごとにアクセス可否を行える。アクセス可否チェック用サーバは(URLのpathで分岐させればいいので)1つでいい。
実装
既存のサービス側での実装と、新規に(もしくは既存の)アクセス可否チェック用サーバを用意する。配置はどのようにしてもいい。独立させておいた方がいい気はする。
サービス側で行うこと
既存サービスではラベルを追加するだけでいい。構成によってはネットワーク追加変更などもする必要がある。
次のようにラベルを追加する:
services:
awebserver:
build: ./nginx
labels:
- "traefik.http.middlewares.blockiplist.forwardauth.address=http://ipfilter:8080/traefik"
- "traefik.http.routers.<app_name>.middlewares=blockiplist@docker"
- "..."
networks:
- traefik_network
networks:
traefik_network:
external: true
forwardAuth
で外部サーバのアドレスとしてhttp://ipfilter:8080/traefik
を指定する。アクセス可否の判断をこのアドレスへ委ねる。
そして、サービスがこのミドルウェアを使うように指定する。
ミドルウェアを複数使う場合は、- "traefik.http.routers.<app_name>.middlewares=mware1@docker,mware2@docker"
のようにカンマ区切りで指定できる。確認した限りでは、最初に書いたものから実行されているようだ。
ipfilterと通信できるように同一のネットワーク(traefik_network)に接続しておく。
チェック用サーバで行うこと
アクセス可否チェック用サーバは新規に立てるので、上より分量は多め。docker-compose.yml
とnginxのconf
ファイルを用意する。
まずはdocker-compose.yml
から:
services:
ipfilter:
image: nginx:alpine
container_name: ipfilter
expose:
- "8080"
volumes:
- ./conf/:/etc/nginx/conf.d/
networks:
- traefik_network
networks:
traefik_network:
external: true
独立してup
するので、traefikに繋がっているexternal
なネットワークに接続しておく。ポート番号は上述のサービスで指定したものと一致していればなんでもよく、ここでは8080
をexpose
しておいている。参考元ではports
でホストのポートと繋いでいたと思うが、それは不要のはず。
参考元の通りcontainer_name
を付けているが、もしかしたら不要かもしれない(未実験)。
そして次にconf
ファイルの指定:
# ./conf/default.conf
server_tokens off;
server {
listen 8080;
location /traefik {
add_header Content-Type "default_type text/plain";
if ($blocked_ip) {
return 403;
}
return 200 "OK";
}
}
参考元とそれほど変わらない、シンプルにtraefikのミドルウェアで指定したアドレスに対応するロケーションディレクティブを定義する。$blocked_ip
は次ファイルのgeo
で定義するアクセス可否用の変数で0以外なら真となる。
次に$blocked_ip
を定義するconf
ファイル:
# ./conf/blocklist.conf
# geo使って可否を判断
# 0を設定すると偽、0以外を設定すると真になる。
geo $blocked_ip {
default 0;
# docker network inspect traefik_networkなどで確認できるtraefikのIPアドレスが存在する範囲を指定。
# proxyを指定していると、XFFを見てくれるようになる。
# proxy 172.18.0.0/16;
# 調べるのが面倒ならプライベートIPアドレスでもいいかも…
proxy 172.16.0.0/12;
# sample here
# 127.0.0.1 1;
# 192.168.0.0/16 1;
# 10.0.0.0/8 1;
# ::1 2;
# 2001:0db8::/32 1;
}
# 異なる制限でのサービスを増やすならgeoディレクティブを増やす。変数名とproxyの指定を忘れないこと。
参考元と異なる点として、proxy
を設定している。traefikから送られてくるので、x-forwarded-for
にアクセス元IPアドレスがある。geoでは、デフォルトで$remote_addr
を基に変数を格納するので、proxy
を設定しないとtraefikのIPアドレスを判別に使ってしまう。$proxy_add_x_forwarded_for
(だったかな?)を基に変数を決めるようにしても良かったが、カンマ区切りなどで予期しない動作をしないとも限らないので、proxy
機能を使ってnginx任せにした。
proxy
にはtraefikのIPアドレスを指定する。docker network inspect traefik_network
で調べられる。
この構成ではプライベートIPアドレス内で繋がっているので、それを指定している。本当はもう少し範囲を狭めるべきかもしれない。しかしプライベートIPアドレスに偽装するのは難しいだろうから大丈夫な気はする。
nginxのgeoの詳細については公式ドキュメントを参照:
その他
他のサービスで異なるフィルタを使いたい場合は、新しいgeoを追加して新しいlocationでそのgeoを使うようにして、そのアドレスをtraefikのforwardauthに指定すればいい。
また、フィルタに引っかかって403
を返すときはアクセス可否チェック用サーバが返したレスポンスをそのままクライアントに渡すようになっている。
エラー専用ページを返すなら、アクセス可否チェック用サーバでエラーページを設定する必要がある。
ホワイトリスト方式
上のgeoで返す値を逆転させればホワイトリスト形式になるが、traefikのmiddlewareにそのままのipwhitelist
というミドルウェアがある。
公式ドキュメント:
nginxのgeoのproxy
に似たオプションもあるようだ。depth
などで信頼するプロキシの数などを指定できるようだ(実際はdepth-1になりそう)。(未実験)
おわり
ホワイトリストに関しては、全然書いていないがこちらはメインではないので仕方がない。
あまりブラックリスト方式を使いたくはないが必要な場合はあるので今回は取り扱った。先人の方々には感謝しています。
traefikのミドルウェアは眺めてみようと思う。
以上です。
Amazonアソシエイト: