Kubernetesの一つのYAMLがGCP組織全体を乗っ取る仕組み

開発者がクラウドリソース――データベース、ストレージバケット、仮想マシンなど――を作成する際には、クラウドプロバイダーに対して認証するための認証情報が必要です。Google Cloudの場合、これはしばしばサービスアカウントキーを意味します。これは、そのユーザーが誰であり、何を許可されているかを証明するJSONファイルです。

サービスアカウントキーの問題点は、それがファイルであることです。ファイルはコピーされ、メールで送信され、誤ってGitリポジトリにコミットされ、ノートPCに置き去りにされ、そして忘れ去られてしまいます。

あるメンバーがチームを離れても、組織側はそのメンバーがどの鍵を持っていたか把握していないことがよくあります。数十人の開発者にまたがる認証情報の追跡とローテーションは、大きな運用上の負担とセキュリティリスクになります。

この現象を業界ではシークレットスプロール(secret sprawl)と呼びます。クラウド認証情報がマシンやパイプライン、コードベースにまたがって散らばり、監査も削除も困難になる状態です。

GitOpsはいかにして認証情報を排除したか

Kubernetesコミュニティはこの問題に対して、GitOpsオペレーター(コントローラーとも呼ばれます)というソリューションを用意しています。

  1. 開発者は必要なリソースを記述したYAML設定ファイルを書き、それをGitにコミットし、Kubernetesクラスターに適用します。
  2. クラスター内で稼働するコントローラーがそれらのファイルを読み取り、開発者に代わってクラウドリソースを作成・更新します。

ここで重要なのは、開発者側にはクラウド認証情報が一切残らないという点です。コントローラーが自身の認証情報を使って、開発者の代わりに認証を行います。

Googleにおけるこの仕組みがGoogle Kubernetes Config Connector(KCC)で、通常はGoogle Kubernetes Engine(GKE)クラスター内で稼働します。KCCはGoogle Cloudリソースを記述した設定ファイルを監視し、対応するGoogle Cloud APIを呼び出してそれらを作成・更新します。

あるアプリケーションにストレージバケットからオブジェクトを読み取る権限を付与したい開発者は、次のようなIAMPolicyMemberリソースを送信できます。

apiVersion: iam.cnrm.cloud.google.com/v1beta1
kind: IAMPolicyMember
metadata:
  name: my-binding
  namespace: my-team
spec:
  member: "serviceAccount:[email protected]"
  role: roles/storage.objectViewer
  resourceRef:
    kind: Project
    external: "my-project"

KCCはWorkload Identityを通じてGoogle Cloudに認証しますが、その際に使用するのはプラットフォームチームが管理するGoogleサービスアカウント(しばしば「KCC GSA」と呼ばれます)です。KCCは複数のプロジェクトやフォルダー、あるいは組織全体にまたがるインフラを管理する場合があるため、このアカウントにはrole/ownerroles/resourcemanager.organizationAdminといった強力なロールが付与されることがあります。

開発者がリソースを送信すると、KCCがそれを検知してGoogle Cloud IAMを呼び出します。権限が作成され、開発者はGoogle Cloudの認証情報には一切触れません。

Image

この仕組みは本来の目的に対してはうまく機能します。開発者側の認証情報は不要になり、すべてがGitに宣言され、プラットフォームチームが管理すべきサービスアカウントは数十個ではなく一つに集約されます。認証情報の乱立という問題は、こうして解決されるわけです。

ネームスペースへのアクセスが組織のオーナー権限に変わるとき

しかし、この同じ仕組みが新たな問題を生み出します。

KCCは、どのKubernetesユーザーがリソースを送信したかに関係なく、すべてのGoogle Cloud操作を自身のサービスアカウントを通じて実行します。そのアカウントが組織レベルの権限を持っている場合、クラスター内での権限が限定されているユーザーであっても、間接的にその権限を行使できてしまいます。この手法は、セキュリティ研究者のJustin O’Leary氏が発見したConfigConfusionと呼ばれています。

攻撃者が次の条件を満たしている場合を考えてみます。

  1. KCCが監視しているKubernetesネームスペースへのアクセス権を持っている。
  2. そのネームスペース内でIAMPolicyMemberリソースを作成する権限を持っている。
  3. Google Cloudの認証情報や権限は自身では一切持っていない。

