【テクニカル・上級編】 コンテナ環境におけるランタイムセキュリティ監視と脆弱性スキャン – ガバナンス・リスク管理 & 生成AIセキュリティ防御ガイド

コンテナセキュリティの幻想:CVEスキャンだけでは「ゼロ日」を防げない理由

世の多くのDevSecOpsエンジニアは、CI/CDパイプラインにTrivyやClairといったイメージ脆弱性スキャナーを組み込み、ビルド成果物に対してseverity: HIGH,CRITICALの静的アセスメントをパスさせることで「安全なコンテナができた」と安眠を貪っている。

だが、現場のインシデントレスポンスを数多くこなしてきた人間から言わせれば、それはセキュリティではなく単なる「儀式」に過ぎない。

静的脆弱性スキャンとは、既知のデータベース(NVDや各ディストリビューションのセキュリティアドバイザリ)とパッケージのバージョンを照合しているに過ぎない。ここにゼロ日脆弱性(CVEが割り当てられていない未知の不具合)や、アプリケーションのロジックの隙を突いたコンテナエスケープが持ち込まれた瞬間、その「完璧なパイプライン」は一瞬で紙細工の防壁と化す。

真のコンテナセキュリティアーキテクチャとは、「侵入されないこと」を前提とするのではなく、「侵入された後に、その足跡をいかにコンテキストレベルで捉え、即座にミリ秒単位で隔離するか」というランタイムの防衛網の厚さで決まる。

今回は、CI/CDでのシフトレフト(脆弱性スキャン)の限界を突破し、カーネル空間からコンテナの挙動を監視するFalcoを用いたランタイムセキュリティの核心、そして低レイヤのシステムコール監視による不正プロセス検知の泥臭い実践知を共有しよう。

—

1. CI/CDパイプラインにおける脆弱性スキャンの限界と補完戦略

イメージ脆弱性スキャンは最初の防衛線としては不可欠だが、それ単体では以下のような「盲点」を抱えている。

  • ベースイメージの毒親問題: 開発者が「便利だから」と使ったパブリックな軽量イメージ(AlpineやDebian-slim)のレイヤ深くに、隠しバックドアや脆弱なライブラリが埋め込まれているケース。
  • 依存関係の肥大化: npmやpip経由でインストールされたサードパーティ製モジュールが、ビルド時には安全であっても、デプロイ後にサプライチェーン攻撃を受けるケース。

これらを補完するためには、CI/CDパイプラインでのスキャンを「ゲートキーパー」として機能させつつ、万が一のすり抜けを前提とした動的検証(DAST / Runtime Application Self-Protection)を本番環境へシームレスに接続するパイプライン設計が求められる。

実践的なGitHub Actionsにおけるスキャン自動化の例

単にスキャンツールを走らせるだけでなく、重大な脆弱性が検知された際のビルド破棄と、例外処理(Risk Acceptance)のトレースを担保するワークフローのサンプルを提示する。

name: Container CI Security Pipeline

on:
  push:
    branches: [ "main" ]

jobs:
  build-and-scan:
    runs-name: Ubuntu Latest Runner
    runs-on: ubuntu-latest
    steps:
      - name: リポジトリのチェックアウト
        uses: actions/checkout@v4

      - name: Dockerイメージのビルド
        run: |
          docker build -t my-app:${{ github.sha }} .

      - name: Trivyによる脆弱性スキャンの実行(高・緊急度のCVEを検知)
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'my-app:${{ github.sha }}}'
          format: 'table'
          exit-code: '1' # CRITICALな脆弱性があればビルドを強制終了する
          ignore-unfixed: true # パッチが未提供の脆弱性は一旦無視(運用ポリシーに合わせて調整)
          severity: 'CRITICAL,HIGH'

      - name: スキャン結果の監査ログ送信(SIEM連携用)
        if: failure()
        run: |
          echo "セキュリティゲートを通過できませんでした。インシデント管理システムへ通知します。"
          # ここにSlackやJira、Webhooksへの通知スクリプトを記述

