セルフサービスの囲いは何を塞ぐのか
一言でいうと
スキーマはリクエストの内容を、RBACは主体の権限を、ResourceQuotaはネームスペースの上限を扱います。許可されたリクエストでも、クォータで拒否されることがあり、参照の失敗は、安全に拒否されたことの証拠ではありません。
なぜ必要なのか
次は練習の状況です。blue-devのRoleには、AppClaimの読み取りと作成しかありません。ところが、Secretの読み取りができます。Roleファイルが誤って適用されたと断定する前に、同じ主体に結び付けられた別のバインディングを見る必要があります。RBACの権限は、許可ルールを合算します。1つのRoleから動詞を除くことは、別のRoleが与えた許可を取り消すdenyルールではありません。
RoleBindingはClusterRoleを参照することもできます。このとき、権限はそのRoleBindingのネームスペースの中で適用されます。ClusterRoleという名前だけを見て、常にクラスター全体の権限だと結論づけないでください。どのバインディングが、どの範囲で結び付けたのかを見る必要があります。公式RBACルールとバインディング
どう動くのか
権限の参照と実際のリクエストを区別します
kubectl auth can-iは、認可の判定を参照します。「create可能」という答えが、入力スキーマやResourceQuotaまで通るという意味ではありません。最終的な動作は、その主体による実際のリクエスト、または安全なサーバーdry-runで確認します。サーバーdry-runは、APIの検証経路を通しつつオブジェクトを保存しない方法であり、アプリケーションのあらゆる動作を実行する方法ではありません。公式APIのdry-run
ラボ環境の管理者コンテキストで、次の参照を実行できます。一般ユーザーに、任意の主体の--asを使う権限が自動で付くわけではありません。
kubectl auth can-i get appclaims.platform.labhub.io \
-n tenant-blue --as=system:serviceaccount:tenant-blue:blue-dev
kubectl auth can-i patch appclaims.platform.labhub.io \
--subresource=status -n tenant-blue \
--as=system:serviceaccount:tenant-blue:blue-dev
後ろのコマンドの--subresource=statusを除くと、別の質問になります。resource/nameの表現のname部分にstatusを入れても、サブリソースの権限を尋ねたことにはなりません。公式can-iの使い方
今回のラボで検証済みのkubectlでは、明示的な許可はstdoutのyesと終了コード0、明示的な拒否はnoと終了コード1で現れます。空のstdout、認証エラー、接続失敗を、「yesではないのでno」に置き換えません。まず、正常なgetを対照群として確認し、拒否の結果も正確に判別します。CLIや認可方式が変わったら、出力形式から確認し直す必要があります。
APIを使う権限と、ポリシーを管理する権限を分けます
| リクエスト | このラボのポリシー | 確認する理由 |
|---|---|---|
| AppClaimのget・list・watch・create・update・patch | 許可 | 自分のチームが申請し、観察し、修正する |
| AppClaimのstatus patch・update | 拒否 | 観測結果を書く主体とユーザーを分離する |
| クォータのcreate・patch | 拒否 | ユーザーが自分の上限を変更しない |
| Role・RoleBindingのcreate | 拒否 | ポリシー管理の機能をセルフサービスAPIと分離する |
| Secretsのget、ほかのnamespaceのAppClaimのlist | 拒否 | 認証情報とテナント範囲の制限 |
| AppClaimのdelete | 拒否 | このラボで選んだ、運用者による回収のポリシー |
ここで、Roleの作成権限さえ得られれば、すぐに管理者権限を作れると理解してはいけません。Kubernetesには、自分が持っていない権限をRoleに入れたりバインドしたりする行為を制限する、APIの検査があります。escalateとbindは、それぞれ関連する検査を越えられる特別な権限なので、より慎重に扱います。このラボは、その防御を無効にせず、Roleの管理そのものもテナントの業務から除外します。公式の権限昇格の防止
ワイルドカードは、現在必要なリソースだけでなく、後から追加される権限の範囲まで広げてしまうことがあります。デフォルトのeditロールは、名前だけでは安全性を判断できず、Secretへのアクセスなどの影響があります。ユーザーの仕事を、具体的なAPIグループ・リソース・動詞で書き、実際のバインディングまで検討します。公式RBACのベストプラクティス
個数クォータは、CPU使用量や外部コストを測りません
count/appclaims.platform.labhub.io: "2"は、この種類のオブジェクトの個数を制限します。1つのAppClaimが外部データベースを10個作ったとしても、そのコストを自動では数えません。個数の制限は、コントロールプレーンのオブジェクトが際限なく増えるのを防ぐ手段であり、作業量・コスト・有効期限のポリシーは、別に設計する必要があります。また、CRDベースではないaggregation APIのクォータは、拡張APIサーバー側の責任を確認する必要があります。公式オブジェクト個数クォータ
クォータを診断するときは、3つの値を分けて読みます。spec.hardは望む上限、status.hardは適用された上限、status.usedは観測された使用量です。使用量がないということは、0という意味ではありません。公式ResourceQuotaフィールド
ラボでusedが空なら、まずCRDのEstablished、API discovery、実際のAppClaimの一覧、クォータのstatusを確認します。反映までの時間を置いてからもう一度読み、続くようなら、管理者がコントローラーの状態を調べます。exceeded quotaとstatus unknown、Forbidden、接続エラーは、同じ失敗ではありません。似たエラーに見えても、出力の原因を区別する必要があります。上限を増やしたりクォータを削除したりすることは、診断の代わりにはなりません。
削除の禁止は、Kubernetesの必須ルールではありません
このラボは、テナントのdeleteを禁止し、運用者が回収するように定めました。実際のプラットフォームでは、ユーザーによる削除を許可し、コントローラーがfinalizerで外部リソースを整理する設計もありえます。finalizerは実行コードではなく、整理の責任を示すキーです。削除のリクエストのあとにdeletionTimestampが付き、コントローラーが整理を終えてfinalizerを取り除くことで、オブジェクトの削除が完了します。担当のコントローラーがなければ、キーを追加するだけで整理が実行されるわけではありません。公式finalizerの動作
現場での姿
LabHubの既存の採点ツールに、権限参照のエラーを注入したところ、空のレスポンスも「拒否を確認」として通過していました。修正後は、正常なgetと明示的なnoを両方とも要求します。クォータについても、実際の個数2、上限2、使用量2を突き合わせ、3つ目のリクエストの失敗がクォータによるものかを確認します。拒否されたという結果だけを記録すると、認証の障害と正しいポリシーを区別できません。
次のラボですること
blue-devの許可・拒否の一覧を埋め、statusと別のネームスペースを別々に確認してください。クォータがいっぱいになったら、2つのservedバージョンで3つ目のAppClaimを送ってみます。最後のレポートには、実際に参照した値を記録し、読めなかった値を推測して埋めないでください。