0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SBOM差分検知ツール:sbomdiffの使い方説明

Last updated at Posted at 2024-05-12

sbomdiffは異なる2つのSBOMファイルから差分を抽出するライブラリです。このツールが対応するSBOMの形式はSPDX と CycloneDX の2種類です。

また、sbomdiffはpythonで作成されたツールであり、pipを使い、インストールすることが可能です。

sbomdiffはSBOMの4つの変更を検知することができます。

  • パッケージのバージョン変更
  • パッケージのライセンス変更
  • 取り除かれたパッケージ
  • 新たに追加されたパッケージ

これらの変更を検知できるため、sbomdiffは、あるソフトウェアに変更を加える、または加えられたときにその差分を検知することができます。

本記事では、実際に、SBOMに人工的に変更を加えていき、sbomdiffがどのように差分を検出するのかを見ていくことが目的です。

コマンドの説明について

sbomdiff [-h]\
				 [--sbom {auto,spdx,cyclonedx}]\
				 [--exclude-license]\
				 [-d]\
				 [-o OUTPUT_FILE]\
				 [-f {text,json,yaml}]\
				 [-V]\
				 FILE1 FILE2

各引数の説明です。

比較する2種類のファイルを指定します。

  • FILE1: ここでは、変更前のSBOMを指定します
  • FILE2: ここでは、変更後のSBOMを指定します

sbomdiffに関する情報の確認

  • -h, --help: ヘルプオプション、各オプションの簡易的な説明を表示
  • -v, --version: バージョンを確認

入力に関するオプション

  • --sbom {auto,spdx,cyclonedx}: 比較するSBOMの形式を指定、defultではautoになっている
  • --exclude-license: パッケージのライセンスの変更情報を除外する

