RBACを狭めトークンを遮断する
目標
クラスターで過剰な権限を持つロールを自分で探し出し、最小権限のRoleに置き換えたうえで、その結果をAPIサーバーに問い合わせて確認します。ServiceAccountトークンがPodに自動で入る経路もすべて閉じます。
なぜ重要なのか
RBACは「誰が何をできるか」を決めますが、それを人が読んで検証するのは非常に困難です。ClusterRole 1つにresources: ["*"]が混ざっていると、そのロールは今後追加されるCRDまで全部含むことになり、その事実に誰も気づきません。そのため、ハードニングの実際の作業は、「ポリシーをうまく書くこと」ではなく、「いま何が許可されているかを一覧にすること」から始まります。
2つ目の軸は認証情報です。Podはデフォルトでトークンを受け取ります。アプリケーションがAPIを使わなくても受け取ります。Podが1つ破られると、攻撃者はすぐにクラスターの認証情報を1つ手に入れることになります。automountServiceAccountToken: falseは1行ですが、侵害時に被害が広がる範囲を大きく減らします。
ステップ
- ネームスペース
cks-rbacを作成し、その中にServiceAccountreport-saを作成してください。 - クラスター内のすべてのClusterRoleのうち、
rules[].verbsに*が含まれるものの名前だけを選んで辞書順に並べ替え、/root/cks-rbac-hardening/wildcard-roles.txtに1行に1つずつ保存してください。 - ClusterRole
cks-pod-readerを作成してください。apiGroupsはコアグループ(空文字列)1つ、resourcesはpodsとpods/log、verbsはget・list・watchの3つです。どのフィールドにも*があってはいけません。 - クラスター内のすべてのClusterRoleのうち、
escalate、bind、impersonateのいずれか1つでもverbsに持つものの名前だけを並べ替えて、/root/cks-rbac-hardening/danger-verb-roles.txtに保存してください。 cks-rbacネームスペースに、RoleBindingreport-sa-pod-readerでreport-saにClusterRolecks-pod-readerをバインドしてください。そのあと、次の2つの質問の答え(yesまたはno)を順番に/root/cks-rbac-hardening/can-i.txtに2行で保存します。1行目はreport-saがcks-rbacでpodsをlistできるか、2行目はreport-saがcks-rbacでsecretsをgetできるかです。cks-rbacにPodreport(イメージnginx:1.27-alpine)を作成してください。serviceAccountNameはreport-sa、PodスペックのautomountServiceAccountTokenはfalseです。cks-rbacにSecretreport-sa-tokenを作成してください。タイプはkubernetes.io/service-account-token、アノテーションkubernetes.io/service-account.nameの値はreport-saです。さらに、Podtoken-app(イメージnginx:1.27-alpine、serviceAccountNamereport-sa)を作成し、トークンはprojectedボリュームで受け取ってください。ボリューム名はapi-token、serviceAccountTokenソースのaudienceはapi、expirationSecondsは3600、pathはtokenです。cks-rbacのdefaultServiceAccountにautomountServiceAccountToken: falseを設定し、defaultのSAがこのネームスペースでpodsをlistできないこと(no)を、/root/cks-rbac-hardening/default-sa-can-i.txtに1行で保存してください。
参考
- ワイルドカード監査の例:
kubectl get clusterrole -o json | jq -r '.items[] | select(...) | .metadata.name' | sort kubectl auth can-i list pods -n cks-rbac --as=system:serviceaccount:cks-rbac:report-sakubectl create clusterrole cks-pod-reader --verb=get,list,watch --resource=pods,pods/log- よくある間違い1:
automountServiceAccountTokenをコンテナスペックの中に入れてしまいます。Podスペックの直下に置きます。 - よくある間違い2: RoleBindingのsubjectsでServiceAccountの
namespaceを抜かすと、バインディングはまったく効果を持ちません。
ネームスペースと専用のServiceAccount
ネームスペースcks-rbacを作成し、その中にServiceAccountreport-saを作成してください。
kubectl create serviceaccountで作成します。ネームスペースを先に作成しておかないと、SAの作成は失敗します。
ワイルドカードのClusterRoleを探し出す
クラスター内のすべてのClusterRoleのうち、rules[].verbsに*が含まれるものの名前だけを選んで辞書順に並べ替え、/root/cks-rbac-hardening/wildcard-roles.txtに1行に1つずつ保存してください。
kubectl get clusterrole -o jsonをjqで走査し、rulesのうちverbsに*が含まれるものを選びます。結果は名前だけを、1行に1つずつ保存します。
絞り込んだClusterRoleを書く
ClusterRolecks-pod-readerを作成してください。apiGroupsはコアグループ(空文字列)1つ、resourcesはpodsとpods/log、verbsはget・list・watchの3つです。どのフィールドにも*があってはいけません。
kubectl create clusterrole --resource= --verb=でひな形を作れます。ワイルドカードは、apiGroups・resources・verbsのどこにも使いません。
危険な動詞を持つロールを選び出す
クラスター内のすべてのClusterRoleのうち、escalate、bind、impersonateのいずれか1つでもverbsに持つものの名前だけを並べ替えて、/root/cks-rbac-hardening/danger-verb-roles.txtに保存してください。
escalate、bind、impersonateの3つの動詞のうち、どれか1つでも持つClusterRoleが対象です。ステップ2と同じ方法で、名前だけを並べ替えて保存します。
auth can-iで結果を確認する
cks-rbacネームスペースに、RoleBindingreport-sa-pod-readerでreport-saにClusterRolecks-pod-readerをバインドしてください。そのあと、次の2つの質問の答え(yesまたはno)を順番に/root/cks-rbac-hardening/can-i.txtに2行で保存します。1行目はreport-saがcks-rbacでpodsをlistできるか、2行目はreport-saがcks-rbacでsecretsをgetできるかです。
--as=system:serviceaccount:<네임스페이스>:<이름>(プレースホルダーはネームスペースとServiceAccountの名前です)の形式で、サブジェクトになりすまします。出力はyesまたはnoの1行だけなので、そのままファイルに集めれば構いません。
Podでトークンの自動マウントをオフにする
cks-rbacにPodreport(イメージnginx:1.27-alpine)を作成してください。serviceAccountNameはreport-sa、PodスペックのautomountServiceAccountTokenはfalseです。
automountServiceAccountTokenはPodスペックの最上位(specの直下)のフィールドです。コンテナの中ではありません。
手動のトークンSecretとprojectedトークン
cks-rbacにSecretreport-sa-tokenを作成してください。タイプはkubernetes.io/service-account-token、アノテーションkubernetes.io/service-account.nameの値はreport-saです。さらに、Podtoken-app(イメージnginx:1.27-alpine、serviceAccountNamereport-sa)を作成し、トークンはprojectedボリュームで受け取ってください。ボリューム名はapi-token、serviceAccountTokenソースのaudienceはapi、expirationSecondsは3600、pathはtokenです。
手動のトークンSecretは、タイプがkubernetes.io/service-account-tokenで、kubernetes.io/service-account.nameアノテーションで持ち主を指定します。もう一方は、PodのprojectedボリュームにserviceAccountTokenソースを置き、audienceと有効期限を付けます。
default ServiceAccountを無力化する
cks-rbacのdefaultServiceAccountにautomountServiceAccountToken: falseを設定し、defaultのSAがこのネームスペースでpodsをlistできないこと(no)を、/root/cks-rbac-hardening/default-sa-can-i.txtに1行で保存してください。
kubectl patch serviceaccount default -n <네임스페이스> -p '...'(プレースホルダーはネームスペースです)で、1行で終わります。そのあと、defaultのSAが本当に何の権限も持たないことをcan-iで確認してください。