今日は神戸へ帰ってきてバテバテでしんどかったのでバドミントン練習はおやすみ。出張で体調を崩してしんどい。
ipv6 とリバースプロキシと xff ヘッダーの扱い
要件の1つに ipv6 での通信ができることという項目がある。OSI参照モデル の概念から言うと ipv6 は第3層であるネットワーク層の話しになる。実際に世の中で運用されている tcp/ip のプロトコルスタックにおいてもネットワーク層の話しであり、レイヤーが異なることからアプリケーション層では影響を受けないはずではある。アプリケーション層からみたら ipv4 であろうと ipv6 であろうと、ネットワーク周りのライブラリやフレームワークが対応していれば問題ないだろうと考えていた。それ自体の認識は誤っていない。
プロキシを経由するときに X-Forwarded-For (以下 xff) ヘッダーをセットすると、そのプロキシへアクセスしてきたリクエスト元の ip アドレスを保持できる。api サーバーでは xff ヘッダーを参照すれば ipv4 または ipv6 でアクセスしてきたクライアントの ip アドレスがわかる。フレームワークの echo における対応 も過去に行っていた。1つ対応漏れがあって xff ヘッダーはプロキシを経由するごとに途中の ip アドレスを追加していく。原則として信頼できるネットワークのアクセス元の ip アドレスを使う。例えば、次のような xff ヘッダーを考える。
X-Forwarded-For: 203.0.113.3, 192.0.2.5, 198.51.100.7"
このとき信頼できるネットワークが 198.51.100.0/24 である場合は xff ヘッダーによるクライアントの ip アドレスは 192.0.2.5 となる。信頼できるネットワークが 192.0.2.0/24 と 198.51.100.0/24 の2つである場合は 203.0.113.3 の ip アドレスを使う。基本的には信頼できるネットワークの左側にある ip アドレスを使うと考えればよい。一方で途中経路のネットワークを知っていて信頼できるネットワークであることを設定しないと、意図したクライアントの ip アドレスを取得することはできない。
さらにリバースプロキシでアクセス制限をしたいという要件がある。docker compose を使うと通常は NAT の構成となり、コンテナネットワークのリバースプロキシ (nginx) からホスト os のリクエスト元の ip アドレスを参照できない。コンテナネットワークのゲートウェイ (172.18.0.1) からアクセスを受けたようにみえる。この振る舞い自体も正しくはあるが、調査したところ、docker の rootless モードだとリクエスト元の ip アドレスを参照できないということがわかった。次の issue によると port forwarder と呼ばれるモジュールがあり、デフォルトものから slirp4netns に変更すれば参照できると次の issue で紹介されていた。
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=slirp4netns"
実際にやってみて ipv4 の ip アドレスを参照することはできたものの ipv6 は未対応らしい。
他のやり方も調査してコンテナネットワーク内の nginx から ipv6 の ip アドレスを参照することができなかった。要件を満たせないことからリバースプロキシをコンテナネットワークから外出しして構築することに決めた。rootless モードでなければ ipv4/ipv6 の両方を取得できるという話しもお手伝い先の同僚から聞いた。こんなところではまるとは思わなかった。
ping や curl コマンドも ipv6 対応されていてオプションを付けなくてもよいけど、ipv6 であることを明示する上では -6
とオプションを付けてもよいかもしれない。それにしても検証するときに ipv6 アドレスを手打ちするのは煩雑なのでいちいちアドレスをコピペすることになって面倒だなと感じた。
$ ping -6 2001:DB8:0:0:8:800:200C:417A
$ curl -s -6 'http://[2001:DB8:0:0:8:800:200C:417A]:8080/api'