コンテンツにスキップ

実行時証拠による優先順位付けの開発

  • 状態: 実装・運用検証済み
  • 開始日: 2026年9月25日
  • 最終更新: 2026年10月15日

目的

KestreLynx は、稼働中のコンテナのイメージを脆弱性スキャナの Trivy でスキャンし、実際の悪用が確認された脆弱性の一覧である CISA KEV と、悪用の見込みを示す EPSS を使って優先順位を付け、Slack・webhook に差分を通知する。この判断に、利用環境でパッケージが使われていることを示す実行時の証拠を加える。

実行時証拠による優先順位付けの成立性調査と、その後の網羅性・イベント観測・観測環境の検証を受けて、使用中とみなす範囲、優先順位への反映、観測する部品の配置と権限を定め、実装に着手する。設計は確定しており、実装とその検証はこれからである。

使用中とみなす範囲

Sensor(実行時の情報を観測する部品)が、OS パッケージと言語パッケージの両方について使用中かを判定する。

  • OS パッケージ
    • apt・apk でインストールされ、dpkg・apk のデータベースでファイルの所有を確認できるものを対象とする
    • 所有するファイルが稼働中のプロセスの実行ファイル、または読み込まれた共有ライブラリであれば使用中とする
    • rpm 系は初回の対象外とする
  • 言語パッケージ
    • 稼働中のプロセスに含まれていれば一律で使用中とし、関数の呼び出しや実際の読み込みは問わない
    • Go・Rust などは、パッケージを含むバイナリが実行中であれば使用中とする
    • Python・Node.js・Java などは、そのコンテナで python・node・java などの実行環境のプロセスが動いていれば、そのコンテナの同じ言語のパッケージをすべて使用中とする
    • 対応していない言語は「観測できない」とする
  • 観測の方法
    • 一定間隔の読み取りと、eBPF(カーネル内の出来事を観測する仕組み)による実行・ファイルのイベント観測を併用する
    • eBPF で curl・git・cron で動くスクリプトなど、短時間だけ動くプログラムを捉える
    • eBPF は Sensor に常に含める
    • eBPF は言語パッケージの読み込みの追跡には使わない
    • eBPF が動かない環境では、一定間隔の読み取りだけで判定を続け、通知に警告を出す
  • 観測状態
    • 使用中 — 実行ファイル、読み込まれた共有ライブラリ、または対応する実行環境のプロセスから使用を確認できる
    • 使用が確認されない — 観測期間中に使用を確認していない状態であり、未使用や安全を意味しない
    • 観測できない — 読み取り権限の不足、Sensor の停止・初期化中、未対応の言語などにより判定できない

たとえば、nginx が読み込む openssl は使用中とし、どのプロセスも実行しない perl は使用が確認されないものとする。

優先順位への反映

優先度の act now・watch・low は、これまでどおり KEV・EPSS・深刻度・Trivy が報告する修正状態の Status で決める。実行時の証拠は、同じ優先度の中での並び順と、その根拠の表示に使う。

  • 使用中の OS パッケージと言語パッケージを、同じ優先度の中で前に並べる
  • 使用中のパッケージに、実行ファイル・公開ポート・実行ユーザーなどの根拠を添える
  • 使用が確認されないことや観測できないことを理由に、優先度を下げたり通知から外したりしない

act now は実際の悪用や悪用の見込みに基づく判断であり、利用環境で使用中であることは別の軸である。このため、使用中であることを理由に優先度の段を上げない。また、観測できないパッケージもあるため、未確認を理由に優先度を下げると、見えないものほど対応が後回しになる。

観測する部品の配置と権限

  • 配置
    • Sensor は任意で追加する
    • 本体とは別のコンテナで、本体と同じイメージを sensor サブコマンドで起動する
    • 実行時の証拠が無効のときは、既存の機能と出力を変えない
  • 権限
    • ホストの PID 名前空間を共有する
    • CAP_SYS_PTRACE・CAP_DAC_READ_SEARCH を付与する(観測環境の検証で非 root でも root と同じ観測結果になることを確認済み)
    • eBPF のプログラムを読み込むための CAP_BPF・CAP_PERFMON は、起動時の読み込みの直後に手放す
  • 本体との分離
    • 本体には観測の権限を持たせない
    • Sensor にはネットワーク・Docker ソケット・通知先の秘密情報を持たせない
    • 証拠は共有ボリュームのファイルで本体へ渡す
    • 本体は証拠を信頼できない入力として検証する
  • Sensor の中の分離
    • /proc を読むプロセスは権限を持ち、ファイルの中身を解析しない
    • パッケージデータベースを解析するプロセスは権限を持たず、ファイル操作を禁止する
  • 制限の仕組み
    • 非 root で実行する
    • no_new_privs を適用する
    • 配布する seccomp のプロファイルを適用する
    • 解析する側に Landlock を適用する
    • AppArmor は Docker の既定のプロファイルを使う
  • eBPF の実装
    • Go から eBPF のプログラムを直接読み込む
    • 外部ツールは同梱しない
  • 残る危険
    • 権限を持つ側が侵害された場合、読める範囲には制限がないことを公開ドキュメントに明記する
  • 同種ツールとの比較
    • Falco の導入手順は root と CAP_SYS_ADMIN などを使う
    • Tetragon の導入手順は --privileged を使う
    • Sensor はこれらの導入構成より権限の範囲を狭くする
    • 比較は導入手順に書かれた権限に限る

通知での表示