—

2. カーネル空間の監視:Falcoによるシステムコール解析のメカニズム

イメージがどれだけクリーンであっても、コンテナの本質は「ホストOSのカーネルを共有する単なるプロセス隔離空間」に過ぎない。したがって、攻撃者がコンテナ内でリモートコード実行(RCE)を達成し、特権昇格やコンテナエスケープを試みる際、必ず特定のシステムコール(System Call)を発行することになる。

例えば、以下のような挙動は明確なインシデントの予兆(IoC: Indicator of Compromise)だ。

  • コンテナ内での予期せぬ execve によるシェル(/bin/sh や /bin/bash)の起動
  • /etc/shadow や /etc/passwd などの機密ファイルへの直接アクセス
  • ptrace システムコールを用いた他のプロセスへのメモリインジェクション
  • KubernetesのAPIサーバーに対する、不正な権限を持ったServiceAccountトークンの窃取と直接通信

Falcoルールエンジンの深掘り

Falcoは、Linuxカーネルモジュール(あるいはeBPF: Extended Berkeley Packet Filter)を利用して、システムコールをリアルタイムでキャプチャし、ユーザースペースで動作するルールエンジンと照合する。

従来のユーザーランド監視(ログ監視など)と異なり、攻撃者が証拠隠滅のためにコンテナ内のログファイルを改ざんしたとしても、カーネルレベルで発生したシステムコールそのものを捉えているため、攻撃を隠し通すことは極めて困難である。

—

3. 実践:Falcoによる不正プロセス検知ルールの構築

ここでは、本番環境のKubernetesクラスタやDocker環境において、「コンテナ内での不正なシェル起動」と「機密ディレクトリへの書き込み」を検知するためのカスタムFalcoルールを定義する。

Falcoのルールファイル(custom_rules.yaml)の設定例を以下に示す。

- macro: container_activity
  condition: container.id != host and container.id != ""

- rule: Detect Unauthorized Shell Spawn in Container
  desc: コンテナ内部で予期せぬシェル(bash/sh)が起動された場合に検知する。攻撃者の侵入直後の偵察活動を捉える。
  condition: spawned_process and container_activity and (proc.name = "sh" or proc.name = "bash" or proc.name = "zsh")
  output: "不審なシェルが検出されました (user=%user.name command=%proc.cmdline container_id=%container.id container_name=%container.name image=%container.image.repository)"
  priority: WARNING
  tags: [container, execution, mitre_execution]

- rule: Detect Sensitive File Modification
  desc: コンテナ内からホスト側マウント、あるいはコンテナ自身の機密ファイル(例: /etc/shadow)への書き込み試行を検知する。
  condition: open_write and container_activity and (fd.name startswith "/etc/shadow" or fd.name startswith "/etc/sudoers")
  output: "機密ファイルへの書き込み試行を検出しました (file=%fd.name command=%proc.cmdline container_name=%container.name)"
  priority: CRITICAL
  tags: [filesystem, mitre_defense_evasion]

パラメーターとチューニングの勘所

Falcoを本番導入する際、最も頭を悩ませるのが「ノイズ(False Positive)」の排除である。CIツールや特定の監視エージェントが、コンテナ内で正当な理由をもってshを起動することが多々ある。

このような正当な挙動をホワイトリスト化するためには、以下のようにexceptionsやマクロの条件式を厳格にチューニングする必要がある。

- rule: Detect Unauthorized Shell Spawn in Container (Tuned)
  condition: >
    spawned_process and container_activity and 
    (proc.name = "sh" or proc.name = "bash") and
    not container.image.repository in (
      "trusted-internal-registry.corp/base/debug-tools", # デバッグ用コンテナは除外
      "gcr.io/google-containers/pause"                  # Pauseコンテナは除外
    )
  output: "不正シェル検知 (tuned) [Container: %container.name]"
  priority: ERROR

