TT Lab
はじめる
学ぶ 学習パス コース

Istio深化 — なぜそう流れるのか

TLS をどこで終端するかがゲートウェイのチェーンの形を決める

TT Labで続きを見る

一言でいうと

Gatewayのservers[].tls.modeは、ゲートウェイEnvoyリスナーのフィルターチェーンの形を決めます。SIMPLE・MUTUAL・OPTIONAL_MUTUAL・ISTIO_MUTUALは、チェーンにDownstreamTlsContextを付けて、ゲートウェイがTLSを終端し、HCMでHTTPルーティングを行います。PASSTHROUGH・AUTO_PASSTHROUGHは、復号せずにSNIだけを読み取ってチェーンを選び、tcp_proxyでバイトを渡します。両者を分ける最も確実な証拠は、クライアントが受け取った証明書が誰のものかです。

なぜ必要なのか

イングレスゲートウェイは、メッシュの外のクライアントが最初に出会うEnvoyです。ここでTLSをどこで終端するかは、セキュリティと運用の妥協です。ゲートウェイで終端すれば、証明書を1か所で管理でき、復号されたHTTPを見てパス・ヘッダーでルーティングし、リトライ・タイムアウト・認可を設定できます。その代わり、平文がゲートウェイに一度さらされます。最後まで暗号化が必要なサービス(独自の証明書でクライアントを検証する決済バックエンド、TLSを直接扱うデータベース)は、ゲートウェイが手を触れずに渡す必要があります。

Istioは、この選択をGatewayの1つのフィールドにしました。問題は、症状が互いに似ていることです。「証明書の名前が合わない」「接続がすぐに切れる」「301が延々と繰り返される」は、どれも、TLSがどこで終わるのかを知らないと、見当違いの場所を直すことになります。モードごとにEnvoyに何ができるかを知っていれば、proxy-config listenerを1回見るだけで原因が見えます。

どう動くのか

Gatewayは、ゲートウェイPodにポートと証明書だけを開きます。トラフィックがどこへ行くかは、そのGatewayを指すVirtualServiceが決めるので、Gatewayだけではルートは空です(AUTO_PASSTHROUGHだけが例外です)。モードごとに、ゲートウェイのリスナーにできるものは次のとおりです。

モード TLSを終端する場所 クライアント証明書 Envoyにできるもの
SIMPLE ゲートウェイ 要求しない チェーンにDownstreamTlsContext(サーバー証明書)とHCM
MUTUAL ゲートウェイ 必ず要求 上にrequire_client_certificate: trueとvalidation_context
OPTIONAL_MUTUAL ゲートウェイ 出せば検証、出さなくても通過 validation_contextだけで、要求はオフ
ISTIO_MUTUAL ゲートウェイ istiodが発行したメッシュ証明書 ワークロード証明書とメッシュルートをSDSで受け取って検証
PASSTHROUGH アップストリーム ゲートウェイは見ない tls_inspectorとserver_namesのマッチとtcp_proxy
AUTO_PASSTHROUGH アップストリーム ゲートウェイは見ない SNI自体がoutbound_.<포트>_.<subset>_.<호스트>の形(プレースホルダーはポートとホストです)のクラスター名で、VirtualService不要

証明書はファイルではなくSDSで入ってきます。credentialName: shop-certは、ゲートウェイと同じネームスペースのSecretを指し、Envoyの設定にはkubernetes://shop-certというSDS名で見えます。MUTUALの検証用CAは、同じSecretのca.crtかshop-cert-cacertから来ます。

ルート設定の名前にもルールがあります。HTTPSサーバーは、https.<포트>.<포트이름>.<Gateway이름>.<네임스페이스>の形の名前(プレースホルダーはポート、ポート名、Gateway名、ネームスペースです)で、Gatewayごとに別々にできます。たとえばhttps.443.https.shop-gw.defaultです。平文のHTTPサーバーは、http.<포트>1つ(プレースホルダーはポートです)に、同じポートのすべてのGatewayのホストがまとまります。httpsRedirect: trueは、その平文サーバーのvirtual hostにrequire_tls: ALLが1行入ったものになり、Envoyはルートを見る前に、301でhttpsを指します。Locationのホストは、リクエストのHostヘッダーそのままなので、ポートが付いていれば、そのポートまで残ります。

PASSTHROUGHでゲートウェイが宛先を選べるのは、TLSの最初のメッセージ(ClientHello)にSNIが平文で載っているからです。tls_inspectorはそれだけを読み、チェーンのserver_namesと照らし合わせます。合うチェーンがなければ、接続はその場で閉じられ、listener.<주소>.no_filter_chain_matchが増えます(プレースホルダーはアドレスです)。Istioが実際に作るSIMPLEのチェーンにもserver_namesが付きます。1つのポートでホストごとに別の証明書を選ぶためです。

現場での姿

PASSTHROUGHなのに、ゲートウェイの証明書を変えても効果がない。クライアントが見ているのは、最初からバックエンドの証明書です。openssl s_client -servernameで受け取った証明書の発行者を見れば、すぐにわかります。

IPで接続したときや、古いクライアントだけ接続が切れる。SNIを送らないと、PASSTHROUGHのチェーンは選べません。curlはハンドシェイク失敗(rc=35)としか見せないので、ゲートウェイのno_filter_chain_matchも一緒に見ます。

MUTUALに変えたら、一部のクライアントが「接続がリセットされた」と言う。TLS 1.3では、クライアントがハンドシェイクを終えたと思った後に拒否が届くので、エラーの形がまちまちです。Envoy側のssl.fail_verify_no_certが増えていれば、証明書を出していないということです。

リダイレクトが変なポートを指す。ロードバランサーがHostヘッダーにポートを付けて渡すと、Locationにも残ります。また、httpsRedirectを有効にした平文サーバーにLBのヘルスチェックが入ると、301なので異常と判定されることがあります。

公式ドキュメント: Gateway・Secure Gateways・Envoy TLS

次のラボですること

Gatewayを書いてistioctlがSIMPLEに証明書を要求するのを見た後、5つのモードをEnvoyの形に書き写します。opensslでCA・ゲートウェイ・クライアント・バックエンドの証明書を作って、SIMPLE・MUTUAL・PASSTHROUGHのリスナーを順に起動し、クライアントが受け取った証明書のフィンガープリントで、TLSがどこで終わったかを証明します。最後に、SNIが合わないときと、httpsRedirectの301を確認します。