取引先のログが見た送信元
目標
本物のIstioメッシュで、Sidecarの範囲・REGISTRY_ONLY・ServiceEntry・Egressゲートウェイ・ゲートウェイの認可を順にかけ、それぞれが何を防ぎ、何を防げないかを、受け取る側のログとステータスコードで確認します。
なぜ重要なのか
出て行くトラフィックはデフォルトが「どこへでも通過」なので、何もしなければ、メッシュは誰がどこへ出て行くのかを知りません。仕組みをかけても、それがネームスペースの設定なのか、ゲートウェイの強制なのかを区別しないと、「防いだ」と信じてしまう隙間が残ります。受け取る側(パートナー)が見た送信元で確認する習慣が、その隙間を見せてくれます。
ステップ
kubectl apply -f /opt/fixtures/istlab/egress-app.yamlで材料(メッシュ外outsideのpartner、メッシュ内shopのclient、otherのintruder)を載せ、Podの準備ができるまで待ってください。その後、/root/istlab-egress/01-baseline.txtに3行を書いてください。clientからhttp://partner.outside/のステータスコードsvc=、partnerのPod IPで直接呼んだステータスコードpodip=、clientのサイドカーが知るクラスター数clusters=(istioctl proxy-config clusters client.shop -o json | jq length)です。/root/istlab-egress/sidecar.yamlに、ネームスペースshopのSidecarであるdefaultを書いて適用してください。egress hostsは、./*(同じネームスペース)とistio-system/*の2つだけです。適用後、clientのクラスター数を/root/istlab-egress/02-scope.txtにclusters=の形で書いてください。/root/istlab-egress/sidecar.yamlのSidecarにoutboundTrafficPolicy.mode: REGISTRY_ONLYを追加して、もう一度適用してください。その後、clientからpartnerのPod IPにx-request-idを付けて呼び、そのリクエストが残したclientのサイドカーのアクセスログの1行を、そのまま/root/istlab-egress/03-blocked.logに保存してください。/root/istlab-egress/serviceentry.yamlに、ネームスペースshopのServiceEntryであるpartner-apiを書いて適用してください。ホストapi.partner.test、location: MESH_EXTERNAL、ポート80のHTTP、resolution: DNS、エンドポイントのアドレスpartner.outside.svc.cluster.localです。適用後、clientでapi.partner.testを名前解決したアドレスを/root/istlab-egress/04-se.txtにvip=の形で、http://api.partner.test/のステータスコードをcode=の形で書いてください。/root/istlab-egress/egress.yamlに3つのリソースを書いて適用してください。(1)ネームスペースshopのIstioのGatewayであるpartner-egress(セレクターistio: egressgateway、ポート80・プロトコルHTTPS・tls.mode: ISTIO_MUTUAL、ホストapi.partner.test)、(2)istio-systemのDestinationRuleであるegressgateway-for-partner(ホストistio-egressgateway.istio-system.svc.cluster.local、サブセットpartnerのポート80にISTIO_MUTUAL・sni: api.partner.test)、(3)shopのVirtualServiceであるpartner-via-egress(ホストapi.partner.test、gatewaysはpartner-egress・meshで、meshから来たものはEgressゲートウェイのサブセットpartnerの80へ、ゲートウェイから来たものはapi.partner.testの80へ送ります)です。適用後、/root/istlab-egress/05-path.txtに、EgressゲートウェイのPod IPegress_pod_ip=と、パスに目印を付けて送ったリクエストについてpartnerのログが残した送信元partner_saw=を書いてください。otherネームスペースのintruderから2回呼んで、/root/istlab-egress/06-bypass.txtに書いてください。http://partner.outside/を直接呼んだステータスコードintruder_direct=、http://api.partner.test/のステータスコードintruder_via_egress=です。同じ直接呼び出しをclientで行った結果も、client_direct=として加えます。/root/istlab-egress/egress-authz.yamlに、istio-systemのAuthorizationPolicyであるpartner-only-shopを書いて適用してください。セレクターはistio: egressgateway、action: ALLOW、送信元のプリンシパルはcluster.local/ns/shop/sa/client、宛先ホストはapi.partner.testです。適用後、clientのapi.partner.testは200、intruderの同じリクエストは403である必要があります。/root/istlab-egress/08-report.mdに6行を書き、その下に学んだことを4行以上書いてください。6行は、clusters_before=・clusters_after=(ステップ1・2)、blocked_code=(ステップ3のログ行のステータスコード)、partner_saw=(ステップ5でpartnerが見た送信元がEgressゲートウェイならegressgateway、clientならclient)、intruder_direct=・intruder_via_egress=(ステップ6)です。
参考
- このVMは準備に2–4分かかります。サイドカーのDNSプロキシが有効なので、ServiceEntryのホストを名前で呼べます。
- clientでコマンドを打つときは、
kubectl -n shop exec client -c curl -- curl …のようにコンテナを指定してください。 - partnerのログは
kubectl -n outside logs partnerで、ゲートウェイのログはkubectl -n istio-system logs deploy/istio-egressgatewayで見ます。サイドカーのログではリクエストIDを付けて探し、ゲートウェイを通るリクエストはゲートウェイがIDを新しく振り直すので、パスに目印を付けて探します。 - 設定を変えた直後は、プロキシに行き渡るまで数秒かかります。
- よくある失敗: VirtualServiceから
meshゲートウェイを抜いてしまうケースです。そうするとサイドカーがEgressゲートウェイを通らずにそのまま出て行き、パートナーのログにclientのPod IPが記録されます。
デフォルトではどこへでも出て行ける
kubectl apply -f /opt/fixtures/istlab/egress-app.yamlで材料(メッシュ外outsideのpartner、メッシュ内shopのclient、otherのintruder)を載せ、Podの準備ができるまで待ってください。その後、/root/istlab-egress/01-baseline.txtに3行を書いてください。clientからhttp://partner.outside/のステータスコードsvc=、partnerのPod IPで直接呼んだステータスコードpodip=、clientのサイドカーが知るクラスター数clusters=(istioctl proxy-config clusters client.shop -o json | jq length)です。
メッシュのデフォルトの外部ポリシーはALLOW_ANYです。サイドカーが知らない宛先(サービスではないPod IP、外部アドレス)へ行くリクエストは、PassthroughClusterでそのまま素通りさせます。そしてサイドカーは、デフォルトでクラスターのすべてのサービスを知っているため、サービスが増えると、すべてのプロキシの設定が一緒に大きくなります。2つの事実を数字で残しておけば、後のステップで何が変わったかを比べられます。
Sidecarリソースでプロキシが知る範囲を狭める
/root/istlab-egress/sidecar.yamlに、ネームスペースshopのSidecarであるdefaultを書いて適用してください。egress hostsは、./*(同じネームスペース)とistio-system/*の2つだけです。適用後、clientのクラスター数を/root/istlab-egress/02-scope.txtにclusters=の形で書いてください。
名前がdefaultでセレクターのないSidecarは、そのネームスペースのすべてのサイドカーにかかります。egress hostsは「このプロキシが知るべきサービス」の一覧なので、ここにないネームスペースのサービスは設定から外れます。数千のサービスがあるメッシュで、プロキシのメモリとistiodのプッシュの負担を減らす主な方法です。減ったクラスター一覧にpartner.outsideがないかも見てください。
知らない宛先は防ぐ: REGISTRY_ONLY
/root/istlab-egress/sidecar.yamlのSidecarにoutboundTrafficPolicy.mode: REGISTRY_ONLYを追加して、もう一度適用してください。その後、clientからpartnerのPod IPにx-request-idを付けて呼び、そのリクエストが残したclientのサイドカーのアクセスログの1行を、そのまま/root/istlab-egress/03-blocked.logに保存してください。
REGISTRY_ONLYは「サービスレジストリにある場所にだけ」という意味です。サイドカーが知らない宛先は、PassthroughClusterではなくBlackHoleClusterへ行き、502になります。このネームスペースのレジストリはステップ2で狭めたので、partner.outsideのServiceも、もう「知らない場所」です。ログでリクエストを探すには、リクエストIDを自分で付けてください。kubectl -n shop logs client -c istio-proxy | grep <id>です。
パートナーをレジストリに載せる: ServiceEntry
/root/istlab-egress/serviceentry.yamlに、ネームスペースshopのServiceEntryであるpartner-apiを書いて適用してください。ホストapi.partner.test、location: MESH_EXTERNAL、ポート80のHTTP、resolution: DNS、エンドポイントのアドレスpartner.outside.svc.cluster.localです。適用後、clientでapi.partner.testを名前解決したアドレスを/root/istlab-egress/04-se.txtにvip=の形で、http://api.partner.test/のステータスコードをcode=の形で書いてください。
ServiceEntryは、メッシュの外の宛先をレジストリに載せて、REGISTRY_ONLYの下でも出て行けるようにします。ところがapi.partner.testという名前は、クラスターのDNSにありません。このメッシュはサイドカーのDNSプロキシを有効にしているので、サイドカーがこの名前に自動で割り当てた仮想IP(240.240.x.x)を答えます。その値はServiceEntryのstatus.addressesにもあります。Podの中でnslookup api.partner.testで確認してください。
出て行く道をEgressゲートウェイ1つにまとめる
/root/istlab-egress/egress.yamlに3つのリソースを書いて適用してください。(1)ネームスペースshopのIstioのGatewayであるpartner-egress(セレクターistio: egressgateway、ポート80・プロトコルHTTPS・tls.mode: ISTIO_MUTUAL、ホストapi.partner.test)、(2)istio-systemのDestinationRuleであるegressgateway-for-partner(ホストistio-egressgateway.istio-system.svc.cluster.local、サブセットpartnerのポート80にISTIO_MUTUAL・sni: api.partner.test)、(3)shopのVirtualServiceであるpartner-via-egress(ホストapi.partner.test、gatewaysはpartner-egress・meshで、meshから来たものはEgressゲートウェイのサブセットpartnerの80へ、ゲートウェイから来たものはapi.partner.testの80へ送ります)です。適用後、/root/istlab-egress/05-path.txtに、EgressゲートウェイのPod IPegress_pod_ip=と、パスに目印を付けて送ったリクエストについてpartnerのログが残した送信元partner_saw=を書いてください。
VirtualService 1つが2つの区間を担当します。サイドカー(mesh)では宛先をEgressゲートウェイに変え、ゲートウェイでは本当の宛先へ送ります。サイドカーからゲートウェイまでの区間のmTLSは、自然にはかかりません。Gatewayを平文のHTTPにすると、リクエストは通りますが、ゲートウェイが送ってきた側の身元を知りません(ステップ7で必要です)。DestinationRuleをistio-systemに置くのは、他のネームスペースのサイドカーもサブセットpartnerを見つけられるようにするためです。shopに置くと、shopのサイドカーだけが見ます。通ったかどうかは、受け取る側で見てください。ゲートウェイはx-request-idを新しく振るので、http://api.partner.test/<표식>のようにパスに目印を付けて(プレースホルダーは目印です)、partnerのログで探し、その行の送信元がEgressゲートウェイのPod IPかを見ます。
ゲートウェイを通らない道がまだ残っている
otherネームスペースのintruderから2回呼んで、/root/istlab-egress/06-bypass.txtに書いてください。http://partner.outside/を直接呼んだステータスコードintruder_direct=、http://api.partner.test/のステータスコードintruder_via_egress=です。同じ直接呼び出しをclientで行った結果も、client_direct=として加えます。
SidecarリソースとREGISTRY_ONLYは、shopのプロキシの設定にすぎません。他のネームスペースのプロキシは相変わらずALLOW_ANYで、サイドカーのないPodなら、そもそもこの設定を知りません。Istioのドキュメントも、Egressゲートウェイだけでは「すべての外部トラフィックがゲートウェイを通る」ことを強制できず、ネットワークポリシーのような別の仕組みを併用する必要があると述べています。その隙間を数字で残してください。
ゲートウェイで誰が出られるかを決める
/root/istlab-egress/egress-authz.yamlに、istio-systemのAuthorizationPolicyであるpartner-only-shopを書いて適用してください。セレクターはistio: egressgateway、action: ALLOW、送信元のプリンシパルはcluster.local/ns/shop/sa/client、宛先ホストはapi.partner.testです。適用後、clientのapi.partner.testは200、intruderの同じリクエストは403である必要があります。
サイドカーからEgressゲートウェイまではmTLSなので、ゲートウェイはリクエストを送ったワークロードの身元(SPIFFE)を知っています。そのため、「どのネームスペース・サービスアカウントがこのパートナーへ出られるか」を、ゲートウェイ1か所で決められます。Egressゲートウェイを置く本当の理由がこれです。ALLOWポリシーが1つでもあれば、一致しないリクエストはすべて拒否されます。
出て行く道をどこで防いだかを整理する
/root/istlab-egress/08-report.mdに6行を書き、その下に学んだことを4行以上書いてください。6行は、clusters_before=・clusters_after=(ステップ1・2)、blocked_code=(ステップ3のログ行のステータスコード)、partner_saw=(ステップ5でpartnerが見た送信元がEgressゲートウェイならegressgateway、clientならclient)、intruder_direct=・intruder_via_egress=(ステップ6)です。
値は前のステップのファイルから写してください。説明の行には、「Sidecar・REGISTRY_ONLY・Egressゲートウェイ・認可ポリシーが、それぞれ何を防ぎ、何を防げないか」を、自分の言葉で書いておいてください。