Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scripts: zephyr_module: Add URL, version to SPDX #125

Merged
merged 1 commit into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1633,9 +1633,8 @@ if(CONFIG_BUILD_OUTPUT_BIN AND CONFIG_BUILD_OUTPUT_UF2)
set(BYPRODUCT_KERNEL_UF2_NAME "${PROJECT_BINARY_DIR}/${KERNEL_UF2_NAME}" CACHE FILEPATH "Kernel uf2 file" FORCE)
endif()

set(KERNEL_META_PATH ${PROJECT_BINARY_DIR}/${KERNEL_META_NAME} CACHE INTERNAL "")
if(CONFIG_BUILD_OUTPUT_META)
set(KERNEL_META_PATH ${PROJECT_BINARY_DIR}/${KERNEL_META_NAME} CACHE INTERNAL "")

list(APPEND
post_build_commands
COMMAND ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/zephyr_module.py
Expand All @@ -1649,6 +1648,9 @@ if(CONFIG_BUILD_OUTPUT_META)
post_build_byproducts
${KERNEL_META_PATH}
)
else(CONFIG_BUILD_OUTPUT_META)
# Prevent spdx to use invalid data
file(REMOVE ${KERNEL_META_PATH})
endif()

# Cleanup intermediate files
Expand Down
39 changes: 39 additions & 0 deletions doc/develop/modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,45 @@ Build files located in a ``MODULE_EXT_ROOT`` can be described as:
This allows control of the build inclusion to be described externally to the
Zephyr module.

.. _modules-vulnerability-monitoring:

Vulnerability monitoring
========================

The module description file :file:`zephyr/module.yml` can be used to improve vulnerability monitoring.

If your module needs to track vulnerabilities using an external reference
(e.g your module is forked from another repository), you can use the ``security`` section.
It contains the field ``external-references`` that contains a list of references that needs to
be monitored for your module. The supported formats are:

- CPE (Common Platform Enumeration)
- PURL (Package URL)

.. code-block:: yaml

security:
external-references:
- <module-related-cpe>
- <an-other-module-related-cpe>
- <module-related-purl>

A real life example for `mbedTLS` module could look like this:

.. code-block:: yaml

security:
external-references:
- cpe:2.3:a:arm:mbed_tls:3.5.2:*:*:*:*:*:*:*
- pkg:github/Mbed-TLS/[email protected]

.. note::
CPE field must follow the CPE 2.3 schema provided by `NVD
<https://csrc.nist.gov/projects/security-content-automation-protocol/specifications/cpe>`_.
PURL field must follow the PURL specification provided by `Github
<https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst>`_.


Build system integration
========================

Expand Down
2 changes: 2 additions & 0 deletions doc/develop/west/zephyr-cmds.rst
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ This generates the following SPDX bill-of-materials (BOM) documents in
- :file:`app.spdx`: BOM for the application source files used for the build
- :file:`zephyr.spdx`: BOM for the specific Zephyr source code files used for the build
- :file:`build.spdx`: BOM for the built output files
- :file:`modules-deps.spdx`: BOM for modules dependencies. Check
:ref:`modules <modules-vulnerability-monitoring>` for more details.

Each file in the bill-of-materials is scanned, so that its hashes (SHA256 and
SHA1) can be recorded, along with any detected licenses if an
Expand Down
15 changes: 15 additions & 0 deletions scripts/west_commands/zspdx/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,21 @@ def __init__(self):
# SPDX ID, including "SPDXRef-"
self.spdxID = ""

# primary package purpose (ex. "LIBRARY", "APPLICATION", etc.)
self.primaryPurpose = ""

# package URL
self.url = ""

# package version
self.version = ""

# package revision
self.revision = ""

# package external references
self.externalReferences = []

# the Package's declared license
self.declaredLicense = "NOASSERTION"

Expand Down
6 changes: 6 additions & 0 deletions scripts/west_commands/zspdx/sbom.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,10 @@ def makeSPDX(cfg):
log.err("SPDX writer failed for build document; bailing")
return False

# write modules document
writeSPDX(os.path.join(cfg.spdxDir, "modules-deps.spdx"), w.docModulesExtRefs)
if not retval:
log.err("SPDX writer failed for modules-deps document; bailing")
return False

