Skip to content
Published on

eBPFランタイムセキュリティ — Tetragon、Falco、そしてBPF LSM

Authors

はじめに

イメージスキャンを通過し、シークレットも安全に管理し、ネットワークポリシーも適用しました。それで終わりでしょうか。2020年代のセキュリティインシデントは「いいえ」と答えます。正常に見えた依存パッケージがビルドパイプラインを通過した後、ランタイムで悪意ある動作を始めるサプライチェーン攻撃。脆弱なカーネル機能や誤設定された権限を突くコンテナエスケープ。窃取された認証情報で侵入した攻撃者の内部移動(ラテラルムーブメント)。これらはすべて、デプロイ前の静的検査では捕まえられません。実行中に起きることだからです。

ランタイムセキュリティは「実行中のワークロードが今、何をしているのか」を観察し、ポリシーに反する行動を検知またはブロックするレイヤです。そして、このレイヤを実装する現在最も有力な技術がeBPFです。これまでの記事で扱ったeBPFの基礎と可観測性の技術が、今回はセキュリティへ向かいます。Falcoのルールベース検知、Tetragonのカーネルレベルのブロック、そしてその基盤となるBPF LSMまで順に見ていきます。

なぜランタイムセキュリティなのか

典型的なコンテナ侵害シナリオを時間軸で見ると、静的セキュリティとランタイムセキュリティがカバーする区間がはっきり分かれます。

攻撃チェーンと防御レイヤ
            ビルドタイム                  ランタイム
+---------------------------+ +------------------------------------+
| イメージスキャン、SBOM、署名 | | ここからはランタイムセキュリティの領域 |
+---------------------------+ +------------------------------------+
                                  |
  脆弱性悪用 / 悪性依存の発動      |
        v                         v
  [初期侵入] --> [偵察] --> [権限昇格] --> [永続化] --> [目的の実行]
   Webシェル      ls, id    コンテナ       cron登録      データ流出
   リバースシェル /etc閲覧   エスケープ     バックドア    暗号通貨マイニング
        ^             ^            ^             ^             ^
        |             |            |             |             |
   execイベント  ファイルアクセス カーネルシスコール ファイル書込  ネットワーク
   検知可能      検知可能      検知/ブロック    検知可能       検知可能

核心となる観察はこれです。攻撃のすべての段階は、結局システムコールとして現れます。プロセス実行はexecve、ファイルアクセスはopenat、ネットワーク接続はconnectです。システムコール境界を観察できれば攻撃者の行動に隠れ場所はなく、eBPFはまさにその境界を低オーバーヘッドで観察(そしてカーネル5.7以降はブロック)できる技術です。

検知とブロックは別の問題です

ランタイムセキュリティツールを評価する際、最初に区別すべき軸です。

区分検知 (Detection)ブロック (Enforcement)
動作イベントを観察してアラートを生成ポリシー違反の動作自体を拒否/中断
タイミング行為発生後 (非同期)行為発生時点 (同期)
誤検知のコストアラートノイズ、疲労正常サービスの停止 — はるかに高い
代表ツールFalco (デフォルトモード)Tetragon (sigkill/override)、BPF LSM、seccomp
導入難易度低い (観察のみのため)高い (ポリシー精度の確保が先)

非同期検知には根本的な限界が一つあります。イベントがユーザー空間エージェントに届いてルールが評価される間に、悪意ある行為はすでに実行されている可能性があります(TOCTOU問題)。そのため成熟した運用モデルは「広範な検知 + 狭く確実なブロック」の組み合わせです。この記事の最後の導入ロードマップで再び扱います。

Falco: ルールベースのランタイム検知の標準

FalcoはCNCFのgraduatedプロジェクトで、システムコールのストリームをルールと照合して異常な振る舞いを検知します。

アーキテクチャ

+--------------------------------------------------------------+
|                      Falcoアーキテクチャ                       |
|                                                              |
|  イベントソース                                               |
|   ├── システムコール: 最新eBPFプローブ (CO-RE、デフォルト)     |
|   │              旧式: カーネルモジュール / レガシーeBPF       |
|   └── プラグイン: k8s audit log、クラウドログなど             |
|        |                                                     |
|        v                                                     |
|  libs (libscap/libsinsp): イベント収集、コンテナメタデータ付加 |
|        |                                                     |
|        v                                                     |
|  ルールエンジン: YAMLルールと条件式のマッチング                |
|        |                                                     |
|        v                                                     |
|  出力: stdout / gRPC / ファイル --> Falcosidekick             |
|                                  ├── Slack / PagerDuty       |
|                                  ├── Elasticsearch / Loki    |
|                                  └── SIEM / Webhook          |
+--------------------------------------------------------------+

