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生成
最後に、パッケージmatplotlib
、numpy
を削除し、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.json
とsbom_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として、それぞれの合計が記載されています。
[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形式の方が使い勝手が良さそうです。
{
"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形式
[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形式
{
"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の活用方法について少し解説していきたいと思っています。