TT Lab
はじめる
学ぶ 学習パス コース

コンテナセキュリティ

インベントリ、SBOM、そしてゲート

TT Labで続きを見る

このラボは本物のVM上で動きます

この箱はPodではなく、KubeVirtが起動した仮想マシンです。Linuxカーネルが 別に動き、systemdが実際にサービスを管理し、dockerは偽物ではなく 本物のDockerエンジンです。docker runで起動したコンテナは、実際にプロセスに なり、docker execもdocker logsもそのまま動作します。

以前は、このラボはPodの中で動いていました。カーネルの権限をすべて下ろした箱だったため、 コンテナを起動するステップが塞がれており、そのためイメージのアーカイブを自分で展開して みるという回り道で学んでいました。もう回り道は必要ありません。

知っておくべきことが2つあります。

目標

スキャナーが実際に行うこと(パッケージのインベントリ → DBとの照合)を、手で再現します。ベースと ランタイムのイメージのパッケージ一覧を取り出して、増えた攻撃対象領域を数字で数え、SBOMを自分で 作成し、ポリシーゲートのスクリプトを書いたあと、イメージに焼き付けられたシークレットを見つけて修正します。

なぜ重要なのか

スキャナーを魔法の箱のままにしておくと、2つの誤った結論に陥ります。「きれいだから安全だ」と、 「1,247件すべて直すべきだ」です。スキャナーはイメージを実行することもコードを分析することもせず、 インストールされたパッケージの名前・バージョンを脆弱性DBと照合するだけです。そのため、curl | tar xz で入れたバイナリはまったく見えず、一度も呼び出されないパッケージの脆弱性は、 そのまま上がってきます。この原理を手で再現してみると、「何を直すか」よりも 「なぜこのパッケージがイメージにあるのか」が先に出てこなければならないことが、はっきりします。 アプリケーションがリンクする共有ライブラリが8個なのに、イメージにパッケージが432個 入っているなら、問題は脆弱性ではなく、イメージの構成です。

ステップ

  1. /root/sec2ディレクトリを作成し、alpine:3.20にインストールされたパッケージを、이름-버전の1行形式(プレースホルダーは名前とバージョンです)で/root/sec2/alpine-pkgs.txtに保存します。10行以上である必要があり、muslが入っている必要があります。
  2. 同じ形式で、nginx:1.27-alpineのパッケージ一覧を/root/sec2/nginx-pkgs.txtに保存します。10行以上である必要があり、nginxが入っている必要があり、ステップ1より行数が多い必要があります。
  3. 2つの一覧を、バージョンを除いたパッケージ名を基準に比較して、nginx側にだけあるパッケージの個数を/root/sec2/extra-count.txtに数字だけで保存します(誤差2以内は許容)。
  4. /root/sec2/sbom.jsonを作成します。最上位のimageの値はnginx:1.27-alpine、packagesは配列で、項目数がステップ2のファイルの行数と同じである必要があります。各項目には、空ではないnameとversionがある必要があります。
  5. /root/sec2/deny.txtに、禁止パッケージの名前を1行に1つずつ書きます(必ずcurlを含めます)。そして、実行権限のある/root/sec2/gate.shを作成します。第1引数で受け取ったパッケージ一覧のファイルに禁止パッケージがなければ終了コード0、あれば0以外の終了コードで終了しつつ、該当したパッケージ名を出力する必要があります。
  6. ARGで受け取った値をENV APP_TOKENに渡すDockerfileでlabhub/leak:v1をビルドし、イメージ設定からそのまま読み取れるトークンの値を/root/sec2/leak.txtに保存します。
  7. 同じアプリをトークンなしでビルドして、labhub/leak:v2を作成します(イメージのEnvにAPP_TOKENがない必要があります)。そして、labhub/leak:v2でsec-runtimeコンテナを起動し、APP_TOKENを実行時に注入します。
  8. /root/sec2/scan.mdに、次の3行を正確にこの形式で書きます。
    • package_count=<2단계 파일의 줄 수>(プレースホルダーはステップ2のファイルの行数です)
    • denied_hits=0
    • secret_in_image=no

参考

ベースイメージのパッケージインベントリ

