🔓 Kubernetes etcdの3つの致命的な弱点 — あなたのSecretは本当に安全ですか?

Secretという名前に反して、Kubernetesの機密データ管理は穴だらけです。

>

##

🎯 この記事で扱うこと

  • etcdがSecretをデフォルトで暗号化せずに平文(Base64)で保存する理由と危険性
  • Podを作成できる権限一つでSecret RBAC制御全体を迂回する攻撃経路
  • PodがSecretを毎回読み込んでも監査ログ(Audit Log)が事実上空白である構造的理由
  • 各脆弱性に対する実質的な対応方法

📌 導入 — etcdはKubernetesの心臓でありアキレス腱

Kubernetesクラスターのすべての状態情報は、etcdという分散キーバリューストアに格納されます。Deployment情報、ConfigMap、そして今日注目するSecretまで、すべてここにあります。

etcdを奪取すれば、クラスター全体を手に入れたも同然です。しかし、この重要なストレージには3つの構造的な脆弱性が存在します。単純な設定ミスではなく、設計上の決定と運用上の利便性が生んだ構造的な問題です。


🔍 脆弱性1 — etcdの暗号化、なぜデフォルトではないのか?

問題の核心

Kubernetes Secretの値は、デフォルトでBase64でエンコードされ、暗号化なしにetcdに保存されます。Base64は暗号化ではありません。単に別の形式に変換しただけです。echo “bXlwYXNzd29yZA==” | base64 -d の一行で原文が得られます。

etcdに直接アクセスできる人なら誰でも、このデータを簡単に復号して読み取ることができます。これが、保存時の暗号化を必ず有効にする必要がある理由です。

なぜデフォルトではないのか?

Kubernetesが暗号化をデフォルトで有効にしないのには、現実的な理由があります。

① キー管理の複雑性: 暗号化キーをどこに保存するのか?ローカルキーでSecretデータを暗号化すればetcdの奪取には対応できますが、ホスト自体が侵害された場合には保護されません。暗号化キーがEncryptionConfiguration YAMLファイルに保存されており、腕の立つ攻撃者であればそのファイルにアクセスできるためです。

② 既存データ移行: 暗号化を有効にしても、すでに保存されているSecretは自動的に暗号化されません。既存のSecretをすべて再度書き込む必要があります。

③ パフォーマンスオーバーヘッド: すべての読み書きに暗号化/復号が追加されます。

現在提供されている暗号化方式

Kubernetesは複数の暗号化プロバイダーを提供しています。identityはデフォルトで暗号化を全く行いません。AES-CBCはパディングオラクル脆弱性のためもはや安全ではなく、キーをコントロールプレーンノードに保存するためホスト侵害には無力です。AES-GCMはAES-CBCよりも改善されていますが、やはりキーがホストにあるという限界があります。

正しい対応

# /etc/kubernetes/enc.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - kms:               # ✅ 本番環境推奨: KMS連携によるキーの外部管理
          name: myKmsPlugin
          endpoint: unix:///tmp/socketfile.sock
          apiVersion: v2
      - identity: {}       # fallback — 復号専用としてのみ使用

kube-apiserver設定の追加:

# /etc/kubernetes/manifests/kube-apiserver.yaml
spec:
  containers:
  - command:
    - kube-apiserver
    - --encryption-provider-config=/etc/kubernetes/enc.yaml  # この行を追加

プロダクション環境では、AWS KMS、GCP KMS、Azure Key Vaultなどの外部KMSと連携し、キー自体をクラスター外部で管理するのが正解です。


🔍 脆弱性2 — Pod作成権限でSecret RBACを迂回する

最も巧妙な脆弱性

これが3つの中で最も巧妙です。あるユーザーがsecretsリソースに対するget権限を持っていなくても、Podを作成できる権限があればSecretにアクセスできます。

RBACシステムは、権限のあるユーザーのみがPodを作成するように強制しますが、そのユーザーがPod定義の中に何を入れるかは制御しません。適切なAdmission Controlがなければ、Pod作成権限を持つユーザーは任意のイメージと任意の権限を持つコンテナを作成できます。

攻撃シナリオ3つ

シナリオA: 環境変数でSecretを抜き出す

# Secretのget権限がなくても、このようにPodを作成すると...
apiVersion: v1
kind: Pod
metadata:
  name: secret-stealer
spec:
  containers:
  - name: attacker
    image: busybox
    command: ["sh", "-c", "echo $DB_PASSWORD && sleep 3600"]
    env:
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-credentials  # アクセスしたいSecret
          key: password

Podログを見ると、DB_PASSWORDの値がそのまま出力されます。

シナリオB: Service Accountトークン奪取

Podが高い権限を持つService Accountのトークンを奪取するように設計された悪性イメージを使用するPodを作成できます。攻撃者はこのトークンを利用してAPIアクションを実行し、権限を昇格させることができます。

# Pod内部でマウントされたトークンを確認
cat /var/run/secrets/kubernetes.io/serviceaccount/token
# このトークンでAPIサーバーにリクエスト
curl -H "Authorization: Bearer $(cat /var/run/secrets/...)" https://k8s-api/api/v1/secrets

シナリオC: hostPathマウントでetcdに直接アクセス

ノードセレクターを使用してコントロールプレーンノードにPodをスケジュールし、ホストファイルシステムをマウントできる場合、そのノードにchrootしてルート権限を獲得し、etcdデータベースを直接読み取ることができます。

対応方法