現代のFalcoのデフォルトドライバはCO-REベースのmodern eBPFプローブです。カーネルモジュールのインストールが不要なためノードOSのアップグレードに強く、BTFのあるカーネル(5.8以上推奨)ならすぐに動作します。

ルール文法と実戦例

Falcoのルールはcondition(条件式)、output(アラートメッセージ)、priority(深刻度)で構成され、macroとlistで再利用可能な部品を作ります。

例1 — コンテナ内でのシェル実行の検知 (最も古典的なルール):

- macro: container
  condition: (container.id != host)

- macro: spawned_process
  condition: (evt.type in (execve, execveat) and evt.dir = <)

- list: shell_binaries
  items: [bash, sh, zsh, dash, ksh]

- rule: Shell Spawned in Container
  desc: A shell was spawned inside a container, possible interactive access
  condition: >
    spawned_process and container
    and proc.name in (shell_binaries)
  output: >
    Shell spawned in container
    (user=%user.name container=%container.name image=%container.image.repository
     parent=%proc.pname cmdline=%proc.cmdline)
  priority: WARNING
  tags: [container, shell, mitre_execution]

例2 — 機密ファイルへのアクセス検知:

- list: sensitive_files
  items: [/etc/shadow, /etc/sudoers, /root/.ssh/authorized_keys]

- rule: Sensitive File Opened for Reading
  desc: Detect attempts to read sensitive files by non-trusted programs
  condition: >
    evt.type in (open, openat, openat2) and evt.dir = <
    and fd.name in (sensitive_files)
    and not proc.name in (sshd, systemd, sudo)
  output: >
    Sensitive file opened (file=%fd.name user=%user.name
    proc=%proc.name cmdline=%proc.cmdline container=%container.name)
  priority: CRITICAL
  tags: [filesystem, secrets, mitre_credential_access]

例3 — 想定外のアウトバウンド接続の検知 (マイニング/流出のシグナル):

- rule: Unexpected Outbound Connection from Web Container
  desc: Web tier should only talk to the app tier and DNS
  condition: >
    evt.type = connect and evt.dir = <
    and container.image.repository = "myorg/web-frontend"
    and fd.type = ipv4
    and not fd.sip in ("10.0.20.0/24")
    and not fd.sport = 53
  output: >
    Unexpected outbound connection (dest=%fd.rip:%fd.rport
    container=%container.name image=%container.image.repository)
  priority: NOTICE
  tags: [network, mitre_exfiltration]

ルール作成の実務的なコツは三つです。第一に、conditionの否定条件(not ...)が例外リストになるため、最初からmacro/listに分離して管理します。第二に、outputにコンテナ/イメージ/コマンドラインのフィールドを十分に入れることで、トリアージが速くなります。第三に、priorityは対応手順と1対1で結び付けておくことで、アラートが行動につながります。

Tetragon: 観察を超えてブロックへ

TetragonはCiliumエコシステム(Isovalent、現Cisco)のeBPFベースのランタイムセキュリティツールで、CNCF傘下のプロジェクトです。Falcoとの最大の違いはカーネル内でのインラインフィルタリングとブロックです。イベントをすべてユーザー空間へ送って評価する代わりに、ポリシーをeBPFプログラムとしてカーネルに送り込み、マッチ時にカーネル内で即座にシグナル(SIGKILL)を送ったり戻り値をオーバーライドしたりできます。

アーキテクチャとTracingPolicy CRD

+---------------------------------------------------------------+
|                     Tetragonアーキテクチャ                      |
|                                                               |
|  TracingPolicy CRD (YAML、クラスタリソース)                    |
|        |  Tetragonオペレーター/エージェントが解釈               |
|        v                                                      |
|  ノードごとのTetragonエージェント (DaemonSet)                  |
|        |  ポリシーをeBPFプログラムにコンパイル/ロード           |
|        v                                                      |
|  カーネル: kprobe / tracepoint / LSMフックにアタッチ           |
|        ├── マッチしたイベント --> ringbuf --> エージェント      |
|        │                                  --> JSON/gRPC       |
|        └── matchActions: Sigkill / Override (カーネル内で即実行)|
+---------------------------------------------------------------+

Kubernetesネイティブである点も重要です。イベントにPod/名前空間/コンテナのメタデータが自動で付与され、ポリシーもCRDなのでGitOpsで管理されます。