/root/sec2ディレクトリを作成し、alpine:3.20にインストールされたパッケージを、이름-버전の1行形式(プレースホルダーは名前とバージョンです)で/root/sec2/alpine-pkgs.txtに保存してください。10行以上である必要があり、muslが入っている必要があります。

スキャナーが最初に行うことが、これです。alpineは、apkでインストールされたパッケージを参照でき、名前とバージョンが付いた1行の形式で出力するオプションがあります。ネットワークなしでローカルのDBだけを読むので、オフラインでも動作します。

ランタイムイメージと比較する

同じ形式で、nginx:1.27-alpineのパッケージ一覧を/root/sec2/nginx-pkgs.txtに保存してください。10行以上である必要があり、nginxが入っている必要があり、ステップ1より行数が多い必要があります。

ステップ1とまったく同じ形式で出力しないと、次のステップで比較できません。同じalpineベースなのに、nginxイメージ側の行数のほうが多いのが正常です。その差が、そのまま増えた攻撃対象領域です。

増えた攻撃対象領域を数える

2つの一覧を、バージョンを除いたパッケージ名を基準に比較して、nginx側にだけあるパッケージの個数を/root/sec2/extra-count.txtに数字だけで保存してください(誤差2以内は許容)。

バージョンの文字列が付いていると、同じパッケージも違って見えます。이름-버전からバージョンを除いて、名前だけで比較してください(プレースホルダーは名前とバージョンです)。並べ替えたあと、片方にだけある項目を数える標準的なツールがあります。ファイルには、数字だけを残してください。

SBOMを自分で作ってみる

/root/sec2/sbom.jsonを作成してください。最上位のimageの値はnginx:1.27-alpine、packagesは配列で、項目数がステップ2のファイルの行数と同じである必要があります。各項目には、空ではないnameとversionがある必要があります。

ツールを使うのではなく、構造を理解するステップです。ステップ2の一覧の各行を、名前とバージョンに分けて、JSONの配列にしてください。項目数がステップ2の行数とまったく同じである必要があり、名前だけがあってバージョンが空の項目が1つでもあると、照合が不可能なので失敗します。

ポリシーゲートのスクリプト

/root/sec2/deny.txtに、禁止パッケージの名前を1行に1つずつ書いてください(必ずcurlを含めます)。そして、実行権限のある/root/sec2/gate.shを作成します。第1引数で受け取ったパッケージ一覧のファイルに禁止パッケージがなければ終了コード0、あれば0以外の終了コードで終了しつつ、該当したパッケージ名を出力する必要があります。

引数で受け取った一覧のファイルを検査して、通過なら0、違反なら0以外の値で終了する必要があります。違反のとき、どのパッケージが該当したかを出力しないと、誰も原因がわかりません。そして、バージョンを除いた名前で正確に比較してください。curlのルールがlibcurlを誤って捕捉してはいけません。

ENVで入れたトークンはイメージに焼き付けられる

ARGで受け取った値をENV APP_TOKENに渡すDockerfileでlabhub/leak:v1をビルドし、イメージ設定からそのまま読み取れるトークンの値を/root/sec2/leak.txtに保存してください。

ARGで受け取った値をENVに渡すDockerfileを作成し、ビルド引数でトークンを渡してください。そして、その値がイメージ設定からそのまま読み取れることを確認して、ファイルに書きます。イメージを作った人でなくても読めるというのが、要点です。

イメージはきれいに、シークレットは実行時に

同じアプリをトークンなしでビルドして、labhub/leak:v2を作成してください(イメージのEnvにAPP_TOKENがない必要があります)。そして、labhub/leak:v2でsec-runtimeコンテナを起動し、APP_TOKENを実行時に注入します。

同じアプリをトークンなしで再ビルドして、イメージのEnvに何も残らないようにし、必要な値はコンテナを起動するときに注入してください。イメージに残っていると、このステップは失敗します。

スキャンの要約

/root/sec2/scan.mdに、次の3行を正確にこの形式で書いてください。

3行とも、키=값の形式です(プレースホルダーはキーと値です)。パッケージの数は、ステップ2のファイルの行数と同じである必要があり、最終的なイメージに禁止パッケージがなく、シークレットもない必要があるため、残りの2つの値は、それぞれ0とnoになります。実際の状態と違えば、採点が見つけ出します。