BlackDuckがソフトウェア構成情報解析を行うための情報源と条件

BlackDuckはBlack Duckという会社が提供している有償のSCA (ソフトウェアコンポーネントアナリシスツール)です。
BlackDuckは検出したソフトウェアコンポーネント情報に対して、脆弱性検知やライセンス情報を取得、違反確認を行うことができる便利なツールです。ソフトウェア構成情報をSBOMとして出力することもできます。

本記事では、BlackDuckがそもそもソフトウェアコンポーネント情報をどのファイルを見て、どんな条件で検出するのかを以下のドキュメント、[githubに公開されているソースコード](https://github.com/blackducksoftware/detect)を読み調査しました。



### 話さないこと
BlackDuckは有償かつ、ドキュメントの量が多いため、全てを調査することができていません。そのため、以下は本記事では説明できません。
- ファイル内のメタデータのパース内容、アルゴリズム
- SBOM生成機能
- 脆弱性検知機能
- ライセンス検知機能
- ほかのツールとの比較

## BlackDuckで利用できる4つスキャンツール
BlackDuckはツール名として知られていますが、実際はいくつかのツールが組み合わさって作られたツール群です。
BlackDuck内でソフトウェアコンポーネントの情報を検知するツールは4つあり、以下表にまとめます。各ツールがコンテナイメージスキャン、ディレクトリスキャンどちらで対応しているかを示すために〇、×でその対応を表しています：

|ツール名|採取できる情報|コンテナイメージスキャン時|ディレクトリスキャン時|
|:----|:----|:----|:----|
|Docker Inspector|メタデータ (Linuxディストリビューションパッケージ) |○|×|
|Detector|メタデータ (言語パッケージ)|×|○|
|Black Duck Signature Scanner|signature (特徴量) <br>補足：パッケージマネージャ経由では採取できない情報、例えばソースコード内部にコピペされた他OSSのソースコードなどをsignatureとして検出<br>また、Javaにおけるjarファイル内のMANIFEST.MFやpom.xmlからも情報を検出可能|○|○|
|Black Duck Binary Analysis|バイナリ|○|○|

上表からもわかる通り、コンテナイメージスキャン時のみLinuxディストリビューションの情報を検出でき、ディレクトリに対するスキャン時のみ言語パッケージの情報を検出できます。Black Duck Signature ScannerとBlack Duck Binary Analysis[^1]はコンテナイメージ、ディレクトリの両方にそれぞれ対応しています。
[^1]: Black Duck Binary AnalysisはBlackDuckへ課金後、さらに課金が必要なため、公式ドキュメントに説明があまり載っていませんでした。そのため、情報が不足しています。ごめんなさい。


## 対応パッケージメタデータ対応早見表
以下の表では、上で説明した4つのツールがどのパッケージのメタデータを参照し検出するのかを示しています。この表はあくまで対象としているかを示しただけであり、実際に検出するための条件は示していないので注意してください。条件については後述します。[^2]
[^2]: 表に上げた言語とメタデータファイルは、現在よく使われているものを上げています。しかし、BlackDuckは表のほかにも、C、C++、PHPなど幅広く対応しています。

表からもわかる通り、`requirements.txt`、`pom.xml`、`package.json`など依存関係を記述するマニフェストファイルが検知対象となっています。


|言語・OS distro|検知ファイル|DockerInspector|Detector|Black Duck Signature Scanner|Black Duck - Binary Analysis|
|:----|:----|:----|:----|:----|:----|
|Python|METADATA|×|×|×|×|
| |PKG--INFO|×|×|×|×|
| |**/requirements*.txt|×|○|×|×|
| |**/poetry.lock|×|○|×|×|
| |**/Pipfile.lock|×|○|×|×|
| |**/setup.py|×|○|×|×|
|Java|JAR/WAR/EAR|×|×|○|○|
| |**/pom.xml|×|○|×|×|
| |gradle.lockfile|×|×|×|×|
|golang|**/go.mod|×|○|×|×|
| |gobinary|×|×|×|○|
|javascript|**/package.json|×|○|×|×|
| |**/package-lock.json|×|○|×|×|
| |**/yarn.lock|×|○|×|×|
| |**/pnpm-lock.yaml|×|○|×|×|
|Ruby|**/Gemfile.lock|×|○|×|×|
| |*.gemspec|×|○|×|×|
|dpkg|**/var/lib/dpkg/status|○|×|×|×|
|rpm|**/var/lib/rpm/{Packages,Packages.db,rpmdb.sqlite}|○|×|×|×|
|apk|**/lib/apk/db/installed|○|×|×|×|

## 各言語メタデータを取得するための条件
Black Duck の検出ツールの一つであるDetectorは言語パッケージマネージャが取り扱うメタデータファイルを検知します。ここでは、BlackDuckがどんな条件でパッケージ情報の記載されたメタデータファイルを検出するのかを説明します。
以下のドキュメントを読みまとめました。

表の見方ですが、順に
1. パッケージマネージャー：パッケージマネージャー名
2. Detector：Detector内で起動する検出ツール名
3. 必須ファイル：検出するために必要なマニフェストファイル、または、lockファイル
4. 必須実行バイナリ：
5. 精度：パッケージ情報の検知精度、HIGHかLOWの二値で表現している[^3]
6. 検出するための操作：検出をonにするために必要な操作です

となっています。
[^3]: ここで、検知精度というのは実際にマニフェストファイルに従ってインストールした場合の情報を取得する際の情報量の多さを示しています。簡単に言うと、推移的依存関係まで検知できるかを表しています。pom.xmlのみだと、ダイレクト依存関係しか定義されないため、推移的依存関係のパッケージは検出ができません。そのため、精度が`LOW`になります。しかし、mavenが環境にあれば、精度を`HIGH`にすることができます。なぜならば、BlackDuckがmavenを呼び出し、`mvn dependency:tree`コマンドで推移的依存関係を取得するためです。

以下の例だと、Detectorは`Maven CLI`は、`pom.xml`とmvnまたはmvnwの実行バイナリを利用して、パッケージ情報を検出していることがわかります。


|パッケージマネージャ|Detector|必須ファイル|必須実行バイナリ|精度|検出するための操作|
|:----|:----|:----|:----|:----|:----|
|MAVEN|Maven CLI|pom.xml|mvnw or mvn|HIGH| |


### Java
|パッケージマネージャ|Detector|必須ファイル|必須実行バイナリ|精度|検出するための操作|
|:----|:----|:----|:----|:----|:----|
|GRADLE|Gradle Native Inspector|build.gradle or build.gradle.kts|gradlew or gradle|HIGH| |
| |Gradle Project Inspector|build.gradle or *.gradle| |LOW|Accuracyを変える|
|MAVEN|Maven CLI|pom.xml|mvnw or mvn|HIGH| |
| |Maven Wrapper CLI|pom.groovy|mvnw or mvn|HIGH| |
| |Maven Project Inspector|pom.xml| |LOW|Accuracyを変える|


### Python
|パッケージマネージャ|Detector|必須ファイル|必須実行バイナリ|精度|検出するための操作|
|:----|:----|:----|:----|:----|:----|
|PIP|Pipenv Lock|Pipfile or Pipfile.lock|python or python3,pipenv|HIGH|--detect.python.pathで指定する|
| |PIP Native Inspector|setup.py requirements.txt|python or python3, pip or pip3|HIGH|--detect.python.path--detect.pip.path|
| |Pipfile Lock|Pipfile.lock, Option:Pipfile| |HIGH|pipfileだけでは無理、lockが必須|
| |PIP Requirements File Parse|requirements.txt| |LOW| |
|POETRY|Poetry Lock|Poetry.lock, pyproject.toml| |HIGH|tomlだけでは無理|
|Setuptools|Setuptools Pip Detector|pyproject.toml and optionally setup.cfg, or setup.py- Executables: pip or pip3 (specified via --detect.pip.path)|pip or pip3|HIGH|requires = [“setuptools”] と記述のあるもの--detect.pip.pathで指定|
| |Setuptools Detector|pyproject.toml and optionally setup.cfg, or setup.py| |LOW|requires = [“setuptools”] と記述のあるもの|



### Node.js
|パッケージマネージャ|Detector|必須ファイル|必須実行バイナリ|精度|検出するための操作|
|:----|:----|:----|:----|:----|:----|
|NPM|NPM Shrinkwrap|npm-shrinkwrap.json<br>オプション:package.json||HIGH|lock.jsonに同じ|
| |NPM Package Lock| package-lock.json<br>オプション:package.json|-|HIGH|古いpackage.lock.jsonだと取れない。packagesというフィールドがないと取れない|
| |NPM CLI|node_modules, package.json|npm|HIGH|node_modulesがないとだめnpm_installを実行する必要がある|
| |NPM Package Json Parse|package.json||LOW|detect.accuracy.required=NONEとすればとることが可能|
|PNPM|Pnpm Lock|pnpm-lock.yaml<br> オプション：package.json||HIGH|lockファイルさえあれば取得可能|
|YARN|Yarn Lock|yarn.lock and package.json|-|HIGH| yarn.lockとpackage.jsonが一緒にないと動かない|


### Golang
|パッケージマネージャ|Detector|必須ファイル|必須実行バイナリ|精度|検出するための操作|
|:----|:----|:----|:----|:----|:----|
|GO_MOD|GoMod CLI|go.mod|go|HIGH|goのパスを渡す必要がある|

### Ruby
|パッケージマネージャ|Detector|必須ファイル|必須実行バイナリ|精度|検出するための操作|
|:----|:----|:----|:----|:----|:----|
RUBYGEMS|Gemfile Lock|Gemfile.lock||HIGH| ||
||Gemspec Parse|gemspec file (.gemspec extension)||LOW|Accuracyを変える|

## Linuxディストリビューションパッケージ (dpkg, apk, rpm)の検知方法
Black Duck の検出ツールの一つであるDockerInspectorはコンテナイメージスキャン時に起動し、Linuxディストリビューションパッケージマネージャが取り扱うメタデータファイルから情報採取を行います。基本的には各種パッケージマネージャーが参照しているデータベースファイルを検知します。しかし、dbファイルを直接見に行くのではなく、少しトリッキーな方法で検知を行っています。


## メリット・デメリット
### メリット
- あまりメジャーではないものも対象としており、広く浅くスキャンできる。
- 有償のサポートがある。
- pom.xmlやrequirements.txtなどのライセンス情報に記述のないファイルから、パッケージ情報を抽出する際、**ライセンスがでる**(と書いてある)。おそらく、推論されており、精度は不明

### デメリット
- 検出対象(と書いてある)パッケージは多いが、**検出するための条件がある**。
- 全部のdetectorを使ってスキャンすると、どこで何をスキャンしてきたのかがわからなくなる
- Purlは出るが、CPEは生成されず、手入力する必要がある。

## BlackDuckが苦手な状況
本番環境において、言語とLinuxディストリビューションのパッケージマネージャーを経由して取得したパッケージやライブラリの検出、脆弱性検知を正確に行う状況は苦手であると言えます。

なぜならば、言語パッケージとLinuxディストリビューションパッケージを一緒に検知できないからです。
上でも示した通り、コンテナイメージスキャン、ディレクトリスキャンで言語パッケージとLinuxディストリビューションパッケージが完全に分離してしまっています。これだと、本番環境がVMやオンプレミスの場合、Linuxディストリビューションパッケージは検知できませんし、コンテナの場合、言語パッケージが検知できません。VMやオンプレミス環境でもBlack Duck Signature ScannerやBlack Duck Binary Analysisを使えば、ソースコードやバイナリを検知し、情報が取れそうですが、パッケージメタデータに比べると極めて筋が悪いです。

## BlackDuckを利用していくために
BlackDuckを利用していくためには、条件を用意し、適切にオプションを設定してやる必要があります。
- スキャンしたいメタデータに応じて、オプションを適切に設定していく必要がある
- 開発時の言語のパッケージ情報取得時、BlackDuckが精度良く情報を取得できる条件を用意する
- 本番環境はコンテナイメージを想定する