例1 — コンテナ内でのパッケージマネージャ実行をブロック(sigkill):

apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: deny-package-managers
spec:
  kprobes:
    - call: "sys_execve"
      syscall: true
      args:
        - index: 0
          type: "string"
      selectors:
        - matchArgs:
            - index: 0
              operator: "Prefix"
              values:
                - "/usr/bin/apt"
                - "/usr/bin/apk"
                - "/usr/bin/yum"
                - "/usr/bin/dnf"
          matchActions:
            - action: Sigkill

ランタイムにパッケージをインストールするコンテナは、ほぼ常に悪い兆候(イミュータブルイメージ原則の違反、または侵害)なので、比較的安全にブロックできる代表例です。

例2 — 特定ファイルへの書き込みをLSMフックで拒否(戻り値オーバーライド):

apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: protect-ssh-authorized-keys
spec:
  lsmhooks:
    - hook: "file_open"
      args:
        - index: 0
          type: "file"
      selectors:
        - matchArgs:
            - index: 0
              operator: "Postfix"
              values:
                - ".ssh/authorized_keys"
          matchActions:
            - action: Override
              argError: -1

LSMフックベースのブロックはsigkillより一段ときれいです。行為が起きた後にプロセスを殺すのではなく、カーネルがその操作自体を権限エラーとして拒否するからです(BPF LSMが有効なカーネルが必要)。

Falco vs Tetragon比較

項目FalcoTetragon
主なモデルユーザー空間ルールエンジンによる検知カーネル内フィルタリング + 検知/ブロック
ブロック能力基本はアラート (対応は外部連携)Sigkill、Overrideなどを内蔵
ポリシー形式独自YAMLルール (macro/list)Kubernetes CRD (TracingPolicy)
デフォルトルールセット豊富なコミュニティ標準ルールポリシーを自前で設計する傾向
エコシステムFalcosidekickの幅広い出力連携Cilium/Hubbleとの自然な統合
成熟度CNCF graduated、長い運用実績CNCF傘下、急速に成長

実務では二者択一というより、「Falcoで広く検知し、確信が積み上がった狭いポリシーだけTetragonでブロックする」併用構成も十分合理的です。

BPF LSM: カーネルのセキュリティフックにeBPFを挿す

LSM(Linux Security Modules)は、SELinuxやAppArmorが使うカーネルのセキュリティ決定ポイント群です。ファイルオープン、プログラム実行、ソケット作成といった操作ごとにセキュリティフックが呼ばれ、フックが0以外を返すとその操作は拒否されます。カーネル5.7からは、このフックにeBPFプログラムを直接アタッチできます。つまり、SELinuxポリシー言語の代わりにCでセキュリティポリシーを書けるようになったのです。

有効かどうかは、カーネルブートパラメータのlsmリストにbpfが含まれているかで確認します。

cat /sys/kernel/security/lsm
# 例: lockdown,capability,landlock,yama,apparmor,bpf

ミニ例 — 特定パスのファイル実行を拒否するBPF LSMプログラム:

// SPDX-License-Identifier: GPL-2.0
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

#define EPERM 1

/* bprm_check_security: プログラム実行直前に呼ばれるLSMフック */
SEC("lsm/bprm_check_security")
int BPF_PROG(deny_tmp_exec, struct linux_binprm *bprm)
{
    const char *path = bprm->filename;
    char buf[16];

    bpf_probe_read_kernel_str(buf, sizeof(buf), path);

    /* /tmp/ 配下のバイナリ実行を拒否 */
    if (buf[0] == '/' && buf[1] == 't' && buf[2] == 'm' &&
        buf[3] == 'p' && buf[4] == '/')
        return -EPERM;

    return 0;
}

char LICENSE[] SEC("license") = "GPL";

負のエラーコードを返すと、カーネルがその実行を拒否します。実戦のポリシーならパス比較をマップベースの許可/拒否リストにし、名前空間/cgroupの条件を加えるでしょうが、「LSMフック + eBPF = プログラマブルな強制」という骨格はこのミニ例のとおりです。Tetragonのlsmhooks機能と、かつてKRSI(Kernel Runtime Security Instrumentation)と呼ばれたこのメカニズムは同じ基盤です。

seccomp、AppArmorとの関係

eBPFランタイムセキュリティは既存メカニズムを置き換えるものではありません。レイヤが異なります。