# Admission Controllerで特権Podをブロック
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
# またはPod Security Admissionを使用
---
# namespaceにPSAラベルを適用
kubectl label namespace production 
  pod-security.kubernetes.io/enforce=restricted
  pod-security.kubernetes.io/enforce-version=latest

核心原則:

  • automountServiceAccountToken: false をデフォルトで設定
  • hostPath, hostPID, hostNetwork マウント禁止ポリシー
  • Admission Controller(OPA Gatekeeper, Kyverno)でイメージの出所および権限を検証

🔍 脆弱性3 — Secretアクセス監査ログの構造的空白

この部分が核心です。PodがマウントされたSecretを読み込むたびに監査ログが記録されません。なぜ?

Kubernetes監査ログの動作方式

Kubernetes監査ログはkube-apiserverコンポーネント内部で開始されます。APIサーバーに対する各リクエスト(request)ごとに監査イベントが生成されます。

ここに核心的な問題があります。監査ログはAPIサーバーを介したリクエストに対してのみ記録されます。

SecretがPodに渡される方式

[파드 생성 시]
API Server → kubelet → 파드 볼륨 마운트
              ↑
        이 시점에 1번만 Secret GET 요청 발생 → 로그 기록됨

[파드 실행 중]
파드 컨테이너 → /var/run/secrets/... 파일 직접 읽기
                ↑
        이건 그냥 파일시스템 read() 시스템 콜
        → API 서버를 거치지 않음 → 감사 로그 없음

Secretが環境変数として注入された場合も同様です。コンテナランタイムがPod起動時に一度値を取得して環境変数として設定し、その後のアクセスはすべてプロセスレベルで発生します。APIサーバーは無関係です。

KubernetesはSecretアクセスに関する詳細な監査ログをデフォルトで提供しません。これにより、誰がいつSecretにアクセスしたかを監視することが困難になります。

なぜこれを「放置」するのか?

設計の観点から見ると、意図されたトレードオフに近いものです:

  1. パフォーマンス: Secretをファイルとしてマウントすれば、コンテナが数百回読み込んでもAPIサーバーの負荷はありません。毎回API呼び出しで処理すると、クラスター全体のパフォーマンスが急落します。
  2. ボリュームマウントのアーキテクチャ的限界: kubeletはSecretをtmpfsにマウントします。その後のアクセスはOSカーネルレベルのファイルI/Oです。Kubernetesがこれを傍受する方法はありません。
  3. 監査ログのデフォルト無効化: デフォルトではKubernetesクラスターは監査ログが有効になっておらず、監査ポリシーを作成しkube-apiserver設定に明示する必要があります。

監査ログ設定 + 限界認識

# /etc/kubernetes/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
  - "RequestReceived"
rules:
  # Secret APIリクエストはMetadataレベルで記録 (値の露出防止)
  - level: Metadata
    resources:
      - group: ""
        resources: ["secrets"]
  # Podの作成/削除は追跡
  - level: RequestResponse
    verbs: ["create", "delete", "patch"]
    resources:
      - group: ""
        resources: ["pods"]

⚠️ 注意: RequestResponseレベルでSecretをロギングすると、Secret値自体が監査ログに記録されます。監査ログが別の情報漏洩経路となります。Metadataレベルが現実的な妥協案です。

真の解決策 — ランタイム監査

PodのSecretファイルアクセスまで検知するには、OSレベルのツールが必要です。

# Falcoルール例 — /var/run/secretsへのアクセスを検出
- rule: Read Kubernetes Service Account Token
  desc: Detect any read of a Kubernetes service account token
  condition: >
    open_read
    and fd.name startswith /var/run/secrets/kubernetes.io/serviceaccount
    and not proc.name in (known_sa_readers)
  output: >
    "Service account token read (user=%user.name command=%proc.cmdline
    file=%fd.name container=%container.name)"
  priority: WARNING

FalcoTetragoneBPFベースのランタイムセキュリティツールがこの空白を埋めます。


⚠️ 注意事項 / よくある間違い

  • AES-CBCはもはや安全ではありません — 必ずKMSまたはAES-GCMを使用してください
  • 監査ログにSecret RequestResponseレベル設定を禁止 — ログにSecret値がそのまま記録されます
  • pods/exec権限もSecret露出経路です — execでコンテナに侵入するとマウントされたSecretファイルにアクセス可能。この経路はKubelet APIを直接使用するためAPIサーバーを迂回し、監査ログすら残りません。
  • 暗号化キーを誤って削除するとクラスターの復旧が不可能になります。キー管理はVaultやクラウドKMSに委任してください。

✅ まとめ — 3つの脆弱性対応要約

脆弱性 核心問題 対応
1. 暗号化未適用 Base64のみ適用、暗号化なし KMS連携 + EncryptionConfiguration
2. Pod作成で迂回 RBACがPodコンテンツを制御できない PSA Restricted + OPA Gatekeeper
3. 監査空白 APIサーバー迂回ファイルアクセスは未記録 Falco/Tetragon eBPFランタイム監視

3つの脆弱性すべてが「機能欠陥」ではなく、パフォーマンス・柔軟性とセキュリティ間のトレードオフから生じています。これを理解することで、正しい対応が可能になります。

次のステップとしては、HashiCorp VaultのDynamic SecretまたはKubernetes Secrets Store CSI Driverを通じてSecretをetcdから完全に分離するアーキテクチャを検討することをお勧めします。


Comments

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です