執筆者 : 浜島 大介
はじめに
今回は Cilium に関連する第2弾のブログとして、CiliumサービスメッシュのmTLS (Mutual Transport Layer Security) について 解説編 と 実装編 の2本立てで記載する。
従来のサービスメッシュでは各Podにサイドカープロキシを配置して通信制御や情報収集といった処理を透過的に行うという手法が一般的であったが、CiliumではeBPF (extended Berkeley Packet Filter) を用いてL4/L7ネットワークのパケットを操作することでサイドカープロキシを不要としていることが大きな特徴となる。
CiliumおよびeBPFについては以下の弊社ブログ記事が参考になる。
- 「Cilium入門」 (著: 藤本 健)
- 「詳説 eBPF 概論編」 (著: 稲葉 貴昭)
- 「詳説 eBPF 実装編」 (著: 稲葉 貴昭)
サービスメッシュのサイドカーレスという点では Istio の Ambient Mode も同様でありmTLSの機能も備えているが、CiliumのmTLSについては日本語の記事がなかったので自身の学習も兼ねて調べてみることにした。
mTLS (Mutual TLS)
一般的なTLS通信はサーバ証明書をクライアント側に送付してクライアント側がサーバの認証を行い通信の暗号化を行うというもので、クライアント側の認証は行われない。
mTLS通信は名前の通りクライアント側とサーバ側が相互 (Mutual) に認証を行い暗号化通信を行うものとなる。
Cilium における mTLS
Ciliumのドキュメントには mTLS/Mutual TLS という項目はなく、関連項目として Mutual Authentication (相互認証) という項目が記載されている。
Mutual Authentication のアーキテクチャは CFP(Cilium Feature Proposal)-22215 に記載されており、CFPの内容から Ciliumでは相互認証と暗号化の機能が独立しており、両者を有効化することで総合的に mTLS 通信を実現するようである。
CiliumのMutual Authentication (相互認証) 機能
Ciliumでは認証 (と認可) に必要な証明書およびエンドポイントのIDを管理するためのフレームワークとして SPIRE を使用する。
相互認証が完了したエンドポイントの情報はAuth-Table
というローカルホスト上のデータ構造に保存する。
(2回目以降の通信はAuth-Table上にエントリがあるため、認証プロセスは省略となる)
Auth-Tableに保存される主な情報
- 送信元/送信先のエンドポイントのIPアドレス
- 上記IPアドレスに紐付けられたID
- IPアドレスとIDの両者で管理するのはエンドポイント (Pod) が破棄された後に別用途のPodが同じIPアドレスで起動された場合でも通信制御が正しく行えるようにするため
- 有効期限
- 有効期限内であれば相互認証済みと判定され、TLSハンドシェイク不要で通信が許可される
認証プロセスの流れ
例として Pod-A ⇒ Pod-B 通信時の相互認証を行う場合を考える。
- 初回の通信では
Auth-Table
にはPod-A,Pod-Bに関する認証情報がないためパケットドロップが発生する - パケットドロップのイベントをトリガとして ノード上で起動しているCilium-Agentが対向ノードのCilium-Agentと
SPIRE
にて管理される証明書を用いて相互認証 (TLSハンドシェイク) を行う - 認証が正常完了すると
Auth-Table
に認証情報をキャッシュする - 以降は
Auth-Table
を参照して認証済みであればパケットをドロップせずにTLSハンドシェイクを省略して通信を行う
アーキテクチャ図
(CPF-22215 より転載)
初回通信 (未認証通信) 時はパケットのドロップが発生する ことには注意が必要となる。
TCPであれば初回のパケット (SYN) がドロップされても自動再送されて通信は継続されるため、エンドユーザ側からは「初回だけ少し遅くなる」程度だが、UDPは再送や順序制御を行わないのでドロップされたパケットはロストしてしまう。
UDPを採用している時点でパケットのロストの発生は許容できる通信が大半だと思うが、UDP通信による認証を行う場合は上記を念頭に置いておく必要がある。
通信許可の判定にAuth-Tableを参照するという設計 については、TLS ハンドシェイクで得られた認証情報を格納するAuth-Table
を eBPF によるパケット処理 (L3/L4) で参照するため、OSIモデルのレイヤ分離の観点からはLayer Violation
とも捉えられる。
このような方式を採用している理由を推測してみたが、以下が考えられる。
- 通信ごとにTLSハンドシェイクを行うのは通信コストが高くなるため、一度認証した通信はeBPFによるIPベースの処理で高速化する
- 通信を許可するにあたり、送信元IPアドレス以外の要素として以下をチェックする
- IPアドレスとIDのマッピング (整合性)
- 証明書の有効期限
Ciliumの相互認証の利点
Ciliumの相互認証の利点は大別すると下記2点が考えられる。
- 消費リソースおよび処理時間の軽減
- 相互認証のプロセスは各ノードのCilium-Agentにて実施されるため、Podごとにプロキシサイドカーを配置する必要がなく、必要なシステムリソースの軽減とPod起動時間の短縮が実現できる
- 証明書の有効期間内ではTLSハンドシェイクは最初の1度だけ行うため、通信のオーバーヘッドを抑えることができる
- 特定のプロトコルに依存しない
- 相互認証はIPアドレスに関連付けたIDごとに実施されるため、TCP通信だけでなくUDPを含むすべてのIPトラフィックに対して適用可能
- (Istioは Sidecar Mode/Ambient Mode どちらもTCP通信にのみ対応なので、この点ではCiliumにアドバンテージがある)
なお、もう一つの利点として下記要素が考えられるが、セキュリティ上の考慮事項があるためそれを認識した上で利用する必要がある。
- 認証と暗号化の分離
- 相互認証はやりたいが 暗号化は不要といったニーズにも対応可能
Ciliumの相互認証におけるセキュリティ上の考慮事項
Ciliumの相互認証における通信可否は各ノードのAuth-Tableのエントリに依存するため、特定の状況では悪意あるPodとの通信が許可されてしまう。
上記URLの例に倣って ID: foo
と ID:bar
のPod間で相互認証が完了している状況において、ID:foo
のPodの代わりに悪意あるPodに差し替えることを考える。
必要な条件は以下
- 任意のPodの作成/削除権限を有していること
- ターゲットの通信先ノードとapi-serverとの通信をなんらかの方法で遮断できること
上記の条件下においてID: foo
のPodを削除した後に悪意あるPod (ID:baz
) を起動する。
(ID: foo
のPodのIPアドレスが獲得できるまで起動を実施する)
通信対向のノード側はapi-serverとの通信遮断によりIPアドレス/IDの更新情報が伝わらないため、通信元のIPアドレスの情報から悪意あるPodをID: foo
のPodとみなしてしまい、通信を許可する動作となる。
(下記解説の中で用いている画像は 参考ドキュメント より転載)
通常時 (攻撃前)
ID:foo
とID:bar
のPod間通信において相互認証がなされている- それぞれのノードの
ipcache
は同期しており、上記PodのID/IPアドレスの情報がキャッシュされている
攻撃時
ID:foo
のPodを削除して、同じIPアドレスを持つ別のPod (ID:baz
) を起動する- ノードBはapi-serverとの通信遮断により
ipcache
の情報が更新されず、新たに起動されたPod (ID:baz
) をID:foo
とみなして通信を許可してしまう。
上記はエンドポイントのIDを偽装できてしまうことが問題であるため、ID指定によるネットワークポリシーが設定されている場合のみ本問題点を考慮する必要がある。(という理解)
対応方法
対応方法としては相互認証を行う際は通信の暗号化とセットで用いるということが同様のドキュメントに記載されている。
暗号化 (カプセル化) の際にIDの情報を追加付与することでIPアドレスとIDの情報をパケットから直接取得することでキャッシュ情報に依存せず、前述したセキュリティの問題点に対応できるというものである。
もう一つの方法としては、現状のIDベースの認証方式ではなくコネクション単位で都度認証を行う方式 (Connection-based Mutual Authentication) も検討されている。
この方式によりTLSハンドシェイクをコネクションごとに実施することで 前述したセキュリティリスクを低減するというものである。
現バージョン (v1.17) ではまだ実装されていないようであるが、下記のCFPおよびIssueで管理されている。
Ciliumの暗号化機能
- 参考URL:Transparent Encryption
Ciliumでは 通信暗号化の方式として IPSec と WireGuard の2つの方式が用意されている。
どちらの方式でも暗号化自体には問題ないが 下記の点からWireGuardによる暗号化がより良いと考える。
- IPSecと比べて暗号/復号の処理が高速
- IPSecと比べて設定が簡単
- IPSecでは設定を保持するための
Secret
リソースの作成が別途必要となる
- IPSecでは設定を保持するための
- 通信暗号化を強制する
Encryption strict mode
はWireGuardのみ対応している
(WireGuardを使用するには Kernel 5.6 以上が必要)
なお、どちらの暗号化方式を採用しても Cilium では同一ノード内の通信の暗号化は意図的に行わない挙動となっている。
これは ノード内の通信では物理ネットワークを経由せずにKernel内で処理されるため、暗号化を行う意味がない (復号したパケットが観測できてしまう) ためと思われる。
まとめ
- Ciliumは認証と暗号化の機能が独立しており、両機能を併用することでmTLSが実現できる
- 現状の認証機能にはセッションハイジャック可能なセキュリティ上の問題が存在する
- 現在はベータ機能の位置づけであるが、セキュリティ問題の解消も含めた改善が予定されている
次回の 実装編 では相互認証と暗号化の機能を有効化してそれぞれの動作検証を行う。