Kubernetesスペシャリストが注目する関連ツール探求 21

「Tetragon」でKubernetesネイティブのランタイム制御を試してみよう

第21回の今回は、eBPFを活用したランタイムセキュリティツール「Tetragon」でLinux上のプロセス実行時の振る舞いを監視・制御する方法について解説します。

羽山 公平

6:30

はじめに

3-shakeのSreake事業部所属の羽山(@hymaaa_k)です。

コンテナ基盤の運用やAI Agent利用時に「このプロセスが想定外のファイルにアクセスしていないか」「不審な子プロセスが起動していないか」をリアルタイムで把握・制御したくなる場面があるのではないでしょうか。脆弱性スキャンは事前の静的チェックには有効です。一方で、実行時の動的な振る舞いは追えません。

本記事では、eBPFを活用したランタイムセキュリティツール「Tetragon」を使って、Linux上のプロセスの実行時の振る舞いを監視・制御する方法を紹介します。

eBPFとTetragon

eBPFとは

Linuxには「ユーザ空間」と「カーネル空間」という2つの処理領域があります。通常のアプリケーションはユーザ空間で動作し、syscallを通じてカーネル空間の機能を利用します。

eBPFはカーネル空間にサンドボックス化された実行環境を持つ機能です。JIT(Just-In-Time)コンパイラを通じて、ユーザが定義した処理をカーネルのソースコード変更やモジュールなしにカーネル空間に組み込むことができます。

【出典】https://ebpf.io/static/e293240ecccb9d506587571007c36739/f2674/overview.png

Tetragonとは

TetragonはCNCFが管理するCiliumプロジェクトの傘下のOSSです。eBPFの特性を活かし、プロセスの実行・syscallのアクティビティ・ネットワーク通信・ファイルアクセスといったシステム上のイベントをフックして、観測(Observability)と実行時の強制対応(Runtime Enforcement)を提供します。

【出典】https://tetragon.io/images/smart_observability.png

観測できるイベントはkprobe/kretprobeでフックしたカーネル内の関数など幅広く、検知したイベントに対してSIGKILLの送信やsyscallの戻り値を書き換えて失敗させるといった強制対応をカーネル内で即座に実行できます。

観測したイベントにはPod名・Namespace・ラベルといったKubernetesのメタデータが自動で付与されるため、ログを見た瞬間に“どのPodの挙動か”が分かります。

ポリシーはKubernetesのCRDであるTracingPolicyとして定義するため、既存のKubernetesワークフローに自然に組み込めます。

Tetragonのインストール

本記事はローカルの kind クラスター上で検証します。検証には、TetragonがインストールされたKubernetes環境が必要です。

詳細なインストール手順については、Tetragon公式ドキュメントのインストールガイドを参照してください。

本記事の検証で利用するTetragonのオプション

本記事の動作確認で使用するTracingPolicyを機能させるため、Tetragonのインストール時に以下のオプションの設定が必要です。

helm install tetragon cilium/tetragon -n kube-system \  
  --set rthooks.enabled=true \ 
  --set rthooks.interface=nri-hook \ 
  --set "tetragon.extraArgs.disable-kprobe-multi=true"
  • rthooks.enabled=true
    • Runtime hooksを有効化する設定
  • rthooks.interface=nri-hook
    • Runtime hooksのインターフェースでNRI(Node Resource Interface)を使用する設定
  • tetragon.extraArgs.disable-kprobe-multi=true<>
    • kprobe_multiインターフェースを無効化する設定

TracingPolicyの構造

TetragonのポリシーはTracingPolicyというCRDで定義します。主要なフィールドは以下の3つです。

  • kprobes: フックするカーネル関数と取得する引数を定義する
  • selectors: どのイベントに反応するかの条件(ファイルパス・プロセス名・コンテナイメージなど)
    • matchArgs
    • matchBinaries
  • actions: 条件にマッチしたときの動作(Post・SIGKILL・Overrideなど)
    • Post: イベントをTetragonエージェントへ送信(デフォルト)
    • Sigkill: 条件にマッチしたプロセスをカーネルから直接強制終了
    • Override: カーネル関数の戻り値を指定した値(主にエラーコード)に書き換え
spec:
  kprobes:
  - call: "..."       # フックする関数
    args: [...]       # 取得する引数
    selectors:        # 絞り込み条件
    - matchArgs: ...
      matchActions:   # マッチ時の動作
      - action: Post

コンテナ内のファイルアクセスを検知する

Tetragonのポリシーを適用して、実際にカーネルの動作を検知できるか確認します。今回は/etc/passwdへのファイルアクセスをフックするポリシーを書いていきます。

