メッシュの身元、クラスタの証明書、二種類の目
一言でいうと
サービスメッシュのmTLSは、ワークロードごとにサービスアカウントに基づく身元を与え、通信経路を暗号化しますが、認可は行わず、サイドカーが横取りしないトラフィックは保護できません。クラスターのPKIは、3つのCA(kubernetes-ca・etcd-ca・front-proxy-ca)の下にサーバー証明書・クライアント証明書が連なっていて、kubeadmが作成したクライアント証明書は1年後に期限切れになります。監査ログは「誰がAPIに何をリクエストしたか」を、Falcoのようなランタイム検知は「ノードとコンテナの中で実際に何が起きたか」を教えてくれ、両者は互いの死角を補います。
なぜ必要なのか
KCSAのプラットフォームセキュリティのドメインは、クラスター自体ではなく、その上に載せる層を問います。メッシュを入れれば安全になるという話、証明書はkubeadmがうまくやってくれるという話、監査ログを有効にしたからすべて見えるという話。3つとも、半分しか正しくありません。どこまでが正しく、どこからが間違いなのかを知らないと、セキュリティ統制があると信じている場所に穴が残ります。前のモジュールで見たAPIサーバー迂回リスクのドキュメントが、kubelet APIとetcdへの直接アクセスは監査ログに残らないと記していることが、その穴の一例です。
どう動くのか
サービスメッシュ: 代わりにやってくれることと、できないこと
Istioのセキュリティの概念のドキュメントによると、メッシュはサービス間の通信を、クライアント側とサーバー側のEnvoyプロキシでトンネリングします。クライアントのEnvoyがサーバーのEnvoyとmTLSのハンドシェイクを行う際に、secure namingの検査で、サーバー証明書のサービスアカウントがそのサービスを運用する権限を持っているかを確認し、接続が確立すると、サーバーのEnvoyがリクエストを認可します。そのため、メッシュが代わりにやってくれることは明確です。アプリケーションごとにTLS証明書を発行・配布・交換していた作業と、「このリクエストはどのワークロードから来たか」をIPではなくサービスアカウントの身元で確認する作業です。
Istioのセキュリティのベストプラクティスのドキュメントは、できないことを挙げています。1つ目は、デフォルトのPERMISSIVEモードは平文も受け付けるため、STRICTに移すまでは暗号化が保証されないことです。2つ目は、mTLSは認証しか提供しないため、有効な証明書を持つ誰もがサービスに到達でき、閉じるにはAuthorizationPolicyをdefault-denyのパターン(例: すべてを拒否するallow-nothingポリシーの後ろに、条件付きの許可を置く)にする必要があることです。ポリシーが1つもないワークロードに対して、Istioはすべてのリクエストを許可します。3つ目は、サイドカーはTCPしか横取りせず、UDP・ICMPは通過し、ポート22をはじめとする一部のポートはinboundのキャプチャから外れ、アプリケーションとサイドカーが同じネットワーク・プロセスのネームスペースにあるため、アプリケーションがリダイレクションのルールを消してサイドカーを迂回できることです。ドキュメントの結論は「すべてのトラフィックが必ずキャプチャされると信じるのは安全ではない」であり、そのためNetworkPolicyを重ねる多層防御を勧めています。AuthorizationPolicyは、selectorやtargetRefsで適用対象を選び、ルートネームスペースに置くと、メッシュ全体に適用されます。
| メッシュが与えるもの | メッシュが与えないもの |
|---|---|
| ワークロードの身元(サービスアカウントの証明書) | 認可: AuthorizationPolicyが別に必要 |
| サイドカー間の暗号化 | アプリケーションとサイドカーの間の保護(同じPod内は平文) |
| STRICTでの平文の拒否 | UDP・ICMP・除外ポート・迂回されたトラフィックの保護 |
| L7の属性に基づくポリシー | L3/L4の境界: NetworkPolicyで補強 |
PKI: 3つのCAと、有効期限の時計
PKI証明書と要件のドキュメントは、クラスターに必要な証明書を2つのグループに分けています。サーバー証明書は、APIサーバーのエンドポイント、etcdサーバー、ノードごとのkubelet、オプションでfront-proxyにあります。クライアント証明書は、kubeletがAPIサーバーに認証するとき、APIサーバーがetcdに認証するとき、コントローラーマネージャー・スケジューラー・kube-proxyがAPIサーバーに認証するとき、そして管理者にあります。これらに署名するCAは3つです。
| CA | ファイル | 用途 |
|---|---|---|
| kubernetes-ca | /etc/kubernetes/pki/ca.crt |
Kubernetes一般用のCA |
| etcd-ca | /etc/kubernetes/pki/etcd/ca.crt |
etcdに関するすべての証明書 |
| kubernetes-front-proxy-ca | /etc/kubernetes/pki/front-proxy-ca.crt |
front-end proxy用 |
これに加えて、サービスアカウントトークンの署名用の鍵ペアsa.key・sa.pubがあります。CAを階層にするには、管理者が統制するルートCA1つから中間CAを作成して、残りの発行をKubernetesに任せることができます。脅威モデルで重要なのは、各CAの重さです。etcd-caが署名したクライアント証明書は、etcdのすべてのデータへのアクセスであり(迂回リスクのドキュメント: 「etcdが信頼するCAが発行したどんな証明書でも、etcd内のデータへの完全なアクセスを許可する」)、kubernetes-caでO=system:mastersの証明書を作成すればスーパーユーザーです。ドキュメントは、kube-apiserverのkubeletクライアント証明書にsystem:mastersの代わりに、より特権の低いグループを使えること、kubeadmはkubeadm:cluster-adminsグループを使うことを記しています。
SANもセキュリティの項目です。証明書のhosts(SAN)にない名前で接続するとTLSの検証が失敗するため、kube-apiserverの証明書には、ホスト名・ホストIP・advertise IPに加えて、ロードバランサーのアドレスと、kubernetes、kubernetes.default、kubernetes.default.svc、kubernetes.default.svc.cluster、kubernetes.default.svc.cluster.localが入ります。あとからロードバランサーのアドレスを変えると、SANがなくて接続が壊れるのが、典型的な事故です。
kubeadmの証明書管理のドキュメントの最初の一文が、時計です。kubeadmが作成したクライアント証明書は、1年後に期限切れになります。kubeadm certs check-expirationは、/etc/kubernetes/pkiの証明書と、admin.conf・controller-manager.conf・scheduler.confに埋め込まれたクライアント証明書の期限を表示し、ドキュメントの出力例では、CAの残りは9年と出ています。更新の方法は2つです。kubeadmはコントロールプレーンのアップグレード時にすべての証明書を更新するため、1年以内に1回アップグレードすれば別にやることはなく(無効にするには--certificate-renewal=false)、手動ではkubeadm certs renewを使いますが、複製されたコントロールプレーンならすべてのノードで実行する必要があり、実行後にコントロールプレーンのPodを再起動する必要があります(動的な再読み込みはされません)。kubeletのserving証明書は、デフォルトが自己署名のため、外部のサービスがTLSで検証できないという点も、同じドキュメントにあります。
セキュリティのオブザーバビリティ: 監査ログとランタイム検知
監査のドキュメントによると、監査はクラスターの行為を時系列で記録して、何が・いつ・誰が・何に対して・どこで起きたかに答えられるようにします。記録はkube-apiserverの中で始まります。リクエストの段階ごとに(RequestReceived、ResponseStarted、ResponseComplete、Panic)イベントが生成され、ポリシーが残すかどうかとレベルを決め、バックエンド(ログファイルまたはWebhook)が保存します。ポリシーはルールを順番に比較して、最初に一致したルールがレベルを決め、レベルはNone・Metadata・Request・RequestResponseの4つです。--audit-policy-fileを指定しなければ何も記録されず、ルールが0個のポリシーは不正です。監査はAPIサーバーのメモリ使用量を増やします。
監査ログの死角は、迂回リスクのドキュメントが正確に記しています。kubelet APIへの直接アクセス、etcdへの直接アクセス、ランタイムソケットは、アドミッションも監査ログも経由しません。監査ログは、APIサーバーが見たものしか示しません。
その死角を見る目が、ランタイム検知です。Falcoのドキュメントによると、Falcoはホスト・コンテナ・Kubernetes・クラウド環境でランタイムセキュリティを提供するCNCFの卒業プロジェクトで、カーネルのシステムコールをリアルタイムで解析してルールエンジンと照合し、ルールに違反すると警告を出します。コンテナランタイムとKubernetesのメタデータをイベントに付けて「どのネームスペースのどのPodで」を教えてくれ、プラグインでシステムコール以外のイベントソースも受け取れます。デフォルトのルールが検出するものとして、ドキュメントは、特権コンテナを利用した権限昇格、setnsのようなツールを使ったネームスペースの変更、よく知られたディレクトリに対する読み書きを挙げています。警告はSIEMやデータレイクに送って、調査に使います。
2つの目は、同じ出来事を違う場所から見ます。誰かがPodにexecで入ると、監査ログにはpods/execサブリソースに対するリクエストとリクエスト元が残り、Falcoには、そのコンテナの中でシェルが生成されたというシステムコールが残ります。監査ログにはないのにFalcoにだけシェルが見えるなら、APIサーバーを経由しない経路(kubelet API、ランタイムソケット、static Pod)を疑えますし、監査ログにはあるのにFalcoに何もなければ、リクエストが拒否されたか、まだ実行されていないということです。片方しか有効にしていなければ、この照合はできません。
現場での姿
1年後のある日、kubectlがx509エラーを出す。kubeadmでクラスターを構築し、アップグレードなしで1年を超えた場合です。admin.confの証明書とコントロールプレーンコンポーネントの証明書が同じ日に期限切れになるため、APIサーバー自体は動いていても、誰も話しかけられません。check-expirationを定期点検に入れておけば、数か月前に残り時間が見えます。
監査ログに何の痕跡もない侵害。ノードにSSHで入った攻撃者がランタイムソケットでコンテナを起動したなら、APIサーバーは何も見られません。この場合、唯一の記録は、ノードのシステムコールを見るランタイム検知と、ノードの認証ログであり、迂回リスクのドキュメントがノードへのアクセスそのものを制限せよと述べる理由です。
次のクイズで確認すること
クイズでは、mTLSが与えないもの、サイドカーが横取りできないトラフィック、クラスターのCA 3つの役割、kubeadmの証明書の期限切れと更新、監査ポリシーのルールの評価方式、そして監査ログとランタイム検知がそれぞれ見るものを問います。