ネイティブ・イメージのソフトウェア部品表(SBOM)

GraalVMネイティブ・イメージは、ビルド時にソフトウェア部品表(SBOM)を構成して、既知のセキュリティの脆弱性の影響を受ける可能性のあるライブラリを検出できます。ネイティブ・イメージには、SBOMをネイティブ実行可能ファイルに埋め込むための--enable-sbomオプションが用意されています(Oracle GraalVMでのみ使用可能)。SBOMは、--enable-sbom=classpath,exportを使用して、埋め込む以外にクラスパスに追加することもJSONファイルとしてエクスポートすることもできます。

CycloneDX形式がサポートされており、これがデフォルトです。CycloneDX SBOMをネイティブ実行可能ファイルに埋め込むには、--enable-sbomオプションをnative-imageコマンドに渡します。

この実装では、ネイティブ実行可能ファイルに含まれるクラスについて、外部ライブラリ・マニフェストで監視可能なすべてのバージョン情報をリカバリすることによってSBOMが構築されます。また、SBOMは、ネイティブ実行可能ファイル・サイズへの影響を抑えるために圧縮されます。SBOMはgzip形式で格納され、エクスポートされたsbomシンボルはその開始アドレスを参照し、sbom_lengthシンボルはそのサイズを参照します。

SBOMの内容の抽出

圧縮されたSBOMをイメージに埋め込んだ後、SBOMの内容を抽出するには次の2つの方法があります:

ネイティブ・イメージ検査ツール

ネイティブ・イメージ検査ツールでは、--sbomパラメータを使用して圧縮されたSBOMを抽出でき、実行可能ファイルと共有ライブラリの両方からアクセスできます:

native-image-inspect --sbom <path_to_binary>

内容がJSON形式で出力されます:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.5",
  "version": 1,
  "components": [
    {
      "bom-ref": "pkg:maven/io.netty/netty-codec-http2@4.1.104.Final",
      "type": "library",
      "group": "io.netty",
      "name": "netty-codec-http2",
      "version": "4.1.104.Final",
      "purl": "pkg:maven/io.netty/netty-codec-http2@4.1.104.Final",
      "properties": [
        {
          "name": "syft:cpe23",
          "value": "cpe:2.3:a:codec:codec:4.1.76.Final:*:*:*:*:*:*:*"
        },
        {
          "name": "syft:cpe23",
          "value": "cpe:2.3:a:codec:netty-codec-http2:4.1.76.Final:*:*:*:*:*:*:*"
        },
        {
          "name": "syft:cpe23",
          "value": "cpe:2.3:a:codec:netty_codec_http2:4.1.76.Final:*:*:*:*:*:*:*"
        },
        ...
      ]
    },
    ...
  ],
  "dependencies": [
    {
      "ref": "pkg:maven/io.netty/netty-codec-http2@4.1.104.Final",
      "dependsOn": [
        "pkg:maven/io.netty/netty-buffer@4.1.104.Final",
        "pkg:maven/io.netty/netty-codec-http@4.1.104.Final",
        "pkg:maven/io.netty/netty-codec@4.1.104.Final",
        "pkg:maven/io.netty/netty-common@4.1.104.Final",
        "pkg:maven/io.netty/netty-transport@4.1.104.Final"
      ]
    },
    ...
  ],
  "serialNumber": "urn:uuid:51ec305f-616e-4139-a033-a094bb94a17c"
}

Syft

Syftは、コンテナ・イメージおよびファイルシステム用のSBOMを生成するAnchoreによって開発されたオープンソース・ツールです。さらに、埋込みSBOMを抽出し、ネイティブのSyft形式とCycloneDX形式の両方で表示できます。GraalVMチームの貢献により、syftは、Linux、macOSまたはWindows用にビルドされたネイティブ・イメージ内から埋込みSBOMを抽出できます。

ネイティブ実行可能ファイルに対してsyft scanを実行して、SBOMの内容全体を抽出します:

syft scan <path_to_binary> -o cyclonedx-json

含まれているJavaライブラリのみをリストするには、次を実行します:

syft <path_to_binary>

次のようなリストが出力されます:

NAME               VERSION       TYPE
Oracle GraalVM     25+12-LTS     graalvm-native-image
collections        25+12-LTS     java-archive
commons-validator  1.9.0         java-archive
json               20211205      java-archive
...

セキュリティ・スキャンの有効化

生成されたSBOMを利用して、セキュリティ・スキャン・ソリューションと統合できます。アプリケーションの依存関係におけるセキュリティの脆弱性を検出および軽減するのに役立つ様々なツールがあります。

