CycloneDX / cyclonedx-cli

CycloneDX CLI tool for SBOM analysis, merging, diffs and format conversions.
https://cyclonedx.org/
Apache License 2.0
299 stars 60 forks source link

Documention: Hierarchical merge metadata requirement #310

Open robertlagrant opened 1 year ago

robertlagrant commented 1 year ago

The documention states:

Note: To perform a hierarchical merge all BOMs need the subject of the BOM described in the metadata component element.

There are no details here as to what this should look like.

When I go to the Python command cyclonedx-bom, I don't see any options for adding this:

usage: cyclonedx-bom [-h] (-c | -cj | -e | -p | -pip | -r) [-i FILE_PATH] [--format {xml,json}] [--schema-version {1.4,1.3,1.2,1.1,1.0}] [-o FILE_PATH] [-F] [-pb] [-X]

CycloneDX SBOM Generator

options:
  -h, --help            show this help message and exit
  -c, --conda           Build a SBOM based on the output from `conda list --explicit` or `conda list --explicit --md5`
  -cj, --conda-json     Build a SBOM based on the output from `conda list --json`
  -e, --e, --environment
                        Build a SBOM based on the packages installed in your current Python environment (default)
  -p, --p, --poetry     Build a SBOM based on a Poetry poetry.lock's contents. Use with -i to specify absolute path to a `poetry.lock` you wish to use, else we'll look for one in the current working directory.
  -pip, --pip           Build a SBOM based on a PipEnv Pipfile.lock's contents. Use with -i to specify absolute path to a `Pipfile.lock` you wish to use, else we'll look for one in the current working directory.
  -r, --r, --requirements
                        Build a SBOM based on a requirements.txt's contents. Use with -i to specify absolute path to a `requirements.txt` you wish to use, else we'll look for one in the current working directory.
  -X                    Enable debug output

Input Method:
  Flags to determine how this tool obtains it's input

  -i FILE_PATH, --in-file FILE_PATH
                        File to read input from. Use "-" to read from STDIN.

SBOM Output Configuration:
  Choose the output format and schema version

  --format {xml,json}   The output format for your SBOM (default: xml)
  --schema-version {1.4,1.3,1.2,1.1,1.0}
                        The CycloneDX schema version for your SBOM (default: 1.4)
  -o FILE_PATH, --o FILE_PATH, --output FILE_PATH
                        Output file path for your SBOM (set to '-' to output to STDOUT)
  -F, --force           If outputting to a file and the stated file already exists, it will be overwritten.
  -pb, --purl-bom-ref   Use a component's PURL for the bom-ref value, instead of a random UUID

Same with the Node equivalent, @cyclonedx/cyclondedx-npm:

Create CycloneDX Software Bill of Materials (SBOM) from Node.js NPM projects.

Arguments:
  <package-manifest>        Path to project's manifest file. (default: "package.json" file in current working directory)

Options:
  --ignore-npm-errors       Whether to ignore errors of NPM.
                            This might be used, if "npm install" was run with "--force" or "--legacy-peer-deps". (default: false)
  --package-lock-only       Whether to only use the lock file, ignoring "node_modules".
                            This means the output will be based only on the few details in and the tree described by the "npm-shrinkwrap.json" or "package-lock.json", rather than the contents of "node_modules"
                            directory. (default: false)
  --omit <type...>          Dependency types to omit from the installation tree.(can be set multiple times) (choices: "dev", "optional", "peer", default: "dev" if the NODE_ENV environment variable is set to
                            "production", otherwise empty)
  --flatten-components      Whether to flatten the components.
                            This means the actual nesting of node packages is not represented in the SBOM result. (default: false)
  --short-PURLs             Omit all qualifiers from PackageURLs.
                            This causes information loss in trade of shorter PURLs, which might improve digesting these strings. (default: false)
  --spec-version <version>  Which version of CycloneDX spec to use. (choices: "1.2", "1.3", "1.4", default: "1.4")
  --output-reproducible     Whether to go the extra mile and make the output reproducible.
                            This requires more resources, and might result in loss of time- and random-based-values. (env: BOM_REPRODUCIBLE)
  --output-format <format>  Which output format to use. (choices: "JSON", "XML", default: "JSON")
  --output-file <file>      Path to the output file.
                            Set to "-" to write to STDOUT. (default: write to STDOUT)
  --mc-type <type>          Type of the main component. (choices: "application", "firmware", "library", default: "application")
  -V, --version             output the version number
  -h, --help                display help for command

I also can't see a way in this tool to add this metadata information to an existing BOM.

Any guidance greatly appreciated. It would be good to turn it into documentation (and/or added to BOM generation tools).