出力に関するオプション

  • -d, --debug: デバッグの詳細情報について出力する
  • -o OUTPUT_FILE, --output-file OUTPUT_FILE OUTPUT_FILEにファイル名を代入することで、そのファイルに結果を出力できる defaultでは結果が標準出力される
  • -f {text,json,yaml}, --format {text,json,yaml}`: 出力先フォーマットを選ぶことができる、ファイルのフォーマットはtext,json,yamlの中から選ぶことができ、defaultではtext形式で出力される

もしも、ソフトウェア構成情報に変更を加える前のSBOM:sbom_prev.jsonと変更後のSBOM:sbom_post.jsonを比較し、その結果をjson形式でsbom_diff.jsonというファイルに出力したい場合は、以下のようにコマンドをたたきます。

sbomdiff --sbom=cyclonedx -o sbom_diff.json -f=json sbom_prev.json sbom_post.json

実際にSBOMの差分を検出してみる

それでは、Pythonとpipを使って、パッケージのバージョンアップやアンインストールを行い、その変更前後でソフトウェア構成情報をSBOMに記録し、変更前後で生成したSBOMの差分を検出していきます。

初めに、作業用directoryを作成しておきます。

mkdir sbomdiff_test

仮想環境の構築

まず,venvでpythonの仮想環境を構築し、環境をアクティベートします。

python3 -m venv sbomdiff_venv
cd sbomdiff_venv
source bin/activate

パッケージに対する変更とSBOMの生成

パッケージに対して変更を加え、その都度、SBOMを生成していきます。今回作成たSBOMは3種類あり、それらを以下表にまとめておきます。

段階 SBOMの名前 加えた変更
初期 sbom_prev.json Flask1.1.4をインストール
変更1 sbom_post.json Flaskをアップデート、matplotlibを追加
変更2 sbom_post2.json matplotlibとnumpyを削除

以降の節では、上で示したSBOMの作成手順を説明します。

初期:パッケージのインストールとSBOM生成

pip installでパッケージをインストールし、その後、pythonのパッケージのメタデータが存在する仮想環境ディレクトリごと、syftでスキャンします。

今回は、Flaskのバージョン1.1.4をインストールします。そして、sbom_prev.jsonという名前でSBOMを生成します。

sbomはsbomdiff_testディレクトリに配置しておきたいので、cdでそこに移動してください。

pip install Flask==1.1.4
syft dir:~/sbomdiff_test/sbomdiff_venv -o cyclonedx-json=sbom_prev.json

変更1:パッケージのアップデートと追加、SBOM生成

次に、Flaskをアップデート、さらにmatplotlibを新たにインストールし追加します。そして、sbom_post.jsonという名前でSBOMを生成します。

pip install -U Flask
pip install matplotlib
syft dir:~/sbomdiff_test/sbomdiff_venv -o cyclonedx-json=sbom_post.json

変更2:パッケージの削除、SBOM生成

最後に、パッケージmatplotlibnumpyを削除し、sbom_post2.jsonという名前でSBOMを生成します。

pip uninstall matplotlib
pip uninstall numpy
syft dir:~/sbomdiff_test/sbomdiff_venv -o cyclonedx-json=sbom_post2.json

SBOMの差分を検出

上で生成した3種類のSBOMから差分を検出します。どのSBOMをそれぞれ比較するかを以下の表にまとめておきます。

sbom差分ファイル名 変更前のsbomファイル 変更後sbomファイル 検知が期待される差分
sbom_diff1.json sbom_prev.json sbom_post.json パッケージのバージョンやライセンス変更、新たに追加されるパッケージ
sbom_diff2.json sbom_prev.json sbom_post2.json 削除されたパッケージ

パッケージのバージョンやライセンス変更、新規追加パッケージの検出

sbom_prev.jsonsbom_post.jsonの差分を検出します。以下のコマンドを実行し、jsonとtxt、両方の出力結果を見てみます。

sbomdiff --sbom=cyclonedx -o sbom_diff1.json -f=json sbom_prev.json sbom_post.json
sbomdiff --sbom=cyclonedx -f=text sbom_prev.json sbom_post.json > sbom_diff1.txt

実際に、パッケージに加えられた変更を検知することができました。以下、結果の詳細です。

こちらは、txt形式の出力結果です。それぞれパッケージの変更情報が直接書かれています。

  • [VERSION]:バージョンの変更
  • [LICENSE]:ライセンスの変更
  • [ADDED ]:追加されたパッケージの情報
  • [REMOVED]:削除されたパッケージの情報

最後に、Summaryとして、それぞれの合計が記載されています。

sbom_diff1.txt
[VERSION] Flask: Version changed from 1.1.4 to 3.0.3
[LICENSE] Flask: License changed from BSD-3-Clause to NOT FOUND
[VERSION] Jinja2: Version changed from 2.11.3 to 3.1.4
[LICENSE] Jinja2: License changed from BSD-3-Clause to NOT FOUND
[VERSION] Werkzeug: Version changed from 1.0.1 to 3.0.3
[LICENSE] Werkzeug: License changed from BSD-3-Clause to NOT FOUND
[VERSION] click: Version changed from 7.1.2 to 8.1.7
[VERSION] itsdangerous: Version changed from 1.1.0 to 2.2.0
[LICENSE] itsdangerous: License changed from BSD to NOT FOUND
[ADDED  ] blinker: (Version 1.8.2) (License NOT FOUND)
___________
___________
...........
___________
Summary
Version changes:  5
License changes:  12
Removed packages: 0
New packages:     4

json形式ではtxt形式とは異なり、パッケージ名とそのステータス、どのように変更があったかが、key:value形式で表されています。変更数が多い場合に、jsonファイルを加工してトリアージする場合はjson形式の方が使い勝手が良さそうです。

sbom_diff1.json
{
  "tool": {
    "name": "sbomdiff",
    "version": "0.5.3"
  },
  "file_1": "sbom_prev.json",
  "file_2": "sbom_post.json",
  "differences": [
    {
      "package": "Flask",
      "status": "change",
      "version": {
        "from": "1.1.4",
        "to": "3.0.3"
      },
      "license": {
        "from": "BSD-3-Clause",
        "to": "NOT FOUND"
      }
    },
    {
      "package": "Jinja2",
      "status": "change",
      "version": {
        "from": "2.11.3",
        "to": "3.1.4"
      },
      "license": {
        "from": "BSD-3-Clause",
        "to": "NOT FOUND"
      }
    },
    {
      "package": "Werkzeug",
      "status": "change",
      "version": {
        "from": "1.0.1",
        "to": "3.0.3"
      },
      "license": {
        "from": "BSD-3-Clause",
        "to": "NOT FOUND"
      }
    },
    {
      "package": "click",
      "status": "change",
      "version": {
        "from": "7.1.2",
        "to": "8.1.7"
      }
    },
    {
      "package": "itsdangerous",
      "status": "change",
      "version": {
        "from": "1.1.0",
        "to": "2.2.0"
      },
      "license": {
        "from": "BSD",
        "to": "NOT FOUND"
      }
    },
    {
      "package": "blinker",
      "status": "add",
      "version": {
        "from": "1.8.2"
      },
      "license": {
        "to": "NOT FOUND"
      }
    },
    
    
    {
      "package": "six",
      "status": "add",
      "version": {
        "from": "1.16.0"
      },
      "license": {
        "to": "MIT"
      }
    }
  ],
    ___________
    ___________
    ...........
    ...........
    ___________
  "summary": {
    "version_changes": 5,
    "new_packages": 12,
    "removed_packages": 0,
    "license_changes": 4
  }
}

削除されたパッケージの検知

削除されたパッケージの検知についても同様です。

sbomdiff --sbom=cyclonedx -o sbom_diff2.json -f=json sbom_prev.json sbom_post2.json
sbomdiff --sbom=cyclonedx -f=text sbom_prev.json sbom_post2.json > sbom_diff2.txt

txt形式

sbom_diff2.txt
[REMOVED] matplotlib: (Version 3.8.4)
[REMOVED] numpy: (Version 1.26.4)

Summary

Version changes:  0
License changes:  0
Removed packages: 2
New packages:     0

json形式

sbom_diff2.json
{
  "tool": {
    "name": "sbomdiff",
    "version": "0.5.3"
  },
  "file_1": "sbom_post.json",
  "file_2": "sbom_post2.json",
  "differences": [
    {
      "package": "matplotlib",
      "status": "remove",
      "version": {
        "from": "3.8.4"
      }
    },
    {
      "package": "numpy",
      "status": "remove",
      "version": {
        "from": "1.26.4"
      }
    }
  ],
  "summary": {
    "version_changes": 0,
    "new_packages": 0,
    "removed_packages": 2,
    "license_changes": 0
  }
}

sbomdiffをshellスクリプトで利用するときの注意点とその対策

sbomdiffを使う際に以下2つの注意点に配慮する必要があります。

  • txt形式で出力する際にSummaryがでないこと
  • 差分を検知した場合、修了ステータスが1となること
    です。

txt形式で出力する場合にバグがある

sbomdiffでは、ドキュメントが推奨する以下のようなコマンドの実行を行うとSummaryがtxtへ出力されません。

sbomdiff --sbom=cyclonedx -o sbom_diff1.txt -f=text sbom_prev.json sbom_post.json

そこで、txtへSummaryを出力させるために、以下のようにコマンドを実行します。

sbomdiff --sbom=cyclonedx -f=text sbom_prev.json sbom_post.json > sbom_diff1.txt

差分を検知するとエラーになる

sbomdiffはdiffコマンドと似た仕様になっています。実際にソースコードを確認してみると、
https://github.com/anthonyharrison/sbomdiff/blob/3e095b3972a99b6c9fa0fe30ac795060f9b02382/sbomdiff/cli.py#L247

    # Return code indicates if any differences have been detected
    if (version_changes or license_changes or removed_packages or new_packages) != 0:
        return 1

    return 0


if __name__ == "__main__":
    sys.exit(main())

ファイルの差分を検知した場合、終了ステータスに1を返すことがわかります。そのため、スクリプトなどで後続する処理がある場合はそこでエラー終了してしまいます。

この仕様により、CI/CDなどでsbomdiffを利用する場合、パイプラインが止まってしまいます。

そこで、スクリプトに書いた一連の処理でsbomdiffを利用する場合は、差分があってもエラーとして扱われないよう対策する必要があります。

解決策1: ||trueをつけて、終了ステータスを0に上書きする

コマンドの後に、||trueをつけて、終了ステータスを0に上書きすることで、スクリプトが止まるのを防ぎます。

sbomdiff --sbom=cyclonedx -o sbom_diff1.txt -f=text sbom_prev.json sbom_post.json

解決策2: 終了ステータスに1を返す場合も実行されるようにスクリプトを書く

sbomdiff --sbom=cyclonedx -o sbom_diff1.txt -f=text sbom_prev.json sbom_post.json
# 直前のコマンドの終了ステータスを取得
exit_code=$?
echo ${exit_code}
# 終了コードが1の場合はスキップする
if [ $exit_code -eq 0 ]; then
    echo "can't detect the differences of two sbom files."
        #exit $exit_code
elif [ $exit_code -eq 1 ]; then
    echo "detect the differences of two sbom files."
fi

sbomdiffの活用先について

ここまでの説明を読んで、sbomdiffの使い方はわかったと思います。しかし、実際にどう活用したらよいのかわからない方も多いのではないのでしょうか?

そこで、次回はsbomdiffの活用方法について少し解説していきたいと思っています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?