Kubernetes要塞化の急所:API ServerのRBAC最小権限とServiceAccountトークンの深層防御
数多くのKubernetesクラスタのペネトレーションテストやインシデントレスポンスに関わってきた中で、攻撃者が侵入後に真っ先に狙う「旨味のある標的」が何か知っているだろうか? それはアプリケーションの脆弱性そのものではない。多くの場合、コンテナの脱出(Container Escape)を経て、あるいはアプリのSSRF(Server-Side Request Forgery)の踏み台を踏み破った先で手に入りやすい、Kubernetes API Serverの認証トークンである。
「たかがPod内のサービスアカウントトークン」と侮っているとしたら、あなたのクラスタはすでに内部犯行やサプライチェーン攻撃に対して無防備と言っていい。デフォルトで全Podにマウントされる強力なトークンと、緩すぎるClusterRoleBindingの組み合わせは、攻撃者にクラスタ全体の管理者権限(Kubeletの乗っ取り、全シークレットの窃取、さらには他名前空間への横方向展開:Lateral Movement)をいとも簡単にプレゼントしてしまう。
今回は、最高峰のセキュリティアーキテクトの視点から、Kubernetes API Serverの要塞化における「RBACの最小権限原則」と「ServiceAccountトークン管理」の泥臭い実務、そして攻撃者の視点から見たその盲点を徹底的に解剖する。
—
1. 攻撃者の視点:なぜServiceAccountとRBACが狙われるのか
Kubernetesのコントロールプレーンの中核であるAPI Serverは、すべてのクラスタ内通信のハブであり、認証(Authentication)、認可(Authorization)、アドミッションコントロール(Admission Control)という厳重な関所を持っている。
しかし、現場のインフラエンジニアが利便性を優先するあまり、以下の「3つの大罪」を犯しているケースを後を絶たない。
1. 過剰な権限付与(Over-privileged Roles): アプリケーションが必要としていないにもかかわらず、cluster-adminや広範なリソース(*)にアクセスできるClusterRoleがバインドされている。
2. 自動マウントの放置(Automatic Token Mounting): Pod内のコンテナがAPI Serverと通信する必要がないにもかかわらず、デフォルトで/var/run/secrets/kubernetes.io/serviceaccount/tokenにServiceAccountトークンが自動配置されている。
3. レガシーなLong-lived Token: 期限切れのない静的なServiceAccountトークンが生成され、CI/CDやサードパーティ製ツールにハードコードされている。
攻撃者は、コンテナ内のファイルシステムから平文のJWT(JSON Web Token)であるServiceAccountトークンを拾い上げ、curlやkubectlを用いてAPI Serverへ直接リクエストを投げる。この際、もしRBACの設定が緩ければ、API Serverはそれを正当な権限を持つ要求として処理し、クラスタの主導権が渡ってしまうのだ。
—
2. 対策の核心:ServiceAccountトークンの自動マウント無効化
まずは、不要な露出を極限まで減らす「ゼロ・トラスト」の原則をPodとServiceAccountに適用する。すべてのPodにトークンを配るというKubernetesのレガシーなデフォルト挙動は、今すぐモダンな設定に上書きすべきだ。
ServiceAccount自体の自動マウント禁止
特定のServiceAccountに対して、トークンの自動マウントをデフォルトで禁止する。これにより、このServiceAccountを使用するPodは、明示的に要求しない限りトークンを持たなくなる。
apiVersion: v1
kind: ServiceAccount
metadata:
name: secure-app-sa
namespace: production
# ServiceAccountレベルでトークンの自動マウントを拒否する
automountServiceAccountToken: false
Pod仕様での個別制御
もし特定のPodだけがAPI Serverと通信する必要がある場合でも、ServiceAccount全体ではなく、Podの仕様(PodSpec)において個別に制御するか、あるいはServiceAccountトークン投影(TokenRequest APIを用いた短寿命トークン)を利用すべきだ。
以下は、トークンのマウントを完全に排除しつつ、必要な最小限のセキュリティコンテキストを付与したPodの模範的なマニフェストである。
apiVersion: v1
kind: Pod
metadata:
name: hardened-api-pod
namespace: production
spec:
# このPodに関連づけるサービスアカウント
serviceAccountName: secure-app-sa
# Podレベルでも明示的にトークンの自動マウントを無効化
automountServiceAccountToken: false
containers:
- name: app-container
image: my-secure-app:v1.0.0
securityContext:
# 読み取り専用のルートファイルシステムにし、コンテナ改ざんを防ぐ
readOnlyRootFilesystem: true
# 特権コンテナとしての実行を厳禁
privileged: false
# rootユーザーでの実行を禁止し、非特権ユーザーで動かす
runAsNonRoot: true
runAsUser: 10001
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumeMounts:
# 一時的な書き込み領域としてのみemptyDirを使用
- mountPath: /tmp
name: tmp-dir
volumes:
- name: tmp-dir
emptyDir: {}
—
3. RBACの最小権限原則(PoLP)の実装と監査
「とりあえず動くようにする」という理由で、verbs: ["*"]やresources: ["*"]を指定したRoleやClusterRoleを書くのは、セキュリティエンジニアの前では万死に値する行為だ。
本当に必要なリソース、必要な動詞(get, list, watch, create, update, patch, delete)だけに絞り込み、さらに影響範囲をクラスタ全体ではなく特定の名前空間(RoleとRoleBinding)に限定しなければならない。
悪い例(アンチパターン)
以下の設定は、production名前空間において、このサービスアカウントに事実上の管理者権限を与えている。これではコンテナが乗っ取られた瞬間にゲームオーバーだ。
# 【絶対にやってはいけない例】
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: dangerous-binding
subjects:
- kind: ServiceAccount
name: secure-app-sa
namespace: production
roleRef:
kind: ClusterRole
name: cluster-admin # クラスタ全体の全権限を与えている
apiGroup: rbac.authorization.k8s.io
正しい例:最小権限に基づくRoleとRoleBinding
アプリケーションが自身の名前空間内で特定のConfigMapの読み取りと、特定のPodsの状態確認しか必要としない場合の厳格な定義は以下のようになる。
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: app-restricted-role
rules:
- apiGroups: [""]
resources: ["configmaps"]
# 変更系や削除系の権限は与えず、取得と監視のみに限定する
resourceNames: ["my-app-config"] # 特定のConfigMap名にまで絞るのが理想
verbs: ["get", "watch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-restricted-binding
namespace: production
subjects:
- kind: ServiceAccount
name: secure-app-sa
namespace: production
roleRef:
kind: Role
name: app-restricted-role
apiGroup: rbac.authorization.k8s.io
—
4. 動的なトークン発行とAuditing(監査)の極意
モダンなKubernetes(v1.22以降)では、従来の永続的なServiceAccountシークレットはデフォルトで自動生成されなくなった。その代わり、TokenRequest APIを用いて、有効期限付きの短寿命JWT(Bound ServiceAccount Tokens)を動的に発行・投影(Projected Volume)する仕組みが標準となっている。
もし古いクラスタを運用しており、シークレットとして永続トークンがばらまかれている場合は、以下の手順で直ちに棚卸しと移行を行うべきだ。
1. 権限の過剰付与を検出するスクリプト・ツールの活用
手動での監査には限界があるため、オープンソースのセキュリティ監査ツールをCI/CDパイプラインや定期バッチに組み込むことが不可欠だ。
- Kube-bench: CIS Benchmarkに基づくKubernetes設定の自動監査
- Trivy / Checkov: マニフェストファイル(IaC)の静的解析によるRBACの過剰権限検知
- Kubiscan: クラスタ内の危険な権限(
cluster-admin, 特権昇格パスなど)をスキャンする専用ツール
例えば、Kubiscanを用いて危険なバインドをあぶり出すコマンドは以下のようになる。
# クラスタ内で過剰な権限を持つロールバインドをスキャンする例(Kubiscan使用)
python3 kubiscan.py -p
2. API Serverの監査ログ(Audit Logging)の有効化
攻撃者が不正なAPIリクエストを投げた痕跡を追うため、API Serverの監査ログを必ず有効化し、適切なポリシーを設定する。特に、resourceAccessやrequestResponseBodyのレベルを適切に調整し、誰が・どのIPから・どのServiceAccountで・どのリソースにアクセスしたかをSIEM(SplunkやDatadog、Elasticsearch等)に転送して監視体制を構築する。
以下は、API Server起動時の監査ポリシー設定の断片例である。
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# SecretやConfigMapへのアクセスは詳細にログを残す
- level: RequestResponse
resources:
- group: ""
resources: ["secrets", "configmaps"]
# 通常の静的な読み取りリクエストはメタデータのみにしてログ肥大化を防ぐ
- level: Metadata
resources:
- group: ""
resources: ["pods", "services"]
—
5. まとめ:セキュリティは「設定の引き算」から始まる
インフラストラクチャのセキュリティにおいて、新しいツールを導入することよりも、「不要なものを削ぎ落とすこと(引き算)」の方が圧倒的に価値が高い。
Kubernetes API Serverの要塞化も全く同じだ。
- すべてのPodに自動マウントされるトークンを無効化する。
cluster-adminやワイルドカード(*)を排除し、厳格な最小権限のRole/RoleBindingを設計する。- 長寿命の静的トークンを廃し、短寿命の投影トークンへ移行する。
この地道で泥臭いチューニングの積み重ねこそが、ゼロ・トラストアーキテクチャの土台となり、最悪のインシデントが発生した際の手痛い被害を最小限に食い止める防壁となる。
プロフェッショナルとしてのプライドを持ち、今すぐあなたのクラスタのRBAC設定とServiceAccountのあり方を再点検してほしい。
コメント