Kubernetesの「デフォルト」を信じるな:API Serverを狙う権限昇格を防ぐ実戦的RBAC管理
現場でインシデント対応をしていると、溜息が出るほど多いのが「とりあえず動けばいい」という理由で、Podにcluster-admin権限を付与したり、defaultのServiceAccountをそのまま使い回したりしているケースだ。
KubernetesのAPI Serverは、クラスタの心臓部だ。ここに攻撃者が触れれば、コンテナの脱獄(Breakout)は時間の問題となる。今回は、現場の泥臭い経験から、Kubernetesのセキュリティを「守り」から「攻め」の視点で要塞化するための勘所を叩き込む。
—
なぜAPI Serverが狙われるのか?(PoC的な視点)
攻撃者がPodへの侵入に成功したとき、真っ先に確認するのが /var/run/secrets/kubernetes.io/serviceaccount/token だ。
もし、このトークンに不適切な権限が付与されていたらどうなるか? 攻撃者はそのトークンを使い、API Serverに対して以下のようなクエリを投げる。
# 攻撃者がPod内で実行するクエリのイメージ
# クラスタ内の全シークレットを列挙する
curl -k -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
https://kubernetes.default.svc/api/v1/namespaces/default/secrets
これが通ってしまった瞬間、DBの接続情報やクラウドのクレデンシャルが盗まれ、被害はコンテナの枠を超えてクラウドインフラ全体へ波及する。これを防ぐには、「不要なものは持たせない」「必要なものだけを、短期間だけ貸し出す」という鉄則を徹底するしかない。
—
1. 自動マウントされるトークンの無効化
まず、多くの現場で放置されているのが「全Podに対するServiceAccountトークンの自動マウント」だ。これ、実はほとんどのPodで不要だ。
Podの定義(YAML)で、明示的にマウントを拒否するように設定しよう。
# deployment.yaml の設定例
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-app
spec:
template:
spec:
# トークンの自動マウントを無効化する(これが基本!)
automountServiceAccountToken: false
containers:
- name: app
image: my-app:latest
これだけで、Podが侵入されてもAPI Serverへの踏み台として利用されるリスクを劇的に下げられる。
—
2. 最小権限を実現するRBAC設計
どうしてもAPI Serverと通信が必要なアプリがある場合は、専用のServiceAccountを作成し、RoleBindingで極小の権限を与える。ClusterRoleは強力すぎる。原則としてRoleでスコープを限定せよ。
以下は、「特定の名前空間で、特定のPodのステータス取得のみを許可する」セキュアな設計例だ。
# service-account.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-limited-sa
namespace: production
---
# role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"] # 最小限の操作のみ
---
# rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods-binding
namespace: production
subjects:
- kind: ServiceAccount
name: app-limited-sa
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
—
3. 実践:PythonでAPIトークンを利用する際のベストプラクティス
もしアプリ側でKubernetes APIを叩く必要がある場合、標準のクライアントライブラリを使うはずだ。この時も、ハードコーディングは厳禁。環境変数やマウントされたトークンを扱う際は、以下の点に注意せよ。
from kubernetes import client, config
def get_pod_list():
# クラスタ内ならロード、外ならkubeconfigを参照するセキュアなロード手法
try:
config.load_incluster_config()
except config.ConfigException:
config.load_kube_config()
v1 = client.CoreV1Api()
# 権限がある範囲内でのみリクエストを投げる
pods = v1.list_namespaced_pod(namespace="production")
return [pod.metadata.name for pod in pods.items]
# このコードを実行するコンテナは、必ず上記で作成した
# ServiceAccount(app-limited-sa) を利用するように設定すること。
—
セキュリティチーフからの「最後の警告」
最後に、運用フェーズで必ずやってほしいことがある。それは「過剰権限の棚卸し」だ。
以下のコマンドで、現在クラスタ内に「権限が強すぎるBinding」がないか、定期的にチェックする習慣をつけてほしい。
# 権限が強すぎるClusterRoleBindingを探索する(手動チェック用)
kubectl get clusterrolebindings -o json | jq '.items[] | select(.roleRef.name=="cluster-admin")'
ここでsystem:serviceaccountが返ってきたら、それは赤信号だ。すぐにそのServiceAccountの用途を調査し、必要なければ削除、必要なら適切なRoleへの付け替えを行うこと。
Kubernetesのセキュリティは「設定して終わり」ではない。攻撃者は常に「設定の隙間」を探している。最小権限原則(Principle of Least Privilege)は、単なる教科書のスローガンではなく、君たちが開発したサービスを死守するための最後の防壁なのだ。
手を動かせ。そして、今日から automountServiceAccountToken: false をデプロイの標準にすること。それが、最強の防御への第一歩だ。
コメント