NTIA Conformance CheckerでSPDX形式のJSONファイルがNTIAのSBOMの最小要素を満たすか調べる

# はじめに

近年、製品やサービスが複雑化しており、ライセンス管理やセキュリティ対応などの重要性が高まっています。

ソフトウェアの構成情報をやりとりするためのSBOM(Software Bill of Materials)が注目されています。
きっかけとなったのが2021年5月の米国大統領令です。
その発表を受けて、NTIA (米国商務省電気通信情報局)は2021年7月に[The Minimum Elements For a Software Bill of Materials (SBOM)](https://www.ntia.doc.gov/report/2021/minimum-elements-software-bill-materials-sbom)を公開し、SBOMの最小要素を定義しました。

SBOMのフォーマットの1つに[SPDX](https://spdx.dev/)があります。
SBOMを生成するツールは有償、無償を問わずさまざまなものがあります。
無償のツールとしてはFOSSologyやsyftがあります。
* [FOSSologyでソースコードを解析してファイル毎のライセンスを把握する](https://qiita.com/machida-yuki/items/8738961fa3133296d4c7)
* [syftを使ってCentOS 7のrpmdbを元にSPDX-JSON形式のSBOMを生成する](https://qiita.com/ykmchd/items/b28bca1663d957ae1956)

ツールが生成したSBOMをライセンス管理やセキュリティ対応で活用する前に、NTIAが定義するSBOMの最小要素を満たしていることを確認することは重要です。

2023年5月にカーネギーメロン大学で行われたDevSecOps Days 2023で、The Linux FoundationのKateさんの発表([SPDX SBOMs: Enabling Automation of Safety & Security Analysis](https://resources.sei.cmu.edu/library/asset-view.cfm?assetid=978945))で[NTIA Conformance Checker](https://github.com/spdx/ntia-conformance-checker)が紹介されていました。
NTIA Conformance CheckerはSPDXのドキュメントがNTIAが定義するSBOMの最小要素を満たすか確認するツールです。

本記事ではSPDXのコミュニティから提供されているNTIA Conformance Checkerを使い、SPDX形式のJSONファイルがNTIAが定義するSBOMの最小要素を満たすか調べる方法をご紹介します。
具体的には[syft](https://github.com/anchore/syft)でrpmdbからSPDX形式のJSONファイルを出力し、NTIA Conformance Checkerで調べることにします。

# 環境情報

macOS上のDockerでRocky Linux 9のコンテナを立ち上げて、その中で作業をします。

今回の環境情報は以下です。

| コンポーネント | バージョン | 備考 |
|:-:|:-:|:-:|
| PC | M1 MacBook Pro  |   |
| OS | macOS Ventura 13.4  |   |
| Docker | 4.20.0 | 個人以外の用途で使うときはライセンスにご注意ください  |
| Rocky Linux | 9.2  | |
| syft | v0.82.0 | |
| ntia-conformance-checker | 0.3.1 | Python 3.8以降であること |

# Rocky Linux 9のコンテナを立ち上げる

Rocky Linux 9のイメージを取得し、コンテナを立ち上げます。
```shell
$ docker pull rockylinux:9
$ docker run -it -d --name rockylinux9 rockylinux:9
```

ログインして、バージョンを確認します。
```shell
$ docker exec -it rockylinux9 /bin/bash
# cat /etc/redhat-release 
Rocky Linux release 9.2 (Blue Onyx)
```

# syftをインストール

syftをインストールします。
```shell
# curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
```

バージョンを確認します。
```shell
# syft --version
syft 0.82.0
```

# SPDX形式のJSONファイルを生成する

syftでRocky Linux 9のrpmdbからSPDX形式のJSONファイルを出力し、rockylinux9_rpm.jsonという名前で保存します。
```shell
# syft /var/lib/rpm -o spdx-json > rockylinux9_rpm.json
```

ちゃんと出力されたのか、opensslパッケージで確認します。
```shell
# cat rockylinux9_rpm.json | jq . | less
(snip)
  {
   "name": "openssl",
   "SPDXID": "SPDXRef-Package-rpm-openssl-980236006ab5b149",
   "versionInfo": "1:3.0.7-6.el9_2",
   "originator": "Organization: Rocky Enterprise Software Foundation",
   "downloadLocation": "NOASSERTION",
   "sourceInfo": "acquired package info from RPM DB: rpmdb.sqlite",
   "licenseConcluded": "NOASSERTION",
   "licenseDeclared": "LicenseRef-ASL-2.0",
   "copyrightText": "NOASSERTION",
   "externalRefs": [
    {
     "referenceCategory": "SECURITY",
     "referenceType": "cpe23Type",
     "referenceLocator": "cpe:2.3:a:rockyenterprisesoftwarefoundation:openssl:1\\:3.0.7-6.el9_2:*:*:*:*:*:*:*"
    },
    {
     "referenceCategory": "SECURITY",
     "referenceType": "cpe23Type",
     "referenceLocator": "cpe:2.3:a:openssl:openssl:1\\:3.0.7-6.el9_2:*:*:*:*:*:*:*"
    },
    {
     "referenceCategory": "PACKAGE-MANAGER",
     "referenceType": "purl",
     "referenceLocator": "pkg:rpm/openssl@3.0.7-6.el9_2?arch=aarch64\u0026epoch=1\u0026upstream=openssl-3.0.7-6.el9_2.src.rpm"
    }
   ]
  },
(snip)
```

# NTIA Conformance Checkerをインストールする

pipをインストールして、最新版に更新しておきます。
```shell
# dnf install python3-pip
# python3 -m pip install -U pip
```

ntia-conformance-checkerモジュールをインストールします。
```shell
# python3 -m pip install ntia-conformance-checker
```

インストールされたことを確認します。
```shell
# python3 -m pip freeze | grep ntia-conformance-checker
ntia-conformance-checker==0.3.1
```

# NTIA Conformance CheckerでSBOMの最小要素を満たすか調べる

ntia-checkerコマンドを使って、先ほど出力したSPDX形式のJSONファイルがNTIAのSBOMの最小要素を満たすか確認します。
```
# ntia-checker --file rockylinux9_rpm.json
```

実行結果は以下で、NTIAのSBOMの最小要素を満たしませんでした。
```shell
# ntia-checker --file rockylinux9_rpm.json 

Is this SBOM NTIA minimum element conformant? False

Individual elements                            | Status
-------------------------------------------------------
All component names provided?                  | True
All component versions provided?               | True
All component identifiers provided?            | True
All component suppliers provided?              | False
SBOM author name provided?                     | True
SBOM creation timestamp provided?              | True
Dependency relationships provided?             | True

# 
```

# なぜNTIAのSBOMの最小要素を満たしていないのか

syftが出力したSPDX形式のJSONファイルはNTIAのSBOMの最小要素を満たしていないのでしょうか。

ntia-checkerの実行結果を見ると、以下の"すべてのコンポーネントでサプライヤーが提供されているか?"という評価項目がFalseになっていることがわかります。
```
All component suppliers provided?              | False
```

rockylinux9_rpm.jsonはSPDX 2.3を採用しています。
SPDX 2.3の仕様を見ると[7.5 Package supplier field](https://spdx.github.io/spdx-spec/v2.3/package-information/#75-package-supplier-field)という項目があるため、SPDX 2.3としてサプライヤーの情報を表現することは可能そうです。

rockylinux9_rpm.jsonのopensslパッケージの項目には7.5 Package supplier fieldに相当する項目が見当たりません。
これがSBOMの最小要素を満たしていない原因のようです。

# どうしたらNTIA Conformance CheckerでSBOMの最小要素を満たせるのか

rockylinux9_rpm.jsonのopensslパッケージには、supplierと似たような項目であるoriginatorがあります。
```
   "originator": "Organization: Rocky Enterprise Software Foundation",
```
rockylinux9_rpm.jsonの元になっているrpmdbを見てみると、opensslパッケージのVenderという項目で提供元であるRocky Enterprise Software Foundationが記載されており、この項目をoriginatorとして出力しているようです。
```shell
# rpm -qi openssl
Name        : openssl
Epoch       : 1
Version     : 3.0.7
Release     : 6.el9_2
Architecture: aarch64
Install Date: Sat May 13 20:21:29 2023
Group       : Unspecified
Size        : 1852491
License     : ASL 2.0
Signature   : RSA/SHA256, Tue May  9 08:01:08 2023, Key ID 702d426d350d275d
Source RPM  : openssl-3.0.7-6.el9_2.src.rpm
Build Date  : Tue May  9 07:41:06 2023
Build Host  : pb-bcdd069a-daaa-4574-ad8c-4665090b05d8-b-aarch64
Packager    : Rocky Linux Build System (Peridot) <releng@rockylinux.org>
Vendor      : Rocky Enterprise Software Foundation
URL         : http://www.openssl.org/
Summary     : Utilities from the general purpose cryptography library with TLS implementation
Description :
The OpenSSL toolkit provides support for secure communications between
machines. OpenSSL includes a certificate management tool and shared
libraries which provide various cryptographic algorithms and
protocols.
# 
```

改めて、SPDX 2.3の仕様を確認してみます。
[7.5 Package supplier field](https://spdx.github.io/spdx-spec/v2.3/package-information/#75-package-supplier-field)は、パッケージ/ディレクトリの実際の配布元を表現する項目です。
[7.6 Package originator field](https://spdx.github.io/spdx-spec/v2.3/package-information/#76-package-originator-field)は、このパッケージのもともと誰から提供されたものかを表現する項目です。

ソフトウェアはサプライチェインで成り立っています。
opensslパッケージはもともとRocky Enterprise Software Foundationという組織から提供されたものですが、それをさらに配布するときにはその個人または組織が配布元となるのです。
rpmdbには配布元となる個人または組織に相当する情報がないため、syftで7.5 Package supplier fieldに記載する情報を入力してあげることができれば、NTIAのSBOMの最小要素を満たすSBOMを出力できそうです。

# NTIAのSBOMの最小要素を満たす

rockylinux9_rpm.jsonのパッケージ情報にサプライヤを追加してみます。

PythonでSPDX形式のファイルを扱う[tools-python](https://github.com/spdx/tools-python)モジュールを使って、　SPDX形式のJSONファイルのパッケージ情報にサプライヤを追加するプログラムを書いてみました。

```python
#!/usr/bin/env python3

from spdx_tools.spdx.model import Actor, ActorType
from spdx_tools.spdx.parser.parse_anything import parse_file
from spdx_tools.spdx.writer.write_anything import write_file

def main():
    document = parse_file('rockylinux9_rpm.json')
    for p in document.packages:
        p.supplier = Actor(ActorType.PERSON, "A Package Supplier")
    write_file(document, "rockylinux9_rpm_added_suppliers.json")
    

if __name__ == "__main__":
    main()
```

上記プログラムを実行し、生成したrockylinux9_rpm_added_suppliers.jsonを確認すると、"supplier"という項目が追加されていることがわかります。

```
(snip)
        {
            "SPDXID": "SPDXRef-Package-rpm-openssl-980236006ab5b149",
            "copyrightText": "NOASSERTION",
            "downloadLocation": "NOASSERTION",
            "externalRefs": [
                {
                    "referenceCategory": "SECURITY",
                    "referenceLocator": "cpe:2.3:a:rockyenterprisesoftwarefoundation:openssl:1\\:3.0.7-6.el9_2:*:*:*:*:*:*:*",
                    "referenceType": "cpe23Type"
                },
                {
                    "referenceCategory": "SECURITY",
                    "referenceLocator": "cpe:2.3:a:openssl:openssl:1\\:3.0.7-6.el9_2:*:*:*:*:*:*:*",
                    "referenceType": "cpe23Type"
                },
                {
                    "referenceCategory": "PACKAGE_MANAGER",
                    "referenceLocator": "pkg:rpm/openssl@3.0.7-6.el9_2?arch=aarch64&epoch=1&upstream=openssl-3.0.7-6.el9_2.src.rpm",
                    "referenceType": "purl"
                }
            ],
            "filesAnalyzed": true,
            "licenseConcluded": "NOASSERTION",
            "licenseDeclared": "LicenseRef-ASL-2.0",
            "name": "openssl",
            "originator": "Organization: Rocky Enterprise Software Foundation",
            "sourceInfo": "acquired package info from RPM DB: rpmdb.sqlite",
            "supplier": "Person: A Package Supplier",
            "versionInfo": "1:3.0.7-6.el9_2"
        },
(snip)
```

そして、再度NTIA Conformance Checkerでrockylinux9_rpm_added_suppliers.jsonを引数に実行してみると、NTIAのSBOMの最小要素を満たすSBOMになったことがわかります。
```python
# ntia-checker --file rockylinux9_rpm_added_suppliers.json 

Is this SBOM NTIA minimum element conformant? True

Individual elements                            | Status
-------------------------------------------------------
All component names provided?                  | True
All component versions provided?               | True
All component identifiers provided?            | True
All component suppliers provided?              | True
SBOM author name provided?                     | True
SBOM creation timestamp provided?              | True
Dependency relationships provided?             | True

# 
```

# 最後に

NTIA Conformance Checkerを使い、SPDX形式のJSONファイルがNTIAが定義するSBOMの最小要素を満たすか調べる方法をご紹介しました。
実際にSBOMを活用するためにはNTIAが定義するSBOMの最小要素以外の項目が必要になるとは思います。
しかし、まずはツールが出力したSBOMが最低限の範囲を満たすか確認するためにNTIA Conformance Checkerを利用するのは有効だと思います。

# 参考情報

* SPDX
    * https://spdx.dev/
* syft
    * https://github.com/anchore/syft
* NTIA Conformance Checker
    * https://github.com/spdx/ntia-conformance-checker
    * https://pypi.org/project/ntia-conformance-checker/
* tools-python
    * https://github.com/spdx/tools-python
* SPDX SBOMs: Enabling Automation of Safety & Security Analysis
    * https://resources.sei.cmu.edu/library/asset-view.cfm?assetid=978945
* オープンソースコンプライアンスの透明性と相互運用性からオートメーションに向けて / From Transparency and Interoperability to Automation in Open Source Compliance
    * https://qiita.com/TakashiNinjouji/items/4848b35f57b699c4d997
* Software Design 2023年6月号
    * https://gihyo.jp/magazine/SD/archive/2023/202306