CNI、ストレージ、クライアント — 境界にある構成要素の危険
一言でいうと
NetworkPolicyはCNIプラグインが実行するため、実行するプラグインがなければ、ポリシーは何の効果もありません。hostPathはノードの認証情報とランタイムソケットへの通り道であり、CSIドライバーはストレージバックエンドのシークレットを握っています。クライアント側では、kubeconfig 1ファイルが、クラスター全体の鍵であると同時に、任意のコマンドを実行させるファイルにもなりえます。
なぜ必要なのか
前の読み物の3つのコンポーネントがコントロールプレーンとノードの中にあったなら、この3つはクラスターの境界にあります。CNIはPodとネットワークの間、ストレージはPodとディスクの間、クライアントは人間とAPIサーバーの間です。境界は、両側の信頼レベルが異なる場所なので、脅威モデルで最初に引く線です。ところが3か所とも、「Kubernetesがうまく守ってくれるだろう」と誤解しやすい場所です。NetworkPolicyを作成したから防がれているでしょう、PVCを使ったから分離されているでしょう、kubeconfigは単なる設定ファイルでしょう。3つとも間違いです。
どう動くのか
CNI: ポリシーはプラグインが実行する
ネットワークポリシーのドキュメントの前提条件のセクションは明確です。ネットワークポリシーはネットワークプラグインが実装するものであり、NetworkPolicyをサポートするネットワーキングソリューションを使う必要があり、実装するコントローラーなしにNetworkPolicyリソースを作成しても、何の効果もありません。APIサーバーはオブジェクトを保存するだけで拒否しないため、ポリシーがあるのにトラフィックが流れる状況が、静かに生じます。
ポリシーのデフォルトの状態も知っておく必要があります。ネームスペースにポリシーが1つもなければ、そのネームスペースのPodに出入りするすべてのトラフィックが許可されます。分離はingressとegressが別々に宣言され、ドキュメントの表現のとおり、「分離」は絶対的なものではなく、「何らかの制限が適用される」という意味です。そのためセキュリティチェックリストは、ネームスペースごとにすべてのPodを選ぶデフォルト拒否ポリシーを置いて、許可リスト方式にすることを勧め、使っているCNIがポリシーをサポートしているかを最初の項目に置いています。CNIプラグイン自体は、前の読み物で見たとおり、コンテナランタイムがロードするため、プラグインのバイナリと設定ディレクトリに書き込める権限は、ノードのネットワークポリシーの実行を無力化できる権限です。転送中の暗号化はすべてのCNIが提供するわけではなく、なければサービスメッシュが代替になるという点も、チェックリストにあります。
ストレージ: hostPathの通り道とCSIのシークレット
ボリュームのドキュメントのhostPathのセクションは、警告から始まります。hostPathは多くのセキュリティリスクを抱えているので、避けられるなら避け、代わりにlocal PersistentVolumeを使うようにという内容です。理由は具体的です。ホストのファイルシステムに触れられると、kubeletの認証情報のような特権的なシステムの認証情報や、コンテナランタイムソケットのような特権的なAPIが露出し、コンテナからの脱出や、クラスターのほかの部分への攻撃に使われかねません。アドミッションで特定のディレクトリだけを許可するように制限しても、そのマウントを読み取り専用に強制して初めて、制限が有効になります。信頼できないPodに、どのホストパスであれ読み書きを許可すると、そのPodのコンテナがマウントをひっくり返せるからです。Pod Security StandardsのbaselineプロファイルがhostPathを防ぐのが、このリスクに対する基本的な防御です。
CSI側のシークレットは、別の種類です。CSIドライバーのドキュメントのSecrets and Credentialsのセクションによると、ドライバーがバックエンドにアクセスするのに必要な認証情報は、2つの層に分かれます。ドライバー単位のシークレット(バックエンドのサービスアカウントのようなもの)は、デプロイ時に標準的なSecretの配布方式でドライバーのPodに直接注入し、操作単位・ボリューム単位のシークレットは、CSIの仕様がCreateVolumeのような各操作のリクエストに載せて送れるようにしていて、管理者がSecretを作成して、StorageClassやVolumeSnapshotClassにそのキーを書いて渡します。サイドカーコンテナのSecret関連のRBACルールは、権限を減らすためにデフォルトで無効になっていて、必要なときだけ有効にし、仕様は機微なフィールドにcsi_secretマーカーを付けて、ログに残さないようにしています。脅威モデルに置き換えるとこうなります。CSIコントローラーのPodのServiceAccountや、StorageClassが参照するSecretを読める主体は、ストレージバックエンド全体に対する認証情報を得ます。
クライアント: kubeconfigは鍵であり、実行ファイルでもある
kubeconfigによるクラスターアクセスの構成のドキュメントは、kubectlがデフォルトで$HOME/.kube/configを読み、KUBECONFIG環境変数や--kubeconfigフラグで別のファイルを指定できると説明したあと、警告を付けています。信頼できる出所のkubeconfigだけを使うようにということで、特別に作られたkubeconfigは、悪意のあるコードの実行やファイルの露出につながりかねないため、信頼できないファイルは、シェルスクリプトを見るときのように、まず検査するようにと述べています。
なぜ設定ファイルがコードの実行になるのかは、認証のドキュメントの認証情報プラグインのセクションが説明しています。client-goと、それを使うkubectl・kubeletは、ユーザーの認証情報を得るために外部コマンドを実行できます(1.22で安定化)。LDAP・Kerberos・OAuth2・SAMLのように、client-goが直接サポートしないプロトコルのための機能で、プラグインが返したトークンをclient-goがbearerトークンとして使い、サーバー側はWebhookトークン認証器でTokenReviewを処理します。したがって、kubeconfigのexec項目に書かれたコマンドは、kubectlを叩くたびにユーザーの権限で実行されます。受け取ったkubeconfigを開いて、execブロックがどのバイナリを指しているかを見ることが、そのために必要です。
kubeconfigに入っている認証情報の性質も、リスクを決めます。認証メカニズムのハードニングガイドは、X.509クライアント証明書の制約を挙げています。個別に失効させることができず、漏えいすると有効期限まで使え、無効にするにはCAを再発行する必要があり、発行の記録がクラスターに残らず、秘密鍵をパスワードで保護できないため、ファイルを読める誰もが使え、グループが証明書のOの値に埋め込まれていて、有効期間中は変更できません。ServiceAccountトークンも同様に、認証のドキュメントが「クラスターの外で使っても完全に有効である」と記しているので、パイプラインのツールに入れておいたトークンは、kubeconfigと同じ重みで扱う必要があります。対応は、クラスターのセキュリティのドキュメントが述べるとおりです。短い有効期間、自動の交換、発行されるトークンの有効期間を統制できる認証プロバイダーです。
現場での姿
NetworkPolicyをデプロイしたのに、すべて突破されます。kwokや、ポリシーをサポートしないプラグインで作ったクラスターでよくあります。kubectl get networkpolicyにオブジェクトが見えることと、トラフィックが防がれることは別の話で、実行されているかどうかは、実際に接続を試みて確認する必要があります。
チームのチャンネルに共有されたkubeconfig。ファイル内のclient-certificate-dataは失効させられないため、漏えいが確認されても、有効期限まで有効です。証明書ベースのユーザー認証情報を使っているなら、有効期間を短くし、人間のユーザーはOIDCのような外部の認証プロバイダーに移行することが、ハードニングガイドが指す方向です。
次のクイズで確認すること
クイズでは、スケジューラーの認証フラグを空にしたときの動作、kube-proxyのメトリクスポートのデフォルトのバインディング、ランタイムソケットへのアクセスが意味すること、実行するコントローラーのないNetworkPolicyの効果、hostPathの制限が有効になる条件、そしてkubeconfigがコードの実行になる理由を問います。