return True
142 changes: 118 additions & 24 deletions scripts/west_commands/zspdx/walker.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import os
import yaml
import re

from west import log
from west.util import west_topdir, WestNotFound
Expand Down Expand Up @@ -47,6 +48,7 @@ def __init__(self, cfg):
self.docZephyr = None
self.docApp = None
self.docSDK = None
self.docModulesExtRefs = None

# dict of absolute file path => the Document that owns that file
self.allFileLinks = {}
Expand All @@ -69,6 +71,40 @@ def __init__(self, cfg):
# SDK install path from parsed CMake cache
self.sdkPath = ""

def _build_purl(self, url, version=None):
if not url:
return None

purl = None
# This is designed to match repository with the following url pattern:
# '<protocol><base_url>/<namespace>/<package>
COMMON_GIT_URL_REGEX=r'((git@|http(s)?:\/\/)(?P<base_url>[\w\.@]+)(\/|:))(?P<namespace>[\w,\-,\_]+)\/(?P<package>[\w,\-,\_]+)(.git){0,1}((\/){0,1})$'

match = re.fullmatch(COMMON_GIT_URL_REGEX, url)
if match:
purl = f'pkg:{match.group("base_url")}/{match.group("namespace")}/{match.group("package")}'

if purl and (version or len(version) > 0):
purl += f'@{version}'

return purl

def _normalize_module_name(self, module_name):
# Replace "_" by "-" since it's not allowed in spdx ID
return module_name.replace("_", "-")

def _add_describe_relationship(self, doc, cfgpackage):
# create DESCRIBES relationship data
rd = RelationshipData()
rd.ownerType = RelationshipDataElementType.DOCUMENT
rd.ownerDocument = doc
rd.otherType = RelationshipDataElementType.PACKAGEID
rd.otherPackageID = cfgpackage.spdxID
rd.rlnType = "DESCRIBES"

# add it to pending relationships queue
self.pendingRelationships.append(rd)

# primary entry point
def makeDocuments(self):
# parse CMake cache file and get compiler path
Expand Down Expand Up @@ -162,16 +198,7 @@ def setupAppDocument(self):
pkgApp = Package(cfgPackageApp, self.docApp)
self.docApp.pkgs[pkgApp.cfg.spdxID] = pkgApp

# create DESCRIBES relationship data
rd = RelationshipData()
rd.ownerType = RelationshipDataElementType.DOCUMENT
rd.ownerDocument = self.docApp
rd.otherType = RelationshipDataElementType.PACKAGEID
rd.otherPackageID = cfgPackageApp.spdxID
rd.rlnType = "DESCRIBES"

# add it to pending relationships queue
self.pendingRelationships.append(rd)
self._add_describe_relationship(self.docApp, cfgPackageApp)

def setupBuildDocument(self):
# set up build document
Expand All @@ -195,7 +222,7 @@ def setupBuildDocument(self):
# add it to pending relationships queue
self.pendingRelationships.append(rd)

def setupZephyrDocument(self, modules):
def setupZephyrDocument(self, zephyr, modules):
# set up zephyr document
cfgZephyr = DocumentConfig()
cfgZephyr.name = "zephyr-sources"
Expand All @@ -216,39 +243,67 @@ def setupZephyrDocument(self, modules):
cfgPackageZephyr.spdxID = "SPDXRef-zephyr-sources"
cfgPackageZephyr.relativeBaseDir = relativeBaseDir

zephyr_url = zephyr.get("remote", "")
if zephyr_url:
cfgPackageZephyr.url = zephyr_url

if zephyr.get("revision"):
cfgPackageZephyr.revision = zephyr.get("revision")

purl = None
zephyr_tags = zephyr.get("tags", "")
if zephyr_tags:
# Find tag vX.Y.Z
for tag in zephyr_tags:
version = re.fullmatch(r'^v(?P<version>\d+\.\d+\.\d+)$', tag)
purl = self._build_purl(zephyr_url, tag)

if purl:
cfgPackageZephyr.externalReferences.append(purl)

# Extract version from tag once
if cfgPackageZephyr.version == "" and version:
cfgPackageZephyr.version = version.group('version')

if len(cfgPackageZephyr.version) > 0:
cpe = f'cpe:2.3:o:zephyrproject:zephyr:{cfgPackageZephyr.version}:-:*:*:*:*:*:*'
cfgPackageZephyr.externalReferences.append(cpe)

