フォレンジックコンテナ分析

Authors: Adrian Reber (Red Hat)

前回投稿したKubernetesにおけるフォレンジックコンテナチェックポイント処理では、Kubernetesでのチェックポイントの作成や、それがどのようにセットアップされ、どのように使用されるのかを紹介しました。 機能の名前はフォレンジックコンテナチェックポイントですが、Kubernetesによって作成されたチェックポイントの実際の分析方法については、詳細を説明しませんでした。 この記事では、チェックポイントがどのように分析されるのかについての詳細を提供します。

チェックポイントの作成はまだKubernetesでalpha機能であり、この記事ではその機能が将来どのように動作するのかについてのプレビューを提供します。

準備

チェックポイント作成のサポートを有効にするためのKubernetesの設定方法や、基盤となるCRI実装方法についての詳細はKubernetesにおけるフォレンジックコンテナチェックポイント処理を参照してください。

一例として、この記事内でチェックポイントを作成し分析するコンテナイメージ(quay.io/adrianreber/counter:blog)を準備しました。 このコンテナはコンテナ内でファイルを作成することができ、後でチェックポイント内で探したい情報をメモリーに格納しておくこともできます。

コンテナを実行するためにはPodが必要であり、この例では下記のPodマニフェストを使用します。

apiVersion: v1
kind: Pod
metadata:
  name: counters
spec:
  containers:
  - name: counter
    image: quay.io/adrianreber/counter:blog

この結果、counterと呼ばれるコンテナがcountersと呼ばれるPod内で実行されます。

一度コンテナが実行されると、コンテナで下記アクションが行えます。

$ kubectl get pod counters --template '{{.status.podIP}}'
10.88.0.25
$ curl 10.88.0.25:8088/create?test-file
$ curl 10.88.0.25:8088/secret?RANDOM_1432_KEY
$ curl 10.88.0.25:8088

最初のアクセスはコンテナ内でtest-fileという内容でtest-fileと呼ばれるファイルを作成します。 次のアクセスで、コンテナのメモリー内のどこかにシークレット情報(RANDOM_1432_KEY)を記憶します。 最後のアクセスは内部のログファイルに1行追加するだけです。

チェックポイントを分析する前の最後のステップは、チェックポイントを作成することをKubernetesに指示することです。 前回の記事で説明したように、これにはkubelet限定のチェックポイントAPIエンドポイントへのアクセスを必要とします。

default名前空間内のcountersという名前のPod内のcounterという名前のコンテナに対して、kubelet APIエンドポイントが次の場所で到達可能です。

# Podが実行されているNode上で実行する
curl -X POST "https://localhost:10250/checkpoint/default/counters/counter"

厳密には、kubeletの自己署名証明書を許容しkubelet チェックポイントAPIの使用を認可するために、下記のcurlコマンドのオプションが必要です。

--insecure --cert /var/run/kubernetes/client-admin.crt --key /var/run/kubernetes/client-admin.key

チェックポイントの作成が終了すると、/var/lib/kubelet/checkpoints/checkpoint-<pod-name>_<namespace-name>-<container-name>-<timestamp>.tarでチェックポイントが利用可能になります。

この記事の後述のステップでは、チェックポイントアーカイブを分析する際にcheckpoint.tarという名前を使用します。

checkpointctlを使用したチェックポイントアーカイブの分析

チェックポイントが作成したコンテナに関するいくつかの初期情報を得るためには、このようにcheckpointctlを使用します。

$ checkpointctl show checkpoint.tar --print-stats
+-----------+----------------------------------+--------------+---------+---------------------+--------+------------+------------+-------------------+
| CONTAINER |              IMAGE               |      ID      | RUNTIME |       CREATED       | ENGINE |     IP     | CHKPT SIZE | ROOT FS DIFF SIZE |
+-----------+----------------------------------+--------------+---------+---------------------+--------+------------+------------+-------------------+
| counter   | quay.io/adrianreber/counter:blog | 059a219a22e5 | runc    | 2023-03-02T06:06:49 | CRI-O  | 10.88.0.23 | 8.6 MiB    | 3.0 KiB           |
+-----------+----------------------------------+--------------+---------+---------------------+--------+------------+------------+-------------------+
CRIU dump statistics
+---------------+-------------+--------------+---------------+---------------+---------------+
| FREEZING TIME | FROZEN TIME | MEMDUMP TIME | MEMWRITE TIME | PAGES SCANNED | PAGES WRITTEN |
+---------------+-------------+--------------+---------------+---------------+---------------+
| 100809 us     | 119627 us   | 11602 us     | 7379 us       |          7800 |          2198 |
+---------------+-------------+--------------+---------------+---------------+---------------+