レイヤメカニズムポリシー単位強み限界
シスコールフィルタseccomp-bpfプロセスごとのシスコール許可リストシンプル、カーネル標準、コンテナランタイムが標準サポート引数検査が限定的、動的変更が難しい
強制アクセス制御AppArmor / SELinuxパス/ラベルベースのプロファイル成熟、ディストリビューション統合ポリシー作成の難易度、コンテナごとの細分化が面倒
LSM + eBPFBPF LSM、Tetragon任意条件のプログラマブルポリシー豊富なコンテキスト(Podメタデータなど)、動的デプロイ比較的新しい、カーネル要件
検知レイヤFalcoなどルールベースのイベントマッチング導入が容易、可視性基本的に事後検知

推奨される構図は「基本衛生 + 精密ポリシー」の積層です。seccompデフォルトプロファイルとAppArmorで明らかに不要な能力を削って攻撃面を減らし、その上にeBPFレイヤでワークロードごとの行動ポリシーと検知を加えます。一つのレイヤが破られても次のレイヤが支える、多層防御(defense in depth)です。

Kubernetesへのデプロイとイベントパイプライン

ランタイムセキュリティのエージェントはノードごとに必要なので、DaemonSetでデプロイします。FalcoをHelmでデプロイする典型例です。

helm repo add falcosecurity https://falcosecurity.github.io/charts
helm install falco falcosecurity/falco \
  --namespace falco --create-namespace \
  --set driver.kind=modern_ebpf \
  --set falcosidekick.enabled=true \
  --set falcosidekick.config.slack.webhookurl=https://hooks.slack.com/services/XXX

検知イベントは必ずノードの外へ送るべきです。攻撃者がノードを掌握すると、まずローカルログを消すからです。標準的な収集パイプラインは次のとおりです。

[ノード] Falco/Tetragon (DaemonSet)
   |  JSONイベント (stdoutまたはgRPC)
   v
[収集] Falcosidekick / Fluent Bit / Vector
   |  フィルタリング、ルーティング、バッファリング
   v
[保存/分析] Elasticsearch / Loki / S3  --->  SIEM (Splunkなど)
   |                                          相関分析、保存ポリシー
   v
[対応] Slack、PagerDuty通知  /  SOAR自動対応 (Pod隔離など)

Tetragonの場合、イベントはJSONログとgRPCストリームで提供され、同じパイプラインに自然に合流します。

# Tetragonイベントのリアルタイム確認 (tetra CLI)
kubectl exec -ti -n kube-system ds/tetragon -c tetragon -- \
  tetra getevents -o compact

ルール運用: ノイズとの戦い

ランタイムセキュリティの導入が失敗する最も多い理由は技術ではなく、アラート疲労です。運用原則を整理します。

  1. まずベースライニング。ルールを有効にする前に検知専用(audit)モードで1〜2週間回し、環境の「正常」を収集します。どのジョブがシェルを起動するのか、どのエージェントが/etcを読むのか、データで把握します。
  2. 例外はコードで管理。Falcoのmacro/list、TetragonのselectorをGitリポジトリでレビュー付きで変更します。「とりあえず無視」ボタンは例外のブラックホールになります。
  3. 深刻度別に対応チャネルを分離。CRITICALはポケベル、WARNINGは日次ダイジェストのようにチャネルを分けることで、本物のインシデントが埋もれません。
  4. アラートに十分なコンテキストを。Pod、イメージ、コマンドライン、親プロセスがアラート本文にあると、トリアージ時間が大幅に減ります。
  5. ルールセットのバージョン管理と回帰テスト。ルール変更が既存の検知を壊さないか、シミュレーションイベント(例: 意図的なシェル実行)でCIで検証します。
  6. 指標を持ちます。週間アラート数、誤検知率、平均トリアージ時間を追跡し、ルールチューニングの効果を測定します。

回避可能性と限界 — 防御者の視点から

eBPFランタイムセキュリティも万能ではありません。防御設計に織り込むべき限界を整理します(攻撃手法の再現ではなく、防御観点の議論です)。

  • 非同期検知の時間窓: ユーザー空間でのルール評価モデルでは、検知の前に行為が完了し得ます。致命的な資産はLSMベースの同期ブロックで守るのが原則です。
  • 可視性の死角: シスコールベースの検知は、シスコールとして現れない行為(例: すでにマップ済みメモリ内での演算)を見られません。メモリ保護、イメージ署名といった別レイヤが必要な理由です。
  • エージェント自体が攻撃対象: root権限を持つセキュリティエージェントは魅力的な標的です。エージェントのプロセス/設定の改ざん検知(自己保護ルール)と最小権限構成が必要です。
  • カーネル掌握後はゲームオーバー: 攻撃者がカーネル権限を得るとeBPFプログラムを外せます。bpf()呼び出し自体を監査対象とし、カーネル脆弱性のパッチサイクルを短く保つことが前提条件です。
  • イベント殺到によるドロップ: イベントバッファが溢れると検知漏れが生じます。ドロップカウンタを必ず監視対象に含めます。