実行時の証拠は、使用中の OS パッケージと言語パッケージを見分ける印と根拠として通知に加える。

  • Slack の使用中の表示
    • 使用中の OS パッケージと言語パッケージに ▶ in use の印と根拠を付ける
    • OS パッケージの根拠には、実行ファイル・読み込まれた共有ライブラリを示す
    • 言語パッケージの根拠には、対応する実行環境が動いていること、またはパッケージを含むバイナリが実行中であることを示す
    • 短時間の実行を捉えた場合は、その旨も示す
  • Slack の全量表示
    • 末尾に OS パッケージと言語パッケージの状態ごとの件数を出す
    • 使用が確認されないことは未使用を意味しない旨を出す
  • Slack の言語パッケージの表示
    • 行末の [lang] を、パッケージの配布・管理の仕組みを区別するエコシステム名に替える
    • 表示には Trivy の種別の値を使い、[python-pkg]・[node-pkg]・[jar]・[gobinary] などとする
  • webhook の追加フィールド
    • 各検出結果に class(os / lang)と ecosystem を加える
    • 実行時の証拠が有効なときは、OS パッケージと言語パッケージの検出結果に runtime を加える
    • 既存のフィールドは変えず、追加のみとする

eBPF が動かない環境では、その旨と、一定間隔の読み取りだけで判定を続けていることを警告として通知に出す。

エコシステム名の表示と webhook の class・ecosystem は、実行時の証拠の有効・無効にかかわらず導入する。

使用中かどうかの変化は、差分通知の対象にしない。

実装と運用の結果(2026年10月8日)

  • 検証環境と集計期間
    • VPS の環境
      • 6コア、メモリ 12GB 弱の VPS
      • Ubuntu 26.04 LTS、Linux 7.0、Docker Engine 29
    • 動いているコンテナ
      • 常駐のコンテナは4つ
        • Web アプリのフロントエンド(Node.js)
        • Web アプリのバックエンド(Node.js)
        • Elasticsearch(Java)
        • KestreLynx の本体
      • 毎朝、数秒だけ動くバッチのコンテナもある
    • 集計期間
      • 2026年9月28日から本番の VPS で常時運用し、9月29日〜10月6日の1週間を集計した

負荷とサービスへの影響

対象 結果
Sensor の CPU 平均 0.33%、最大 31%
Sensor のメモリ 初日の平均は 70MiB で、5日目以降の平均は 92〜94MiB で推移(期間中の最大は 108MiB)
本体のコンテナのメモリ スキャンしていない間は 50MiB 前後
観測対象のサービス 再起動はなく、Elasticsearch は全期間 healthy
  • 読み取りとイベントの取りこぼし
    • 読み取り間隔の既定値は30秒で、1週間で常駐のコンテナが読み取りの上限に達した記録はなかった
    • カーネル側のバッファあふれによるイベントの取りこぼしは1週間で0件だった
    • その後の高負荷時に1件の取りこぼしがあったが、観測対象のコンテナの数え方には影響していないとみられる
    • カーネル側の取りこぼしは、他の欠落と区別して Sensor の証拠に記録する
    • 毎朝数秒だけ動くバッチのコンテナは観測が間に合わず、イベントが欠落・分類不能に数えられる
  • 通知の表示と設定
    • 使用中の印と、Java の実行環境やヘルスチェックの curl が読み込む TLS ライブラリなどの根拠を通知で確認した
    • 0.0.0.0 に公開されたポートも、使用中のパッケージの根拠に添えて表示された
    • runtime.mute_unfixable_not_in_use は、修正版がなく使用が確認されない所見の通知をオフにする設定で、既定は無効
      • 通知から外れる動作はテストで確認したが、運用環境には該当する所見がなく未確認
    • Sensor の導入・状態と通知オフの条件は稼働時の使用状況に記載している
  • 運用で残る観察
    • 長時間動いている Elasticsearch のコンテナで、観測の一部を確かめられない状態を示す incomplete が続いている
      • 原因は未確定で、状態の理由を Sensor のログで確認しながら観察を続けている
      • この状態の間は、コンテナのパッケージを「使用が確認されない」としない
    • Sensor のメモリの最初の1週間の増加は、終了したコンテナの記録を7日間保持することなど、上限のある記録がたまる途中とみられる
      • 2週目以降の推移で確かめる

Elasticsearch の観測の欠けの原因と修正(2026年10月15日)

  • 原因
    • 別のコンテナが大量のファイルを開くと、共有のパス対応表から起動時の観測に必要な記録が押し出されていた
    • 短命なプロセスは終了後にパスを補えず、観測が欠けた状態が続いていた
  • 修正
    • 対応表の件数上限を変えずに、使われていない古い記録を優先して捨てるようにした
    • 該当する記録がなければ、最も多くの記録を持つコンテナから古い記録を捨てるようにした
  • 確認
    • 再現試験と運用環境で、修正後は Elasticsearch の観測が欠けないことを確認した
    • 確認した負荷では、件数上限を増やさずに、別のコンテナの大量のファイル読み取りから起動時の観測に必要な記録を守れた
  • 残り
    • Elasticsearch のヘルスチェック時にアクセス制御が観測に必要な読み取りを拒否する場合があり、対処が残っている
    • メモリの増加は終了したコンテナの記録保持によるものとみられるが、更新後の推移で確かめる

次の作業

次の作業の順序は未定である。

  • 対象の拡張
  • 継続運用での確認
    • Elasticsearch のヘルスチェックの瞬間に AppArmor が Sensor の読み取りを拒否する場合への対処を進める
    • Sensor のメモリの増加が落ち着くかを、Sensor の更新(2026年10月15日)後の推移で確認する

更新履歴

2026年9月25日

  • 計画を記録

2026年10月8日

  • 実装と運用の結果を記録

2026年10月15日

  • Elasticsearch の観測の欠けの原因と修正

この記事は、KestreLynxの開発記録です。KestreLynxについて