この場合、攻撃者はKCCのサービスアカウントに割り当てが許可されているGoogle Cloud IAMロールを自分自身に付与できてしまいます。組織全体に対するroles/ownerすら例外ではありません。

攻撃自体は、たった一つのコマンドで完結します。

apiVersion: iam.cnrm.cloud.google.com/v1beta1
kind: IAMPolicyMember
metadata:
 name: escalation
 namespace: my-team
spec:
 member: "serviceAccount:[email protected]"
 role: roles/owner
 resourceRef:
 apiVersion: resourcemanager.cnrm.cloud.google.com/v1beta1
 kind: Organization
 external: "123456789"

攻撃者はこのYAMLをKubernetes経由で適用します。KCCがそれを読み取り、自身のサービスアカウントを使ってGoogle CloudにそのIAM変更を要求します。このサービスアカウントにはその変更を行う権限があるため、要求は承認されます。攻撃者はGoogle Cloudの認証情報を一度も保持することなく、Google Cloud組織全体を掌握するに至るわけです。

Image

二つの認可システムと、抜け落ちたチェック

なぜこの攻撃が成立するのかを理解するには、このリクエストを処理する二つの独立した認可システムに目を向ける必要があります。

  1. Kubernetes RBACは、クラスター内でユーザーが何を実行できるかを制御します。ここで問われるのは「このユーザーは、このネームスペースでIAMPolicyMemberリソースを作成することを許可されているか」という点だけです。許可されていれば操作は通ります。RBACはGoogle Cloud側について何も知らず、そのリソースがGoogle Cloud上で何を行うことになるのかを問うことはありません。
  2. Google Cloud IAMは、サービスアカウントがGoogle Cloud上で何を実行できるかを制御します。KCCがバインディングを作成するためにGoogle Cloudを呼び出す際に問われるのは「KCCのサービスアカウントはこのIAMバインディングを設定する権限を持っているか」という点だけです。許可されていれば操作は通ります。Google Cloudは、どのKubernetesユーザーがこのリクエストの発端になったのかを知らず、そのユーザーがそもそもこのリクエストを行う資格を持っていたかどうかを確認することもありません。
Blog_VTL-GoogleKubernetes_Fig3_V1 (1)

結果として、Kubernetes側はクラスター内でリソースが作成されたことしか把握できず、Google Cloud側はKCCのサービスアカウントが変更を行ったことしか把握できません。KCCは、権限の限られたユーザーからのリクエストを受け取り、そのユーザーがGoogle Cloud上では本来持っていない権限を使って、それを実行してしまうことになります。

これは混乱した代理人問題(confused deputy problem)として知られるものです。KCCは強力な権限を持ちながら、それより権限の弱いユーザーからの指示に従って動作し、そのユーザーが本来その権限を使う資格を持っているかどうかを確認しないのです。

開発者からクラウド認証情報を取り除くという設計上の決定自体は、有用であり意図的なものです。しかしそれは同時に、開発者のKubernetes上のアイデンティティと、その開発者に代わって行使されるGoogle Cloud権限との間のつながりも断ち切ってしまいます。KCCが操作を行うとき、Google Cloudが目にするのはKCCそのものであり、リクエストの発端となった人物ではありません。

Googleは「仕様どおり」と回答

ConfigConfusionに対するGoogleの回答は、KCCは設計どおりに動作しているというものでした。

管理者がKCCに組織レベルのサービスアカウントを与えることを選択し、また開発者がKCC管理下のネームスペースでIAMPolicyMemberリソースを作成できるようにすることを選択した――Googleの立場からすれば、これらは設定上の判断であり、KCCは与えられた指示をそのとおりに実行しているに過ぎません。

この説明は技術的には正確であり、KCCは設定されたとおりに動作しています。

問題は、これら二つの判断の間にある関係性が、ドキュメント上で明確になっていない点です。多くの管理者は、この二つを別々の問題として捉えています。「このチームにどのKubernetesリソース種別の作成を許可するか」という問題と、「KCCのサービスアカウントはGoogle Cloud上で何ができるか」という問題です。しかしKCCにおいては、この二つはつながっています。あるチームにIAMPolicyMemberリソースの作成権限を与えることは、そのチームにKCCの持つGoogle Cloud上の権限を利用する手段を与えることにもなり得るのです。

