【実務・中級編】 Kubernetes APIサーバーの監査ログ(Audit Log)の設計とSIEM連携 – インフラ・ネットワーク & クラウドセキュリティ防御ガイド

Kubernetesクラスターの運用において、「APIサーバーの監査ログ(Audit Log)」をただ有効化して満足しているとしたら、それはセキュリティ・エンジニアとしてかなり危うい状態だと言わざるを得ない。

「ログはちゃんと取っています。S3に流し込んでます」という現場ほど、いざインシデントが起きたときに「ノイズの海から本当の攻撃を見つけ出せない」「そもそも誰も見ていない」というお決みのパターンに陥る。

今回は、数々のクラウドリフト&シフト現場で散見される「形骸化した監査ログ」を、攻撃者の手口を封じるための「実戦的な早期警戒レーダー」へと生まれ変わらせるための設計と実装について、現場の知見を交えて徹底的に解説しよう。

—

1. 攻撃者がKubernetes APIサーバーを狙う理由とリスク

Kubernetesクラスターの心臓部は、他でもない kube-apiserver だ。ここに対する不正なAPIコールは、いわば「銀行の金庫のマスターキーを勝手に複製する行為」に等しい。

攻撃者が侵入後に最初に狙うのは、過剰な権限を持ったServiceAccountのトークン窃取や、匿名アクセス、あるいは誤設定されたRBACの隙をついた特権昇格だ。例えば、以下のような攻撃シナリオは日常茶飯事である。

  • 秘密情報の窃取: kubectl get secrets --all-namespaces によるDBパスワードやAPIキーの全取得。
  • バックドアの常駐: 特権コンテナ(privileged: true)やホストのプロセス空間を共有するポッドの不正デプロイ。
  • リソースの悪用(クリプトジャッキング): クラッシュリミットを無視した大量のマイニング用ポッドの起動。

これらを防ぐには、ネットワーク境界の防御(WAFやNginxのリバースプロキシ設定など)に加え、「APIサーバーが誰に、何を言われたか」をミリ秒単位で記録し、異常な相関関係をリアルタイムで検知する仕組みが絶対に不可欠なのだ。

—

2. 監査ポリシーの設計とログ出力レベル

Kubernetesの監査ログは、何も考えずに全出力を有効にすると、Get や List といった日常的なノイズでログストレージが破産するか、SIEMのライセンス費用が高騰して経営陣から怒られることになる。

したがって、監査ポリシー(Audit Policy)では「何を捨て、何を残すか」のメリハリが命となる。以下の設計方針をベースにしてほしい。

1. Read系(Get, List, Watch)のノイズ削減: 健康状態チェック(/healthz)や通常のコントローラーの動作は除外するか、最低限のレベル(Metadata)に抑える。
2. Write系(Create, Update, Patch, Delete)の厳格化: 特にSecrets、ClusterRoleBindings、Podsなどの機密・権限に関わるリソースは、リクエストのボディ(RequestResponse レベル)まで完全に記録する。

実践的な監査ポリシー設定ファイル (audit-policy.yaml)

以下に、実務でそのまま使える堅牢な監査ポリシーのサンプルを示す。

apiVersion: audit.k8s.io/v1
kind: Policy
rules:
  # 1. ノイズになりやすいノース・サウスのヘルスチェックやメトリクス収集はログから除外
  - level: None
    resources:
      - group: ""
        resources: ["endpoints", "services", "services/status"]
    nonResourceURLs:
      - "/healthz"
      - "/livez"
      - "/readyz"
      - "/metrics"

  # 2. 認証情報を扱う Secrets や ConfigMaps への変更は、リクエストボディも含めて完全記録
  - level: RequestResponse
    resources:
      - group: ""
        resources: ["secrets", "configmaps", "serviceaccounts"]
    users: ["kubernetes-admin"] # 特に特権ユーザーの動きは厳視する

  # 3. 権限管理(RBAC)に関連する操作はすべて RequestResponse でキャッチ
  - level: RequestResponse
    resources:
      - group: "rbac.authorization.k8s.io"
        resources: ["clusterroles", "clusterrolebindings", "roles", "rolebindings"]

  # 4. ポッドの作成・削除など、ワークロードの変更はリクエストの内容(メタデータ+ボディ)を記録
  - level: Request
    resources:
      - group: ""
        resources: ["pods"]
    verbs: ["create", "update", "patch", "delete"]

  # 5. その他、デフォルトのフォールバックルールとしてメタデータレベルのログを記録
  - level: Metadata
    omitStages:
      - "RequestReceived"

このポリシーファイルをAPIサーバー起動時の引数(--audit-policy-file=/etc/kubernetes/audit-policy.yaml)として指定し、あわせてログのバックエンド出力設定(--audit-log-path, --audit-log-maxage, --audit-log-maxbackup, --audit-log-maxsize)を適切に行うこと。

—

3. SIEM連携と不正APIコールの相関分析パイプライン

ログをファイルに出力しただけでは、セキュリティインシデントは防げない。これをFluentdやFluent Bitなどのフォワーダーを使ってSIEM(Datadog, Splunk, Elastic Cloud等)にリアルタイム転送し、「攻撃の兆候」をあぶり出す必要がある。

現場で狙うべき「危険な相関パターン」

SIEM側で検知すべき典型的な異常検知クエリ(またはアラート条件)の例を挙げよう。

