Skip to content

Commit

Permalink
Add purl information to SPDX reports
Browse files Browse the repository at this point in the history
This commit adds a new function, get_purl() to spdx_common.py which
uses the `packageurl` library to generate purl strings for given package
objects. The namespace for certain purls is determined using the
/etc/os-release file information collected via the pkg_suppliers field
in base.yml.

This commit then adds purl strings as external references[1] to both
SPDX tag value and SPDX json reports.

[1]https://spdx.github.io/spdx-spec/v2.3/package-information/#721-external-reference-field

Resolves #1206

Signed-off-by: Rose Judge <[email protected]>
Signed-off-by: Ivana Atanasova <[email protected]>
  • Loading branch information
rnjudge authored Mar 20, 2023
2 parents 3624b30 + a5ebbc1 commit c4b3508
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 5 deletions.
16 changes: 13 additions & 3 deletions tern/analyze/default/command_lib/base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ tdnf:
- 'tdnf check-update > /dev/null'
- 'tdnf list installed | cut -f2 -d"." | cut -f1 -d" "'
delimiter: "\n"
pkg_suppliers:
invoke:
1:
container:
- 'tdnf check-update > /dev/null'
- "distro=`/bin/cat /etc/os-release | grep NAME | sed -n '1p' | cut -f 2 -d '=' | cut -d '\"' -f2`"
- 'pkgs=`tdnf list installed | cut -f1 -d"."`'
- "for p in $pkgs; do echo $distro; done"
delimiter: "\n"
files: {}
proj_urls:
invoke:
Expand Down Expand Up @@ -180,8 +189,9 @@ apk:
invoke:
1:
container:
- "distro=`/bin/cat /etc/os-release | grep NAME | sed -n '1p' | cut -f 2 -d '=' | cut -d '\"' -f2`"
- "pkgs=`apk info 2>/dev/null`"
- "for p in $pkgs; do echo 'Alpine Linux'; done"
- "for p in $pkgs; do echo $distro; done"
delimiter: "\n"
licenses:
invoke:
Expand Down Expand Up @@ -363,7 +373,7 @@ pip:
1:
container:
- "pkgs=`pip list --format=freeze 2> /dev/null | cut -f1 -d'='`"
- "for p in $pkgs; do echo 'PyPI'; done"
- "for p in $pkgs; do echo 'Python Package Index'; done"
delimiter: "\n"
licenses:
invoke:
Expand Down Expand Up @@ -413,7 +423,7 @@ pip3:
1:
container:
- "pkgs=`pip3 list --format=freeze 2> /dev/null | cut -f1 -d'='`"
- "for p in $pkgs; do echo 'PyPI'; done"
- "for p in $pkgs; do echo 'Python Package Index'; done"
delimiter: "\n"
licenses:
invoke:
Expand Down
32 changes: 32 additions & 0 deletions tern/formats/spdx/spdx_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from license_expression import get_spdx_licensing
from tern.utils import constants
from tern.formats.spdx.spdxtagvalue import formats as spdx_formats
from packageurl import PackageURL

# global logger
logger = logging.getLogger(constants.logger_name)
Expand Down Expand Up @@ -210,3 +211,34 @@ def get_file_comment(filedata):
comment = comment + \
'{}: {}'.format(notice.level, notice.message) + '\n'
return comment


#######################
# Common PURL Helpers #
#######################

purl_types_with_namespaces = [
'deb',
'rpm',
'apk',
'alpm'
]


def get_purl(package_obj):
'''Return a purl string for a given package'''
purl_type = package_obj.pkg_format
purl_namespace = ''
if purl_type in purl_types_with_namespaces and package_obj.pkg_supplier:
# https://github.com/package-url/purl-spec/pull/214
if package_obj.pkg_supplier.split(' ')[0] == "VMware":
purl_namespace = package_obj.pkg_supplier.split(' ')[1].lower()
else:
purl_namespace = package_obj.pkg_supplier.split(' ')[0].lower()
# TODO- this might need adjusting for alpm. Currently can't test on M1
purl = PackageURL(purl_type, purl_namespace, package_obj.name.lower(), package_obj.version,
qualifiers={'arch': package_obj.arch if package_obj.arch else ''})
try:
return purl.to_string()
except ValueError:
return ''
9 changes: 7 additions & 2 deletions tern/formats/spdx/spdxjson/package_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,14 @@ def get_package_dict(package, template):
mapping['PackageLicenseDeclared']),
'copyrightText': mapping['PackageCopyrightText'] if
mapping['PackageCopyrightText'] else 'NONE',
'comment': get_package_comment(package)
}

# Only add package PURL if it exists
if spdx_common.get_purl(package):
package_dict['externalRefs'] = [{'referenceCategory': 'PACKAGE-MANAGER',
'referenceLocator': spdx_common.get_purl(package),
'referenceType': 'purl'}]
# Put package comment after any potential externalRefs
package_dict['comment'] = get_package_comment(package)
return package_dict


Expand Down
4 changes: 4 additions & 0 deletions tern/formats/spdx/spdxtagvalue/package_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ def get_package_block(package_obj, template):
message=mapping['PackageCopyrightText']) + '\n'
else:
block += 'PackageCopyrightText: NONE\n'
# Package URL
if spdx_common.get_purl(package_obj):
block += 'ExternalRef: PACKAGE-MANAGER purl {}\n'.format(
spdx_common.get_purl(package_obj))
# Package Comments
block += get_package_comment(package_obj)
return block

0 comments on commit c4b3508

Please sign in to comment.