Certified Kubernetes Security Specialist (CKS) #4: RBAC 最小権限の深掘り (Cluster Hardening)
CKA #9 RBAC では、Role と ClusterRole、RoleBinding と ClusterRoleBinding の組み合わせルール、subjects と rules の構造、auth can-i で権限を確認する方法を、運用者の観点で身につけました。あの記事の目標は 誰が何をできるか を設計して動くようにすることでした。CKS の Cluster Hardening ドメインは、その上にもう 1 つの問いを重ねます。この権限は本当に必要な分だけか です。
RBAC を作れることと、RBAC を安全に狭められることは別の能力です。運用中は「とりあえず動かすために」広い権限を与えることがよくあり、そうして積み上がった過剰な権限は攻撃者にとってそのまま権限昇格の通路になります。この記事は 最小権限の原則 (least privilege) を RBAC に適用し、広く開いた権限を見つけて必要な範囲に狭める作業を扱います。試験でも「過剰に広い Role を最小権限に狭めよ」は定番として出ます。
最小権限の原則とは #
最小権限の原則は、すべての主体に作業を遂行するのに必要な権限だけを与え、それ以上は与えない というセキュリティ設計の原則です。RBAC に適用すると 3 つに分かれます。
- resources を狭める。
*(すべてのリソース) ではなく、実際に扱うリソースだけを明示します。 - verbs を狭める。
*(すべての動作) ではなく、実際に行う動作だけを明示します。読み取りだけならget・list・watchだけを与えます。 - scope を狭める。 クラスター全体ではなく特定の namespace に、可能なら特定のリソース名 (resourceNames) に限定します。
攻撃者がクラスター内の 1 つの ServiceAccount を奪取したと考えると、原則の価値がはっきりします。そのトークンが secrets を読めれば他の認証情報まで次々と渡り、Pod を作れれば権限昇格の足場になります。権限が狭いほど、奪取 1 件が生み出せる被害範囲 (blast radius) は小さくなります。
過剰な権限を見つける #
狭めるには、まずどこが広いかを見なければなりません。CKS でよく使う診断コマンドから身につけます。
特定の主体が何をできるかを列挙する #
auth can-i --list は、特定の主体が持つ権限を一目で広げて見せます。--as でユーザーや ServiceAccount になりすまして確認します。
# 現在のユーザーの全権限を列挙
kubectl auth can-i --list
# 特定の ServiceAccount の権限を列挙
kubectl auth can-i --list \
--as=system:serviceaccount:dev:app-sa -n dev出力で Resources 列に *.*、Verbs 列に [*] が見えれば、その主体は事実上なんでもできるという意味です。これが真っ先に狭めるべきシグナルです。
個別の動作ができるかは auth can-i で 1 行で確認します。kubectl auth can-i get secrets --as=system:serviceaccount:dev:app-sa -n dev で yes が出れば、過剰な権限の可能性があります。
wildcard が危険な理由 #
Role や ClusterRole の rules に * が入った瞬間、最小権限は崩れます。
# 危険: すべてのリソースにすべての動作を許可
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]この rule は cluster-admin と事実上同じです。新しいリソース種別が追加されても自動的に権限が付いてくるため、意図しない権限まで広がり続けます。apiGroups・resources・verbs のどの欄でも * が見えれば、狭める対象としてマークしておきます。
危険な権限のリスト #
同じ verb でも、どのリソースに付くかによって危険度が違います。CKS で特に警戒すべき組み合わせを整理します。
| 権限 | 危険 |
|---|---|
get/list secrets | Secret の中のトークン・パスワード・証明書をそのまま読む。他のシステムの認証情報の漏洩に波及 |
create pods/exec | 動いているコンテナにシェルで入る。ノード・ネットワークへの横展開の足場 |
create pods | 権限の高い ServiceAccount をマウントした Pod を起動して権限昇格 |
escalate (verb on roles) | 自分より広い権限を持つ Role を作って自ら権限昇格 |
bind (verb on roles) | 任意の Role を他の主体にバインド。権限付与の統制を回避 |
impersonate (users/groups/serviceaccounts) | 他の主体になりすまして、その人の権限で作業 |
* on * | 事実上 cluster-admin |
escalate と bind は特に注意が必要です。Kubernetes は通常「自分が持つ権限以上は他人に渡せない」という権限昇格防止の仕組みを置くのですが、この 2 つの verb がその仕組みを回避する通路です。secrets に対する get、pods/exec に対する create は、試験で「この権限を外せ」という形でよく登場します。
Role を狭める #
最もよくある試験タイプが、広い Role を最小権限に狭める 作業です。狭める前後を比べてみます。
狭める前: 過剰な Role #
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: dev
name: app-role
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]この Role は dev namespace の中でなんでもできます。アプリが実際には ConfigMap を 1 つ読むだけでよくても、secrets・pods・deployments まで全部に手を出せます。
狭めた後: 最小権限の Role #
アプリが app-config という ConfigMap だけを読めばよいなら、次のように resources・verbs・resourceNames をすべて狭めます。
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: dev
name: app-role
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["app-config"]
verbs: ["get"]狭めた点は 3 つです。resources を configmaps に、verbs を get 1 つに、そして resourceNames で app-config という特定のオブジェクトだけに限定しました。これでこの Role を持つ主体は、他の ConfigMap も secrets も pods も触れなくなります。
resourceNames の制約 #
resourceNames は、名前の決まったオブジェクトに get・update・delete・patch のような動作を与えるときに強力に狭めてくれます。ただし list と create、deletecollection には適用されません。list は名前を知らないまま全体を列挙する動作で、create はまだ名前のないオブジェクトを作る動作なので、名前で絞れないからです。この制約は試験で混同しやすいので覚えておきます。
RoleBinding で結びつける #
狭めた Role を特定の ServiceAccount に接続します。
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: dev
name: app-role-binding
subjects:
- kind: ServiceAccount
name: app-sa
namespace: dev
roleRef:
kind: Role
name: app-role
apiGroup: rbac.authorization.k8s.ioRole と RoleBinding がどちらも dev namespace にあるので、この権限は dev の中に限定されます。
ClusterRoleBinding の乱用を減らす #
権限を広げる最もよくあるミスが ClusterRoleBinding です。ClusterRole 自体はクラスター範囲の定義にすぎませんが、それを ClusterRoleBinding で結びつけると、すべての namespace で その権限が発動します。
# 危険: edit ClusterRole をクラスター全体に付与
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: app-edit-everywhere
subjects:
- kind: ServiceAccount
name: app-sa
namespace: dev
roleRef:
kind: ClusterRole
name: edit
apiGroup: rbac.authorization.k8s.iodev の ServiceAccount が、すべての namespace でリソースを修正できるようになります。アプリが dev の中だけで動くなら、この権限は過剰です。
RoleBinding で namespace に限定する #
核心となるパターンは、ClusterRole はそのままに、RoleBinding で結びつけて特定の namespace に限定する ことです。ClusterRole を RoleBinding から参照すると、その権限は RoleBinding がある namespace の中だけで発動します。
# 安全: edit ClusterRole を dev の中だけに限定
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: dev
name: app-edit-in-dev
subjects:
- kind: ServiceAccount
name: app-sa
namespace: dev
roleRef:
kind: ClusterRole
name: edit
apiGroup: rbac.authorization.k8s.io同じ edit ClusterRole を使いますが、これで権限は dev namespace の中に結びつきました。「ClusterRoleBinding を RoleBinding に変えて権限を狭めよ」は試験の定番のバリエーションです。クラスター全域が本当に必要な場合 (例: ノードや PersistentVolume のように namespace のないリソース) だけ ClusterRoleBinding を残します。
default ServiceAccount の権限を削除する #
namespace ごとに自動で作られる default ServiceAccount は、Pod が明示的に別の SA を指定しなければ自動的にマウントされます。この default SA にいかなる権限もバインドしないのが基本です。さらに進んで、トークンの自動マウント自体を切れば、攻撃者が Pod を奪取しても API トークンを手に入れられません。
apiVersion: v1
kind: ServiceAccount
metadata:
name: default
namespace: dev
automountServiceAccountToken: falsePod 単位でも spec.automountServiceAccountToken: false で切れます。トークンが本当に必要なワークロードにだけ専用の ServiceAccount を置いて明示的にオンにする方向が安全です。トークン管理の詳細は #5 で続けて扱います。
aggregated ClusterRole に注意 #
Kubernetes は aggregationRule で複数の ClusterRole をラベルで束ねて 1 つにまとめる機能を提供します。デフォルトの view・edit・admin ClusterRole がこの方式で作られています。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.example.com/aggregate-to-monitoring: "true"
rules: []注意すべき点は、このラベルを持つ ClusterRole を新しく作ると、その規則が 自動的に aggregated ClusterRole にまとめられることです。誰かが該当するラベルを付けた ClusterRole で secrets get を追加すると、monitoring を使っていたすべての主体が静かに secrets を読めるようになります。aggregated ClusterRole を狭めるときは、本体だけでなく そのラベルでまとめられるすべての ClusterRole を一緒に点検しなければなりません。
狭めた権限の検証 #
狭めた後は必ず検証します。CKS の採点も結局「必要なものはでき、不要なものはできないか」を見ます。--as で対象の主体になりすまして確認します。
# できるべきもの: app-config の読み取りは yes
kubectl auth can-i get configmaps/app-config \
--as=system:serviceaccount:dev:app-sa -n dev
# できてはいけないもの: secrets の読み取りは no
kubectl auth can-i get secrets \
--as=system:serviceaccount:dev:app-sa -n dev
# できてはいけないもの: 別の namespace は no
kubectl auth can-i get configmaps \
--as=system:serviceaccount:dev:app-sa -n kube-system
# 全権限を広げて wildcard が消えたかを確認
kubectl auth can-i --list \
--as=system:serviceaccount:dev:app-sa -n dev期待値は、必要な動作にだけ yes、残りには no が出ることです。--list の出力で *.* と [*] が見えなければ、狭める作業が正しくできています。
試験ポイント #
- 最小権限は resources・verbs・scope をすべて狭めること です。resources を特定の種別に、verbs を実際の動作だけに、scope を namespace や resourceNames に限定します。
- wildcard は真っ先に狭めるシグナル です。rules の
apiGroups・resources・verbsのどこでも*が見えれば除去対象です。 - 危険な権限を識別 しておきます。
secretsのget・list、pods/execのcreate、podsのcreate、そしてescalate・bind・impersonateが核心です。 - ClusterRoleBinding を RoleBinding に変えて namespace に限定する パターンが定番です。ClusterRole はそのままに、バインドだけ RoleBinding に変えます。
- resourceNames は
list・create・deletecollectionには適用されません。 名前ベースで狭められない動作があることを覚えておきます。 - default ServiceAccount には権限を与えず、
automountServiceAccountToken: falseでトークンマウントを切ります。 - 検証は
auth can-i --asで行います。必要なものはyes、不要なものはnoが出るか、両方を確認します。
まとめ #
この記事で押さえたこと:
- 最小権限の原則。 resources・verbs・scope をすべて狭めて、奪取 1 件の被害範囲を減らします。
- 過剰な権限を見つける。
auth can-i --listで wildcard の主体を見つけ、auth can-iで個別の動作を確認します。 - 絞り込みと namespace への限定。 resourceNames で Role を狭め、ClusterRoleBinding を RoleBinding に変えて namespace に結びつけます。
- 危険な権限と default SA。
secrets get・pods/exec・escalate・bind・impersonateを警戒し、default SA には権限を与えず、トークンマウントを切ります。
次へ — ServiceAccount トークン管理 #
権限を狭めたので、次はその権限が乗って出ていく トークン 自体を管理する番です。
#5 ServiceAccount トークン管理、API アクセス制限、クラスターのアップグレード では、ServiceAccount トークンがどう発行されてマウントされるのか、寿命の短い projected token と自動マウントを切る方法、apiserver の匿名アクセスと API アクセスを制限する設定、そしてセキュリティパッチのためのクラスターアップグレードの流れまで整理します。