はじめに:なぜ静的解析だけではKubernetesを守れないのか
セキュリティチームの諸君、日々のデプロイと運用、本当にお疲れ様。
「CI/CDパイプラインにlinterを入れて、マニフェストのセキュリティチェックは自動化しているから万全だ」――もしチームの誰かがそう言っていたら、私はそっと肩を叩いてこう問いかけるだろう。
「じゃあ、開発者が一時的なデバッグ目的で、本番環境のPodを kubectl edit で直接書き換えて、特権(privileged: true)を付与してしまったらどう防ぐ?」
ペネトレーションテスト(侵入テスト)やレッドチームの活動において、私たちが真っ先に狙うのはこうした「CI/CDの監視をバイパスした直接的な操作」や「設定のドリフト(乖離)」だ。GitOpsを導入していても、APIサーバーに直接届く不正なリクエストをリアルタイムで検知・遮断できなければ、クラスタの防壁は容易に突破される。
そこで登場するのが、KubernetesのAdmission Controller(アドミッションコントローラー)だ。今回は、Kubernetesセキュリティの「最後の砦」として機能するAdmission Controllerについて、攻撃者が狙うシナリオを紐解きながら、強力なポリシーエンジンである Kyverno を用いた具体的な防御設定を解説する。
—
1. 攻撃者のシナリオ:設定不備を突いた「コンテナエスケープ」の実態
まずは、ペネテストで私たちがよく使う、防御が甘いクラスタを瞬時に掌握する「邪悪なPod」のプロトタイプを見てみよう。
もし、クラスタのAdmission制御が無効、あるいはバイパス可能な状態であれば、攻撃者は以下のようなマニフェストを送り込んでくる。
【危険な設定例】ホストを掌握する特権Podのマニフェスト (attack-pod.yaml)
apiVersion: v1
kind: Pod
metadata:
name: malicious-escape-pod
namespace: default
spec:
containers:
- name: exploit-container
image: alpine:latest
command: ["/bin/sh", "-c", "sleep 3600"]
securityContext:
# 1. 特権モードの有効化(ホストの全デバイスへのアクセスを許可)
privileged: true
# 2. ホストのPID名前空間を共有(ホスト上の全プロセスを監視・操作可能に)
hostPID: true
# 3. ホストのルートディレクトリをコンテナ内にマウント
volumes:
- name: host-root
hostPath:
path: /
volumeMounts:
- name: host-root
mountPath: /host
この設定がもたらす致命的なリスク
1. privileged: true:
これを許可すると、コンテナ内の root ユーザーは、ホストマシン(Node)上の実際の root とほぼ同等の権限を持つ。カーネルモジュールのロードや物理デバイスへの直接アクセスが可能になる。
2. hostPID: true:
コンテナの中から、ホスト上で動いている他のすべてのプロセス(kubeletやコンテナランタイムなど)のメモリやシグナルを操作できるようになる。
3. hostPath で / をマウント:
ホストのファイルシステム全体をコンテナの /host にマウントしている。これにより、攻撃者は /host/etc/shadow を書き換えてホストに新しいユーザーを追加したり、ホストの cron にリバースシェルを仕込んだりして、コンテナエスケープ(ノードの完全奪取)を完了させる。
どれだけアプリケーションコードがセキュアでも、この1枚のYAMLのデプロイを許した時点で、クラスタ全体、ひいてはクラウドインフラ全体の支配権が攻撃者に渡る。
—
2. 救世主:Admission Controllerによる「ゲートキーピング」
この脅威をAPIサーバーの入り口で一網打尽にするのが、MutatingAdmissionWebhook と ValidatingAdmissionWebhook だ。
[kubectl / API Request] ──> [Mutating Webhook] (設定の自動補正/注入)
│
▼
[Schema Validation]
│
▼
[Validating Webhook] (ポリシー検証:ここで拒否!)
│
▼
[etcd (保存)]
今回は、ポリシーをGo言語やRegoといった特殊な言語で書く必要がなく、KubernetesネイティブなYAMLで直感的に記述できる Kyverno(キベルノ) を採用し、上記の攻撃マニフェストを完全に無効化する設定を構築する。
—
3. コピペで防ぐ:セキュアなKyvernoポリシー実装サンプル
それでは、実務でそのまま使える堅牢なKyvernoポリシーを定義しよう。以下の2つのポリシーをクラスタに適用することで、先ほどの攻撃はAPIサーバーの段階で即座に拒否(Reject)されるようになる。
① 特権コンテナ(privileged)の起動を完全に禁止するポリシー
このポリシーは、Pod内のすべてのコンテナ(initContainers や ephemeralContainers を含む)において、securityContext.privileged が true に設定されているリクエストをブロックする。
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: block-privileged-containers
annotations:
policies.kyverno.io/title: Disallow Privileged Containers
policies.kyverno.io/category: Pod Security Standards (Baseline/Restricted)
policies.kyverno.io/severity: medium
policies.kyverno.io/description: >-
特権コンテナの起動を禁止します。特権コンテナはホストへの直接アクセスを可能にし、
コンテナエスケープの踏み台にされるリスクが極めて高い設定です。
spec:
# 'enforce' にすることで、違反するPodのデプロイを即座にブロックする('audit'は検知のみ)
validationFailureAction: enforce
background: true
rules:
- name: check-privileged
match:
any:
- resources:
kinds:
- Pod
validate:
message: "セキュリティポリシー違反: 特権コンテナ(privileged: true)の起動は許可されていません。"
pattern:
spec:
=(ephemeralContainers):
- securityContext:
# privileged が存在する場合、false でなければならない
=(privileged): false
=(initContainers):
- securityContext:
=(privileged): false
containers:
- securityContext:
=(privileged): false
② ホストパス(hostPath)のマウントを制限するポリシー
次に、ホストのファイルシステムにアクセスするための hostPath ボリュームの使用を制限する。どうしても必要なシステム系Pod(ログ収集エージェントなど)を除き、一般のデプロイメントからはマウントできないようにする。
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-host-path
annotations:
policies.kyverno.io/title: Restrict HostPath Volumes
policies.kyverno.io/category: Pod Security Standards (Baseline/Restricted)
policies.kyverno.io/severity: high
policies.kyverno.io/description: >-
hostPathボリュームの使用を制限します。攻撃者がノードのファイルシステムをマウントし、
機密データへのアクセスやシステム改ざんを行うのを防ぎます。
spec:
validationFailureAction: enforce
background: true
rules:
- name: check-host-path
match:
any:
- resources:
kinds:
- Pod
exclude:
# kube-system などのシステム系Namespaceや、特定のシステムコンポーネントを例外とする場合ここに記述
any:
- resources:
namespaces:
- kube-system
validate:
message: "セキュリティポリシー違反: hostPath ボリュームの使用は許可されていません。"
pattern:
spec:
# volumes 配下に hostPath フィールドが存在すること自体を禁止する
# キーの前に「X」をつけることで、そのキーの存在を不許可とするKyvernoの記述法
=(volumes):
- X(hostPath): "null"
—
4. 現場での導入プロセス:いきなり「Enforce」にしてはいけない
これらのポリシーは強力だ。強力すぎるがゆえに、本番環境でいきなり validationFailureAction: enforce として適用すると、既存の「実は必要だった」インフラ管理系のコンポーネント(DatadogやFluentbitなどのデーモンセット)が再起動時に起動できなくなり、サービスに深刻な影響を与える可能性がある。
実務で安全に導入するためのステップをアドバイスしておこう。
ステップ1:まずは audit モードで様子を見る
定義ファイルの validationFailureAction を audit に設定してデプロイする。
spec:
validationFailureAction: audit
この状態であれば、ポリシー違反があってもPodのデプロイは拒否されない。代わりに、ポリシー違反のログが生成され、Kubernetes上の PolicyReport カスタムリソースに記録される。
ステップ2:違反している「正当なコンポーネント」を洗い出す
以下のコマンドを実行して、現在どのPodがポリシーに違反しているかを確認する。
kubectl get policyreports -A
ログ収集や監視ツールなど、運用上どうしても特権が必要なPodが見つかった場合は、ポリシーの exclude ブロックに対象の namespace や serviceAccount を追加して、例外ルールを定義する。
ステップ3:段階的に enforce へ移行する
例外処理が完了し、本番環境での「誤検知(False Positive)」がゼロになったことを確認できたら、いよいよ enforce に書き換えてポリシーを適用する。
—
おわりに:セキュリティは「多層防御」
Admission Controllerによる制御は、コンテナが起動する「最後のゲート」を閉じる極めて効果的な手段だ。しかし、これだけで満足してはいけない。
- CI/CDでの静的解析(TrivyやKube-linter)による早期フィードバック
- Admission Controller(KyvernoやGatekeeper)による実行時(デプロイ時)の強制
- ランタイムセキュリティ(Falcoなど)による、起動後のコンテナ内での不審な挙動の検知
これらを組み合わせることで初めて、モダンなシステムにふさわしい「壊れないインフラ」が完成する。
チームのみんなが安全かつ迅速にデプロイを行えるよう、まずは開発環境でこのKyvernoポリシーを試してみてほしい。何かわからないことがあれば、いつでも私の席に聞きに来てくれ。堅牢なシステムを共に作っていこう!
コメント