ゼロトラストメッシュ — アイデンティティ、暗号化、そして認可
一言でいうと
メッシュのセキュリティは3つの層でできています。誰なのか(SPIFFEアイデンティティ)→ 暗号化を強制するのか(PeerAuthentication)→ それで何ができるのか(AuthorizationPolicy)の順です。順序を飛ばすと、必ず事故が起きます。
なぜ必要なのか
Kubernetesクラスターの内側は、しばしば「信頼できるネットワーク」として扱われます。しかし実際には、Podが1つ乗っ取られただけで、クラスター内のすべてのサービスに平文で接続できてしまう、フラットなネットワークであることが多いのです。これをアプリケーションで防ごうとすると、サービスごとにTLS証明書の発行・更新・検証のロジックと、権限チェックのロジックを入れなければなりません。言語が4つなら実装も4つになり、4つの動作は微妙に異なります。
さらに、もっと根本的な問題があります。ネットワークポリシーだけでは、「誰が」を表現できません。IPベースの制御は、Podが再起動すると崩れますし、同じノード上の別のPodになりすますことも防げません。メッシュは、身元の基準をIPからKubernetesのサービスアカウントへ移し、その身元を証明書に刻んで暗号学的に検証します。
どう動くのか
アイデンティティ: 形式はSPIFFE標準に従います。次の例は、先頭から順にトラストドメイン、ネームスペース、サービスアカウントです。
spiffe://cluster.local/ns/mesh-lab/sa/payments
------------- -------- --------
트러스트 도메인 네임스페이스 서비스 어카운트
istiodはCAも兼ねます。Podが起動すると、istio-agentがPodの内部で鍵ペアを作り、CSRだけをistiodに送ります。秘密鍵がPodの外へ出ることはありません。istiodは、サービスアカウントトークンを検証して、CSRのSPIFFE IDがそのトークンのネームスペース/SAと合っているかを確認してから署名します。証明書の有効期間は既定で24時間で、期間の半分ほどが過ぎたところで自動更新されます。更新された証明書はSDSでEnvoyに渡されるので、Podの再起動もコネクションのドレインも必要ありません。
暗号化の強制: PeerAuthenticationが、「受け取る接続にmTLSを要求するか」を決めます。
| モード | 動作 | 使うとき |
|---|---|---|
| PERMISSIVE | mTLSと平文の両方を受け付ける(既定値) | 移行期間 |
| STRICT | mTLSだけを受け付ける | 目標とする状態 |
| DISABLE | mTLSをオフにする | 外部のTLS終端装置の背後など、例外 |
適用範囲は3つの層で、狭いほうが勝ちます。ワークロード(selectorを指定)> ネームスペース(selectorなしで該当ネームスペース)> メッシュ全体(ルートネームスペースであるistio-systemにdefaultという名前で置く)の順です。例外が必要なら、portLevelMtlsでポート1つだけを開けます。
ここで方向を混同してはいけません。PeerAuthenticationは受け取る側の設定で、DestinationRuleのtrafficPolicy.tlsは送る側の設定です。受け取る側とはサーバー(インバウンド)、送る側とはクライアント(アウトバウンド)のことです。サーバーがSTRICTなのに、クライアント側のDestinationRuleがDISABLEだと、接続がまるごと失敗します(UFフラグ)。この競合はistioctl analyzeが見つけてくれます。クライアント側で、メッシュが発行した証明書を使うと宣言するのがISTIO_MUTUALです。
エンドユーザーの認証: RequestAuthenticationは、JWTの署名・発行者・有効期間を検証します。ここで最もよくある誤解があります。このリソースだけでは、何も防げません。ルールは「トークンがあるなら有効でなければならない」というもので、トークンがないリクエストはそのまま通過します。トークンを必須にするには、AuthorizationPolicyでrequestPrincipalsを要求する必要があります。
認可: リクエスト1つごとに、評価の順序が決まっています。
1. CUSTOM → 외부 인가기(OPA 등)가 거부하면 즉시 거부
2. DENY → 하나라도 매칭되면 즉시 거부
3. ALLOW → 대상 워크로드에 ALLOW 정책이 하나도 없으면 허용(기본 개방)
ALLOW 정책이 하나라도 있으면 매칭돼야 허용(기본 거부로 전환)
このコードブロックの韓国語コメントは、外部認可サーバーによる拒否・DENYの一致による拒否・ALLOWの扱い(ポリシーがなければ許可、あれば一致したときだけ許可)を順に述べています。
最後の行が核心です。ALLOWポリシーが1つできた瞬間、そのワークロードは許可リストモードになります。この性質を利用したのがspec: {}というイディオムです。ALLOWポリシーは存在するのに、一致するルールが1つもないので、誰も通過できません。ゼロトラストの出発点は、この1行です。
principalsに書く値はSPIFFE IDですが、設定ではspiffe://プレフィックスなしでcluster.local/ns/mesh-lab/sa/frontendのように書くことを覚えておきましょう。そして、principalはmTLS証明書から得られるので、PERMISSIVEの状態で平文で入ってきたリクエストは、principalが空になり、この条件には決して一致しません。STRICTへの切り替えが認可設計の前提条件である理由です。
現場での姿
第一に、順序を飛ばしたSTRICTです。平文の送信元を整理しないままメッシュ全体でSTRICTを有効にすると、サイドカーのないcron、レガシーVM、カスタムプローブが一斉に切れます。定石は、観測(PERMISSIVEを維持して平文の割合を確認)→ 平文の送信元の除去 → 重要度の低いネームスペースからSTRICT → メッシュ全体でSTRICT、の順です。完了基準も数字で決めます。たとえば「平文リクエストが7日間で0件」です。
第二に、認可での403です。よくある原因は3つあります。(1)PERMISSIVEのためprincipalが空で、一致に失敗した。(2)ネームスペースやサービスアカウントの打ち間違い。(3)ALLOWポリシーを追加した瞬間に既定が拒否へ変わることを忘れて、既存の経路を開けておかなかった。
第三に、有効にする前にシャドーで確認します。AUDITアクションとdry-runアノテーションは、トラフィックを止めずに、「有効にしていたら何が拒否されたか」だけを記録します。既定拒否を本番に入れる前に、必ずこの段階を経ます。
第四に、ワークロードごとの専用サービスアカウントです。複数のワークロードがdefaultのSAを共有すると、SPIFFE IDが同じになって、認可を分けられません。アイデンティティの設計が、そのまま認可の設計です。
次のラボですること
2つのラボが続きます。前のラボでは、PERMISSIVEから始めてメッシュ全体にSTRICTを敷き、ワークロード単位・ポート単位の例外を作り、クライアント側のISTIO_MUTUALを設定したあと、STRICTとDISABLEが競合したときに静的分析が何を言うかを確認し、段階的な切り替え計画を文書として残します。後のラボでは、空のルールによる全面拒否から出発して、アイデンティティ・メソッド・パス・ネームスペース・条件で権限を絞り、DENYのセーフティネットとAUDITを載せたうえで、呼び出しごとの許可/拒否マトリクスを作ります。
このラボ環境では、実際のmTLSハンドシェイクは起こりません。その代わり、ポリシーの範囲・優先順位・競合を、マニフェストと静的分析で判定します。現場で起きる事故の大半も、ハンドシェイクの失敗ではなく、ポリシーの範囲の取り方を誤ったことから生まれます。