KCCに必要な権限を正確に洗い出す作業は難しいため、組織レベルのアクセス権を与えてしまうほうが手軽であることが多いのが実情です。これは必ずしも管理の甘さを意味するわけではなく、生じる権限のギャップを可視化しない設計がもたらす自然な帰結だといえます。

修正が難しい理由

最も分かりやすい解決策は、リソースを送信したKubernetesユーザーが対応するGoogle Cloud権限を持っているかどうかを、リクエストを実行する前に確認することでしょう。

しかし、そのチェックを行うには、Kubernetesユーザーが対応するGoogle Cloudのアイデンティティを持っていることが前提になります。KCCはそもそも、そうしたアイデンティティを不要にするよう設計されています。ユーザーごとに対応するアイデンティティを与えれば、KCCが提供しようとしているモデル自体を損なうことになりますし、リソースごとにリクエスト元の権限を照会するようにすれば、すべての調整サイクルに認可チェックと追加のAPI呼び出しが加わることになります。

そのためGoogleが推奨する緩和策は、各リクエストの認可方法そのものを変えるのではなく、KCCを通じて利用可能な権限を絞り込むことに主眼を置いています。

AWSおよびAzureにも存在する同じリスク

この認可上の問題は、KCC固有のものではありません。あるアイデンティティからの指示を受け取り、別のアイデンティティを通じてそれを実行するインフラオペレーターであれば、どれも同じギャップを生み出す可能性があります。プラットフォームごとに異なるのは、オペレーターがどれだけの権限を保持しているか、そしてその権限をどれだけ容易に制限できるかという点です。

  1. AWS Controllers for Kubernetes(ACK)は、サービスごとにコントローラーを分離しています。IAM、Amazon S3、Amazon EC2はそれぞれ専用のコントローラーを持ち、そのサービスに限定されたIAMロールが割り当てられます。仮にユーザーがS3コントローラーのリクエスト経路を掌握したとしても、影響を及ぼせるのはS3リソースに限られ、IAMの変更はできません。そのロールにはそのための権限が与えられていないからです。
  2. Azure Service Operator v2は、Kubernetesのネームスペースごとに別個のマネージドIDを使用できるようにしています。これにより管理者は、オペレーター全体の権限を一つのアイデンティティに集約するのではなく、ネームスペースごとに権限を制限する実用的な手段を得られます。

いずれのアプローチも、根底にある混乱した代理人問題そのものを取り除くわけではありませんが、悪意あるリクエストがオペレーターに到達する前にその権限を制限することで、被害の範囲を抑える効果があります。

Config Connectorを保護するには

このリスクを軽減するには、認可のギャップを構成する両側――KCCのサービスアカウントがGoogle Cloud上で何を実行できるかと、どのKubernetesユーザーがKCCに処理させるリソースを送信できるか――の両方を制限する必要があります。

以下のチェックリストは、その両側をカバーしています。

  • KCCは、ネームスペースごとに個別にスコープされたGoogleサービスアカウントを使用する「ネームスペースモード」で運用されているか。
  • KCCに付与されているIAMロールを、プロジェクト・フォルダー・組織の各レベルで見直したか。
  • roles/ownerやroles/resourcemanager.organizationAdminのような強力なロールは、厳密に必要な場合を除いて削除されているか。
  • IAMPolicyMember、IAMPolicy、IAMPartialPolicyリソースを作成する権限は、承認されたプラットフォームチームやインフラチームに限定されているか。
  • KCCによって行われるフォルダー・組織レベルのIAM変更、特に承認されたGitOpsワークフローの外で行われるものを監視しているか。

脆弱な構成とは、組織レベルのIAM権限を持つKCCサービスアカウントと、KCC管理下のネームスペースへの広範な書き込みアクセス権とが組み合わさった状態を指します。

いずれの条件も、それ単体であれば管理は可能です。しかし両者が揃うと、Google Cloudの認証情報を一切持たないまま悪用できる権限昇格の経路が生まれてしまいます。

翻訳元: https://www.bleepingcomputer.com/news/security/how-one-kubernetes-yaml-can-hand-over-a-gcp-organization/

本記事は bleepingcomputer.com の記事を翻訳・要約したものです。