nscuro commented 1 year ago

"Subject" just refers to the metadata.component element of the BOM. metadata.component should be populated by every SBOM generator per default. It will have the details of the project you generated the SBOM for.

The reason for this requirement is that you can't create a hierarchical structure out of two things that do not have an identity themselves.

robertlagrant commented 1 year ago

Thanks - I can try and add it in manually. Now I know what to look for, I can see the NPM tool does it, but the Python tool doesn't.

The Python metadata:

% cat cyclonedx.json | jq '.["metadata"]'
{
  "timestamp": "2023-03-09T09:45:00.033550+00:00",
  "tools": [
    {
      "vendor": "CycloneDX",
      "name": "cyclonedx-bom",
      "version": "3.11.0"
    },
    {
      "vendor": "CycloneDX",
      "name": "cyclonedx-python-lib",
      "version": "3.1.5",
      "externalReferences": [
        {
          "url": "https://github.com/CycloneDX/cyclonedx-python-lib/actions",
          "type": "build-system"
        },
        {
          "url": "https://pypi.org/project/cyclonedx-python-lib/",
          "type": "distribution"
        },
        {
          "url": "https://cyclonedx.github.io/cyclonedx-python-lib/",
          "type": "documentation"
        },
        {
          "url": "https://github.com/CycloneDX/cyclonedx-python-lib/issues",
          "type": "issue-tracker"
        },
        {
          "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/LICENSE",
          "type": "license"
        },
        {
          "url": "https://github.com/CycloneDX/cyclonedx-python-lib/blob/main/CHANGELOG.md",
          "type": "release-notes"
        },
        {
          "url": "https://github.com/CycloneDX/cyclonedx-python-lib",
          "type": "vcs"
        },
        {
          "url": "https://cyclonedx.org",
          "type": "website"
        }
      ]
    }
  ]
}
robertlagrant commented 1 year ago

Given the above, I think I'd suggest two changes:

  1. make the documentation slightly clearer on what's required - I can probably raise a PR to do this
  2. make the error message from this tool slightly less opaque - it looks like this:
    Processing input file fe.cyclonedx.json
    Contains 694 components
    Processing input file cyclonedx.json
    Contains 58 components
    Unhandled exception: CycloneDX.Utils.Exceptions.MissingMetadataComponentException: Required metadata (top level) component is missing from BOM urn:uuid:6cb39f39-bd95-401a-ac13-0176b71db09a.
    at CycloneDX.Utils.CycloneDXUtils.HierarchicalMerge(IEnumerable`1 boms, Component bomSubject)
    at CycloneDX.Cli.Commands.MergeCommand.Merge(MergeCommandOptions options)
    at System.CommandLine.Invocation.CommandHandler.GetExitCodeAsync(Object value, InvocationContext context)
    at System.CommandLine.Invocation.ModelBindingCommandHandler.InvokeAsync(InvocationContext context)
    at System.CommandLine.Invocation.InvocationPipeline.<>c__DisplayClass4_0.<<BuildInvocationChain>b__0>d.MoveNext()
    --- End of stack trace from previous location ---
    at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass23_0.<<UseParseErrorReporting>b__0>d.MoveNext()
    --- End of stack trace from previous location ---
    at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass16_0.<<UseHelp>b__0>d.MoveNext()
    --- End of stack trace from previous location ---
    at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass27_0.<<UseVersionOption>b__1>d.MoveNext()
    --- End of stack trace from previous location ---
    at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass25_0.<<UseTypoCorrections>b__0>d.MoveNext()
    --- End of stack trace from previous location ---
    at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c.<<UseSuggestDirective>b__24_0>d.MoveNext()
    --- End of stack trace from previous location ---
    at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass22_0.<<UseParseDirective>b__0>d.MoveNext()
    --- End of stack trace from previous location ---
    at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass11_0.<<UseDebugDirective>b__0>d.MoveNext()
    --- End of stack trace from previous location ---
    at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c.<<RegisterWithDotnetSuggest>b__10_0>d.MoveNext()
    --- End of stack trace from previous location ---
    at System.CommandLine.Builder.CommandLineBuilderExtensions.<>c__DisplayClass14_0.<<UseExceptionHandler>b__0>d.MoveNext()
    %
robertlagrant commented 1 year ago

Noticed this is an open bug in the library repo: https://github.com/CycloneDX/cyclonedx-python/issues/391

jkowalleck commented 1 year ago

Noticed this is an open bug in the library repo: CycloneDX/cyclonedx-python#391

not a bug, but a feature nobody was ever willing to contribute. pullrequests are welcome.