mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
scripts/sbom: add SPDX output graph
Implement the SPDX output graph which contains the distributable build outputs and high level metadata about the build. Assisted-by: Cursor:claude-sonnet-4-5 Assisted-by: OpenCode:GLM-4-7 Co-developed-by: Maximilian Huber <maximilian.huber@tngtech.com> Signed-off-by: Maximilian Huber <maximilian.huber@tngtech.com> Signed-off-by: Luis Augenstein <luis.augenstein@tngtech.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
ef0675c871
commit
b01912114e
5
Makefile
5
Makefile
|
|
@ -2213,7 +2213,10 @@ quiet_cmd_sbom = GEN $(sbom_targets)
|
|||
--obj-tree $(abspath $(objtree)) \
|
||||
--roots-file "$(tmp-target)" \
|
||||
--output-directory $(abspath $(objtree)) \
|
||||
--generate-spdx;
|
||||
--generate-spdx \
|
||||
--package-license "GPL-2.0 WITH Linux-syscall-note" \
|
||||
--package-version "$(KERNELVERSION)" \
|
||||
--write-output-on-error;
|
||||
PHONY += sbom
|
||||
sbom: $(notdir $(KBUILD_IMAGE)) include/generated/autoconf.h $(if $(CONFIG_MODULES),modules modules.order)
|
||||
$(call cmd,sbom)
|
||||
|
|
|
|||
|
|
@ -59,6 +59,21 @@ class KernelSbomConfig:
|
|||
spdxId_prefix: str
|
||||
"""Prefix to use for all SPDX element IDs."""
|
||||
|
||||
build_type: str
|
||||
"""SPDX buildType property to use for all Build elements."""
|
||||
|
||||
build_id: str | None
|
||||
"""SPDX buildId property to use for all Build elements."""
|
||||
|
||||
package_license: str
|
||||
"""License expression applied to all SPDX Packages."""
|
||||
|
||||
package_version: str | None
|
||||
"""Version string applied to all SPDX Packages."""
|
||||
|
||||
package_copyright_text: str | None
|
||||
"""Copyright text applied to all SPDX Packages."""
|
||||
|
||||
prettify_json: bool
|
||||
"""Whether to pretty-print generated SPDX JSON documents."""
|
||||
|
||||
|
|
@ -154,6 +169,40 @@ def _parse_cli_arguments(parser: argparse.ArgumentParser) -> dict[str, Any]:
|
|||
default="urn:spdx.dev:",
|
||||
help="The prefix to use for all spdxId properties. (default: urn:spdx.dev:)",
|
||||
)
|
||||
spdx_group.add_argument(
|
||||
"--build-type",
|
||||
default="urn:spdx.dev:Kbuild",
|
||||
help="The SPDX buildType property to use for all Build elements. (default: urn:spdx.dev:Kbuild)",
|
||||
)
|
||||
spdx_group.add_argument(
|
||||
"--build-id",
|
||||
default=None,
|
||||
help="The SPDX buildId property to use for all Build elements.\n"
|
||||
"If not provided the spdxId of the high level Build element is used as the buildId. (default: None)",
|
||||
)
|
||||
spdx_group.add_argument(
|
||||
"--package-license",
|
||||
default="NOASSERTION",
|
||||
help=(
|
||||
"The SPDX licenseExpression property to use for the LicenseExpression "
|
||||
"linked to all SPDX Package elements. (default: NOASSERTION)"
|
||||
),
|
||||
)
|
||||
spdx_group.add_argument(
|
||||
"--package-version",
|
||||
default=None,
|
||||
help="The SPDX packageVersion property to use for all SPDX Package elements. (default: None)",
|
||||
)
|
||||
spdx_group.add_argument(
|
||||
"--package-copyright-text",
|
||||
default=None,
|
||||
help=(
|
||||
"The SPDX copyrightText property to use for all SPDX Package elements.\n"
|
||||
"If not specified, and if a COPYING file exists in the source tree,\n"
|
||||
"the package-copyright-text is set to the content of this file. "
|
||||
"(default: None)"
|
||||
),
|
||||
)
|
||||
spdx_group.add_argument(
|
||||
"--prettify-json",
|
||||
action="store_true",
|
||||
|
|
@ -204,6 +253,16 @@ def get_config() -> KernelSbomConfig:
|
|||
tz=timezone.utc,
|
||||
)
|
||||
spdxId_prefix = args["spdxId_prefix"]
|
||||
build_type = args["build_type"]
|
||||
build_id = args["build_id"]
|
||||
package_license = args["package_license"]
|
||||
package_version = args["package_version"] if args["package_version"] is not None else None
|
||||
package_copyright_text: str | None = None
|
||||
if args["package_copyright_text"] is not None:
|
||||
package_copyright_text = args["package_copyright_text"]
|
||||
elif os.path.isfile(copying_path := os.path.join(src_tree, "COPYING")):
|
||||
with open(copying_path, "r", encoding="utf-8") as f:
|
||||
package_copyright_text = f.read()
|
||||
prettify_json = args["prettify_json"]
|
||||
|
||||
# Hardcoded config
|
||||
|
|
@ -228,6 +287,11 @@ def get_config() -> KernelSbomConfig:
|
|||
write_output_on_error=write_output_on_error,
|
||||
created=created,
|
||||
spdxId_prefix=spdxId_prefix,
|
||||
build_type=build_type,
|
||||
build_id=build_id,
|
||||
package_license=package_license,
|
||||
package_version=package_version,
|
||||
package_copyright_text=package_copyright_text,
|
||||
prettify_json=prettify_json,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,12 +10,18 @@ from sbom.path_utils import PathStr
|
|||
from sbom.spdx_graph.kernel_file import KernelFileCollection
|
||||
from sbom.spdx_graph.spdx_graph_model import SpdxGraph, SpdxIdGeneratorCollection
|
||||
from sbom.spdx_graph.shared_spdx_elements import SharedSpdxElements
|
||||
from sbom.spdx_graph.spdx_output_graph import SpdxOutputGraph
|
||||
|
||||
|
||||
class SpdxGraphConfig(Protocol):
|
||||
obj_tree: PathStr
|
||||
src_tree: PathStr
|
||||
created: datetime
|
||||
build_type: str
|
||||
build_id: str | None
|
||||
package_license: str
|
||||
package_version: str | None
|
||||
package_copyright_text: str | None
|
||||
|
||||
|
||||
def build_spdx_graphs(
|
||||
|
|
@ -38,4 +44,14 @@ def build_spdx_graphs(
|
|||
"""
|
||||
shared_elements = SharedSpdxElements.create(spdx_id_generators.base, config.created)
|
||||
kernel_files = KernelFileCollection.create(cmd_graph, config.obj_tree, config.src_tree, spdx_id_generators)
|
||||
return {}
|
||||
output_graph = SpdxOutputGraph.create(
|
||||
root_files=list(kernel_files.output.values()),
|
||||
shared_elements=shared_elements,
|
||||
spdx_id_generators=spdx_id_generators,
|
||||
config=config,
|
||||
)
|
||||
spdx_graphs: dict[KernelSpdxDocumentKind, SpdxGraph] = {
|
||||
KernelSpdxDocumentKind.OUTPUT: output_graph,
|
||||
}
|
||||
|
||||
return spdx_graphs
|
||||
|
|
|
|||
187
scripts/sbom/sbom/spdx_graph/spdx_output_graph.py
Normal file
187
scripts/sbom/sbom/spdx_graph/spdx_output_graph.py
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only OR MIT
|
||||
# Copyright (C) 2025 TNG Technology Consulting GmbH
|
||||
|
||||
from dataclasses import dataclass
|
||||
import os
|
||||
from typing import Protocol
|
||||
from sbom.environment import Environment
|
||||
from sbom.path_utils import PathStr
|
||||
from sbom.spdx.build import Build
|
||||
from sbom.spdx.core import DictionaryEntry, NamespaceMap, Relationship, SpdxDocument
|
||||
from sbom.spdx.simplelicensing import LicenseExpression
|
||||
from sbom.spdx.software import File, Package, Sbom
|
||||
from sbom.spdx.spdxId import SpdxIdGenerator
|
||||
from sbom.spdx_graph.kernel_file import KernelFile
|
||||
from sbom.spdx_graph.shared_spdx_elements import SharedSpdxElements
|
||||
from sbom.spdx_graph.spdx_graph_model import SpdxGraph, SpdxIdGeneratorCollection
|
||||
|
||||
|
||||
class SpdxOutputGraphConfig(Protocol):
|
||||
obj_tree: PathStr
|
||||
src_tree: PathStr
|
||||
build_type: str
|
||||
build_id: str | None
|
||||
package_license: str
|
||||
package_version: str | None
|
||||
package_copyright_text: str | None
|
||||
|
||||
|
||||
@dataclass
|
||||
class SpdxOutputGraph(SpdxGraph):
|
||||
"""SPDX graph representing distributable output files"""
|
||||
|
||||
high_level_build_element: Build
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
root_files: list[KernelFile],
|
||||
shared_elements: SharedSpdxElements,
|
||||
spdx_id_generators: SpdxIdGeneratorCollection,
|
||||
config: SpdxOutputGraphConfig,
|
||||
) -> "SpdxOutputGraph":
|
||||
"""
|
||||
Args:
|
||||
root_files: List of distributable output files which act as roots
|
||||
of the dependency graph.
|
||||
shared_elements: Shared SPDX elements used across multiple documents.
|
||||
spdx_id_generators: Collection of SPDX ID generators.
|
||||
config: Configuration options.
|
||||
|
||||
Returns:
|
||||
SpdxOutputGraph: The SPDX output graph.
|
||||
"""
|
||||
# SpdxDocument
|
||||
spdx_document = SpdxDocument(
|
||||
spdxId=spdx_id_generators.output.generate(),
|
||||
profileConformance=["core", "software", "build", "simpleLicensing"],
|
||||
namespaceMap=[
|
||||
NamespaceMap(prefix=generator.prefix, namespace=generator.namespace)
|
||||
for generator in [spdx_id_generators.output, spdx_id_generators.base]
|
||||
if generator.prefix is not None
|
||||
],
|
||||
)
|
||||
|
||||
# Sbom
|
||||
sbom = Sbom(
|
||||
spdxId=spdx_id_generators.output.generate(),
|
||||
software_sbomType=["build"],
|
||||
)
|
||||
|
||||
# High-level Build elements
|
||||
config_source_element = KernelFile.create(
|
||||
absolute_path=os.path.join(config.obj_tree, ".config"),
|
||||
obj_tree=config.obj_tree,
|
||||
src_tree=config.src_tree,
|
||||
spdx_id_generators=spdx_id_generators,
|
||||
is_output=True,
|
||||
).spdx_file_element
|
||||
high_level_build_element, high_level_build_element_hasOutput_relationship = _high_level_build_elements(
|
||||
config.build_type,
|
||||
config.build_id,
|
||||
config_source_element,
|
||||
spdx_id_generators.output,
|
||||
)
|
||||
|
||||
# Root file elements
|
||||
root_file_elements: list[File] = [file.spdx_file_element for file in root_files]
|
||||
|
||||
# Package elements
|
||||
package_elements = [
|
||||
Package(
|
||||
spdxId=spdx_id_generators.output.generate(),
|
||||
name=_get_package_name(file.name),
|
||||
software_packageVersion=config.package_version,
|
||||
software_copyrightText=config.package_copyright_text,
|
||||
comment=f"Architecture={arch}" if (arch := Environment.ARCH() or Environment.SRCARCH()) else None,
|
||||
software_primaryPurpose=file.software_primaryPurpose,
|
||||
)
|
||||
for file in root_file_elements
|
||||
]
|
||||
package_hasDistributionArtifact_file_relationships = [
|
||||
Relationship(
|
||||
spdxId=spdx_id_generators.output.generate(),
|
||||
relationshipType="hasDistributionArtifact",
|
||||
from_=package,
|
||||
to=[file],
|
||||
)
|
||||
for package, file in zip(package_elements, root_file_elements)
|
||||
]
|
||||
package_license_expression = LicenseExpression(
|
||||
spdxId=spdx_id_generators.output.generate(),
|
||||
simplelicensing_licenseExpression=config.package_license,
|
||||
)
|
||||
package_hasDeclaredLicense_relationships = [
|
||||
Relationship(
|
||||
spdxId=spdx_id_generators.output.generate(),
|
||||
relationshipType="hasDeclaredLicense",
|
||||
from_=package,
|
||||
to=[package_license_expression],
|
||||
)
|
||||
for package in package_elements
|
||||
]
|
||||
|
||||
# Update relationships
|
||||
spdx_document.rootElement = [sbom]
|
||||
|
||||
sbom.rootElement = [*package_elements]
|
||||
sbom.element = [
|
||||
config_source_element,
|
||||
high_level_build_element,
|
||||
high_level_build_element_hasOutput_relationship,
|
||||
*root_file_elements,
|
||||
*package_elements,
|
||||
*package_hasDistributionArtifact_file_relationships,
|
||||
package_license_expression,
|
||||
*package_hasDeclaredLicense_relationships,
|
||||
]
|
||||
|
||||
high_level_build_element_hasOutput_relationship.to = [*root_file_elements]
|
||||
|
||||
output_graph = SpdxOutputGraph(
|
||||
spdx_document,
|
||||
shared_elements.agent,
|
||||
shared_elements.creation_info,
|
||||
sbom,
|
||||
high_level_build_element,
|
||||
)
|
||||
return output_graph
|
||||
|
||||
|
||||
def _get_package_name(filename: str) -> str:
|
||||
"""
|
||||
Generates a SPDX package name from a filename.
|
||||
Kernel images (bzImage, Image) get a descriptive name, others use the basename of the file.
|
||||
"""
|
||||
KERNEL_FILENAMES = ["bzImage", "Image"]
|
||||
basename = os.path.basename(filename)
|
||||
return f"Linux Kernel ({basename})" if basename in KERNEL_FILENAMES else basename
|
||||
|
||||
|
||||
def _high_level_build_elements(
|
||||
build_type: str,
|
||||
build_id: str | None,
|
||||
config_source_element: File,
|
||||
spdx_id_generator: SpdxIdGenerator,
|
||||
) -> tuple[Build, Relationship]:
|
||||
build_spdxId = spdx_id_generator.generate()
|
||||
high_level_build_element = Build(
|
||||
spdxId=build_spdxId,
|
||||
build_buildType=build_type,
|
||||
build_buildId=build_id if build_id is not None else build_spdxId,
|
||||
build_environment=[
|
||||
DictionaryEntry(key=key, value=value)
|
||||
for key, value in Environment.KERNEL_BUILD_VARIABLES().items()
|
||||
if value
|
||||
],
|
||||
build_configSourceUri=[config_source_element.spdxId],
|
||||
build_configSourceDigest=config_source_element.verifiedUsing,
|
||||
)
|
||||
|
||||
high_level_build_element_hasOutput_relationship = Relationship(
|
||||
spdxId=spdx_id_generator.generate(),
|
||||
relationshipType="hasOutput",
|
||||
from_=high_level_build_element,
|
||||
to=[],
|
||||
)
|
||||
return high_level_build_element, high_level_build_element_hasOutput_relationship
|
||||
Loading…
Reference in New Issue
Block a user