要約すると、ランタイムセキュリティは他のレイヤ(イメージ署名、最小権限、ネットワークポリシー、パッチ)を置き換えるのではなく、最後の可視性と統制の層を加えるものです。

性能影響の測定

導入前の負荷テストで確認すべき項目です。

# 1. シスコール集約型ワークロードのスループット比較 (エージェントon/off)
#    例: ファイルIOベンチマーク
fio --name=randrw --rw=randrw --size=1g --runtime=60 --time_based

# 2. ネットワーク集約型ワークロードのp99比較
wrk -t8 -c256 -d60s http://service.example/api

# 3. エージェント自体のリソース使用
kubectl top pod -n falco
kubectl top pod -n kube-system -l app.kubernetes.io/name=tetragon

# 4. イベントドロップの有無 (Falco内部メトリクス)
#    falco_metricsを有効化してn_drops指標を確認

経験的に、シスコール頻度が非常に高いワークロード(小さなIOを大量に発行するDB、高QPSのプロキシ)でオーバーヘッドが最もよく現れます。一般的なWebワークロードでは一桁パーセント以内が普通ですが、絶対値はルール数とイベント量に左右されるため、自分のワークロードで測定することが唯一信頼できる方法です。

導入ロードマップ: 観測 → 検知 → ブロック

ブロックから始めると必ずサービス障害として跳ね返ってきます。段階を踏むことが早道です。

  1. ステージ1 — 観測 (2〜4週間): エージェントを検知専用でデプロイし、イベントパイプライン(収集/保存/ダッシュボード)を完成させます。アラートはまだ送りません。環境のベースラインを収集する期間です。
  2. ステージ2 — 検知 (4〜8週間): デフォルトルールセットを有効化し、環境に合わせてチューニングします。誤検知率とトリアージ時間を指標として管理し、深刻度別の対応手順をランブック化します。
  3. ステージ3 — 選別的ブロック: 誤検知が事実上ゼロと証明された狭いポリシーからブロックに切り替えます。良い最初の候補は「コンテナ内のパッケージマネージャ実行」「ランタイムでのauthorized_keys変更」「既知のマイニングプールドメインへの接続」のように、正常なワークロードで起きる理由がない行為です。
  4. ステージ4 — 自動対応の連携: SOAR/オペレーターと連携してPod隔離(ネットワークポリシー適用)、ノードのcordonといった対応を自動化します。自動対応にもサーキットブレーカー(1時間あたりの最大実行回数)を設けます。

導入チェックリスト

  • ノードのカーネルが要求バージョンを満たしている (BTFが存在、BPF LSM使用時はlsmリストにbpfを含む)
  • 検知専用モードでベースライン収集期間を経た
  • イベントがノード外部(SIEM/ログストア)へ送信され、保存ポリシーがある
  • ルール/ポリシーがGitでバージョン管理され、変更にレビューが強制される
  • 深刻度別のアラートチャネルと対応ランブックがつながっている
  • イベントドロップカウンタとエージェントのリソースが監視されている
  • ブロックポリシーは誤検知ゼロが検証された項目に限定した
  • エージェント自体への改ざん検知ルールがある
  • 代表的なワークロードで性能影響を測定し記録した
  • seccomp/AppArmorデフォルトプロファイルなど下位レイヤの防御との積層構成である

おわりに

このシリーズで私たちは、eBPFという一つの技術が三つの顔を持つことを見てきました。基礎編ではカーネルを安全にプログラミングする方法として、可観測性編ではシステムのブラックボックスを開く道具として、そして今回のセキュリティ編では実行中のワークロードを守る最後の防衛線としてです。三つの共通基盤は同じです。検証済みのプログラムがカーネルのフックでイベントを見て、マップでデータを共有する構造です。

ランタイムセキュリティの導入はツール選択よりも運用の成熟度の問題に近いです。観測から始めて検知を磨き、確信が積み上がったところにだけブロックをかける順序を守れば、eBPFは誇張なしに、現在最も強力なランタイム防御の手段になってくれるはずです。

参考資料