ゲートウェイを置けば塞がったことになるのか
一言でいうと
メッシュから出るトラフィックのデフォルトはどこへでも通過(ALLOW_ANY)です。それを絞る仕組みは4つあります。プロキシが知る範囲を狭めるSidecarリソース、知らない宛先を防ぐREGISTRY_ONLY、外部の宛先をレジストリに載せるServiceEntry、出て行く道を1か所にまとめるEgressゲートウェイです。4つは防ぐものがそれぞれ違い、どれも単独ではすべてを防げません。
なぜ必要なのか
サイドカーは、デフォルトでクラスターのすべてのサービスを知っています。サービスが数千あれば、すべてのプロキシが数千のクラスターを抱え、istiodはサービスが1つ変わるたびに、それを全員にプッシュします。そしてレジストリにない宛先(外部のアドレス、PodのIP)は、PassthroughClusterでそのまま素通りさせます。誰がどこへ出て行くのかを、メッシュが知らないままです。
セキュリティの要求は逆向きです。「決済サービスだけがカード会社のAPIへ出られる」「外へ出る道は監査できる1か所だけにする」といったものです。メッシュがその要求を助けるには、まず外部の宛先を名前で知る必要があり、次に出口を1つにまとめる必要があります。
どう動くのか
Sidecarリソース: このネームスペースのプロキシが何を知るかを決めます。
egress:
- hosts: ["./*", "istio-system/*"] ← 같은 네임스페이스와 istio-system 만 안다
outboundTrafficPolicy:
mode: REGISTRY_ONLY ← 모르는 곳은 BlackHoleCluster(502)
範囲を狭めるとプロキシの設定が小さくなり、REGISTRY_ONLYを加えると範囲外が防がれます。防がれたリクエストは、アクセスログにルート名block_allとともに502として残ります。
ServiceEntry: 外部の宛先をレジストリに載せます。location: MESH_EXTERNALは「メッシュの外なのでmTLSをかけるな」という意味で、resolution: DNSはエンドポイントの名前をDNSで解決して使うという意味です。ところがホスト名そのもの(api.partner.test)は、クラスターのDNSにありません。サイドカーのDNSプロキシ(ISTIO_META_DNS_CAPTURE)を有効にすると、サイドカーがその名前に240.240.0.0/16の帯域の仮想IPを自動で割り当てて答え、その値はServiceEntryのstatus.addressesにも書かれます。
Egressゲートウェイ: 出て行く道を1か所にまとめます。VirtualService 1つが2つの区間を担当します。
| 区間 | マッチ | 送り先 |
|---|---|---|
| サイドカー → ゲートウェイ | gateways: [mesh] |
istio-egressgateway(mTLS) |
| ゲートウェイ → 外部 | gateways: [partner-egress] |
本当の宛先 |
サイドカーからゲートウェイまでのmTLSは、自然にはかかりません。GatewayのサーバーをHTTPS・tls.mode: ISTIO_MUTUALにし、サイドカーがゲートウェイへ行くときに使うDestinationRuleのサブセットに、ISTIO_MUTUALとsniを指定する必要があります(公式のEgress Gateways with TLS Originationのドキュメントが、同じ組み合わせを使っています)。そのDestinationRuleは、クライアントのネームスペース → サービスのネームスペース → ルートネームスペースの順で探されるので、複数のネームスペースに同じゲートウェイを使わせるなら、ゲートウェイ側(istio-system)に置きます。こうしてmTLSでつながれば、ゲートウェイは送ってきたワークロードの身元を知ります。そのため、認可ポリシーをゲートウェイにかければ、「誰がこの外部へ出られるのか」を1か所で決められます。
防げないもの。SidecarとREGISTRY_ONLYは、そのネームスペースのプロキシの設定にすぎません。他のネームスペースのプロキシは相変わらずALLOW_ANYで、サイドカーのないPodはそもそも知りません。Istioのドキュメントは、Egressゲートウェイだけではすべての外部トラフィックがそこを通るよう強制することはできず、Kubernetesのネットワークポリシーのような別の仕組みで、ゲートウェイ以外の道を塞ぐ必要があると述べています。
現場での姿
REGISTRY_ONLYを有効にしたらメトリクス収集が途切れた場合です。Podが呼んでいた外部のアドレス(モニタリングSaaS、クラウドのメタデータなど)が、レジストリにありませんでした。有効にする前に、PassthroughClusterで出て行くリクエストをアクセスログで先に数えて、ServiceEntryとして載せておくのが順序です。
Egressゲートウェイを作ったのにパートナーのファイアウォールのログにPodのIPが記録される場合です。VirtualServiceのmesh側のルールがないか、ホストが違うために、サイドカーがゲートウェイを通らずにそのまま出て行ったケースです。受け取る側が見た送信元が、最も正直な証拠です。
ゲートウェイで防いだのに、あるチームは相変わらず外に出られる場合です。そのチームのネームスペースにはSidecarの設定がないか、サイドカーのないPodでした。ゲートウェイは、通過したリクエストだけを防ぎます。
公式ドキュメント: Accessing External Services・Egress Gateways・Sidecar・DNS Proxying
次のラボですること
メッシュの外に、パートナーAPIの役のnginxを置き、デフォルトでどこへでも出られることを先に数えます。Sidecarで範囲を狭め、REGISTRY_ONLYで防いだ後、ServiceEntryでパートナーだけを載せ、Egressゲートウェイで道をまとめて、パートナーが見た送信元で確認します。最後に、別のネームスペースからゲートウェイを迂回する道が残っていることを見て、ゲートウェイの認可ポリシーで出られる身元を絞ります。