@@ -285,6 +285,7 @@ addtask do_create_recipe_sbom_setscene
python spdx30_build_started_handler () {
import oe.spdx30_tasks
d = e.data.createCopy()
+ oe.spdx30_tasks.write_layers_spdx(d)
oe.spdx30_tasks.write_bitbake_spdx(d)
}
@@ -89,6 +89,9 @@ SPDX_FILE_EXCLUDE_PATTERNS[doc] = "Space-separated list of Python regular \
(no filtering). Example: \
SPDX_FILE_EXCLUDE_PATTERNS = '\\.patch$ \\.diff$ /test/ \\.pyc$ \\.o$'"
+SPDX_REQUIRE_LAYER_ASSERTION ??= "0"
+SPDX_LAYER_REMOTE ??= "origin"
+
python () {
from oe.cve_check import extend_cve_status
extend_cve_status(d)
@@ -752,6 +752,16 @@ class ObjectSet(oe.spdx30.SHACLObjectSet):
return bb_objset
+ def import_layers_objset(self):
+ deploy_dir_spdx = Path(self.d.getVar("DEPLOY_DIR_SPDX"))
+ layers_objset = load_jsonld(
+ self.d, deploy_dir_spdx / "layers.spdx.json", required=True
+ )
+ self.doc.import_.extend(layers_objset.doc.import_)
+ self.update(layers_objset.objects)
+
+ return layers_objset
+
def import_bitbake_build(self):
def find_bitbake_build(objset):
return objset.find_filter(
@@ -793,6 +803,8 @@ class ObjectSet(oe.spdx30.SHACLObjectSet):
[build],
)
+ layers = self.import_layers_objset()
+
if self.d.getVar("SPDX_INCLUDE_BUILD_VARIABLES") == "1":
for varname in sorted(self.d.keys()):
if varname.startswith("__"):
@@ -1220,6 +1220,80 @@ def create_package_spdx(d):
oe.sbom30.write_recipe_jsonld_doc(d, common_objset, "common-package", deploydir)
+def git_remote_has_rev(path, remote, rev):
+ try:
+ branchlist, _ = bb.process.run(f"git branch --remotes --list '{remote}/*' --contains {rev}", cwd=path)
+ except bb.process.ExecutionError:
+ return False
+ return len(branchlist) > 0
+
+def get_layer_downloadLocation(d, l_path, l_name, l_branch, l_rev, l_ismodified):
+ from urllib.parse import urlparse, urlunparse
+
+ repo = oe.buildcfg.get_metadata_git_toplevel(l_path)
+
+ if not repo or not l_name or l_ismodified:
+ return "NOASSERTION"
+
+ remoteName = d.getVar(f"SPDX_LAYER_REMOTE_{l_name}") or d.getVar(f"SPDX_LAYER_REMOTE")
+ if not remoteName:
+ return "NOASSERTION"
+
+ # parse and reconstruct the url to drop potentially present username and password
+ remoteUrl = urlparse(oe.buildcfg.get_metadata_git_remote_url(repo, remoteName))
+ if not remoteUrl or remoteUrl.scheme not in ["https", "http", "git", "ssh"]:
+ return "NOASSERTION"
+ remoteUrl = urlunparse(remoteUrl)
+
+ # revision = d.getVar(f"SPDX_LAYER_CLAIM_REV_UNCHECKED_{l_name}")
+ # if not revision:
+ revision = l_rev
+ if revision == "<unknown>" or not git_remote_has_rev(repo, remoteName, revision):
+ return "NOASSERTION"
+
+ relpath = os.path.relpath(l_path, repo)
+
+ return f"git+{remoteUrl}@{revision}{'#' + relpath if relpath else ''}"
+
+
+def write_layers_spdx(d):
+ d.setVar("PN", "layers")
+ d.setVar("BB_TASKHASH", "layers")
+ oe.spdx_common.load_spdx_license_data(d)
+ deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX"))
+
+ layer_objset = oe.sbom30.ObjectSet.new_objset(d, "layers")
+
+ layers = oe.buildcfg.get_layer_revisions(d)
+ for (l_path, l_name, l_branch, l_rev, l_ismodified) in layers:
+ downloadLocation = get_layer_downloadLocation(d, l_path, l_name, l_branch, l_rev, l_ismodified)
+
+
+ if oe.utils.vartrue("SPDX_REQUIRE_LAYER_ASSERTION", True, False, d) and downloadLocation == "NOASSERTION":
+ bb.fatal(f"Layer {l_name} does not have a valid downloadLocation")
+ layer = oe.spdx30.software_Package(
+ _id=layer_objset.new_spdxid("layer", l_name),
+ creationInfo=layer_objset.doc.creationInfo,
+ name=l_name,
+ software_packageVersion=l_rev,
+ software_primaryPurpose=oe.spdx30.software_SoftwarePurpose.specification,
+ software_downloadLocation=downloadLocation,
+ software_sourceInfo=json.dumps(
+ {
+ "branch": l_branch,
+ "is-modified": (len(l_ismodified) > 0),
+ },
+ separators=(",", ":"),
+ ),
+ )
+
+ layer_objset.add_root(layer)
+ layer_objset.set_element_alias(layer)
+
+ oe.sbom30.write_jsonld_doc(d, layer_objset, deploy_dir_spdx / "layers.spdx.json")
+
+
+
def write_bitbake_spdx(d):
# Set PN to "bitbake" so that SPDX IDs can be generated
d.setVar("PN", "bitbake")
This adds support for determining the revision and downloadLocation of meta-layers and adds them to the OE generated SPDX document. This downloadLocation will result in a NOASSERTION entry if - the meta-layer is not in a git repo - the git repo is dirty - the git remote does not have a valid URL - the git remote does not contain the used revision as far as can be eluded from the local repo Variables to configure the behavior: - SPDX_LAYER_REMOTE (default: origin): select the name of the git remote to use for layers. Set to an empty string to always return NOASSERTION - SPDX_LAYER_REMOTE_layername: override SPDX_LAYER_REMOTE for a specific layer - SPDX_REQUIRE_LAYER_ASSERTION: set to true to fail a build if determining downloadLocation fails for any layer Signed-off-by: Daniel Wagenknecht <dwagenknecht@emlix.com> --- meta/classes/create-spdx-3.0.bbclass | 1 + meta/classes/spdx-common.bbclass | 3 ++ meta/lib/oe/sbom30.py | 12 +++++ meta/lib/oe/spdx30_tasks.py | 74 ++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+)