1つの例は、Oracleのアプリケーション依存関係管理(ADM)です。SBOMをADM脆弱性スキャナに送信すると、アプリケーションの依存関係が識別され、既知のセキュリティ脆弱性を含むものにフラグが付けられます。ADMは、National Vulnerability Database(NVD)を含むコミュニティ・ソースからの脆弱性レポートに依存しています。また、GitHubアクション、GitLabおよびJenkinsパイプラインとも統合されます。

もう1つの一般的なコマンドライン・スキャナは、Anchoreソフトウェア・サプライ・チェーン管理プラットフォームの一部であるgrypeです。grypeを使用すると、SBOMにリストされているライブラリに、Anchoreのデータベースに記載されている既知の脆弱性があるかどうかを確認できます。native-image-inspectツールの出力を次のコマンドを使用してgrypeに直接入力し、脆弱なライブラリをスキャンできます:

native-image-inspect --sbom <path_to_binary> | grype

これによって、次の出力が生成されます。

NAME                 INSTALLED      VULNERABILITY   SEVERITY
netty-codec-http2    4.1.76.Final   CVE-2022-24823  Medium

生成されたレポートを使用して、実行可能ファイル内の脆弱な依存関係を更新できます。

自動スキャン

CI/CDワークフローへのセキュリティ・スキャンの統合がかつてないほど簡単になりました。GraalVM GitHubアクションでSBOMサポートを使用できるため、生成されたSBOMはGitHubの依存関係送信APIを使用して自動的に送信および分析できます。次のことが可能です:

この統合により、開発ライフサイクル全体を通じてアプリケーションの脆弱性を継続的にモニターできるようになります。

依存関係ツリー

SBOMは、dependenciesフィールドを介してコンポーネントの関係に関する情報を提供します。この依存関係情報は、ネイティブ・イメージの静的分析コール・グラフから導出されます。依存関係グラフを分析すると、特定のコンポーネントがアプリケーションに含まれている理由を理解するのに役立ちます。たとえば、SBOMで予期しないコンポーネントを検出すると、依存関係グラフを通じてその包含をトレースして、アプリケーションのどの部分が使用しているかを特定できます。

GraalVM GitHubアクションを使用すると、GitHubの依存関係グラフ機能にアクセスできます。

Mavenを使用したより正確なSBOM

より正確なSBOMを生成するには、GraalVMネイティブ・イメージ用のMavenプラグインの使用を検討してください。このプラグインは、SBOM作成を改善するためにネイティブ・イメージと統合されます。

プラグインは、cyclonedx-maven-pluginを使用してベースラインSBOMを作成します。ベースラインSBOMは、コンポーネントに属するパッケージ名を定義し、ネイティブ・イメージがクラスをそれぞれのコンポーネントに関連付けるのを支援します。これは、シェーディングJARまたはファットJARが使用されている場合、native-imageツールで困難なタスクです。この共同アプローチでは、ネイティブ・イメージは、最小限のSBOMを生成するために、コンポーネントおよび依存関係をより積極的にプルーニングすることもできます。

これらの拡張機能は、プラグイン・バージョン0.10.4以降で使用でき、--enable-sbomオプションを使用するとデフォルトで有効になります。

SBOMへのクラス・レベルのメタデータの組込み

--enable-sbom=class-levelを使用すると、クラス・レベルのメタデータがSBOMコンポーネントに追加されます。このメタデータには、ネイティブ実行可能ファイルの一部であるJavaモジュール、クラス、インタフェース、レコード、注釈、列挙、コンストラクタ、フィールドおよびメソッドが含まれます。この情報を次のことに使用できます:

クラス・レベルのメタデータを含めると、SBOMのサイズが大幅に増加します。このMicronaut Hello World Restアプリケーションでは、SBOMサイズは埋込みの場合は1.1MB、エクスポートの場合は13.7MBです。クラス・レベルのメタデータのないSBOMは、埋込みの場合は3.5KB、エクスポートの場合は64KBです。SBOMが埋め込まれていないネイティブ・イメージのサイズは約52MBです。

クラス・レベルのメタデータを含めることは、Syftではサポートされていません。このメタデータを含むネストされたコンポーネント・フィールドは抽出されたSBOMから削除されるためです。この制限は、抽出されたSBOMのメタデータ表示にのみ影響します。脆弱性スキャン機能には影響しません。

データ形式

CycloneDX仕様では、親子関係を持つコンポーネントをネストすることで、階層表現を使用できます。クラス・レベルの情報をSBOMコンポーネントに埋め込むために、次のように使用されます:

[component] SBOM Component
└── [component] Java Modules
    └── [component] Java Source Files
        ├── [property] Classes
        ├── [property] Interfaces
        ├── [property] Records
        ├── [property] Annotations
        ├── [property] Enums
        ├── [property] Fields
        ├── [property] Constructors
        └── [property] Methods

