なぜ「デフォルトのKubernetes」はザルなのか?:Pod Security Admissionで封じ込める特権昇格の脅威
現場で数多のコンテナ環境を見てきたが、「とりあえず動けばいい」という設定でクラスタを運用しているエンジニアほど、インシデント発生時の被害が甚大になる。
KubernetesのPodは、何も設定しなければホストのカーネルに近い権限をいとも簡単に奪取できる。攻撃者は一度コンテナ内に侵入すると、hostPathマウントや特権モード(privileged: true)を悪用し、コンテナの壁を突き破ってホストOSのルート権限を掌握する。これが「コンテナ・エスケープ」の定石だ。
今回は、この悪夢を未然に防ぐための最強の盾、Pod Security Admission (PSA) について、実戦的な実装論を説く。
—
1. そもそもPSAで何を封じるのか?
PSAは、Podが起動する前に「こいつは危険な設定をしていないか?」をチェックする門番だ。以下の3つのプロファイルでPodを格付けする。
- Privileged: 全制限なし。悪意あるPodや、カーネルモジュールを触るような特殊な用途以外では絶対禁止。
- Baseline: 最小限の制限。既存アプリを動かすための妥協点。
- Restricted: 極めて堅牢。読み取り専用ルートファイルシステム、非ルートユーザー実行などを強制する。本番環境のWebアプリはこれを目指すべきだ。
—
2. 現場で使える「セキュアな名前空間」の構築
まず、特定の名前空間(例: web-app-prod)に対して、強制的に restricted プロファイルを適用する。これだけで、不用意な特権昇格を伴うPodは起動すらできなくなる。
以下は、kubectl でラベルを付与してPSAを有効化するコマンドだ。
# 名前空間にラベルを付与し、警告(warn)と強制(enforce)の両方をrestrictedに設定
kubectl label namespace web-app-prod \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest \
pod-security.kubernetes.io/warn=restricted
これだけで、privileged: true を含む Deployment をデプロイしようとすると、APIサーバーが即座に拒絶する。これが「デフォルト拒否(Default Deny)」の強みだ。
—
3. 【実務実装】PSAをパスする「正しい」Pod定義
restricted プロファイルでは、securityContext を厳密に制御する必要がある。以下は、セキュリティチーフが推奨する「合格点」の Deployment サンプルだ。
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-web-app
namespace: web-app-prod
spec:
template:
spec:
securityContext:
# 非ルートユーザー実行を強制
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: app
image: my-secure-app:latest
securityContext:
# 特権昇格を禁止
allowPrivilegeEscalation: false
# ルートファイルシステムを読み取り専用に
readOnlyRootFilesystem: true
# 不要なLinuxケーパビリティを全て剥奪
capabilities:
drop:
- ALL
volumeMounts:
- name: tmp-dir
mountPath: /tmp
volumes:
- name: tmp-dir
emptyDir: {}
なぜこの設定が重要なのか?
allowPrivilegeEscalation: false: これがないと、setuidビットを持つバイナリ経由で攻撃者にルート権限を奪われる可能性がある。readOnlyRootFilesystem: true: 攻撃者がシェルを送り込んでも、バイナリを書き換えたりバックドアを設置したりできなくなる。これが物理的な耐性だ。
—
4. 盲点:コードレベルのセキュリティとの二段構え
PSAでインフラを固めても、アプリケーション側の脆弱性(RCEなど)があれば意味がない。特に公開鍵暗号を用いた通信や、認証基盤の秘匿情報管理は別次元の話だ。
例えば、Pythonで外部APIと通信する場合、requests ライブラリの verify=True を外すような愚行は論外だ。以下のコードのように、暗号学的整合性を担保する。
import requests
import ssl
def secure_request(url):
# TLSのバージョンを固定し、検証を強制する
context = ssl.create_default_context()
context.minimum_version = ssl.TLSVersion.TLSv1_2
try:
# verify=Trueは必須。中間者攻撃を防御する
response = requests.get(url, verify=True, timeout=5)
return response.json()
except requests.exceptions.SSLError:
# ログを吐いて即座に遮断する
print("致命的: SSL検証失敗。通信を中断します。")
raise
—
結びに:セキュリティは「自動化」と「諦め」のバランス
多くのエンジニアは「利便性」を優先してセキュリティ設定を緩めるが、それはインフラに対する冒涜だ。KubernetesのPSAは、開発者に「最初から正しい設定を書かせる」ための強力なナッジ(後押し)になる。
「privileged なPodが動かないと不便だ」という声が聞こえたら、それは設計が間違っているサインだ。コンテナの壁を破らなければ達成できない機能など、Webアプリケーションには存在しない。
今日からあなたのクラスタで pod-security.kubernetes.io/enforce=restricted を設定せよ。それが、システムを「守れるエンジニア」になるための第一歩だ。
コメント