25個を手で数えて SBOM として書く
目標
ロックファイル1つから「何が入っているか」を自分で数えて、CycloneDX形式のSBOMとして書き、誰も選んでいないパッケージがどの経路で入ってきたかを追跡します。
なぜ重要なのか
事故が起きたあとに最初に来る質問は、「うちはそれを使っていますか」です。この質問に数時間以内に答えられない組織は、答えが「いいえ」でも、何日も費やします。答えを早く出す方法は、事故のあとに探すことではなく、リリースのたびに一覧を一緒に作っておくことです。ところが、一覧の大部分は私たちが選んだものではありません。直接書いた依存7つが、25個を引き込みます。その25個が実際にデプロイに載るのか、ビルドにだけ使われるのかも、分ける必要があります。直すべき一覧と急ぐべき一覧は、別の一覧だからです。
ステップ
/opt/fixtures/sbom/release/package.jsonから直接依存を取り出して、/root/sbom/01-direct.jsonに書きます。/opt/fixtures/sbom/release/package-lock.jsonからインストールされたものをすべて数えて、/root/sbom/02-installed.jsonに書きます。- 本番と開発専用を分けて、
/root/sbom/03-scope.jsonに書きます。 sock-ttlがどの経路で入ってきたかを追跡して、/root/sbom/04-why.jsonに書きます。- CycloneDX 1.6形式で
/root/sbom/05-bom.jsonを作ります。 - アーティファクトのダイジェストを
/root/sbom/06-subject.jsonに固定します。 - デプロイに載るものだけを残して、
/root/sbom/07-runtime-bom.jsonを作ります。 - 前のリリースと見比べて、
/root/sbom/08-diff.jsonを作ります。
参考
- 作業はすべて
/root/sbomの下で行います。最初にmkdir -p /root/sbomを実行してください。 - 材料は
/opt/fixtures/sbomにあります。何がなぜあるかは、MATERIAL-CARD.mdに書いてあります。 - パッケージ名とアドバイザリ番号は、実習用に作り上げたものです。実際のパッケージの事情ではありません。
- このイメージにはsyftもgrypeもなく、PodはDNSだけが開いています。ロックファイルを直接読むのがこのラボのやり方で、そうすると、ツールが何を飛ばすのかも見えます。
- よくあるミスは、ロックファイルのルート項目(空文字列のキー)も一緒に数えて個数が1つ多くなることと、
devフラグがない項目を開発専用として誤って数えることです。 - 数字を作り上げて書かないでください。採点ツールが、同じ材料から計算し直して照合します。
私たちが直接選んだものから数える
/opt/fixtures/sbom/release/package.jsonを読んで、/root/sbom/01-direct.jsonに保存してください。runtimeはdependenciesの名前を辞書順に、devはdevDependenciesの名前を辞書順に入れた配列です。
package.jsonに書かれているのは、「私たちが選んだもの」だけです。バージョン範囲(^)が付いているので、名前だけを取り出せば済みます。インストールされたものすべては、ここにはありません。
実際にインストールされたものを数える
/opt/fixtures/sbom/release/package-lock.jsonを読んで、/root/sbom/02-installed.jsonにlockfile_version・total・direct・transitiveを保存してください。totalはpackagesマップからルート項目(キーが空文字列)を除いた個数、directはステップ1で数えた直接依存の数、transitiveはその残りです。
lockfileVersion 3のpackagesは、インストール先をキーとするマップで、ルートプロジェクトは空文字列のキーで入ります。ルートを除いて数えないと、1つ多く出ます。
デプロイに載るものと、ビルドにだけ使うものを分ける
/root/sbom/03-scope.jsonにruntime・dev・dev_namesを保存してください。ロックファイルの項目でdevがtrueなら、開発専用です。runtimeとdevは個数、dev_namesは開発専用のパッケージ名を辞書順に入れた配列です。
npmのドキュメントは、「strictly part of the devDependencies tree」のときだけdevがtrueになると書いています。フラグがまったくない項目は、本番側という意味です。
誰も選んでいないパッケージがどう入ってきたかを追跡する
sock-ttlがなぜインストールされたかを、ロックファイルのdependenciesをたどって、/root/sbom/04-why.jsonにpackage・version・direct・depth・pathsを保存してください。pathsはルートから始まる名前の配列の配列で、最初の欄はpaygateです。depthは、その経路の辺の数です。
ロックファイルの各項目にあるdependenciesが辺です。ルートから幅優先で降りていきながら、その名前がどこで最初に出てくるかを見てください。
CycloneDX形式で一覧を書く
/root/sbom/05-bom.jsonにSBOMを保存してください。bomFormatは"CycloneDX"、specVersionは"1.6"、versionは1、metadata.componentはpaygate 1.4.2(type application、purl pkg:npm/paygate@1.4.2)、componentsはインストールされた25個を、名前の辞書順に入れます。各項目はtype "library"・name・version・purl(pkg:npm/名前@バージョン)・scopeを持ち、scopeは開発専用なら"excluded"、そうでなければ"required"です。
bomFormatの値が"CycloneDX"に固定されている理由は、BOMファイルに名前の規則がないからです。ファイルを見るだけで、これが何の形式かがわかる必要がありますから。
一覧がどの成果物のものかを固定する
/opt/fixtures/sbom/release/paygate-1.4.2.jsのSHA-256を計算して、/root/sbom/06-subject.jsonにname(ファイル名だけ)・version・bytes・sha256を保存してください。sha256は、小文字の16進64文字です。
一覧に対象が書かれていないと、「何の一覧か」を誰も証明できません。hashlib.sha256でファイルのバイト列をハッシュしてください。
デプロイ成果物に載るものだけを残す
/root/sbom/05-bom.jsonからscopeがexcludedの項目を除いて、/root/sbom/07-runtime-bom.jsonに保存してください。形式は05-bom.jsonと同じで、componentsだけが減ります。
開発専用の依存の脆弱性は、ユーザーに届きません。2つを混ぜて数えると、直すべき数字が膨らんで、本当に急ぐものが埋もれます。
前のリリースから何が変わったかを数える
/opt/fixtures/sbom/sbom/paygate-1.4.1.cdx.jsonと/root/sbom/05-bom.jsonを見比べて、/root/sbom/08-diff.jsonにadded・removed・changed・unchangedを保存してください。addedとremovedはname・versionを入れたオブジェクトの配列(名前の辞書順)、changedはname・from・toを入れたオブジェクトの配列(名前の辞書順)、unchangedは両方にあってバージョンが同じものの個数です。
リリースノートが答えてくれない質問が、これです。誰も直していないのに増えたものはあるか。名前の集合の差と共通部分を、それぞれ別に見てください。