TrivyでCycloneDXを出力するように対応してみた(SBOM)
Trivy で CycloneDX と呼ばれる SBOM の仕様を実装しましたので、備忘録も含めて CycloneDX の仕様からどのような意図で、Trivy に実装を行なったか記載しようと思います。
概要
SBOM は Software Bill of Materials の略称になっており、あるソフトウェア, 製品を構成するコンポーネントを網羅的に記述するものです。
SBOM とは?と説明を書くのに指が止まってしまったので、Linux Foundationの What is SBOM? を参考に説明します。
A Software Bill of Materials (SBOM) is a complete, formally structured list of components, libraries, and modules that are required to build (i.e. compile and link) a given piece of software and the supply chain relationships between them. These components can be open source or proprietary, free or paid, and widely available or restricted access.
文中のSBOMとは?の問いにはこのように記載されています。 「特定のソフトウェアをコンパイルするために必要なコンポーネント、ライブラリ、モジュールを網羅的に構造化して記したものであり、それらのサプライチェーン関係を示したもの。」
要はソフトウェアをモジュール単位に分割し、OS・ライブラリの依存関係・メタデータを記した、透明性の高いドキュメントを作りましょうということかと思われます。 またこのメタデータには脆弱性情報なども含まれており、Trivy の解析結果を余すことなく利用できます。
SBOM はあくまで考え方であり、実際のフォーマットは主に3つ存在します。
- SPDX
- CycloneDX
- SWID
SPDX は Linux Foundation が推進している SBOM であり、 CycloneDX は OWASP を源流とする CycloneDX Core Working Group が推進している SBOM フォーマットです。 SWID に関しては NIST が推進している SBOM らしいのですが、そもそも SBOM だったのか...?という気持ちです。
今回のブログでは Trivy で CycloneDX に対応した話を書きます。
なぜ CycloneDX なのか
SPDX を対応するか CycloneDX を対応するかで迷ったのですが、 OWASP の方が積極的に CycloneDX について情報提供してくださったりなど、コミュニティとして関わりやすかったので、まずはCycloneDXから対応することになりました。
CycloneDX の仕様について
CycloneDX は GitHub 上で仕様書が管理されており、 公式ページで内容を確認することができます。
https://cyclonedx.org/ https://github.com/CycloneDX/specification
CycloneDX の BOM は XML もしくは JSON で記述され主要なものとして metadata, components, dependencis の3つのセクションに分けられます。
<?xml version="1.0" encoding="UTF-8"?> <bom xmlns="http://cyclonedx.org/schema/bom/1.2" version="1" serialNumber="urn:uuid:7ccad4d5-9c07-469a-990f-5f639a5f0e80"> <metadata> ... </metadata> <components> ... <dependencies> ... </dependencies> </bom>
Metadata について
metadata には、この SBOM は何を対象にして作られたものなのか?について記述し、また SBOM を作成するために利用したツールなども含まれます。 そのほかにも製造元や、サプライヤーの情報なども含めることができます。
https://cyclonedx.org/use-cases/#packaging-and-distribution
例えば Trivy でコンテナの解析結果を記載するなら以下のようになります。
<metadata> <timestamp>2021-06-15T16:38:48Z</timestamp> <tools> <tool> <vendor>aquasecurity</vendor> <name>trivy</name> <version>dev</version> </tool> </tools> <component type="container"> <name>centos:8</name> <version>sha256:dbbacecc49b088458781c16f3775f2a</version> </component> </metadata>
Components について
CycloneDXには Component と呼ばれる、ソフトウェアを構成する部品を定義しています。現時点では以下のコンポーネントが定義されています。
- Application
- Container
- Device
- Library
- File
- Firmware
- Framework
- Operating System
- Service
https://cyclonedx.org/use-cases/#inventory
Components には Trivy で解析し発見された OS, Library, Application などを記述していきます。
解析対象がコンテナイメージだった場合は以下のような Components になります。
<components> <component type="operating-system"> <bom-ref>centos:8 (centos 8.3.2011)</bom-ref> <name>centos</name> <version>8</version> </component> <component type="application"> <bom-ref>/home/hogehoge/Pipfile.lock</bom-ref> <name>/home/hogehoge/Pipfile.lock</name> </component> <component type="library"> <bom-ref>pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64</bom-ref> <name>acl</name> <version>2.2.53-1.el8</version> <purl>pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64</purl> </component> <components>
Application コンポーネントには検知時に発見された Lock ファイルなど依存するライブラリの元を記述していきます。 また各 Component には SBOM 内でユニークな bom-ref を記述する必要があり、これが Component 間の依存関係を記述するのに用いられます。
Dependencies について
Dependencies にはコンポーネント間の依存関係を記述します。 例えば operating-system コンポーネントに acl ライブラリがインストールされていると表現する場合は、以下のように記述します。
<dependency ref="centos:8 (centos 8.3.2011)"> <dependency ref="pkg:rpm/centos/acl@2.2.53-1.el8?arch=x86_64"/> </dependency>
ref の部分に先ほど各コンポーネントで定義した bom-ref を利用します。
カスタムプロパティについて
ここで少し余談ですが、CycloneDX ではコンポーネントに対して、カスタムプロパティと呼ばれる Key-Value型のメタデータを付与することができるのですが、この Key にベンダーのNamespace が入れられるようになっています。
以下は Operating-system コンポーネントに Trivy で利用したいプロパティを埋め込んだ例です。
<component type="operating-system"> <name>centos</name> <version>8.4.2105</version> <properties> <property key="aquasecurity:trivy:Class" value="os-pkgs"/> <property key="aquasecurity:trivy:Type" value="centos"/> </properties> </component>
この Namespace は CycloneDX で管理されており、申請ベースで登録することで信頼性のあるプロパティとみなされます。
開発時には知らなかったのですが、 CycloneDX の方が教えてくださりなおかつ 「Namespace 取ったよー」と申請までやってくださり、本当に助かりました。
Add Aqua Security namespace reservation · CycloneDX/cyclonedx-property-taxonomy@2ee5194 · GitHub
Trivy での実装
Trivy ではコンテナイメージやファイルシステムなどの解析結果を先ほどのコンポーネントに当てはめて記述していきます。
CycloneDX には基本的に出力する SBOM としての Requirements はなく、「〇〇が SBOM として出力されていること」などの要件はありません。 そのため、 Trivy では持ちうる情報を可能な限り出力することを目的としました。
Trivy を用いてコンテナを解析した得られる情報は大きく分けて4つになります。
コンテナの情報
- コンテナの名前とバージョン
OSの情報
- OS の種別とバージョン
アプリケーションライブラリ
- OS にインストールされているライブラリ
- アプリケーションの依存関係ファイル
- Go 言語の実行可能バイナリ
- Jar, War などのアーカイブ
脆弱性情報
コンテナの情報について
コンポーネントの種別は Container を利用し、コンテナ名、コンテナのバージョン、コンテナの sha256 のハッシュ値を出力します。 Trivy で出力すると以下のようになります。
"component": { "type": "container", "name": "centos", "version": "sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6", "properties": [ { "name": "aquasecurity:trivy:SchemaVersion", "value": "2" }, { "name": "aquasecurity:trivy:Digest", "value": "centos@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177" }, { "name": "aquasecurity:trivy:Tag", "value": "centos:latest" } ] }
正直、特に説明することもないです。
OS の情報について
OS の種別(CentOS や Alpine など)やバージョンについては Operating-System のコンポーネントを使用し、定義しています。 Trivy で出力すると以下のようになります。
{ "bom-ref": "centos (centos 8.4.2105)", "type": "operating-system", "name": "centos", "version": "8.4.2105", "properties": [ { "name": "aquasecurity:trivy:Type", "value": "centos" }, { "name": "aquasecurity:trivy:Class", "value": "os-pkgs" } ] },
ここも正直、特に説明することもないです。
アプリケーションライブラリについて
OS にインストールされているライブラリや、アプリケーションライブラリ(pip, bundler など)については Library コンポーネントを利用しています。
CycloneDX では Library の記述方法として PURL を推奨しているため、こちらの仕様に則ってパッケージ情報を変換していきます。
PURL の仕様について少しだけ補足します。 PURL は以下のような URL 形式でパッケージを表現します。
scheme:type/namespace/name@version?qualifiers#subpath
例えば Java の spring パッケージを Trivy で表現するなら以下のようになります。
pkg:jar/org.springframework/spring-core@5.0.1.RELEASE
Maven のグループIDを namespace とし、 アーティファクトID を name にしています。
Trivy では namespace が存在しない PURL もありえます。 本来は GitHub のユーザ名や組織名などを用いるのですが、Ruby の Bundler などはそれらの情報を持ちません。 そのため rails などを PURL に変換するとこのようになります。
pkg:bundler/rails@6.1.4.1
また、 OS のパッケージなどは バージョンや名前だけではなく、 エポックやアーキテクチャなどもパッケージの一意性に関わってくるため qualfiers を利用して表現します。
CentOS の bash を表現する場合は以下のようになります。
pkg:centos/bash@4.4.19?arch=x86_64&release=14.el8&src_name=bash&src_release=14.el8&src_version=4.4.19
最終的には以下のような形で出力します。
{ "bom-ref": "pkg:centos/acl@2.2.53?arch=x86_64&license=GPLv2%2B&release=1.el8&src_name=acl&src_release=1.el8&src_version=2.2.53", "type": "library", "name": "acl", "version": "2.2.53-1.el8", "licenses": [ { "expression": "GPLv2+" } ], "purl": "pkg:centos/acl@2.2.53?arch=x86_64&license=GPLv2%2B&release=1.el8&src_name=acl&src_release=1.el8&src_version=2.2.53", "properties": [ { "name": "aquasecurity:trivy:LayerDiffID", "value": "sha256:74ddd0ec08fa43d09f32636ba91a0a3053b02cb4627c35051aff89f853606b59" } ] },
Trivy では Package の License 情報を取得することが可能なため、それらの情報なども付与しています。
また、 bom-ref には purl を利用しており、これは CycloneDX の仕様書でも推奨されている表現になります。
脆弱性情報について
脆弱性情報については、現在 CycloneDX が XML の形式しか対応していないため、 Trivy では未対応になります。
CycloneDX を対応するための PR に CycloneDX の標準化チームの議長さんがコメントをくださり、 CycloneDX 1.4 で json 対応が追加されるそうですので、気長に待ちたいと思います。
https://github.com/aquasecurity/trivy/pull/1081#discussion_r686905904
また、 CycloneDX での脆弱性の仕様を現在、拡張しており Draft版のPRが既にできあがっています。
ここまでが Trivy での実装についてとなります。
感想
この日本で誰も読まない(興味ない)だろうブログを書いてみましたが、CycloneDX がいつか流行った時に「こいつ... こんな前からやっていたのか 恐ろしい子...」みたいになればいいなと思います。
自分は文章を書くのが苦手で、この文量でも死ぬほど面倒でしたが、エンジニアの技能として文章での表現力はとても重要だと聞いたので、今後はもう少し習慣的に書こうと思います。
追記
アドカレにマージが間に合いませんでした(2021/12/22) github.com