security_file_permission

ファイルの読み書きが行われる際、カーネルは内部で以下のような呼び出しチェーンを経由します。

ユーザー空間: read() / write() syscall
     ↓
カーネル: vfs_read / vfs_write  (VFSレイヤー)
     ↓
カーネル: security_file_permission  (LSMフック)
     ↓
実際のファイルI/O

security_file_permissionはLSM(Linux Security Module)フックと呼ばれる、カーネルがセキュリティポリシーを差し込むための仕組みです。読み書きが発生するたびにこの関数が呼ばれ、アクセスを許可するかどうかを判断します。

この関数は2つの引数を受け取ります。

  • 第1引数(file構造体): アクセス対象のファイル情報(パスを含む)
  • 第2引数(int): アクセス種別(読み取り: 4、書き込み: 2)

TracingPolicyでは、これら引数を絞り込むことで「/etc/passwdへの読み取りだけを検知する」といったピンポイントなフックが実現できます。

動作確認

TracingPolicyの構造とカーネルのフックを元にポリシーを作成します。

apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: "file-access-detection"
spec:
  kprobes:
  - call: "security_file_permission"
    syscall: false #カーネル関数をフックするのでsyscallがfalse
    args:
    - index: 0
      type: "file" # <https://tetragon.io/docs/concepts/tracing-policy/argument_types/>
    - index: 1
      type: "int" # <https://tetragon.io/docs/concepts/tracing-policy/argument_types/>
    selectors:
    - matchArgs:
      - index: 0
        operator: "Equal"
        values:
        - "/etc/passwd"

ポリシーを適用後、tetra getevents -o compactでイベントをストリーム監視します。compact形式はログを整形して「何が起きたか」を一行で追えるビューです。

# イベントを監視(compact形式)
kubectl exec -n kube-system ds/tetragon -c tetragon -- tetra getevents -o compact | grep read

別ターミナルでnginxコンテナを起動し、/etc/passwdにアクセスします。

kubectl run nginx --image=nginx --restart=Never
kubectl exec nginx -- cat /etc/passwd

監視ターミナルに以下のように出力されます。

📚 read    default/nginx /usr/bin/cat /etc/passwd

security_file_permissionがフックされ、default Namespaceのnginx Podで/usr/bin/cat/etc/passwdにアクセスしたことが検知できました。

次に、export-stdoutコンテナのJSONログで実際にどのようなデータが取れているか確認します。

kubectl logs -n kube-system ds/tetragon -c export-stdout -f
{
  "process_kprobe": {
    "process": {
      "binary": "/usr/bin/cat",
      "arguments": "/etc/passwd",
      "pod": {
        "namespace": "default",
        "name": "nginx",
        "container": {
          "image": {
            "name": "docker.io/library/nginx:latest"
          }
        }
      }
    },
    "function_name": "security_file_permission",
    "args": [
      {"file_arg": {"path": "/etc/passwd", "permission": "-rw-r--r--"}},
      {"int_arg": 4}
    ],
    "action": "KPROBE_ACTION_POST",
    "policy_name": "file-access-detection"
  },
  "node_name": "kind-control-plane"
}

binaryargsといったカーネルレベルの情報に加え、pod.namespacepod.namepod.container.imageといったKubernetesのメタデータが同一イベントに付与されています。

これらの情報はセレクターの条件として使用でき、「特定のバイナリを実行したとき」「特定のコンテナイメージのPodだけ」といったピンポイントな絞り込みが可能です。

メタデータを使った絞り込み

外部公開されやすいnginxのようなコンポーネントだけ監視を行いたいというケースのために、pod.container.imageを利用してnginxのコンテナイメージだけ監視してみます。

containerSelectorでコンテナ名やコンテナイメージを使ったフィルタリングを行います。

apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: "file-access-detection"
spec:
  containerSelector:
    matchExpressions:
      - key: repo
        operator: In
        values:
        - nginx
  kprobes:
  - call: "security_file_permission"
    syscall: false # カーネル関数をフックするのでsyscallがfalse
    args:
    - index: 0
      type: "file" # <https://tetragon.io/docs/concepts/tracing-policy/argument_types/>
    - index: 1
      type: "int" # <https://tetragon.io/docs/concepts/tracing-policy/argument_types/>
    selectors:
    - matchArgs:
      - index: 0
        operator: "Equal"
        values:
        - "/etc/passwd"

ポリシーを適用後、nginxコンテナから /etc/passwd にアクセスすると検知されます。

