導入の順序・権限境界・アップグレード
一言でいうと
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か所を診断して直したうえで、最後に「できることと、できてはいけないこと」をすべて含めた権限の検証表を作り、実際の応答と照合します。