これによって、チェックポイントアーカイブ内のチェックポイントについてのいくつかの情報が、すでに取得できています。 コンテナの名前やコンテナランタイムやコンテナエンジンについての情報を見ることができます。 チェックポイントのサイズ(CHKPT SIZE)もリスト化されます。 これは大部分がチェックポイントに含まれるメモリーページのサイズですが、コンテナ内の全ての変更されたファイルのサイズ(ROOT FS DIFF SIZE)についての情報もあります。

追加のパラメーター--print-statsはチェックポイントアーカイブ内の情報を復号化し、2番目のテーブル(CRIU dump statistics)で表示します。 この情報はチェックポイント作成中に収集され、CRIUがコンテナ内のプロセスをチェックポイントするために必要な時間と、チェックポイント作成中に分析され書き込まれたメモリーページ数の概要を示します。

より深く掘り下げる

checkpointctlの助けを借りて、チェックポイントアーカイブについてのハイレベルな情報を得ることができます。 チェックポイントアーカイブをさらに分析するには、それを展開する必要があります。 チェックポイントアーカイブはtarアーカイブであり、tar xf checkpoint.tarの助けを借りて展開可能です。

チェックポイントアーカイブを展開すると、下記のファイルやディレクトリが作成されます。

  • bind.mounts - このファイルにはバインドマウントについての情報が含まれており、復元中に全ての外部ファイルとディレクトリを正しい場所にマウントするために必要になります。
  • checkpoint/ - このディレクトリにはCRIUによって作成された実際のチェックポイントが含まれています。
  • config.dumpspec.dump - これらのファイルには、復元中に必要とされるコンテナについてのメタデータが含まれています。
  • dump.log - このファイルにはチェックポイント作成中に作成されたCRIUのデバッグ出力が含まれています。
  • stats-dump - このファイルには、checkpointctl--print-statsでダンプ統計情報を表示するために使用するデータが含まれています。
  • rootfs-diff.tar - このファイルには、コンテナのファイルシステム上で変更された全てのファイルが含まれています。

ファイルシステムの変更 - rootfs-diff.tar

コンテナのチェックポイントをさらに分析するための最初のステップは、コンテナ内で変更されたファイルを見ることです。 これはrootfs-diff.tarファイルを参照することで行えます。

$ tar xvf rootfs-diff.tar
home/counter/logfile
home/counter/test-file

これでコンテナ内で変更されたファイルを調べられます。

$ cat home/counter/logfile
10.88.0.1 - - [02/Mar/2023 06:07:29] "GET /create?test-file HTTP/1.1" 200 -
10.88.0.1 - - [02/Mar/2023 06:07:40] "GET /secret?RANDOM_1432_KEY HTTP/1.1" 200 -
10.88.0.1 - - [02/Mar/2023 06:07:43] "GET / HTTP/1.1" 200 -
$ cat home/counter/test-file
test-file 

このコンテナのベースになっているコンテナイメージ(quay.io/adrianreber/counter:blog)と比較すると、コンテナが提供するサービスへの全てのアクセス情報を含んだlogfileや予想通り作成されたtest-fileファイルを確認することができます。

rootfs-diff.tarの助けを借りることで、作成または変更された全てのファイルを、コンテナのベースイメージと比較して検査することが可能です。

チェックポイント処理したプロセスを分析する - checkpoint/

ディレクトリcheckpoint/はコンテナ内でプロセスをチェックポイントしている間にCRIUによって作成されたデータを含んでいます。 ディレクトリcheckpoint/の内容は、CRIUの一部として配布されているCRITツールを使用して分析できるさまざまなイメージファイルで構成されています。

まず、コンテナの内部プロセスの概要を取得してみましょう。

$ crit show checkpoint/pstree.img | jq .entries[].pid
1
7
8

この出力はコンテナのPID名前空間の内部に3つのプロセス(PIDが1と7と8)があることを意味しています。

これはコンテナのPID名前空間の内部からの視界を表示しているだけです。 復元中に正確にそれらのPIDが再作成されます。 コンテナのPID名前空間の外部からPIDは復元後に変更されます。

次のステップは、それらの3つのプロセスについての追加情報を取得することです。

$ crit show checkpoint/core-1.img | jq .entries[0].tc.comm
"bash"
$ crit show checkpoint/core-7.img | jq .entries[0].tc.comm
"counter.py"
$ crit show checkpoint/core-8.img | jq .entries[0].tc.comm
"tee"

これは、コンテナ内の3つのプロセスがbashcounter.py(Pythonインタプリター)とteeであることを意味しています。 プロセスの親子関係についての詳細は、checkpoint/pstree.imgに分析するデータがさらにあります。

ここまでで収集した情報をまだ実行中のコンテナと比較してみましょう。

$ crictl inspect --output go-template --template "{{(index .info.pid)}}" 059a219a22e56
722520
$ ps auxf | grep -A 2 722520
fedora    722520  \_ bash -c /home/counter/counter.py 2>&1 | tee /home/counter/logfile
fedora    722541      \_ /usr/bin/python3 /home/counter/counter.py
fedora    722542      \_ /usr/bin/coreutils --coreutils-prog-shebang=tee /usr/bin/tee /home/counter/logfile
$ cat /proc/722520/comm
bash
$ cat /proc/722541/comm
counter.py
$ cat /proc/722542/comm
tee