📚 read    default/nginx /usr/bin/cat /etc/passwd

一方、busyboxコンテナから同様にアクセスしてもイベントは流れてきません。

containerSelector によってnginxイメージのコンテナのみに絞り込まれていることが確認できます。

強制対応で封じ込める

上記の例ではactionを指定せず、関数をフックして監査ログを取得するにとどまっていました。次は実際にTetragonの強みである強制対応を利用して、不審な動作を検知したら即座に対応する方法を体験します。

SIGKILL: プロセスごと止める

不審な操作を検知したら、プロセスを即座に強制終了させるSigkill actionを追加します。matchBinariesで対象バイナリを/usr/bin/catに絞ることで、catコマンドが/etc/passwdにアクセスした瞬間にプロセスをkillするポリシーを定義します。

apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: "file-access-sigkill"
spec:
  kprobes:
  - call: "security_file_permission"
    syscall: false
    args:
    - index: 0
      type: "file"
    - index: 1
      type: "int"
    selectors:
    - matchArgs:
      - index: 0
        operator: "Equal"
        values:
        - "/etc/passwd"
      matchBinaries:
      - operator: "In"
        values:
        - "/usr/bin/cat"
      matchActions:
      - action: Sigkill

ポリシーを適用後、nginxコンテナで /etc/passwd にアクセスするとcatが強制終了されます。

# 監視ターミナル
💥 exit    default/nginx /usr/bin/cat /etc/passwd SIGKILL

# 実行ターミナル
command terminated with exit code 137

exit code 137はSIGKILL(128 + 9)を意味し、プロセスがカーネル内で即座に終了させられたことが確認できます。

Override: 操作だけ失敗させる

Sigkillはプロセスごと終了させますが、アプリケーションを止めたくない場面ではOverride actionが有効です。Overrideはフックした関数の戻り値を書き換えてエラーを返し、プロセス自体は生かしたまま対象の操作だけを失敗させることができます。

先ほどのポリシーのactionSigkillからOverrideに差し替えます。argError: -1でEPERM(Operation not permitted)を返すよう指定します。

apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: "file-access-override"
spec:
  kprobes:
  - call: "security_file_permission"
    syscall: false
    args:
    - index: 0
      type: "file"
    - index: 1
      type: "int"
    selectors:
    - matchArgs:
      - index: 0
        operator: "Equal"
        values:
        - "/etc/passwd"
      matchBinaries:
      - operator: "In"
        values:
        - "/usr/bin/cat"
      matchActions:
      - action: Override
        argError: -1

ポリシーを適用後、同様に/etc/passwdにアクセスすると出力が変わります。

# 監視ターミナル
📚 read    default/nginx /usr/bin/cat /etc/passwd
💥 exit    default/nginx /usr/bin/cat /etc/passwd 1

# 実行ターミナル
cat: /etc/passwd: Operation not permitted
command terminated with exit code 1

exit code 1はcatがエラーを受け取って自ら終了したことを意味します。

Sigkill137(カーネルによる強制終了)とは異なり、Overrideはシステムコールの戻り値を書き換えるだけなので、実際のアプリケーションであればエラーをハンドリングして処理を継続できます。

actionプロセスの継続用途
Sigkill×
(即時終了)
危険なプロセスを即座に排除したい場合
Override
(操作だけ失敗)
アプリを止めずに特定の操作だけをブロックしたい場合

Webサーバのように常時稼働が求められるアプリケーションでは、プロセスごと落とすと可用性に影響します。Overrideを使えばアプリケーションを動かしたまま、機密ファイルへのアクセスだけをカーネルレベルで遮断できます。

まとめ

Tetragonを利用したKubernetesネイティブなランタイム制御はいかがでしたか?

SeccompなどのLinuxネイティブなセキュリティ機能と比較したときのTetragonの強みは、syscallレベルでプロセスの振る舞いを制御できる点、特定のバイナリ・コンテナイメージ・ファイルパスなどの条件を組み合わせてポリシーを組める柔軟さ、そしてそれらをKubernetesのCRDとしてYAMLで完結できる手軽さにあります。

eBPFとKubernetesメタデータを組み合わせることで、カーネルレベルの強力な観測・制御を既存のKubernetesワークフローに自然に組み込めるTetragonは、コンテナ基盤におけるランタイムセキュリティのガードレールとして最適解の1つです。今後さらに注目されていくはずなので、ぜひ試してみてください。

【参考】

この記事をシェアしてください

人気記事トップ10

人気記事ランキングをもっと見る

企画広告も役立つ情報バッチリ! Sponsored