各SBOMコンポーネントは、componentsフィールドにそのJavaモジュールをリストします。各モジュールは名前で識別され、そのJavaソース・ファイルがcomponentsフィールドにリストされます。各ソース・ファイルは、コンポーネントのソース・ディレクトリに対する相対パスによって識別され、そのクラス、インタフェース、レコード、注釈、列挙、フィールド、コンストラクタおよびメソッドがpropertiesフィールドにリストされます。

mymoduleに1つのJavaソース・ファイルを含む単純なコンポーネントの例を考えてみます:

// src/com/sbom/SBOMTestApplication.java
package com.sbom;

import org.apache.commons.validator.routines.RegexValidator;

public class SBOMTestApplication {
    private static final boolean IS_EMPTY_OR_BLANK = new RegexValidator("^[\\s]*$").isValid(" ");

    public static void main(String[] argv) {
        System.out.println(String.valueOf(IS_EMPTY_OR_BLANK));
        ClassInSameFile someClass = new ClassInSameFile("hello ", "world");
        someClass.doSomething();
    }
}

class ClassInSameFile {
    private final String value1;
    private final String value2;

    ClassInSameFile(String value1, String value2) {
        this.value1 = value1;
        this.value2 = value2;
    }

    String concatenate() {
        System.out.println(value1 + value2);
    }

    // This method is unreachable and will therefore not be included in the SBOM
    String unreachable() {
        return value;
    }
}

クラスレベルのSBOMコンポーネントは次のようになります:

{
    "type": "library",
    "group": "com.sbom",
    "name": "sbom-test-app",
    "version": "1.0.0",
    "purl": "pkg:maven/com.sbom/sbom-test-app@1.0.0",
    "bom-ref": "pkg:maven/com.sbom/sbom-test-app@1.0.0",
    "properties": [...],
    "components": [
        {
            "type": "library",
            "name": "mymodule",
            "components": [
                {
                    "type": "file",
                    "name": "com/sbom/SBOMTestApplication.java",
                    "properties": [
                        {
                            "name": "class",
                            "value": "com.sbom.ClassInSameFile"
                        },
                        {
                            "name": "class",
                            "value": "com.sbom.SBOMTestApplication"
                        },
                        {
                            "name": "field",
                            "value": "com.sbom.ClassInSameFile.value1:java.lang.String"
                        },
                        {
                            "name": "field",
                            "value": "com.sbom.ClassInSameFile.value2:java.lang.String"
                        },
                        {
                            "name": "field",
                            "value": "com.sbom.SBOMTestApplication.IS_EMPTY_OR_BLANK:boolean"
                        },
                        {
                            "name": "constructor",
                            "value": "com.sbom.ClassInSameFile(java.lang.String, java.lang.String)"
                        },
                        {
                            "name": "method",
                            "value": "com.sbom.ClassInSameFile.concatenate():java.lang.String"
                        },
                        {
                            "name": "method",
                            "value": "com.sbom.SBOMTestApplication.<clinit>():void"
                        },
                        {
                            "name": "method",
                            "value": "com.sbom.SBOMTestApplication.main(java.lang.String[]):void"
                        }
                    ]
                }
            ]
        }
    ]
}

次の表に、クラス・レベルのメタデータの形式を示します:

種類 CycloneDXオブジェクト type name value 備考
モジュール コンポーネント library モジュール名 - 名前のないモジュールのnameunnamed moduleです
ソース・ファイル コンポーネント file srcディレクトリからの相対パス - .javaで終わり、/で区切られ、パッケージ名から導出されたパス
クラス プロパティ - class 完全修飾名 無名クラス、内部クラスおよびシール・クラスが含まれます
インタフェース プロパティ - interface 完全修飾名 -
レコード プロパティ - record 完全修飾名 -
注釈 プロパティ - annotation 完全修飾名 -
フィールド プロパティ - field className.fieldName:fieldType フィールド宣言
コンストラクタ プロパティ - constructor className(paramType1, paramType2) カンマとスペースで区切ったパラメータ・タイプ
メソッド プロパティ - method className.methodName(paramType1, paramType2):returnType パラメータおよび戻り型を持つメソッド

その他のノート:

シェーディングされたJARまたはファットJARを使用する場合、クラスレベルのメタデータがコンポーネントに正確に関連付けられないことがあります。これが発生すると、未解決のすべてのメタデータがプレースホルダ・コンポーネントに収集されます:

{
    "type": "data",
    "name": "class-level metadata that could not be associated with a component",
    "components": [
      ...
    ]
}