2026年7月にKubeCon + CloudNativeCon Japan 2026で、Liz Rice氏の「Detecting Compromised CI with eBPF and Cilium Tetragon」を聞きました。
TetragonでCI内の不審な動きを検知・防止する、というセッションで、自分でも試してみたくなったので、GitHub ActionsのジョブをAWS CodeBuild上で実行して、そこでTetragonを動かしてみました。依存パッケージのpostinstallが勝手に通信する状況を作って、それを止められるかどうか確認しました。
Tetragonとは
Tetragonは、Linux上で動くプロセスや、そのプロセスによる通信・ファイルアクセスなどを監視するツールです。eBPFを使ってカーネル側でイベントを捉え、ポリシーに合う操作を記録したり、プロセスを強制終了したりできます。
パッケージに既知の脆弱性があるかを調べるのではなく、実際に動いたときに何をするかを見ます。何を止めるかは、自分でポリシーに書きます。
postinstallからcurlを呼んでみる
検証用に、アプリ役のdemo/victimと、そのアプリにインストールする自作ライブラリdemo/compromised-dependencyを用意しました。自作ライブラリには、/usr/bin/curlを呼び出すpostinstallを仕込んでいます。
アプリ側のpackage.jsonのdependenciesに、この自作ライブラリを登録します。その状態でアプリ側のnpm ciを実行するとライブラリがインストールされ、そのpostinstallからcurlが動きます。「依存ライブラリを入れたら、インストール処理の中で通信された」という状況を再現するための構成です。どちらも同じリポジトリ内に置いてあり、npmには公開していません。
送信できたかを確認するため、CIジョブ内でNode.jsの簡単なHTTPサーバーを起動します。ポート18080で待ち受け、curlから固定のダミー値が届いたら、受信記録をファイルに残します。
送る側のcurlも、受ける側のNode.jsも、同じCodeBuild runner内で動きます。外部の送信先をこの受信プログラムで代用しているので、ダミー値をインターネットへ送る構成ではありません。本物のマルウェアや認証情報も使っていません。
TetragonはCodeBuildのPRE_BUILDで起動し、そのあとGitHub Actionsのjobを実行します。
CodeBuildの設定メモ
ビルドイメージはaws/codebuild/amazonlinux-x86_64-standard:5.0、Host kernelはLINUX_KERNEL_6、Privilegedは有効にしています。
Tetragonは、eBPFプログラムを実行環境のカーネルに合わせて読み込むために、BTFという型情報を使います。今回はBTFを利用できるLinux 6系の環境を選びました。
TetragonがeBPFプログラムをカーネルにロード・アタッチできるよう、この構成では特権モードを使っています。
ホストカーネルはイメージとは別の設定です。CDKでは次のように指定しました。
const cfnProject = this.runnerProject.node.defaultChild as codebuild.CfnProject;
cfnProject.addPropertyOverride('Environment.HostKernel', 'LINUX_KERNEL_6');
workflowにはbuildspec-override:trueを付けて、PRE_BUILDでTetragonを起動できるようにしています。AWSのrunner設定手順
runs-on:
- codebuild-tetragon-codebuild-guard-${{ github.run_id }}-${{ github.run_attempt }}
- buildspec-override:true
- tetragon-${{ matrix.mode }}
ポリシーを入れる
条件はシンプルに、/usr/bin/curlが127.0.0.0/8以外へ接続しようとしたらSIGKILL、にしました。
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: block-curl-egress
spec:
kprobes:
- call: tcp_connect
syscall: false
args:
- index: 0
type: sock
selectors:
- matchArgs:
- index: 0
operator: NotDAddr
values:
- 127.0.0.0/8
matchBinaries:
- operator: In
values:
- /usr/bin/curl
matchActions:
- action: Sigkill
これを、ポリシーなしのbaseline、記録だけ行うobserve、実際に止めるenforceの3パターンで実行します。Tetragon自体は3つとも動かしています。
observeとenforceは、同じYAMLを読み込むときのモードを変えるだけです。ファイルをコピーしたTetragonコンテナ内で、次のコマンドを使います。それぞれ別のrunnerで実行しています。
# observe
tetra tracingpolicy add --mode monitor /tmp/block-curl-egress.yaml
# enforce
tetra tracingpolicy add --mode enforce /tmp/block-curl-egress.yaml
実行結果
| モード | ポリシーの接続イベント | curlの終了結果 | ダミー値の受信 |
|---|---|---|---|
| baseline | 0件 | exit 0 | あり |
| observe | 1件 | exit 0 | あり |
| enforce | 1件 | SIGKILL | なし |
enforceだけ、curlがSIGKILLで終了して受信記録が残りませんでした。baselineとobserveでは送信が完了し、npm ciも正常終了しています。
enforceのjobも緑ですが、これは「止められたら検証成功」にしているためです。模擬攻撃のstepはcontinue-on-errorで続行し、後続のstepで結果を判定しています。画面下にあるenforceのエラーは、その模擬攻撃が失敗した記録です。
postinstallのログにも、curlの終了理由が出ています。
The simulated request did not complete: SIGKILL
判定では、ポリシーの接続先がテスト用サーバーと一致すること、curlの終了signalがSIGKILLであること、受信記録がないことを確認しました。
おわりに
今回、Tetragonが「この依存パッケージは悪意がある」と判断したわけではなく、curlと接続先の条件で止めています。この設定のままだと、同じ条件に当てはまる普通のダウンロードも止めてしまいます。実際のCIで使うなら、まずobserveで普段どんな通信をしているかを見て、許可するものを整理してからenforceを試したいです。
今度は、CodeBuild上でプロセスの親子関係を追えるか確認して、npmから起動されたcurlに対象を絞れるかも試してみたいです。