1. 特権コンテナ作成の検知:

  • 条件: verb == "create" かつ リクエストボディに securityContext.privileged == true が含まれている。

2. 不審なIPからの管理者権限アクセス:

  • 条件: 社内VPNや指定踏み台以外のIPアドレスから、ClusterRoleBinding が作成された。

3. 短時間での大量の Secret 読み出し(クレデンシャルハーベスト):

  • 条件: 同一のServiceAccountが、5分以内に50回以上の secrets に対する list または get リクエストを実行した。

—

4. 自動化スクリプト:不正検知イベントのWebhookハンドラー(Python実装)

SIEMに飛ばす手前の軽量なプロキシや、AWS EventBridge / GCP Pub/Subから受け取った監査ログのストリームをリアルタイムで解析し、SlackやPagerDutyに緊急通知を飛ばすためのPythonスクリプトのサンプルを提示する。

実務では、このロジックをAWS LambdaやCloud Functions、あるいはFastAPIサーバーとして実装し、Webhookのエンドポイントとして稼働させることが多い。

import json
import logging
import sys
from typing import Dict, Any

# ロガーの設定
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
logger = logging.getLogger("K8sAuditAnalyzer")

def analyze_audit_event(event: Dict[str, Any]) -> None:
    """
    Kubernetesの監査ログイベントを解析し、危険な操作が含まれていないか判定する。
    """
    verb = event.get("verb")
    user = event.get("user", {}).get("username", "unknown")
    source_ips = event.get("sourceIPs", [])
    object_ref = event.get("objectRef", {})
    resource = object_ref.get("resource", "")
    request_object = event.get("requestObject", {})

    logger.debug(f"Processing event: user={user}, verb={verb}, resource={resource}")

    # 検知ルール 1: 特権コンテナの作成試行
    if resource == "pods" and verb == "create":
        containers = request_object.get("spec", {}).get("containers", [])
        for container in containers:
            sec_context = container.get("securityContext", {})
            if sec_context.get("privileged") is True:
                trigger_security_alert(
                    title="【緊急】特権コンテナの作成を検知",
                    severity="HIGH",
                    details=f"User: {user} が特権コンテナを持つ Pod を作成しました。Source IPs: {source_ips}"
                )

    # 検知ルール 2: クラスター管理者権限(ClusterRoleBinding)の不正付与
    elif resource == "clusterrolebindings" and verb in ["create", "update"]:
        role_ref = request_object.get("roleRef", {}).get("name", "")
        if role_ref == "cluster-admin":
            trigger_security_alert(
                title="【重大】cluster-admin 権限の付与・変更を検知",
                severity="CRITICAL",
                details=f"User: {user} が cluster-admin 権限をバインドしました。対象リクエストを確認してください。"
            )

def trigger_security_alert(title: str, severity: str, details: str) -> None:
    """
    セキュリティチームへアラートを通知するモック関数(実際にはSlackやPagerDutyへ送信)
    """
    alert_payload = {
        "alert_title": title,
        "severity": severity,
        "message": details
    }
    # 実際の実装では requests.post("https://hooks.slack.com/...", json=alert_payload) などを記述
    logger.warning(f"SECURITY ALERT TRIGGERED: {json.dumps(alert_payload, ensure_ascii=False)}")

# --- テスト実行用のモックデータと処理 ---
if __name__ == "__main__":
    # テストケース: 特権コンテナ作成の監査ログ(一部抜粋)
    mock_audit_event = {
        "verb": "create",
        "user": {"username": "system:serviceaccount:default:compromised-sa"},
        "sourceIPs": ["198.51.100.42"],
        "objectRef": {"resource": "pods", "namespace": "default", "name": "evil-pod"},
        "requestObject": {
            "spec": {
                "containers": [
                    {
                        "name": "malicious-container",
                        "securityContext": {"privileged": True}
                    }
                ]
            }
        }
    }

    print("--- 監査ログ解析シミュレーション開始 ---")
    analyze_audit_event(mock_audit_event)
    print("--- 監査ログ解析シミュレーション終了 ---")

—

5. チーフエンジニアからの実践的なアドバイス

最後に、インフラの現場で幾多の修羅場をくぐってきた私から、これを導入する際の「生々しい注意点」をいくつか授けておこう。

1. 「ログを取るだけ」で満足するな、アラートのテストをしろ:
監査ポリシーを適用したら、必ずステージング環境で kubectl run を使って実際に特権コンテナを作成し、SIEMやSlackにアラートが飛ぶか「カオスエンジニアリング」的にテストすること。通知されない監視は存在しないのと同じだ。
2. 監査ログ自体の改ざん耐性を考慮せよ:
万が一、クラスターのコントロールプレーンそのものが侵害された場合、攻撃者は監査ログのローカルファイル(/var/log/kubernetes/audit/)を消去・改ざんしようとする。本番環境では、ログは出力した瞬間にWORM(Write Once, Read Many)特性を持つクラウドストレージ(AWS S3のオブジェクトロック等)へリアルタイム転送し、クラスター外部に退避させることが鉄則である。

セキュリティの強度は「どれだけ強固な壁を作るか」ではなく、「侵入されたときに、どれだけ早く異変に気づき、敵の動線を断つか」で決まる。Kubernetesの監査ログを制する者は、クラウドネイティブインフラの安全を制する。今すぐ君のクラスターの監査ポリシーを見直してほしい。

コメント

タイトルとURLをコピーしました