VPN を終端し、同時に macvlan ネットワークでコンテナを動かす Linux ホストを想像してください。LAN は 192.168.60.0/24、コンテナは 192.168.60.210 を持ち、VPN クライアントは 10.95.0.0/24 から来ます。クライアントはサービスに到達し、応答は決して戻らず、ホスト上のファイアウォール規則はすべて正しく見えます。この症状を生む制約は二つあり、一方を直しても他方はそのまま残ります。

二つの制約、一つの症状

第一の制約は分離で、Docker の macvlan ドライバーは、コンテナが親インターフェース経由で自分のホストと通信することを許しません。ホストのスタックとコンテナの間の通信には経路がなく、ホスト上のルートをどう書いても変わりません。

第二の制約は戻り経路です。コンテナのデフォルトゲートウェイは通常 LAN ルーターなので、VPN クライアント宛ての応答はそのルーターから出ていきます。ルーターに VPN プールへ戻るルートがなければ、応答は途中のどこかで破棄されます。

この二つの障害はクライアント側からは、要求が届くのに応答が決して届かないという同じ姿に見えます。必要な修理は別々で、広い masquerade 規則はどちらがまだ壊れているかを隠してしまいます。だから最初のテストは、ファイアウォールをもう一つ変えることではなく、コンテナ側でのパケット取得です。要求がそこに決して現れないなら、効いているのは分離の制約です。

ホスト側の shim を狭く作る

同じ親にブリッジモードで接続した二つ目の macvlan インターフェースは、ホストのスタックにその LAN セグメント上での独自の識別子を与えます。DHCP プールの外側、かつコンテナの範囲の外側のアドレスをそれ用に確保し、コンテナだけをそこ経由で通してください。

ip link add macvlan-vpn link eth0 type macvlan mode bridge
ip address add 192.168.60.250/32 dev macvlan-vpn
ip link set macvlan-vpn up
ip route add 192.168.60.210/32 dev macvlan-vpn src 192.168.60.250

最初のコマンドはインターフェースをブリッジモードで作成しますが、これはこのドライバーの文書化された既定であり、ホストとコンテナの間の通信に望むモードです。二つ目は /32 を割り当ててアドレスが LAN のプレフィックスと衝突しないようにし、四つ目はコンテナのアドレスだけを shim 経由で送ります。

新しいインターフェースに LAN 全体を流してはいけません。通常の接続ルートはそのままにし、ホストルートは意図的に狭くして、一つのコンテナが経路を得てもセグメントの残りが挙動を変えないようにします。shim を指すデフォルトルートは別の結果を伴う別の設計になります。具体的に保てば、影響範囲は小さく留まります。

これらのコマンドは 稼働中のホストのネットワーク設定を変えます。アプライアンスや管理されたプラットフォームでは、対話的な root シェルでの変更がアップグレード後も残るとは限らないため、そのプラットフォームがサポートする設定手段を使ってください。

ソースをどう保つかを決める

shim があればコンテナはホストに到達でき、問いは応答がどの送信元アドレスを運ぶかになります。元の VPN 送信元を保つほうが素直なモデルで、コンテナは実際のクライアントアドレスを見られ、サービスのログやアクセス規則もそれを使えます。

それが機能するには、コンテナのネットワーク設定がこの経路について 10.95.0.0/24 の通信をゲートウェイとして 192.168.60.250 へ送る必要があります。これはコンテナ自身の設定の内側にある、サポートされたルートまたはゲートウェイの設定であり、ホスト側の小細工ではありません。設定が効いたと決めつけず、コンテナの内側から確認してください。LAN ルーターを指したままのデフォルトゲートウェイは、応答を同じ穴へ送り続けます。

承認された設計が代わりに VPN 送信元を隠す場合は、翻訳の範囲を実際の VPN プール、出力インターフェースとしての shim、そして特定のコンテナとサービスに狭めてください。

iptables -t nat -A POSTROUTING -s 10.95.0.0/24 -o macvlan-vpn -j SNAT --to-source 192.168.60.250

shim を出力インターフェースとして合わせ、トンネルとは合わせません。トンネルを出力インターフェースと名指す規則は、コンテナへ向かうパケットに一致しませんが、これがこの種の構成にありがちな誤りです。規則は上の文と同じくらい狭く保ち、プールが変わったら見直してください。

ホストではなくクライアントから検証する

ホスト自身から shim のアドレスに束縛した要求は、shim がコンテナに到達できることを証明します。しかし、そのテストの送信元アドレスは shim 自身のものなので、コンテナが保持された 10.95.0.x の送信元へ通信を返せることは証明しません。

curl -sS --interface 192.168.60.250 -o /dev/null -w '%{http_code}\n' http://192.168.60.210:8080/health
sudo tcpdump -ni macvlan-vpn host 192.168.60.210

最初のコマンドはホストからコンテナまでの区間を試し、二つ目は実際の VPN クライアントが要求する間に shim を何が通るかを示します。入ってくる経路の送信元アドレスと、同じフローで応答が現れるかを見てください。

ホストで転送を有効にする必要があり、転送経路にはサービスポート上で VPN プールから shim への許可と、確立済みの応答への許可が要ります。ホストの INPUT 規則は転送される通信を許可しませんが、これはルーティングが正しく見えた後にパケットが破棄されるよくある理由です。

実際のアプリケーションを実際の資格情報で動かし、その後、同じコンテナ上の無関係な二つ目のサービスが依然として拒否することを確認してください。動かす唯一の方法がすべての転送規則を開けることだったなら、設計は未完成です。今後そのサービスのアクセス規則が使うことになるため、サービスがどの送信元アドレスを記録したかも確認してください。

関連記事