KubernetesのPodセキュリティ:なぜ「デフォルト設定」があなたのクラスターを沈没させるのか
現場で数々のインシデントを見てきたが、悲しいかな、多くのエンジニアが「動けばいい」という甘い誘惑に負け、KubernetesのPodをデフォルト設定のまま野放しにしている。
いいか、KubernetesにおけるPodとは、単なるコンテナの集まりではない。「クラスターという巨大な要塞の壁に開けられた、無数の穴」だ。もし君が securityContext を適切に設定せず、特権コンテナを許可しているなら、それは玄関の鍵を開けっ放しにして、泥棒に「どうぞお宝を持って行ってください」と言っているのと同じだ。
今日は、Kubernetesの標準機能である「Pod Security Admission (PSA)」を使い、泥臭い戦場でも通用する堅牢な要塞化手法を伝授する。
—
1. 攻撃者の視点:Podはこうして乗っ取られる
攻撃者は、アプリケーションの脆弱性(例えば、未修正のライブラリやWebアプリのRCE)を突いてPod内に侵入する。その時、Podがprivileged: trueで実行されていたらどうなるか?
攻撃者はホストの /var/run/docker.sock や /var/lib/kubelet/ を参照し、ホストOSを直接操作したり、他のPodへ横展開(ラテラルムーブメント)を開始する。これがいわゆる「コンテナエスケープ」だ。一度ホストを掌握されれば、そのノード上の全データ、全シークレットは攻撃者のものになる。
これを防ぐ唯一の防御策が、「最小権限の原則」を強制するPSAの導入だ。
—
2. Pod Security Admission (PSA) の実装
PSAはKubernetes v1.25から標準搭載されたゲートキーパーだ。導入には「ポリシーをどの名前空間に、どのレベルで適用するか」を決めるラベルを付与するだけ。
ステップ1:名前空間へのラベル付け
まずは、本番環境の production 名前空間を restricted(制限)モードで固めよう。
# production名前空間に、最も厳しい「restricted」ポリシーを適用し、違反時はログを出力させる設定
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted
これで、この名前空間で「特権コンテナ」や「ルートユーザーでの実行」を伴うPodを作成しようとすると、APIサーバーが即座に拒否するようになる。
—
3. 実践:セキュアなPodマニフェストのテンプレート
後輩たちによく見せる「これが通るなら合格」というセキュアなPod定義だ。securityContext をここまで絞り込むのが、プロのエンジニアの流儀だ。
apiVersion: v1
kind: Pod
metadata:
name: secure-app
namespace: production
spec:
containers:
- name: web-app
image: my-app:latest
securityContext:
# ルート権限での実行を禁止
runAsNonRoot: true
# 特定の非特権ユーザーIDで実行
runAsUser: 1000
# 権限昇格を一切許可しない
allowPrivilegeEscalation: false
# 必要最小限のLinux能力(Capability)のみを保持
capabilities:
drop:
- ALL
# 書き込み禁止ルートファイルシステム
readOnlyRootFilesystem: true
# 一時的な書き込み領域が必要な場合のみメモリを指定
volumeMounts:
- name: tmp-volume
mountPath: /tmp
volumes:
- name: tmp-volume
emptyDir:
medium: Memory
なぜこの設定なのか?
runAsNonRoot: true: 攻撃者がコンテナ内でrootになっても、ホスト上のroot権限とは切り離される。allowPrivilegeEscalation: false:setuidビットを利用した権限昇格攻撃を無効化する。drop: ALL: 不要なシステムコールを叩かせない。これが最強の防壁だ。
—
4. 運用上の注意:泥臭いハンドリング
PSAを導入すると、既存のアプリが「動かなくなる」という悲鳴が上がるはずだ。特に readOnlyRootFilesystem を有効にすると、PHPやPythonのフレームワークがキャッシュ書き込みに失敗してエラーを吐く。
その場合の修正ヒントを記しておく。
Python (Flask/Django) の場合:
/tmp ディレクトリ以外の書き込みが必要なパスは、全て emptyDir ボリュームをマウントして解決する。コード側でログ出力先を /tmp に変更するのを忘れるな。
アプリケーションのログ出力設定例 (Python):
import logging
import os
# 書き込み可能な唯一の場所、/tmp にログを出す
log_path = os.getenv("LOG_FILE", "/tmp/app.log")
logging.basicConfig(filename=log_path, level=logging.INFO)
—
最後に:セキュリティは「妥協」の反対側にある
「利便性」を優先してポリシーを緩めることは、セキュリティチーフとしては断じて認められない。もし開発者が「動かない!」と嘆いていたら、それは設計の不備を修正する絶好のチャンスだ。
PSAは、君たちがコードを書く際に「どうすれば権限を絞れるか」を常に意識させるための強力な規律となる。最初から完璧を目指せとは言わないが、少なくとも privileged: true を安易に使うような設計だけは、今すぐやめるべきだ。
明日からは、クラスターの各名前空間に restricted ラベルが貼られているか、確認することから始めてほしい。それが、君たちのクラスターを「ただのサーバー群」から「要塞」に変える第一歩だ。
コメント