この出力では、まずコンテナ内の最初のプロセスのPIDを取得しています。 そしてコンテナを実行しているシステム上で、そのPIDと子プロセスを探しています。 3つのプロセスが表示され、最初のものはコンテナPID名前空間の中でPID 1である"bash"です。 次に/proc/<PID>/commを見ると、チェックポイントイメージと正確に同じ値を見つけることができます。

覚えておく重要なことは、チェックポイントはコンテナのPID名前空間内の視界が含まれていることです。 なぜなら、これらの情報はプロセスを復元するために重要だからです。

critがコンテナについて教えてくれる最後の例は、UTS名前空間に関する情報です。

$ crit show checkpoint/utsns-12.img
{
    "magic": "UTSNS",
    "entries": [
        {
            "nodename": "counters",
            "domainname": "(none)"
        }
    ]
}

UTS名前空間内のホストネームがcountersであることを教えてくれます。

チェックポイント作成中に収集された各リソースCRIUについて、checkpoint/ディレクトリは対応するイメージファイルを含んでいます。 このイメージファイルはcritを使用することで分析可能です。

メモリーページを見る

CRITを使用して復号化できるCRIUからの情報に加えて、CRIUがディスクに書き込んだ生のメモリーページを含んでいるファイルもあります。

$ ls  checkpoint/pages-*
checkpoint/pages-1.img  checkpoint/pages-2.img  checkpoint/pages-3.img

最初にコンテナを使用した際に、メモリー内のどこかにランダムキー(RANDOM_1432_KEY)を保存しました。 見つけることができるかどうか見てみましょう。

$ grep -ao RANDOM_1432_KEY checkpoint/pages-*
checkpoint/pages-2.img:RANDOM_1432_KEY

そして実際に、私のデータがあります。 この方法で、コンテナ内のプロセスの全てのメモリーページの内容を簡単に見ることができます。 しかし、チェックポイントアーカイブにアクセスできるなら誰でも、コンテナのプロセスのメモリー内に保存された全ての情報にアクセスできることを覚えておくことも重要です。

さらなる分析のためにgdbを使用する

チェックポイントイメージを見るための他の方法はgdbです。 CRIUリポジトリは、チェックポイントをコアダンプファイルに変換するcoredumpスクリプトを含んでいます。

$ /home/criu/coredump/coredump-python3
$ ls -al core*
core.1  core.7  core.8

coredump-python3スクリプトを実行すると、チェックポイントイメージがコンテナ内の各プロセスに対し1つのコアダンプファイルに変換されます。 gdbを使用してプロセスの詳細を見ることもできます。

$ echo info registers | gdb --core checkpoint/core.1 -q

[New LWP 1]

Core was generated by `bash -c /home/counter/counter.py 2>&1 | tee /home/counter/logfile'.

#0  0x00007fefba110198 in ?? ()
(gdb)
rax            0x3d                61
rbx            0x8                 8
rcx            0x7fefba11019a      140667595587994
rdx            0x0                 0
rsi            0x7fffed9c1110      140737179816208
rdi            0xffffffff          4294967295
rbp            0x1                 0x1
rsp            0x7fffed9c10e8      0x7fffed9c10e8
r8             0x1                 1
r9             0x0                 0
r10            0x0                 0
r11            0x246               582
r12            0x0                 0
r13            0x7fffed9c1170      140737179816304
r14            0x0                 0
r15            0x0                 0
rip            0x7fefba110198      0x7fefba110198
eflags         0x246               [ PF ZF IF ]
cs             0x33                51
ss             0x2b                43
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0

この例では、チェックポイント中の全てのレジストリの値を見ることができ、コンテナのPID 1のプロセスの完全なコマンドライン(bash -c /home/counter/counter.py 2>&1 | tee /home/counter/logfile)を見ることもできます。

まとめ

コンテナチェックポイントを作成することで、コンテナを停止することやチェックポイントが作成されたことを知ることなく、実行中のコンテナのチェックポイントを作成することが可能です。 Kubernetesにおいてコンテナのチェックポイントを作成した結果がチェックポイントアーカイブです。 checkpointctltarcritgdbのような異なるツールを使用して、チェックポイントを分析できます。 grepのようなシンプルなツールでさえ、チェックポイントアーカイブ内の情報を見つけることが可能です。

この記事で示したチェックポイントの分析方法のさまざまな例は出発点にすぎません。 この記事ではチェックポイントの分析を始める方法を紹介しましたが、要件によってはかなり詳細に特定の物事を見ることも可能です。

参加するためにはどうすればよいですか?

SIG Nodeにはいくつかの方法でアクセスできます。