TT Lab
はじめる
学ぶ 学習パス コース

CRDとオペレータ

導入の順序・権限境界・アップグレード

TT Labで続きを見る

一言でいうと

Operatorは、クラスターの状態を代わりに操作する自動化された管理者です。そのため、Operatorのサービスアカウントが窃取されると、攻撃者はそのOperatorのすべての権限をそのまま手に入れます。

なぜ必要なのか

ほとんどのワークロードは、自分のネームスペースの中の仕事をします。Webアプリケーションが侵害されても、被害は概してそのアプリケーションとそのデータに限られます。Operatorは違います。複数のネームスペースのCRをwatchし、Deploymentを作り、Secretを読み、ときにはRBACオブジェクトまで作ります。コントローラーのPod 1つで見つかった脆弱性が、そのままクラスター全体の掌握につながることがあります。

そのため、Operatorの運用で最も重要な2つは、派手な機能ではなく、インストールの順序と権限の境界です。

どう動くのか

インストールの順序は、依存関係のグラフです。順序を守らないと、黙っておかしな動作をします。

1. CustomResourceDefinition   — 새 타입을 API 에 먼저 등록
2. ServiceAccount / ClusterRole / ClusterRoleBinding — 권한 부여
3. Deployment                 — 컨트롤러 기동

CRDが先でなければならない理由は、コントローラーが起動した直後に、その型をwatchするからです。型がないとwatchの設定が失敗し、コントローラーがクラッシュループに陥ります。権限が先でなければならない理由も同じです。権限なしで起動したコントローラーは、一覧の取得から403に当たります。GitOpsでデプロイするなら、この順序を同期ウェーブや依存関係として明示する必要があります。

権限は、verbを絞ることが核心です。初心者のOperatorで最もよくあるミスが、verbs: ["*"]です。実際に必要なものは、もっとずっと狭いです。

コントローラーがすること 必要なverb 不必要に広い権限
CRを読んで監視 get, list, watch create, delete
子リソースの作成/更新 get, list, watch, create, update, patch delete(所有者参照で代替可能)
状態の報告 update, patch(statusサブリソース) メインリソース全体のupdate
イベントの記録 create, patch get, list

特にdeleteには注意が必要です。子の後片付けは、ほとんどの場合、所有者参照とガベージコレクターに任せられるため、コントローラーに削除権限がなくてもかまいません。なければ、侵害されたコントローラーがリソースを大量削除するシナリオを、根本から遮断できます。

もう1つ、よく抜け落ちるのがサブリソースの権限です。webservicesに対するupdate権限があっても、webservices/statusは別のリソースとして扱われるため、別に与える必要があります。ファイナライザーを扱うなら、webservices/finalizersも必要です。この2つが抜けて、「権限は与えたはずなのに、コントローラーがstatusを書けない」という症状がよく出ます。

リーダー選出は、重複実行を防ぎます。コントローラーを2つ起動すると、同じオブジェクトを2つが同時に調整して衝突します。リーダー選出は、複数のレプリカのうち1つだけを実際に働かせる仕組みで、リーダーはcoordination.k8s.ioのLeaseオブジェクトに自分の身元を書き、定期的に更新します。リーダーが落ちるとリースが期限切れになり、ほかのレプリカが引き継ぎます。ここでも最小権限の感覚が必要です。Leaseの権限は、resourceNamesでその1つだけに限定できます。

アップグレードでCRDに手を触れてはいけません。コントローラーのイメージを上げるのはローリングアップデートで安全ですが、CRDの古いバージョンを一緒に削除するのは危険です。status.storedVersionsにそのバージョンが残っていれば、すでに保存されたオブジェクトを読めなくなります。アップグレードの手順は、常に「CRDは足すだけで、引くのは再保存を終えた後」です。

このラボ環境についての正直な説明

このラボには、実際のコントローラーのバイナリがなく、Podの中を覗く手段(kubectl exec、本物のログ)もありません。そのため、Operatorのインストールのラボでは、マニフェストの正確さを扱います。ネームスペースとラベル、サービスアカウントとClusterRoleのverbの集合、Deploymentのアカウント・リーダー選出の引数・プローブ・リソース制限・セキュリティコンテキスト、そしてリーダー選出のLeaseを、作成して検証します。権限が本当に意図どおりにかかっているかは、ログではなくkubectl auth can-i --as=で確認します。この方法は、実際の現場でも、最も速くて確実なRBACのデバッグ手段です。

現場での姿

1つ目は、1文字違いのバインディング事故です。ClusterRoleBindingのsubjectの名前が、実際のサービスアカウントの名前と1文字でも違うと、エラーなしにバインディングが作られ、コントローラーだけが403に当たります。存在しないサブジェクトを指すバインディングは、完全に有効なオブジェクトだからです。

2つ目は、CRを通じた権限昇格です。OperatorがユーザーのCRを信頼してRBACオブジェクトを作ってあげると、ユーザーは、自分では直接作れない権限を、Operatorを迂回路として手に入れられます。防御は何重にもなります。OperatorがRBACを動的に作らないように設計し、どうしても必要なら、リクエストできる権限を許可リストで制限します。

3つ目は、権限はドキュメントではなく検証の対象であることです。「このOperatorはSecretを読めない」という文は、Wikiに書いておくものではなく、kubectl auth can-i get secrets --as=...で確認してnoを得ておくものです。できることだけでなく、できてはいけないことも一緒に検証表に入れてはじめて、権限の境界が実際に守られているという証拠になります。

次のラボですること

Operator専用のネームスペースをラベル付きで作成し、インストールの順序をドキュメント化し、ワイルドカードのない最小権限のClusterRoleとバインディングを作ります。コントローラーのDeploymentに、専用のアカウント・リーダー選出・プローブ・メモリ制限・非rootでの実行を設定し、リーダー選出のLeaseを作成します。そのあと、イメージを上げながらCRDのバージョンが保存されるか確認し、壊れたOperatorのマニフェストの3か所を診断して直したうえで、最後に「できることと、できてはいけないこと」をすべて含めた権限の検証表を作り、実際の応答と照合します。