Tetragon を使って k8s の Observability を強化する
概要
tetragon について
Tetragon は extended Berkeley Packet Filter (eBPF) を利用して k8s pod runtime のプロセスや kernel の関数の実行に関するイベントを収集・監視するための OSS プロジェクトです。
tetragon は Isovalent が管理する OSS プロジェクトであり、他のプロダクトには k8s CNI で有名な Cilium や Hubble があります。tetragon は元々 cilium の機能の一部として開発されてきましたが、2022 年に独立した OSS project という位置づけになったそうです。
tetragon でできること
tetragon サイトのトップページにある Revolutionize Your Observability and Security に記載されている通り、k8s 環境における Observability とセキュリティの向上を目的としています。
- Monitor Process Execution
- pod 内のプロセスに関するイベントを記録し、プロセスの生成 ~ 終了までのライフサイクルを監視する。
- Runtime Security Policies
- コンテナのセキュリティポリシーや不要な権限の監視。
- Real Time Enforcement
- eBPF を利用した CRD により独自のポリシーを動的にカーネルに反映させる。
Observability の 3 大要素 (log, metrics, trace) では、k8s クラスタの node や pod, pod 間通信などの状況は逐次観測することができますが、pod のコンテナ内でどのようなプロセスの生成や権限の昇格が行われているかについてはほとんどカバーすることができません(logs 等を頑張ればできるかもしれませんが)。そのため、もし pod やコンテナ、コンテナイメージのセキュリティ対応が不十分な場合、コンテナ上で不適切な権限でプロセスが実行されたり、不正アクセスによって悪意あるプログラムを実行されるような状況が発生する可能性があります。tetragon ではそのような操作をイベントとして監視し、eBPF を用いたポリシーを動的に適用することで悪意あるプロセス実行をカーネルのレイヤーから強制的に終了させる等が実現できるようになっています。
この記事でやること
ドキュメントでは Use Cases で tetragon を使ってどのようなことができるか使用例が示されているため、この記事では上記の内容に沿った検証を行って tetragon の動作や記録されるイベントの詳細を確認したいと思います。
また、tetragon では eBPF を使ったポリシーを使用する都合上 Linux カーネルや eBPF の知識が多少必要になってきます。ただ私自身カーネルの知識はほとんどなくこのあたりを突き詰めていくと泥沼にはまりそうなので、必要になった際に最小限触れる程度にしていきます。
インストール
検証に使用する k8s クラスタは control-plane 用のノード x1, worker node x2 で構成します。
$ k get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-m1 Ready control-plane 46h v1.29.4 10.0.0.30 <none> Ubuntu 23.04 6.2.0-34-generic containerd://1.7.16
k8s-w1 Ready <none> 46h v1.29.4 10.0.0.31 <none> Ubuntu 23.04 6.2.0-34-generic containerd://1.7.16
k8s-w2 Ready <none> 46h v1.29.4 10.0.0.32 <none> Ubuntu 23.04 6.2.0-34-generic containerd://1.7.16
tetragon は eBPF を使う都合上、前提条件として ノードの kernel version が 4.19 以上である必要があります。
Tetragon needs Linux kernel version 4.19 or greater.
上記の k get node
の KERNEL-VERSION
から各ノードの kernel version が確認できます。今回は 6.2.0
なので ok。新し目の OS を使っていればだいたい条件は満たせると思います。
tetragon はインストール手順 https://tetragon.io/docs/installation/kubernetes/ の通り helm でインストールします。
helm repo add cilium https://helm.cilium.io
helm repo update
helm install tetragon cilium/tetragon -n kube-system
helm でのインストールでは operator と tetragon 本体の daemonset が構築されます。
$ k get pod -n kube-system -o wide -l app.kubernetes.io/instance=tetragon
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
tetragon-9x6sd 2/2 Running 0 47h 10.0.0.30 k8s-m1 <none> <none>
tetragon-m8mnc 2/2 Running 0 47h 10.0.0.32 k8s-w2 <none> <none>
tetragon-operator-867595557c-2x9z7 1/1 Running 0 47h 10.244.2.2 k8s-w2 <none> <none>
tetragon-pz5gh 2/2 Running 0 47h 10.0.0.31 k8s-w1 <none> <none>
これで tetragon を使う準備ができたので、Use Cases で紹介されている機能を試していきます。
プロセスのライフサイクルの監視
プロセスイベントの監視
POD 内のコンテナ上で(PID 1 で起動しているプロセスとは別の)何らかの新しいプロセスが開始して終了する際、Linux 的にはプロセスの開始時に fork → exec, プロセス終了時に exit のシステムコールが実行されています。tetragon ではデフォルトでこれらのシステムコールが呼ばれた際にイベントとして json 形式の記録を収集しており、コンテナ内でプロセスがいつ始まりいつ終わったかというライフサイクルを監視することが可能となっています。
ドキュメントではデモ用のアプリケーションを使ってライフサイクルイベントの確認を行っていますが、ここでは自分で作成した pod に対してイベントを確認してみます。
まずはじめに、シェルスクリプト内で別のシェルスクリプトを実行する検証用の簡単なコンテナイメージを作成します。
FROM ubuntu
COPY sub.sh /
COPY subsub.sh /
ENTRYPOINT [ "" ]
CMD [ "" ]
シェルスクリプト sub.sh
内では subsub.sh
を呼ぶように設定。
#!/bin/bash
echo "Start sub.sh"
sleep 5
/subsub.sh
sleep 5
echo "Finish sub.sh"
#!/bin/bash
echo "Start subsub.sh"
sleep 5
echo "Finish subsub.sh"
これを registry.centre.com:5000/process-monitor
としてビルドし、以下のマニフェストを使って pod を作成します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: process-monitor
namespace: myapp
labels:
app: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: process-monitor
image: registry.centre.com:5000/process-monitor
command:
- sleep
- "3600"
tetragon では各ノード上に配置される tetragon pod がノード上の他の pod で発生するイベントを自動で収集し、tetragon の pod log から確認できるようになっています。これは k logs -n kube-system [tetragon-pod-name] export-stdout
で確認できますが、ドキュメントの通り始めは pod 内の tetra CLI を使ってイベントを取得した方が何が起こっているかわかりやすいため、こちらの方法で見てみます。
まず pod 名と pod が配置されている node を確認。pod 名は process-monitor-98f96db4d-nsc59
で node は k8s-w2
。
$ k get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
process-monitor-98f96db4d-nsc59 1/1 Running 0 18m 10.244.2.15 k8s-w2 <none> <none>
上記で確認した node 上で起動している tetragon pod 名を確認。下記の例では tetragon-m8mnc
。
$ k get pod -o wide -n kube-system -l app.kubernetes.io/instance=tetragon
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
tetragon-9x6sd 2/2 Running 0 2d 10.0.0.30 k8s-m1 <none> <none>
tetragon-m8mnc 2/2 Running 0 2d 10.0.0.32 k8s-w2 <none> <none>
tetragon-operator-867595557c-2x9z7 1/1 Running 0 2d 10.244.2.2 k8s-w2 <none> <none>
tetragon-pz5gh 2/2 Running 0 2d 10.0.0.31 k8s-w1 <none> <none>
tetragon pod 内の tetragon
コンテナで tetra getevents
コマンドを実行。オプションは以下。
-
-o compact
: 取得されるイベントを簡易表示する -
--pods [pod_name]
: イベント収集対象の pod 名を指定
$ k exec -n kube-system -it tetragon-m8mnc -c tetragon -- tetra getevents -o compact --pods process-monitor-98f96db4d-nsc59
これで pod process-monitor-98f96db4d-nsc59
で発生するイベントを待ち受ける状態になり、イベントが発生した際にターミナル上に出力されます。
別のターミナル (terminal 2 とする) を開き、pod 内で先ほど作成した sub.sh
を実行します。
$ k exec -it process-monitor-98f96db4d-nsc59 -- /sub.sh
Start sub.sh
Start subsub.sh
Finish subsub.sh
Finish sub.sh
pod 内で sub.sh → subsub.sh が実行されるので、シェルスクリプト内の echo や sleep の実行結果が terminal 2 上に出力されます。一方で terminal 1 上ではこれらのプロセスの作成や終了に関する多数のイベントが出力されます。
$ k exec -n kube-system -it tetragon-m8mnc -c tetragon -- tetra getevents -o compact --pods process-monitor-98f96db4d-nsc59
🚀 process myapp/process-monitor-98f96db4d-nsc59 /sub.sh /sub.sh
❓ syscall myapp/process-monitor-98f96db4d-nsc59 /sub.sh commit_creds
🚀 process myapp/process-monitor-98f96db4d-nsc59 /usr/bin/sleep 5
💥 exit myapp/process-monitor-98f96db4d-nsc59 /usr/bin/sleep 5 0
❓ syscall myapp/process-monitor-98f96db4d-nsc59 /sub.sh commit_creds
🚀 process myapp/process-monitor-98f96db4d-nsc59 /subsub.sh /subsub.sh
❓ syscall myapp/process-monitor-98f96db4d-nsc59 /subsub.sh commit_creds
🚀 process myapp/process-monitor-98f96db4d-nsc59 /usr/bin/sleep 5
💥 exit myapp/process-monitor-98f96db4d-nsc59 /usr/bin/sleep 5 0
💥 exit myapp/process-monitor-98f96db4d-nsc59 /subsub.sh /subsub.sh 0
❓ syscall myapp/process-monitor-98f96db4d-nsc59 /sub.sh commit_creds
🚀 process myapp/process-monitor-98f96db4d-nsc59 /usr/bin/sleep 5
💥 exit myapp/process-monitor-98f96db4d-nsc59 /usr/bin/sleep 5 0
💥 exit myapp/process-monitor-98f96db4d-nsc59 /sub.sh /sub.sh 0
各列の内容は以下のようになっています。
- 1 列目: イベントの種類
-
process
がプロセスの作成、exit
がプロセスの終了、syscall
が内部的なシステムコールに対応。
-
- 2 列目: イベント発生元の pod 名。
- 今回はイベント収集対象を
--pods process-monitor-98f96db4d-nsc59
で指定しているの でこれのみ。
- 今回はイベント収集対象を
- 3 列目以降: 実行プロセス、実行時引数やシステムコールの内容
例えば 3, 4 行目は sub.sh 内の sleep 5
に対応していますが、これは sleep コマンドを実行するために内部的にプロセスが exec で実行されたのち exit で終了されるというような具合になっています。
イベント詳細の確認
上記の出力結果は -o compact
を指定した際の簡易表記であるため、今度は各イベントの詳細について見ていきます。一度コマンドを中断し、-o compact
を外して同じコマンドを実行してイベントを待ち受けます。terminal 2 にて再度 sub.sh を実行すると、今度は上記のイベントの詳細が json 形式で出力されます。
$ k exec -n kube-system -it tetragon-m8mnc -c tetragon -- tetra getevents --pods process-monitor-98f96db4d-nsc59
{"process_exec":{"process":{"exec_id":"azhzLXcyOjE3ODgwOTg4MTU5MDQ2NToxMjE1NzQ5","pid":1215749,"uid":0,"cwd":"/","binary":"/sub.sh","arguments":"/sub.sh","flags":"execve rootcwd clone","start_time":"2024-05-02T16:11:28.000705650Z","auid":4294967295,"pod":{"namespace":"myapp","name":"process-monitor-98f96db4d-nsc59","container":{"id":"containerd://59ae3c4ce0ac30ff87ec7037020f767bfcf35b4f84c8f4d7172fb4f75a7ab89f","name":"process-monitor","image":{"id":"registry.centre.com:5000/process-monitor@sha256:dd77f475ef3cf922bad4e33d6795ecd1d4dfa2ad6eb0700fa8d79ea620385b1e","name":"registry.centre.com:5000/process-monitor:latest"},"start_time":"2024-05-02T15:36:40Z","pid":47},"pod_labels":{"app":"myapp","pod-template-hash":"98f96db4d"},"workload":"process-monitor","workload_kind":"Deployment"},"docker":"59ae3c4ce0ac30ff87ec7037020f767","parent_exec_id":"azhzLXcyOjE3NjcyMjMzMTAxMzAxNDoxMjAxMjg1","tid":1215749},"parent":{"exec_id":"azhzLXcyOjE3NjcyMjMzMTAxMzAxNDoxMjAxMjg1","pid":1201285,"uid":0,"cwd":"/run/containerd/io.containerd.runtime.v2.task/k8s.io/1f90009cb0f20435e42a4ec951b6ae592ea2af1065b65d87d85eba375af18d61","binary":"/usr/local/bin/containerd-shim-runc-v2","arguments":"-namespace k8s.io -id 1f90009cb0f20435e42a4ec951b6ae592ea2af1065b65d87d85eba375af18d61 -address /run/containerd/containerd.sock","flags":"execve clone","start_time":"2024-05-02T15:36:40.450126392Z","auid":4294967295,"parent_exec_id":"azhzLXcyOjE3NjcyMjMyNTYzNTY2MjoxMjAxMjc4","tid":1201285}},"node_name":"k8s-w2","time":"2024-05-02T16:11:28.000704070Z"}
...
まずは process myapp/process-monitor-98f96db4d-nsc59 /sub.sh /sub.sh
に対応するイベントの詳細を見てみます。イベント詳細はフィールド数が多くて見づらいので、可読性のため適宜 json → yaml 形式に変換して確認します。
process_exec:
process:
exec_id: azhzLXcyOjE3ODgwOTg4MTU5MDQ2NToxMjE1NzQ5
pid: 1215749
uid: 0
cwd: /
binary: /sub.sh
arguments: /sub.sh
flags: execve rootcwd clone
start_time: "2024-05-02T16:11:28.000705650Z"
auid: 4294967295
pod:
namespace: myapp
name: process-monitor-98f96db4d-nsc59
container:
id: containerd://59ae3c4ce0ac30ff87ec7037020f767bfcf35b4f84c8f4d7172fb4f75a7ab89f
name: process-monitor
image:
id: registry.centre.com:5000/process-monitor@sha256:dd77f475ef3cf922bad4e33d6795ecd1d4dfa2ad6eb0700fa8d79ea620385b1e
name: registry.centre.com:5000/process-monitor:latest
start_time: "2024-05-02T15:36:40Z"
pid: 47
pod_labels:
app: myapp
pod-template-hash: 98f96db4d
workload: process-monitor
workload_kind: Deployment
docker: 59ae3c4ce0ac30ff87ec7037020f767
parent_exec_id: azhzLXcyOjE3NjcyMjMzMTAxMzAxNDoxMjAxMjg1
tid: 1215749
parent:
exec_id: azhzLXcyOjE3NjcyMjMzMTAxMzAxNDoxMjAxMjg1
pid: 1201285
uid: 0
cwd: /run/containerd/io.containerd.runtime.v2.task/k8s.io/1f90009cb0f20435e42a4ec951b6ae592ea2af1065b65d87d85eba375af18d61
binary: /usr/local/bin/containerd-shim-runc-v2
arguments: -namespace k8s.io -id 1f90009cb0f20435e42a4ec951b6ae592ea2af1065b65d87d85eba375af18d61 -address /run/containerd/containerd.sock
flags: execve clone
start_time: "2024-05-02T15:36:40.450126392Z"
auid: 4294967295
parent_exec_id: azhzLXcyOjE3NjcyMjMyNTYzNTY2MjoxMjAxMjc4
tid: 1201285
node_name: k8s-w2
time: "2024-05-02T16:11:28.000704070Z"
イベント内の各フィールドにはそのイベントが発生した node や pod、コマンドの詳細などが記載されています。重要そうなものについていくつか見てみます。
process_exec
トップレベルの process_exec
はこれが process が生成された際(process の exec)に対応するイベントであることを示しています。
parent
parent フィールドにはプロセスの親プロセスの情報等が記載されます。上記のイベントはターミナルから直接 pod 内の sub.sh
を実行した際の記録ですが、この場合はコンテナのプロセス、つまり pod が起動している node 上のコンテナに対応するプロセスに関する情報が記載されています。例えば今回の k8s クラスタではコンテナランタイムに containerd を使用しているため、このコンテナに対応するプロセスは binary: /usr/local/bin/containerd-shim-runc-v2
となっています。また、cwd や arguments に含まれる 1f90009cb0f2...
は node 上で実際に起動しているコンテナの id に対応しており、対象ノード上でプロセスや id でコンテナを検索することで確認できます。
$ ps aux | grep -v grep | grep 1f90009cb0f20435e42a4ec951b6ae592ea2af1065b65d87d85eba375af18d61
root 1201285 0.0 0.1 1238452 14328 ? Sl 15:36 0:00 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 1f90009cb0f20435e42a4ec951b6ae592ea2af1065b65d87d85eba375af18d61 -address /run/containerd/containerd.sock
$ sudo nerdctl -n k8s.io ps -f id=1f90009cb0f2
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1f90009cb0f2 registry.k8s.io/pause:3.6 "/pause" 58 minutes ago Up k8s://myapp/process-monitor-98f96db4d-nsc59
process
process フィールドには対象のプロセスに関する詳細が記載されます。重要そうなものは以下。
-
process.binary
では実行プロセス(ファイルシステム上のパス)、process.arguments
ではそのプロセスに渡された引数が記載されます。このイベントは terminal 2 から pod 内の/sub.sh
を実行したという内容なのでそれぞれ /sub.sh が入っています。 -
process.uid
はプロセスを実行した pod 内の ユーザー id が記載されます。今回は特にユーザーを指定していないので、process-test のベースイメージubuntu
のデフォルトユーザー root の uid 0 が入っています。 -
process.flags
は gRPC API Reference の flags に記載のとおり対応するシステムコールに関する情報が記載されます。ただあくまでデバッグ目的で記載されるだけであり、信頼できる情報であるとみなすべきではないとのこと。 -
process.pod
ではプロセスが実行された pod やコンテナに関する情報が記載されます。
その他のフィールドは Process に説明が記載されています、
node_name
プロセスが実行された pod が存在するノード名が記載されます。
上記の通りイベントの詳細を見ることで実行されたプロセスや pod, node に関する多くの情報を引き出せるようになっています。
他のイベントの詳細についても見てみます。たとえば sub.sh 内で sleep を実行した際のイベントは以下のようになっています。
process_exec:
process:
exec_id: azhzLXcyOjE3ODgwOTg4Mzg2NTQ4OToxMjE1NzU1
pid: 1215755
uid: 0
cwd: /
binary: /usr/bin/sleep
arguments: "5"
flags: execve rootcwd clone
start_time: "2024-05-02T16:11:28.002979631Z"
auid: 4294967295
...
parent:
exec_id: azhzLXcyOjE3ODgwOTg4MTU5MDQ2NToxMjE1NzQ5
pid: 1215749
uid: 0
cwd: /
binary: /sub.sh
arguments: /sub.sh
flags: execve rootcwd clone
start_time: "2024-05-02T16:11:28.000705650Z"
auid: 4294967295
始めのイベントとの違いは parent.binary
が /sub.sh になっていることです。sleep は /sub.sh プロセス (pid: 1215749) から実行される子プロセスという関係であるため、このイベントでは parent が /sub.sh に設定されているということになります。
その他、subsub.sh 内で実行される sleep に対応するイベントは以下。
process_exec:
process:
exec_id: azhzLXcyOjE3ODgxNDg5MTYzNDgyMzoxMjE1ODA0
pid: 1215804
uid: 0
cwd: /
binary: /usr/bin/sleep
arguments: "5"
flags: execve rootcwd clone
start_time: "2024-05-02T16:11:33.010748488Z"
auid: 4294967295
...
parent:
exec_id: azhzLXcyOjE3ODgxNDg4NzM3ODQyODoxMjE1ODAz
pid: 1215803
uid: 0
cwd: /
binary: /subsub.sh
arguments: /subsub.sh
flags: execve rootcwd clone
start_time: "2024-05-02T16:11:33.006492464Z"
この sleep は /subsub.sh 内で実行される子プロセスであるため、parent.binary は /subsub.sh に設定されています。というわけでこの sleep からプロセスを辿ると以下の親子関係であることがわかります。
/usr/local/bin/containerd-shim-runc-v2
└── /sub.sh
└── /subsub.sh
└── /usr/bin/sleep
このようにプロセスのイベントでは親プロセスの情報が記載されるため、あるプロセスから別のプロセスが実行されるような状況においてプロセスの親子関係を辿ることができます。ドキュメントではプロセス実行の監視を Observe the complete lifecycle of every process on your machine with Kubernetes context awareness と記載していますが、この表現の通りプロセス生成 ~ 終了までの詳細をプロセス間の関係を含めて追うことができるようになっています。
ファイルへのアクセスの監視
tetragon ではファイルへの read/write や様々な操作時に発生するイベントも補足できます。この機能はデフォルトで有効化されていないため、tetragon の カスタムリソースである TracingPolicy を作成して対象のファイルや操作を指定します。
ファイルへの read/write イベントを記録する
ドキュメントでは以下の TracingPolicy を作成することで、pod 内の /etc
以下のファイルに read/write アクセスが発生した際のイベントを取得できると記載されています。
TracingPolicy の内容を深堀りすると長くなるので一旦置いといて、ここでは上記の内容を参考に、指定したパスにあるファイルへのアクセスが発生した際にイベントが取得できるか検証してみます。
検証用のイメージ作成
動作を確認するために、flask を使って /read1, /read2 にアクセスしたら test1.txt, test2.txt のファイルを読み取って返すような python スクリプトを作成します。
from flask import Flask, request
app = Flask(__name__)
@app.route("/", methods=["GET", "POST"])
def route():
print(request.get_data())
return "ok"
@app.route("/read1")
def read():
with open("/test1.txt", "r") as f:
msg = f.read()
return msg
@app.route("/read2")
def read2():
with open("/test2.txt", "r") as f:
msg = f.read()
return msg
if __name__ == "__main__":
app.run(host="0.0.0.0")
対象ファイルは Dockerfile 内で作成します。
FROM python:3.12-slim
RUN pip install flask
RUN echo "this is text1" > /test1.txt && \
echo "this is text2" > /test2.txt
COPY . /
ENTRYPOINT [ "python" ]
CMD [ "/main.py" ]
これを使う pod を作成するマニフェスト
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: myapp
labels:
app: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: registry.centre.com:5000/myapp
ports:
- containerPort: 5000
---
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: myapp
labels:
run: myapp
spec:
ports:
- port: 5000
protocol: TCP
selector:
app: myapp
検証用の TracingPolicy 作成
TracingPolicy に関しては、ドキュメントのポリシーを参考に /test1.txt
への read アクセスが発生した際のイベントをキャッチするように記述します。比較のため /test2.txt
に対するポリシーは設定しません。
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: myapp-monitor
spec:
kprobes:
- call: "security_file_permission"
syscall: false
return: true
args:
- index: 0
type: "file" # (struct file *) used for getting the path
- index: 1
type: "int" # 0x04 is MAY_READ, 0x02 is MAY_WRITE
returnArg:
index: 0
type: "int"
returnArgAction: "Post"
selectors:
- matchArgs:
- index: 0
operator: "Equal"
values:
- "/test1.txt" # Reads to sensitive directories
- index: 1
operator: "Equal"
values:
- "4" # MAY_READ
動作確認
ライフサイクルのときと同様に対象の pod に対してイベントが発生するのを待ち受けます。
$ k exec -it -n kube-system daemonsets/tetragon -c tetragon -- tetra getevents -o compact --pods myapp-c67fd8d6c-p87s6
別のターミナルで [svc_ip]:5000/[path]
にリクエストを送信すると、それぞれファイルの内容を読み取ったレスポンスが返ってきます。
$ curl 10.103.222.137:5000/read1
this is text1
$ curl 10.103.222.137:5000/read2
this is text2
terminal 1 では python スクリプト (main.py) の中で text1.txt
の内容を読み取った時のイベントが記録されます。
$ k exec -it -n kube-system daemonsets/tetragon -c tetragon -- tetra getevents -o compact --pods myapp-c67fd8d6c-p87s6
📚 read myapp/myapp-c67fd8d6c-p87s6 /usr/local/bin/python /test1.txt
📚 read myapp/myapp-c67fd8d6c-p87s6 /usr/local/bin/python /test1.txt
📚 read myapp/myapp-c67fd8d6c-p87s6 /usr/local/bin/python /test1.txt
📚 read myapp/myapp-c67fd8d6c-p87s6 /usr/local/bin/python /test1.txt
イベント詳細を見ると、出力で確認したように 4 つのイベントが記録されています。
{"process_kprobe":{"process":{"exec_id":"azhzLXcyOjI2OTEwMjI5NjA4Mjc5ODoxODQwNTA3","pid":1840507,"uid":0,"cwd":"/","binary":"/usr/local/bin/python","arguments":"/main.py","flags":"execve rootcwd clone","start_time":"2024-04-30T10:43:44.471018318Z","auid":4294967295,"pod":{"namespace":"myapp","name":"myapp-c67fd8d6c-p87s6","container":{"id":"containerd://7506af60826e05f1d0a46ba0c76ac26b5f69cd8e4fbe213887d2fde278e1e7f9","name":"myapp","image":{"id":"registry.centre.com:5000/myapp@sha256:067e34df0cebadd38961dd91ef6a913d3b26528f72ab5c26076e229debdb3421","name":"registry.centre.com:5000/myapp:latest"},"start_time":"2024-04-30T10:43:44Z","pid":1},"pod_labels":{"app":"myapp","pod-template-hash":"c67fd8d6c"},"workload":"myapp","workload_kind":"Deployment"},"docker":"7506af60826e05f1d0a46ba0c76ac26","parent_exec_id":"azhzLXcyOjI2OTEwMTkwNzU4ODU4NjoxODQwNDU1","refcnt":1,"tid":1847109},"parent":{"exec_id":"azhzLXcyOjI2OTEwMTkwNzU4ODU4NjoxODQwNDU1","pid":1840455,"uid":0,"cwd":"/run/containerd/io.containerd.runtime.v2.task/k8s.io/6326d54e72528ec59d5b1d73723b7cbe192749146106aeac4274891ede4db19b","binary":"/usr/local/bin/containerd-shim-runc-v2","arguments":"-namespace k8s.io -id 6326d54e72528ec59d5b1d73723b7cbe192749146106aeac4274891ede4db19b -address /run/containerd/containerd.sock","flags":"execve clone","start_time":"2024-04-30T10:43:44.082523614Z","auid":4294967295,"parent_exec_id":"azhzLXcyOjI2OTEwMTg5NzM5NDExMjoxODQwNDQ3","tid":1840455},"function_name":"security_file_permission","args":[{"file_arg":{"path":"/test1.txt"}},{"int_arg":4}],"return":{"int_arg":0},"action":"KPROBE_ACTION_POST","policy_name":"myapp-monitor"},"node_name":"k8s-w2","time":"2024-04-30T10:48:04.302583244Z"}
{"process_kprobe":{"process":{"exec_id":"azhzLXcyOjI2OTEwMjI5NjA4Mjc5ODoxODQwNTA3","pid":1840507,"uid":0,"cwd":"/","binary":"/usr/local/bin/python","arguments":"/main.py","flags":"execve rootcwd clone","start_time":"2024-04-30T10:43:44.471018318Z","auid":4294967295,"pod":{"namespace":"myapp","name":"myapp-c67fd8d6c-p87s6","container":{"id":"containerd://7506af60826e05f1d0a46ba0c76ac26b5f69cd8e4fbe213887d2fde278e1e7f9","name":"myapp","image":{"id":"registry.centre.com:5000/myapp@sha256:067e34df0cebadd38961dd91ef6a913d3b26528f72ab5c26076e229debdb3421","name":"registry.centre.com:5000/myapp:latest"},"start_time":"2024-04-30T10:43:44Z","pid":1},"pod_labels":{"app":"myapp","pod-template-hash":"c67fd8d6c"},"workload":"myapp","workload_kind":"Deployment"},"docker":"7506af60826e05f1d0a46ba0c76ac26","parent_exec_id":"azhzLXcyOjI2OTEwMTkwNzU4ODU4NjoxODQwNDU1","refcnt":1,"tid":1847109},"parent":{"exec_id":"azhzLXcyOjI2OTEwMTkwNzU4ODU4NjoxODQwNDU1","pid":1840455,"uid":0,"cwd":"/run/containerd/io.containerd.runtime.v2.task/k8s.io/6326d54e72528ec59d5b1d73723b7cbe192749146106aeac4274891ede4db19b","binary":"/usr/local/bin/containerd-shim-runc-v2","arguments":"-namespace k8s.io -id 6326d54e72528ec59d5b1d73723b7cbe192749146106aeac4274891ede4db19b -address /run/containerd/containerd.sock","flags":"execve clone","start_time":"2024-04-30T10:43:44.082523614Z","auid":4294967295,"parent_exec_id":"azhzLXcyOjI2OTEwMTg5NzM5NDExMjoxODQwNDQ3","tid":1840455},"function_name":"security_file_permission","args":[{"file_arg":{"path":"/test1.txt"}},{"int_arg":4}],"return":{"int_arg":0},"action":"KPROBE_ACTION_POST","policy_name":"myapp-monitor"},"node_name":"k8s-w2","time":"2024-04-30T10:48:04.302621628Z"}
{"process_kprobe":{"process":{"exec_id":"azhzLXcyOjI2OTEwMjI5NjA4Mjc5ODoxODQwNTA3","pid":1840507,"uid":0,"cwd":"/","binary":"/usr/local/bin/python","arguments":"/main.py","flags":"execve rootcwd clone","start_time":"2024-04-30T10:43:44.471018318Z","auid":4294967295,"pod":{"namespace":"myapp","name":"myapp-c67fd8d6c-p87s6","container":{"id":"containerd://7506af60826e05f1d0a46ba0c76ac26b5f69cd8e4fbe213887d2fde278e1e7f9","name":"myapp","image":{"id":"registry.centre.com:5000/myapp@sha256:067e34df0cebadd38961dd91ef6a913d3b26528f72ab5c26076e229debdb3421","name":"registry.centre.com:5000/myapp:latest"},"start_time":"2024-04-30T10:43:44Z","pid":1},"pod_labels":{"app":"myapp","pod-template-hash":"c67fd8d6c"},"workload":"myapp","workload_kind":"Deployment"},"docker":"7506af60826e05f1d0a46ba0c76ac26","parent_exec_id":"azhzLXcyOjI2OTEwMTkwNzU4ODU4NjoxODQwNDU1","refcnt":1,"tid":1847109},"parent":{"exec_id":"azhzLXcyOjI2OTEwMTkwNzU4ODU4NjoxODQwNDU1","pid":1840455,"uid":0,"cwd":"/run/containerd/io.containerd.runtime.v2.task/k8s.io/6326d54e72528ec59d5b1d73723b7cbe192749146106aeac4274891ede4db19b","binary":"/usr/local/bin/containerd-shim-runc-v2","arguments":"-namespace k8s.io -id 6326d54e72528ec59d5b1d73723b7cbe192749146106aeac4274891ede4db19b -address /run/containerd/containerd.sock","flags":"execve clone","start_time":"2024-04-30T10:43:44.082523614Z","auid":4294967295,"parent_exec_id":"azhzLXcyOjI2OTEwMTg5NzM5NDExMjoxODQwNDQ3","tid":1840455},"function_name":"security_file_permission","args":[{"file_arg":{"path":"/test1.txt"}},{"int_arg":4}],"return":{"int_arg":0},"action":"KPROBE_ACTION_POST","policy_name":"myapp-monitor"},"node_name":"k8s-w2","time":"2024-04-30T10:48:04.302634756Z"}
{"process_kprobe":{"process":{"exec_id":"azhzLXcyOjI2OTEwMjI5NjA4Mjc5ODoxODQwNTA3","pid":1840507,"uid":0,"cwd":"/","binary":"/usr/local/bin/python","arguments":"/main.py","flags":"execve rootcwd clone","start_time":"2024-04-30T10:43:44.471018318Z","auid":4294967295,"pod":{"namespace":"myapp","name":"myapp-c67fd8d6c-p87s6","container":{"id":"containerd://7506af60826e05f1d0a46ba0c76ac26b5f69cd8e4fbe213887d2fde278e1e7f9","name":"myapp","image":{"id":"registry.centre.com:5000/myapp@sha256:067e34df0cebadd38961dd91ef6a913d3b26528f72ab5c26076e229debdb3421","name":"registry.centre.com:5000/myapp:latest"},"start_time":"2024-04-30T10:43:44Z","pid":1},"pod_labels":{"app":"myapp","pod-template-hash":"c67fd8d6c"},"workload":"myapp","workload_kind":"Deployment"},"docker":"7506af60826e05f1d0a46ba0c76ac26","parent_exec_id":"azhzLXcyOjI2OTEwMTkwNzU4ODU4NjoxODQwNDU1","refcnt":1,"tid":1847109},"parent":{"exec_id":"azhzLXcyOjI2OTEwMTkwNzU4ODU4NjoxODQwNDU1","pid":1840455,"uid":0,"cwd":"/run/containerd/io.containerd.runtime.v2.task/k8s.io/6326d54e72528ec59d5b1d73723b7cbe192749146106aeac4274891ede4db19b","binary":"/usr/local/bin/containerd-shim-runc-v2","arguments":"-namespace k8s.io -id 6326d54e72528ec59d5b1d73723b7cbe192749146106aeac4274891ede4db19b -address /run/containerd/containerd.sock","flags":"execve clone","start_time":"2024-04-30T10:43:44.082523614Z","auid":4294967295,"parent_exec_id":"azhzLXcyOjI2OTEwMTg5NzM5NDExMjoxODQwNDQ3","tid":1840455},"function_name":"security_file_permission","args":[{"file_arg":{"path":"/test1.txt"}},{"int_arg":4}],"return":{"int_arg":0},"action":"KPROBE_ACTION_POST","policy_name":"myapp-monitor"},"node_name":"k8s-w2","time":"2024-04-30T10:48:04.302644682Z"}
一番上のイベントについて見てみます。
process_kprobe:
process:
exec_id: azhzLXcyOjI2OTEwMjI5NjA4Mjc5ODoxODQwNTA3
pid: 1840507
uid: 0
cwd: /
binary: /usr/local/bin/python
arguments: /main.py
flags: execve rootcwd clone
start_time: "2024-04-30T10:43:44.471018318Z"
auid: 4294967295
pod:
namespace: myapp
name: myapp-c67fd8d6c-p87s6
container:
id: containerd://7506af60826e05f1d0a46ba0c76ac26b5f69cd8e4fbe213887d2fde278e1e7f9
name: myapp
image:
id: registry.centre.com:5000/myapp@sha256:067e34df0cebadd38961dd91ef6a913d3b26528f72ab5c26076e229debdb3421
name: registry.centre.com:5000/myapp:latest
start_time: "2024-04-30T10:43:44Z"
pid: 1
pod_labels:
app: myapp
pod-template-hash: c67fd8d6c
workload: myapp
workload_kind: Deployment
docker: 7506af60826e05f1d0a46ba0c76ac26
parent_exec_id: azhzLXcyOjI2OTEwMTkwNzU4ODU4NjoxODQwNDU1
refcnt: 1
tid: 1847109
parent:
exec_id: azhzLXcyOjI2OTEwMTkwNzU4ODU4NjoxODQwNDU1
pid: 1840455
uid: 0
cwd: /run/containerd/io.containerd.runtime.v2.task/k8s.io/6326d54e72528ec59d5b1d73723b7cbe192749146106aeac4274891ede4db19b
binary: /usr/local/bin/containerd-shim-runc-v2
arguments: -namespace k8s.io -id 6326d54e72528ec59d5b1d73723b7cbe192749146106aeac4274891ede4db19b -address /run/containerd/containerd.sock
flags: execve clone
start_time: "2024-04-30T10:43:44.082523614Z"
auid: 4294967295
parent_exec_id: azhzLXcyOjI2OTEwMTg5NzM5NDExMjoxODQwNDQ3
tid: 1840455
function_name: security_file_permission
args:
- file_arg:
path: /test1.txt
- int_arg: 4
return:
int_arg: 0
action: KPROBE_ACTION_POST
policy_name: myapp-monitor
node_name: k8s-w2
time: "2024-04-30T10:48:04.302583244Z"
フィールドの中身について
- トップレベルは
process_exec
ではなくprocess_kprobe
になっており、プロセスの生成ではなく TracingPolicy による kprobe の hook により発生したイベントとなっています。 -
process.binary: /usr/local/bin/python
,arguments: /main.py
により、/main.py
内で対象ファイルの読み取りに関する操作が発生していると判別できます。 -
function_name: security_file_permission
は TracingPolicy で指定した kprobes の関数名になっています。 -
action
は hook 時の Action が指定されます。TracingPolicy で何も指定しない場合はデフォルトでKPROBE_ACTION_POST
が指定されるようです(後述)。
今回 TracingPolicy で指定した security_file_permission
はカーネルの関数で実態は https://elixir.bootlin.com/linux/latest/source/security/security.c#L2672 で定義されてます。
/**
* security_file_permission() - Check file permissions
* @file: file
* @mask: requested permissions
*
* Check file permissions before accessing an open file. This hook is called
* by various operations that read or write files. A security module can use
* this hook to perform additional checking on these operations, e.g. to
* revalidate permissions on use to support privilege bracketing or policy
* changes. Notice that this hook is used when the actual read/write
* operations are performed, whereas the inode_security_ops hook is called when
* a file is opened (as well as many other operations). Although this hook can
* be used to revalidate permissions for various system call operations that
* read or write files, it does not address the revalidation of permissions for
* memory-mapped files. Security modules must handle this separately if they
* need such revalidation.
*
* Return: Returns 0 if permission is granted.
*/
int security_file_permission(struct file *file, int mask)
{
return call_int_hook(file_permission, 0, file, mask);
}
上記より関数の実行時に file オブジェクトと mask に対応する int を引数に取ることがわかります。そのため、TracingPolicy 内で定義されている args
の値はそれぞれこれに対応していることがわかります。
spec:
kprobes:
- call: "security_file_permission"
syscall: false
args:
- index: 0
type: "file" # (struct file *) used for getting the path
- index: 1
type: "int" # 0x04 is MAY_READ, 0x02 is MAY_WRITE
実際のイベント記録では file に read が行われた test1.txt の絶対パスが指定されており、int は 4 が渡されています。
args:
- file_arg:
path: /test1.txt
- int_arg: 4
この int は umask に対応しており https://elixir.bootlin.com/linux/latest/source/include/linux/fs.h#L97 あたりで定義されています。4 は MAY_READ なのでファイルへの read 操作となっています。
#define MAY_EXEC 0x00000001
#define MAY_WRITE 0x00000002
#define MAY_READ 0x00000004
#define MAY_APPEND 0x00000008
#define MAY_ACCESS 0x00000010
#define MAY_OPEN 0x00000020
#define MAY_CHDIR 0x00000040
/* called from RCU mode, don't block */
#define MAY_NOT_BLOCK 0x0000008
というわけで、イベント詳細の上記のあたりを見ることで /test1.txt に対して file read 操作が行われたということが確認できます(具体的には file permission で read 権限が付加されているか確認する際のイベントを見ていることになりますが)。
もう一度 TracingPolicy を見返してみると、selectors.matchArgs
で file と mask に対して条件を指定しています。これは security_file_permission
が発生するイベントに対して file が /test1.txt, mask が 4 = MAY_READ のときのイベントを記録するという条件になるので、他のファイルに対する操作や /test1.txt への write 操作などはイベントとして補足しないということになります。
selectors:
- matchArgs:
- index: 0
operator: "Equal"
values:
- "/test1.txt" # Reads to sensitive directories
- index: 1
operator: "Equal"
values:
- "4" # MAY_READ
そのため、/test1.txt に対する read イベントは terminal 1 に表示されますが、/test2.txt に対するイベントは補足対象外のため実行しても表示されない挙動になっています。
$ k exec -it -n kube-system daemonsets/tetragon -c tetragon -- tetra getevents -o compact --pods myapp-c67fd8d6c-p87s6
📚 read myapp/myapp-c67fd8d6c-p87s6 /usr/local/bin/python /test1.txt
📚 read myapp/myapp-c67fd8d6c-p87s6 /usr/local/bin/python /test1.txt
📚 read myapp/myapp-c67fd8d6c-p87s6 /usr/local/bin/python /test1.txt
📚 read myapp/myapp-c67fd8d6c-p87s6 /usr/local/bin/python /test1.txt
ファイルへの read/write アクセスを拒否する
TracingPolicy では matchActions を追加することで指定した条件を満たすイベントが発生した際にいろいろなアクションを実行することができます。デフォルトでは何も指定しない場合は Post Action が適用されるようになっています。
The Post action allows an event to be transmitted to the agent, from kernelspace to userspace. By default, all TracingPolicy hook will create an event with the Post action except in those situations:
a NoPost action was specified in a matchActions;
a rate-limiting parameter is in place, see details below.
This action allows you to specify parameters for the Post action.
event to be transmitted to the agent, from kernelspace to userspace
というのが具体的にどのようなことに対応しているのかはいまいちわかりませんが、今までの検証を見る限り tetragon pod がイベントを補足してログに出力する動作になっていると推測されます。
一方で Enforce File Access Monitoring に記載されている file_monitoring_enforce.yaml の Tracing Policy では、 action: Sigkill
を指定することでファイルへの read/write 操作が発生した際にそのプロセスに sigkill を送るようになっています。これにより read/write 操作を行うプロセスを kill することでファイルの読み取りを拒否する例となっています。
動作確認
上記の例に倣って、/test1.txt
に read 操作が発生した際に拒否する動作を確認してみます。
先ほど作成した TracingPolicy に matchActions.actions: Sigkill
を追加します。
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: myapp-monitor
spec:
kprobes:
- call: "security_file_permission"
syscall: false
return: true
args:
- index: 0
type: "file" # (struct file *) used for getting the path
- index: 1
type: "int" # 0x04 is MAY_READ, 0x02 is MAY_WRITE
returnArg:
index: 0
type: "int"
returnArgAction: "Post"
selectors:
- matchArgs:
- index: 0
operator: "Equal"
values:
- "/test1.txt" # Reads to sensitive directories
- index: 1
operator: "Equal"
values:
- "4" # MAY_READ
+ matchActions:
+ - action: Sigkill
上記のポリシーを適用し、先ほどと同様に /read1
にアクセスするとレスポンスが帰ってきません。
$ curl 10.103.222.137:5000/read1
curl: (52) Empty reply from server
イベントを確認すると exit myapp/myapp-c67fd8d6c-p87s6 /usr/local/bin/python /main.py SIGKILL
より /test1.txt
の読み取りを行うプロセスが kill されていることがわかります。
$ -it -n kube-system daemonsets/tetragon -c tetragon -- tetra getevents -o compact --pods myapp-c67fd8d6c-p87s6
📚 read myapp/myapp-c67fd8d6c-p87s6 /usr/local/bin/python /test1.txt
📚 read myapp/myapp-c67fd8d6c-p87s6 /usr/local/bin/python /test1.txt
💥 exit myapp/myapp-c67fd8d6c-p87s6 /usr/local/bin/python /main.py SIGKILL
🚀 process myapp/myapp-c67fd8d6c-p87s6 /usr/local/bin/python /main.py
また、これによりコンテナ内で起動していた /main.py
のプロセスが sigkill されていることがわかります。pod の詳細を確認するとコンテナの再起動により RESTARTS
の回数が増えていることが確認できます(このあたりはイメージ内のエラーハンドリングの処理によっても異なるかも)。
$ k get pod
NAME READY STATUS RESTARTS AGE
myapp-c67fd8d6c-p87s6 1/1 Running 2 (20s ago) 138m
read 操作は umask 4 でしたが write は 2, exec は 1 なので、TracingPolicy でこれらを指定すれば write 操作も sigkill できると想定されます。これを確認するため、前の python スクリプトを修正してアクセスするパスに応じて test.sh
に read, write, exec を実行できるようにします。
import subprocess
from flask import Flask, request
app = Flask(__name__)
@app.route("/", methods=["GET", "POST"])
def route():
print(request.get_data())
return "ok"
@app.route("/test_read")
def test_read():
with open("/test.sh", "r") as f:
msg = f.read()
return msg
@app.route("/test_write")
def test_write():
added = "#!/bin/sh\necho 'added line'\n"
with open("/test.sh", "w") as f:
f.write(added)
return "write complete"
@app.route("/test_exec")
def test_exec():
try:
ret = subprocess.run(
["/test.sh"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)
if ret.returncode == 0:
return ret.stdout
else:
return ret.stderr
except Exception as e:
return str(e)
if __name__ == "__main__":
app.run(host="0.0.0.0")
!/bin/sh
sleep 5
echo "complete"
TracingPolicy では write, exec 操作を拒否するために 2, 1 を追加します。
selectors:
- matchArgs:
- index: 0
operator: "Equal"
values:
- "/test.sh" # Reads to sensitive directories
- index: 1
operator: "Equal"
values:
+ - "2"
+ - "1"
試してみると想定通り read は成功しますが write は拒否されます。
$ curl 10.99.149.89:5000/test_read
#!/bin/sh
sleep 5
echo "complete"
$ curl 10.99.149.89:5000/test_write
curl: (52) Empty reply from server
$ k exec -n kube-system -it tetragon-m8mnc -c tetragon -- tetra getevents -o compact --pods read-write-75dbf6896b-pp8j7
📝 write myapp/read-write-75dbf6896b-pp8j7 /usr/local/bin/python /test.sh
📝 write myapp/read-write-75dbf6896b-pp8j7 /usr/local/bin/python /test.sh
💥 exit myapp/read-write-75dbf6896b-pp8j7 /usr/local/bin/python /main.py SIGKILL
🚀 process myapp/read-write-75dbf6896b-pp8j7 /usr/local/bin/python /main.py
一方で test.sh の exec に関しては TracingPolicy で指定しているにも関わらず特に拒否されずに成功します。
$ k exec -n kube-system -it tetragon-m8mnc -c tetragon -- tetra getevents -o compact --pods read-write-75dbf6896b-pp8j7
🚀 process myapp/read-write-75dbf6896b-pp8j7 /test.sh /test.sh
🚀 process myapp/read-write-75dbf6896b-pp8j7 /usr/bin/sleep 5
💥 exit myapp/read-write-75dbf6896b-pp8j7 /usr/bin/sleep 5 0
💥 exit myapp/read-write-75dbf6896b-pp8j7 /test.sh /test.sh 0
このことから exec 時には security_file_permission
が実行されないようなので別の kernel 関数などを TracingPolicy に設定する必要があります。
binary の実行を拒否する
Advanced Process execution を見てみると binary の実行時は security_bprm_creds_from_file
というカーネル関数が実行されるようなので、こちらに hook を仕掛けて確認してみます。
参考: https://elixir.bootlin.com/linux/v6.8.9/source/include/linux/security.h#L298
TracingPolicy は以下。
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: read-write-monitor
spec:
kprobes:
- call: "security_bprm_creds_from_file"
syscall: false
args:
- index: 0
type: "nop"
- index: 1
type: "file"
これでイベントを確認してみると、確かに exec 時には security_bprm_creds_from_file が呼ばれていそうです。
$ k exec -n kube-system -it tetragon-m8mnc -c tetragon -- tetra getevents -o compact --pods read-write-75dbf6896b-pp8j7
❓ syscall myapp/read-write-75dbf6896b-pp8j7 /usr/local/bin/python security_bprm_creds_from_file
🚀 process myapp/read-write-75dbf6896b-pp8j7 /test.sh /test.sh
❓ syscall myapp/read-write-75dbf6896b-pp8j7 /test.sh security_bprm_creds_from_file
🚀 process myapp/read-write-75dbf6896b-pp8j7 /usr/bin/sleep 5
💥 exit myapp/read-write-75dbf6896b-pp8j7 /usr/bin/sleep 5 0
💥 exit myapp/read-write-75dbf6896b-pp8j7 /test.sh /test.sh 0
1 つめの /usr/local/bin/python security_bprm_creds_from_file
のイベント詳細は以下のようになっています。
process_kprobe:
process:
exec_id: azhzLXcyOjI1OTUwNTUwMDc1NTA2OToxNzU5MjU1
pid: 1759255
uid: 0
cwd: /
binary: /usr/local/bin/python
arguments: /main.py
flags: execve
start_time: "2024-05-03T14:36:23.619870218Z"
auid: 4294967295
...
parent:
exec_id: azhzLXcyOjI1NDAwODgyMDY2MjkwODoxNzIyMjA4
pid: 1722208
uid: 0
cwd: /
binary: /usr/local/bin/python
arguments: /main.py
flags: execve rootcwd clone
start_time: "2024-05-03T13:04:46.939776203Z"
auid: 4294967295
...
function_name: security_bprm_creds_from_file
args:
- file_arg:
path: /usr/bin/dash
permission: -rwxr-xr-x
action: KPROBE_ACTION_POST
policy_name: read-write-monitor
return_action: KPROBE_ACTION_POST
node_name: k8s-w2
time: "2024-05-03T14:36:23.620270828Z"
function_name
が hook を仕掛けた関数名 (security_bprm_creds_from_file)、args[].file_arg
の中身が実行される binary の詳細になっています。上記では /usr/bin/dash
となっていますが、これは
-
/usr/bin/sh
でシェルスクリプト /test.sh を実行しようとする。 - Debian 系でのコンテナイメージでは
/usr/bin/sh
は/usr/bin/dash
のシンボリックリンクになっている。
root@read-write-75dbf6896b-pp8j7:/# ls -l /usr/bin/sh
lrwxrwxrwx 1 root root 4 Jan 5 2023 /usr/bin/sh -> dash
root@read-write-75dbf6896b-pp8j7:/# ls -l /usr/bin/dash
-rwxr-xr-x 1 root root 125640 Jan 5 2023 /usr/bin/dash
-
/usr/bin/dash
の binary が実行されシェルスクリプトの内容が解釈される。
という処理になっています。
3 行目の syscall myapp/read-write-75dbf6896b-pp8j7 /test.sh security_bprm_creds_from_file
のイベント詳細では file_arg.path: /usr/bin/sleep
になっていますが、これは /test.sh
内で sleep を実行する処理に対応します。
function_name: security_bprm_creds_from_file
args:
- file_arg:
path: /usr/bin/sleep
permission: -rwxr-xr-x
security_bprm_creds_from_file
に hook を仕掛けることでイベントを補足できることがわかったので、今度はこれに selector を追加して特定の binary を実行しようとした際に sigkill を送信して拒否する動作を確認してみます。
特定の binary の実行を拒否するには TracingPolicy に selector を追加しますsecurity_bprm_creds_from_file
の定義は以下のようになっており、 2 つめの引数 file に実行する binary のパスが指定されます。
int security_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file);
なので例えば sleep コマンドを対象とする場合は matchArgs にこの条件を指定します。また、matchActions[].action: Sigkill
とすることで sleep コマンドが実行された時に sigkill を送信します。これらの条件を合わせて TracingPolicy に以下を追加します。
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: read-write-monitor
spec:
kprobes:
- call: "security_bprm_creds_from_file"
syscall: false
args:
- index: 0
type: "nop"
- index: 1
type: "file"
+ selectors:
+ - matchArgs:
+ - index: 1
+ operator: "Equal"
+ values:
+ - "/usr/bin/sleep"
+ matchActions:
+ - action: Sigkill
このポリシーを適用すると、test.sh 内で実行した sleep コマンドが 4 行目の /usr/bin/sleep 5 SIGKILL
で kill されていることが確認できます。
$ k exec -n kube-system -it tetragon-m8mnc -c tetragon -- tetra getevents -o compact --pods read-write-75dbf6896b-pp8j7
🚀 process myapp/read-write-75dbf6896b-pp8j7 /test.sh /test.sh
❓ syscall myapp/read-write-75dbf6896b-pp8j7 /test.sh security_bprm_creds_from_file
🚀 process myapp/read-write-75dbf6896b-pp8j7 /usr/bin/sleep 5
💥 exit myapp/read-write-75dbf6896b-pp8j7 /usr/bin/sleep 5 SIGKILL
💥 exit myapp/read-write-75dbf6896b-pp8j7 /test.sh /test.sh 0
また、matchArgs を /usr/bin/dash
に指定した場合は /test.sh を実行するための dash の起動が kill されるので、この場合は /test.sh がすぐに終了します。
$ k exec -n kube-system -it tetragon-m8mnc -c tetragon -- tetra getevents -o compact --pods read-write-75dbf6896b-pp8j7
❓ syscall myapp/read-write-75dbf6896b-pp8j7 /usr/local/bin/python security_bprm_creds_from_file
🚀 process myapp/read-write-75dbf6896b-pp8j7 /test.sh /test.sh
💥 exit myapp/read-write-75dbf6896b-pp8j7 /test.sh /test.sh SIGKILL
test.sh
のようなシェルスクリプトは単なるテキストファイルなのでこれ自体の実行を制限することはできませんが、シェルスクリプト内で実行される各コマンドやシェルスクリプトを解釈するための shell 自体に sigkill を送信するように設定することで間接的に実行を拒否できます。
このように特定の binary の実行を拒否したい場合は security_bprm_creds_from_file
に hook を設定し、selector に適切な条件をつけた TracingPolicy を作成することで実現できます。
Linux process credentials 変更の監視
Linux ではプロセス毎に process credentials と呼ばれる属性のようなものが設定されており、これに応じてファイルへのアクセス可否やセキュリティチェックが実行されます。ドキュメントでは以下の要素が挙げられています。
- Traditional UNIX credentials
- Linux Capabilities
- Secure management flags (securebits)
- Linux Security Module (LSM)
Linux Capabilities は docker や kubernetes でたまに --cap-add
や --cap-drop
等で capabilities を設定するような場面がありますが、これは上記の linux capabilities を操作しています。どのような項目があるかは検索すると色々出てきますが、例えば docker ドキュメント を参照。
LSM は AppArmor や SELinux などがありますが、このあたりは k8s でも pod に設定できるため馴染みがあるかも。例えば k8s ドキュメント を参照。
process credentials は sudo
でルートユーザーとしてコマンドを実行したり、su
で別のユーザーに切り替える際などに変更が伴います。ドキュメントではこのようなイベントを補足する方法として sys_setuid などの システムコールを補足して監視する方法 と commit_creds などの カーネル関数を監視する方法 が紹介されています。カーネル関数を監視する方法では以下のようなメリットがあるようなのでこちらの動作を試してみます。
- Not vulnerable to user space arguments tampering.
- Ability to display the full new credentials to be applied.
- It is more reliable since it has full context on where and how the new credentials should be applied including the user namespace.
- A catch all layer for all system calls, and every normal kernel path that manipulate credentials.
- One potential disadvantage is that this approach may generate a lot of events, so appropriate filtering must be applied to reduce the noise.
検証用イメージの作成
検証では Monitor Process Credentials changes at the Kernel layer の内容に基づき、コンテナ内であるユーザから別のユーザに su する際のイベントを確認してみます。このために以下のユーザーを事前に作成したコンテナイメージを用意します。
username | uid |
---|---|
user1 | 1001 |
user2 | 1002 |
FROM ubuntu
ENV user1=user1
ENV user2=user2
RUN apt update && apt install -y sudo openssl
RUN useradd -d /home/${user1} -s /bin/bash -m -b /home/${user1} --uid 1001 ${user1} -p $(openssl passwd -1 ${user1}) && \
useradd -d /home/${user2} -s /bin/bash -m -b /home/${user2} --uid 1002 ${user2} -p $(openssl passwd -1 ${user2})
RUN usermod -aG sudo ${user1} && \
usermod -aG sudo ${user2}
USER user1
ENTRYPOINT [ "" ]
CMD [ "" ]
これをイメージ名 registry.centre.com:5000/process-test
として build し、以下のマニフェストで pod を作成します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: process-test
namespace: myapp
labels:
app: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: registry.centre.com:5000/process-test
ports:
- containerPort: 5000
command:
- sleep
- "3600"
---
apiVersion: v1
kind: Service
metadata:
name: process-test
namespace: myapp
labels:
run: myapp
spec:
ports:
- port: 5000
protocol: TCP
selector:
app: myapp
動作確認
イベントを補足するための TracingPolicy はチュートリアルに記載されているものをそのまま使用します。
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: "process-creds-changed"
#annotations:
#author: "Djalal Harouni"
spec:
kprobes:
- call: "commit_creds"
syscall: false
args:
- index: 0 # The new credentials to apply
type: "cred"
selectors:
- matchNamespaces:
- namespace: Pid
operator: NotIn
values:
- "host_ns"
matchActions:
- action: Post
rateLimit: "1m"
ファイルアクセスの時と同様に 1 つめのターミナルでは tetragon pod 上で tetra getevents
を実行してイベントを待ち受けます。
k exec -n kube-system -it tetragon-m8mnc -c tetragon -- tetra getevents -o compact --pods process-test-77f84fdf95-stqwd
別のターミナルで検証用 pod にログインし、su user2
で user2 に切り替えます。
$ k exec -it process-test-77f84fdf95-stqwd -- bash
user1@process-test-77f84fdf95-stqwd:/$ su user2
Password:
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
user2@process-test-77f84fdf95-stqwd:/$
このとき 1 つめのターミナルではかなりの量のイベントが発生していることが確認できます。
🚀 process myapp/process-test-77f84fdf95-stqwd /usr/bin/su user2
❓ syscall myapp/process-test-77f84fdf95-stqwd /usr/bin/su commit_creds
❓ syscall myapp/process-test-77f84fdf95-stqwd /usr/bin/su commit_creds
🚀 process myapp/process-test-77f84fdf95-stqwd /bin/bash
❓ syscall myapp/process-test-77f84fdf95-stqwd /bin/bash commit_creds
🚀 process myapp/process-test-77f84fdf95-stqwd /usr/bin/groups
💥 exit myapp/process-test-77f84fdf95-stqwd /usr/bin/groups 0
❓ syscall myapp/process-test-77f84fdf95-stqwd /bin/bash commit_creds
🚀 process myapp/process-test-77f84fdf95-stqwd /usr/bin/cat
💥 exit myapp/process-test-77f84fdf95-stqwd /usr/bin/cat 0
❓ syscall myapp/process-test-77f84fdf95-stqwd /bin/bash commit_creds
🚀 process myapp/process-test-77f84fdf95-stqwd /usr/bin/dircolors -b
💥 exit myapp/process-test-77f84fdf95-stqwd /usr/bin/dircolors -b 0
su user2
を実行した際に上記のようなイベントが発生することがわかったので、今度は -o compact
を外してイベントの詳細を確認します。
k exec -n kube-system -it tetragon-m8mnc -c tetragon -- tetra getevents --pods process-test-77f84fdf95-stqwd
terminal 2 で再度 su user2
を実行すると terminal1 にイベント詳細が大量に出力されます。まずはじめに出力されるイベントの詳細を見てみます。
イベント詳細
process_kprobe:
process:
exec_id: azhzLXcyOjk3MjYwNjA2NDkyNjc5OjY2ODA4Nw==
pid: 668087
uid: 1001
cwd: /
binary: /usr/bin/su
arguments: user2
flags: execve
start_time: "2024-05-01T17:32:18.725608142Z"
auid: 4294967295
pod:
namespace: myapp
name: process-test-77f84fdf95-stqwd
container:
id: containerd://4b670843d77b0983c820b930a92ab7631a9fcb297310052099397312f1045841
name: myapp
image:
id: registry.centre.com:5000/process-test@sha256:ae86b86057f2da2aaea4174c1af956ef00b6e9ee05932cf14750a84e3bad0b8d
name: registry.centre.com:5000/process-test:latest
start_time: "2024-05-01T16:55:04Z"
pid: 16
pod_labels:
app: myapp
pod-template-hash: 77f84fdf95
workload: process-test
workload_kind: Deployment
docker: 4b670843d77b0983c820b930a92ab76
parent_exec_id: azhzLXcyOjk3MjM5NzM3MDc0MTU5OjY2NzkzOQ==
refcnt: 1
tid: 668087
parent:
exec_id: azhzLXcyOjk3MjM5NzM3MDc0MTU5OjY2NzkzOQ==
pid: 667939
uid: 1001
cwd: /
binary: /usr/bin/su
arguments: user2
flags: execve rootcwd clone
start_time: "2024-05-01T17:31:57.856189078Z"
auid: 4294967295
pod:
namespace: myapp
name: process-test-77f84fdf95-stqwd
container:
id: containerd://4b670843d77b0983c820b930a92ab7631a9fcb297310052099397312f1045841
name: myapp
image:
id: registry.centre.com:5000/process-test@sha256:ae86b86057f2da2aaea4174c1af956ef00b6e9ee05932cf14750a84e3bad0b8d
name: registry.centre.com:5000/process-test:latest
start_time: "2024-05-01T16:55:04Z"
pid: 16
pod_labels:
app: myapp
pod-template-hash: 77f84fdf95
workload: process-test
workload_kind: Deployment
docker: 4b670843d77b0983c820b930a92ab76
parent_exec_id: azhzLXcyOjk3MjI3NTMzMjg4OTk1OjY2NzgzMQ==
tid: 667939
function_name: commit_creds
args:
- process_credentials_arg:
uid: 1001
gid: 1002
euid: 0
egid: 1002
suid: 0
sgid: 1002
fsuid: 0
fsgid: 1002
caps:
permitted:
- CAP_CHOWN
- DAC_OVERRIDE
- CAP_FOWNER
- CAP_FSETID
- CAP_KILL
- CAP_SETGID
- CAP_SETUID
- CAP_SETPCAP
- CAP_NET_BIND_SERVICE
- CAP_NET_RAW
- CAP_SYS_CHROOT
- CAP_MKNOD
- CAP_AUDIT_WRITE
- CAP_SETFCAP
effective:
- CAP_CHOWN
- DAC_OVERRIDE
- CAP_FOWNER
- CAP_FSETID
- CAP_KILL
- CAP_SETGID
- CAP_SETUID
- CAP_SETPCAP
- CAP_NET_BIND_SERVICE
- CAP_NET_RAW
- CAP_SYS_CHROOT
- CAP_MKNOD
- CAP_AUDIT_WRITE
- CAP_SETFCAP
user_ns:
level: 0
uid: 0
gid: 0
ns:
inum: 4026531837
is_host: true
action: KPROBE_ACTION_POST
policy_name: process-creds-changed
return_action: KPROBE_ACTION_POST
node_name: k8s-w2
time: "2024-05-01T17:32:18.725874725Z"
このイベント内の process.uid: 1001
や "binary": "/usr/bin/su", "arguments": "user2"
から、uid:1001 の user1 が su user2
を実行した際の記録と判別できます。args[0].process_credentials_arg
からは "uid": 1001, "gid": 1002,
などが確認できますが、user1 から user2 に process credentials を変更する際の値に対応していることが確認できます。euid や suid が 0 なのはおそらく su 実行時に sudo 相応の権限が必要になるため。
参考: https://book.hacktricks.xyz/v/jp/linux-hardening/privilege-escalation/euid-ruid-suid
フィールドの中身について
commit_creds は https://elixir.bootlin.com/linux/latest/source/kernel/cred.c#L392 で定義されています。
/**
* commit_creds - Install new credentials upon the current task
* @new: The credentials to be assigned
*
* Install a new set of credentials to the current task, using RCU to replace
* the old set. Both the objective and the subjective credentials pointers are
* updated. This function may not be called if the subjective credentials are
* in an overridden state.
*
* This function eats the caller's reference to the new credentials.
*
* Always returns 0 thus allowing this function to be tail-called at the end
* of, say, sys_setgid().
*/
int commit_creds(struct cred *new)
commit_creds の引数の struct cred
の定義は https://elixir.bootlin.com/linux/latest/source/include/linux/cred.h#L111
struct cred {
atomic_long_t usage;
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
...
struct cred では uid や cap_inheritable が定義されていることがわかります。そのため、イベント内の process_credentials_arg
フィールド以下に設定されている値は commit_creds を実行する際に引数に渡している値であると推測できます。
その後に出力されているイベントもみていきます。
process_exec:
process:
exec_id: azhzLXcyOjk3MjYwNjA3NTU2ODE3OjY2ODA4Nw==
pid: 668087
uid: 1002
cwd: /
binary: /bin/bash
flags: execve rootcwd clone
start_time: "2024-05-01T17:32:18.726671136Z"
auid: 4294967295
pod:
...
parent:
exec_id: azhzLXcyOjk3MjM5NzM3MDc0MTU5OjY2NzkzOQ==
pid: 667939
uid: 1001
cwd: /
binary: /usr/bin/su
arguments: user2
flags: execve rootcwd clone
start_time: "2024-05-01T17:31:57.856189078Z"
auid: 4294967295
pod:
...
node_name: k8s-w2
time: "2024-05-01T17:32:18.726670385Z"
このイベントでは parent
は "binary": "/usr/bin/su", "arguments": "user2",
、process
は "uid": 1002
, "binary": "/bin/bash"
であることから、user1 → user2 に切替時のパスワード認証に成功し、user2 内で interactive shell の /bin/bash を開始した際のイベントに対応していると判断できます (コンパクト表示の際の上から 4 つめの /bin/bash
に対応)。
🚀 process myapp/process-test-77f84fdf95-stqwd /usr/bin/su user2
❓ syscall myapp/process-test-77f84fdf95-stqwd /usr/bin/su commit_creds
❓ syscall myapp/process-test-77f84fdf95-stqwd /usr/bin/su commit_creds
🚀 process myapp/process-test-77f84fdf95-stqwd /bin/bash
❓ syscall myapp/process-test-77f84fdf95-stqwd /bin/bash commit_creds
ちなみに上記のイベント直後に process.binary
等が同じ以下のイベントも出力されていますが、こちらは "function_name": "commit_creds"
が含まれていることから、上から 5 つめの /bin/bash commit_creds
の方のイベントに対応することがわかります。
process_kprobe:
process:
exec_id: azhzLXcyOjk3MjYwNjEwMzA2OTIxOjY2ODA4OA==
pid: 668088
uid: 1002
cwd: /
binary: /bin/bash
flags: execve
start_time: "2024-05-01T17:32:18.729421450Z"
auid: 4294967295
pod:
...
parent:
exec_id: azhzLXcyOjk3MjYwNjA3NTU2ODE3OjY2ODA4Nw==
pid: 668087
uid: 1002
cwd: /
binary: /bin/bash
flags: execve rootcwd clone
start_time: "2024-05-01T17:32:18.726671136Z"
auid: 4294967295
pod:
...
function_name: commit_creds
args:
- process_credentials_arg:
uid: 1002
gid: 1002
euid: 1002
egid: 1002
suid: 1002
sgid: 1002
fsuid: 1002
fsgid: 1002
caps: {}
user_ns:
level: 0
uid: 0
gid: 0
ns:
inum: 4026531837
is_host: true
action: KPROBE_ACTION_POST
policy_name: process-creds-changed
return_action: KPROBE_ACTION_POST
node_name: k8s-w2
time: "2024-05-01T17:32:18.730196100Z"
このように Linux process credentials の変更を伴うイベントを監視することで、コンテナ内でユーザーを切り替えてプロセスが実行されるような操作を検出することができます。
使い道としてはドキュメントにあるように一般ユーザーから root ユーザーに昇格して意図しないファイルの書き換えや悪意のあるスクリプト実行を検出・拒否することでセキュリティを強化することがあります(このあたりは基本的に pod に security context を適切に設定したり、コンテナイメージで適切なユーザーを作成することで対応できるのでここまでやる必要はあるのかという気もしますが)。
Capabilities について
上記のイベント詳細内に process_credentials_arg.cap.permitted
などが設定されているイベントがありましたが、このフィールドにはデフォルトで有効化されている linux capabilities が記載されています。また、k8s では pod マニフェストの定義に securityContext を設定することで capabilities を追加・削除することが可能となっています。ここでは Set capabilities for a Container のように capabilities を追加した際にコンテナ内プロセスにも反映されるのかどうかを見てみます。
先程の process.yml
に NET_ADMIN, SYS_TIME の capabilities を追加します。
spec:
template:
spec:
containers:
- name: myapp
image: registry.centre.com:5000/process-test
ports:
- containerPort: 5000
command:
- sleep
- "3600"
+ securityContext:
+ capabilities:
+ add: ["NET_ADMIN", "SYS_TIME"]
先程と同様に pod 内でユーザー切り替えを実行し、その時のイベントを確認します。
args:
- process_credentials_arg:
uid: 1001
gid: 1002
euid: 0
egid: 1002
suid: 0
sgid: 1002
fsuid: 0
fsgid: 1002
caps:
permitted:
- CAP_CHOWN
- DAC_OVERRIDE
- CAP_FOWNER
- CAP_FSETID
- CAP_KILL
- CAP_SETGID
- CAP_SETUID
- CAP_SETPCAP
- CAP_NET_BIND_SERVICE
- CAP_NET_ADMIN
- CAP_NET_RAW
- CAP_SYS_CHROOT
- CAP_SYS_TIME
- CAP_MKNOD
- CAP_AUDIT_WRITE
- CAP_SETFCAP
effective:
- CAP_CHOWN
- DAC_OVERRIDE
- CAP_FOWNER
- CAP_FSETID
- CAP_KILL
- CAP_SETGID
- CAP_SETUID
- CAP_SETPCAP
- CAP_NET_BIND_SERVICE
- CAP_NET_ADMIN
- CAP_NET_RAW
- CAP_SYS_CHROOT
- CAP_SYS_TIME
- CAP_MKNOD
- CAP_AUDIT_WRITE
- CAP_SETFCAP
user_ns:
...
先程の結果と比較すると、cap.permitted
と cap.effective
にそれぞれ CAP_NET_ADMIN, CAP_SYS_TIME が追加されていることがわかります。よって pod に指定した capabilities が実際に追加されていることがイベント記録からも確認できます。
その他
本編の記事と直接関係はないが、調査の中で役に立ちそうな情報を記載。
TracingPolicy 内の引数について
例えば Signal Action の例ではシステムコールとして sys_write
が指定されており、args が 3 つ設定されています。
- call: "sys_write"
syscall: true
args:
- index: 0
type: "fd"
- index: 1
type: "char_buf"
sizeArgIndex: 3
- index: 2
type: "size_t"
これが何を示しているかというとシステムコールの関数に渡される引数に対応しています。
asmlinkage long sys_write(unsigned int fd, const char __user *buf,
size_t count);
定義によると引数が 3 つで type は第一引数から順に fd, char buf, size_t となっているため、それぞれ args の type に対応していることがわかります。なお TracingPolicy の定義は https://github.com/cilium/tetragon/blob/main/pkg/k8s/apis/cilium.io/client/crds/v1alpha1/cilium.io_tracingpolicies.yaml にあり、args で指定可能な type は https://github.com/cilium/tetragon/blob/367c4a004a35089a07d9f05eeef16359ceb1338f/pkg/k8s/apis/cilium.io/client/crds/v1alpha1/cilium.io_tracingpolicies.yaml#L150 に一覧があります。
なので args が何を示しているかは基本的にソースコードの引数を見れば良さそう。
TracingPolicy の記述例
github examples/tracingpolicy にたくさんあるのでこちらを参照。
イベントの filtering
tetragon が収集するイベントは多岐に渡るため、k8s クラスタ内の pod が増えるにつれて大量のイベントが収集されて tetragon pod のログサイズが増大します。この対策として tetragon ではイベント内容や対象の pod, namespace 等に基づいて収集可否を選択してログサイズを削減する設定項目が用意されています。
Export filetering
export filtering では イベントの process_exec
や process_exit
などの値に基づいてイベント収集の可否を選択することができます。
これは helm chart の exportAllowList
や exportDenyList
で指定でき、configmap としてデプロイされます。
# Allowlist for JSON export. For example, to export only process_connect events from
# the default namespace:
#
# exportAllowList: |
# {"namespace":["default"],"event_set":["PROCESS_EXEC"]}
exportAllowList: |-
{"event_set":["PROCESS_EXEC", "PROCESS_EXIT", "PROCESS_KPROBE", "PROCESS_UPROBE", "PROCESS_TRACEPOINT"]}
# Denylist for JSON export. For example, to exclude exec events that look similar to
# Kubernetes health checks and all the events from kube-system namespace and the host:
#
# exportDenyList: |
# {"health_check":true}
# {"namespace":["kube-system",""]}
#
exportDenyList: |-
{"health_check":true}
{"namespace":["", "cilium", "kube-system"]}
Field Filtering
Field filetering では収集されるイベント詳細の中で不要なフィールドを除去することでログサイズを削減することができます。こちらも helm chart で指定できます。
k8s filtering
こちらは TracingPolicy マニフェストに含めることができるオプションであり、ポリシーを適用する対象を namespace や pod, container のラベルに基づいて選択できます。
おわりに
Tetragon では eBPF を利用した TracingPolicy という CRD を使うことでカーネルレベルでのイベントを監視したり、カーネル関数に hook を仕掛けてプロセスの kill などのアクションを実行できます。これにより Observability の 3 大要素 (logs, metrics, traces) ではカバーしきれない k8s pod のコンテナ内のプロセスレベルでのイベント監視を実現し、k8s クラスタの Observability やセキュリティをさらに強化することができます。
ドキュメントの Use Cases を元にイベント内容などをわりと詳細に見ていきましたが、調べながら記事を書いていると思ったより分量が多くなったのでユースケース 6 のうち 3 つについて検証を行いました。残りについては機会があれば書こうと思います。
Discussion