—

4. インシデント発生時のオートメーションとフォレンジックの極意

Falcoがアラートを検知した瞬間、セキュリティオペレーションセンター(SOC)やSREチームが取るべきアクションは「目視確認」ではない。ミリ単位の初動遅れが、クラスタ全体のクラック(横展開:Lateral Movement)を許す。

真のプロフェッショナルな環境では、FalcoのアラートをWebhook経由でKnativeやAWS Lambda、あるいはKubernetesのCustom Controller(Operator)に飛ばし、以下の自動カウンタートランザクション(Remediation)を即座に発動させる。

1. コンテナの不変性隔離 (Network Isolation):
不正プロセスを検知したコンテナに対してネットワークポリシー(NetworkPolicy)を動的に適用し、外部通信およびクラスタ内通信を完全に遮断する(Ingress/EgressともにDrop)。ただし、フォレンジック用のメモリダンプを取得するため、コンテナ自体を即座にkillせず、「フリーズ状態」で保持するのがベストプラクティスである。
2. メモリフォレンジックの自動化:
揮発性データの消失を防ぐため、コンテナのプロセス空間やメモリイメージを安全なS3バケット等のストレージへ自動エクスポートする。

自動対応を実現するKubernetes Operatorの設計思想

検知後の自動隔離スクリプトの概念を、PythonによるK8s APIクライアントのコード片として示す。

from kubernetes import client, config

def isolate_compromised_pod(pod_name, namespace):
    """
    FalcoからのWebhookトリガーを受け取り、該当Podに悪意ある通信を遮断するための
    厳格なNetworkPolicyを動的にアタッチして孤立させる。
    """
    config.load_incluster_config()
    v1 = client.CoreV1Api()
    networking_v1 = client.NetworkingV1Api()

    # 1. 対象Podのラベルを取得
    pod = v1.read_namespaced_pod(name=pod_name, namespace=namespace)
    pod_labels = pod.metadata.labels

    # 2. すべての通信をブロックするNetworkPolicyを動的生成
    isolation_policy = {
        "apiVersion": "networking.k8s.io/v1",
        "kind": "NetworkPolicy",
        "name": f"isolate-{pod_name}",
        "namespace": namespace,
        "spec": {
            "podSelector": {
                "matchLabels": pod_labels # 該当Podのラベルをピンポイントで指定
            },
            "policyTypes": ["Ingress", "Egress"],
            "ingress": [], # すべて拒否
            "egress": []   # すべて拒否
        }
    }

    try:
        networking_v1.create_namespaced_network_policy(
            namespace=namespace,
            body=isolation_policy
        )
        print(f"[SECURITY ALERT] Pod '{pod_name}' in namespace '{namespace}' has been successfully isolated via NetworkPolicy.")
    except Exception as e:
        print(f"[ERROR] Failed to isolate pod: {str(e)}")

—

5. まとめ:静的防衛の幻想を捨て、動的監視の歯車を噛み合わせろ

セキュリティにおいて「100%安全な状態」などという言葉を口にする人間がいたら、それはペテン師か、あるいは実務を何も知らないコンサルタントのどちらかだ。

モダンなインフラストラクチャにおける真のレジリエンスとは、「いつか必ず侵入される」という冷徹な現実を受け入れた上で、

  • CI/CDでの脆弱性スキャンによる「侵入コストの引き上げ」
  • Falco等のランタイム監視による「侵入瞬時の検知とカーネルレベルの捕捉」
  • 自動隔離とフォレンジックによる「被害の極限までの極小化(Blast Radiusの制御)」

この3つの歯車を狂いなく噛み合わせることに他ならない。

ツールを導入して満足するな。カーネルの鼓動に耳を澄まし、システムコールの微弱なノイズから攻撃者の息遣いを嗅ぎ取る――それこそが、現代のセキュリティアーキテクトに求められる本当の「技量」なのだ。

コメント

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