pkgZephyr = Package(cfgPackageZephyr, self.docZephyr)
self.docZephyr.pkgs[pkgZephyr.cfg.spdxID] = pkgZephyr

self._add_describe_relationship(self.docZephyr, cfgPackageZephyr)

for module in modules:
module_name = module.get("name", None)
module_path = module.get("path", None)
module_url = module.get("remote", None)
module_revision = module.get("revision", None)

if not module_name:
log.err(f"cannot find module name in meta file; bailing")
return False

# Replace "_" by "-" since it's not allowed in spdx ID
module_name = module_name.replace("_", "-")
module_name = self._normalize_module_name(module_name)

# set up zephyr sources package
cfgPackageZephyrModule = PackageConfig()
cfgPackageZephyrModule.name = module_name
cfgPackageZephyrModule.name = module_name + "-sources"
cfgPackageZephyrModule.spdxID = "SPDXRef-" + module_name + "-sources"
cfgPackageZephyrModule.relativeBaseDir = module_path

if module_revision:
cfgPackageZephyrModule.revision = module_revision

if module_url:
cfgPackageZephyrModule.url = module_url

pkgZephyrModule = Package(cfgPackageZephyrModule, self.docZephyr)
self.docZephyr.pkgs[pkgZephyrModule.cfg.spdxID] = pkgZephyrModule

# create DESCRIBES relationship data
rd = RelationshipData()
rd.ownerType = RelationshipDataElementType.DOCUMENT
rd.ownerDocument = self.docZephyr
rd.otherType = RelationshipDataElementType.PACKAGEID
rd.otherPackageID = cfgPackageZephyr.spdxID
rd.rlnType = "DESCRIBES"
self._add_describe_relationship(self.docZephyr, cfgPackageZephyrModule)

# add it to pending relationships queue
self.pendingRelationships.append(rd)
return True

def setupSDKDocument(self):
# set up SDK document
Expand Down Expand Up @@ -278,6 +333,42 @@ def setupSDKDocument(self):
# add it to pending relationships queue
self.pendingRelationships.append(rd)

def setupModulesDocument(self, modules):
# set up zephyr document
cfgModuleExtRef = DocumentConfig()
cfgModuleExtRef.name = "modules-deps"
cfgModuleExtRef.namespace = self.cfg.namespacePrefix + "/modules-deps"
cfgModuleExtRef.docRefID = "DocumentRef-modules-deps"
self.docModulesExtRefs = Document(cfgModuleExtRef)

for module in modules:
module_name = module.get("name", None)
module_security = module.get("security", None)

if not module_name:
log.err(f"cannot find module name in meta file; bailing")
return False

module_name = self._normalize_module_name(module_name)

module_ext_ref = []
if module_security:
module_ext_ref = module_security.get("external-references")

# set up zephyr sources package
cfgPackageModuleExtRef = PackageConfig()
cfgPackageModuleExtRef.name = module_name + "-deps"
cfgPackageModuleExtRef.spdxID = "SPDXRef-" + module_name + "-deps"

for ref in module_ext_ref:
cfgPackageModuleExtRef.externalReferences.append(ref)

pkgModule = Package(cfgPackageModuleExtRef, self.docModulesExtRefs)
self.docModulesExtRefs.pkgs[pkgModule.cfg.spdxID] = pkgModule

self._add_describe_relationship(self.docModulesExtRefs, cfgPackageModuleExtRef)


# set up Documents before beginning
def setupDocuments(self):
log.dbg("setting up placeholder documents")
Expand All @@ -287,7 +378,8 @@ def setupDocuments(self):
try:
with open(self.metaFile) as file:
content = yaml.load(file.read(), yaml.SafeLoader)
self.setupZephyrDocument(content["modules"])
if not self.setupZephyrDocument(content["zephyr"], content["modules"]):
return False
except (FileNotFoundError, yaml.YAMLError):
log.err(f"cannot find a valid zephyr_meta.yml required for SPDX generation; bailing")
return False
Expand All @@ -297,6 +389,8 @@ def setupDocuments(self):
if self.cfg.includeSDK:
self.setupSDKDocument()

self.setupModulesDocument(content["modules"])

return True

# walk through targets and gather information
Expand Down
Loading