diff --git a/README.md b/README.md index 11418382..b834d65a 100644 --- a/README.md +++ b/README.md @@ -325,12 +325,26 @@ Many compliance tools are compatible with SPDX. Tern follows the [SPDX specifica ``` $ tern report -f spdxtagvalue -i golang:1.12-alpine -o spdx.txt ``` +By default, the report will be in SPDX version 2.2 (ISO SPDX). + +The following syntax will output SPDX 2.3: +``` +$ tern report -f spdxtagvalue@2.3 -i golang:1.12-alpine -o spdx.txt +``` + ## SPDX JSON Format The SPDX JSON format contains the same information that an SPDX Tag-value document does. The only difference between these two formats is the way the information is represented. The 'spdxjson' format represents the container information as a collection of key-value pairs. In some cases, the SPDX JSON format may be more interoperable between cloud native compliance tools. ``` $ tern report -f spdxjson -i golang:1.12-alpine -o spdx.json ``` +By default, the report will be in SPDX version 2.2 (ISO SPDX). + +The following syntax will output SPDX 2.3: +``` +$ tern report -f spdxjson@2.3 -i golang:1.12-alpine -o spdx.json +``` + ## CycloneDX JSON Format [OWASP CycloneDX](https://cyclonedx.org/) is a lightweight software bill of materials standard designed for use in application security contexts and supply chain component analysis. The National Telecommunications and Information Administration (NTIA) [recognizes CycloneDX](https://www.ntia.gov/files/ntia/publications/sbom_options_and_decision_points_20210427-1.pdf) as one of three valid SBOM formats that satisfies the minimum viable requirements for an SBOM in accordance with President Biden's [Executive Order on Improving the Nation's Cybersecurity](https://www.whitehouse.gov/briefing-room/presidential-actions/2021/05/12/executive-order-on-improving-the-nations-cybersecurity/). diff --git a/tern/formats/spdx/spdxjson/formats.py b/tern/formats/spdx/spdxjson/formats.py index cc2f8743..c2b42381 100644 --- a/tern/formats/spdx/spdxjson/formats.py +++ b/tern/formats/spdx/spdxjson/formats.py @@ -9,7 +9,7 @@ # document level strings -spdx_version = 'SPDX-2.2' +spdx_version = 'SPDX-' data_license = 'CC0-1.0' spdx_id = 'SPDXRef-DOCUMENT' document_name = 'Tern report for {image_name}' diff --git a/tern/formats/spdx/spdxjson/generator.py b/tern/formats/spdx/spdxjson/generator.py index 6c28c5e1..f688e800 100644 --- a/tern/formats/spdx/spdxjson/generator.py +++ b/tern/formats/spdx/spdxjson/generator.py @@ -42,11 +42,11 @@ def get_document_namespace_snapshot(timestamp): timestamp=timestamp, uuid=spdx_common.get_uuid()) -def get_document_dict(image_obj, template): +def get_document_dict(image_obj, template, spdxv): '''Return document info as a dictionary''' docu_dict = { 'SPDXID': json_formats.spdx_id, - 'spdxVersion': json_formats.spdx_version, + 'spdxVersion': json_formats.spdx_version+spdxv, 'creationInfo': { 'created': json_formats.created.format( timestamp=spdx_common.get_timestamp()), @@ -140,7 +140,7 @@ def get_document_dict_snapshot(layer_obj, template): class SpdxJSON(generator.Generate): - def generate(self, image_obj_list, print_inclusive=False): + def generate(self, image_obj_list, print_inclusive=False, spdxv='2.2'): '''Generate an SPDX document WARNING: This assumes that the list consists of one image or the base image and a stub image, in which case, the information in the stub @@ -159,7 +159,7 @@ def generate(self, image_obj_list, print_inclusive=False): # input is a list of length 1 image_obj = image_obj_list[0] template = SPDX() - report = get_document_dict(image_obj, template) + report = get_document_dict(image_obj, template, spdxv) return json.dumps(report) diff --git a/tern/formats/spdx/spdxtagvalue/formats.py b/tern/formats/spdx/spdxtagvalue/formats.py index 678e71aa..5b613186 100644 --- a/tern/formats/spdx/spdxtagvalue/formats.py +++ b/tern/formats/spdx/spdxtagvalue/formats.py @@ -12,7 +12,7 @@ block_text = '\n{message}\n' # document level strings -spdx_version = 'SPDXVersion: SPDX-2.2' +spdx_version = 'SPDXVersion: SPDX-' data_license = 'DataLicense: CC0-1.0' spdx_id = 'SPDXID: SPDXRef-DOCUMENT' document_name = 'DocumentName: Tern report for {image_name}' diff --git a/tern/formats/spdx/spdxtagvalue/generator.py b/tern/formats/spdx/spdxtagvalue/generator.py index e67e4c5d..bcc366fb 100755 --- a/tern/formats/spdx/spdxtagvalue/generator.py +++ b/tern/formats/spdx/spdxtagvalue/generator.py @@ -32,9 +32,9 @@ def get_document_namespace(image_obj): uuid=spdx_common.get_uuid()) -def get_document_block(image_obj): +def get_document_block(image_obj, spdxv): '''Return document related SPDX tag-values''' - block = spdx_formats.spdx_version + '\n' + block = spdx_formats.spdx_version + spdxv + '\n' block = block + spdx_formats.data_license + '\n' block = block + spdx_formats.spdx_id + '\n' block = block + spdx_formats.document_name.format( @@ -51,7 +51,7 @@ def get_document_block(image_obj): class SpdxTagValue(generator.Generate): - def generate(self, image_obj_list, print_inclusive=False): + def generate(self, image_obj_list, print_inclusive=False, spdxv='2.2'): '''Generate an SPDX document WARNING: This assumes that the list consists of one image or the base image and a stub image, in which case, the information in the stub @@ -134,7 +134,7 @@ def generate(self, image_obj_list, print_inclusive=False): # first part is the document tag-value # this doesn't change at all - report += get_document_block(image_obj) + '\n' + report += get_document_block(image_obj, spdxv) + '\n' # this is the image part # this will bring in layer and package information diff --git a/tern/report/report.py b/tern/report/report.py index 9e84eedc..cf967673 100644 --- a/tern/report/report.py +++ b/tern/report/report.py @@ -37,13 +37,20 @@ def clean_image_tars(image_obj): def generate_report(args, *images): '''Generate a report based on the command line options''' + args.spdxv = '2.2' + if args.report_format and (args.report_format == 'spdxtagvalue@2.3'): + args.report_format = 'spdxtagvalue' + args.spdxv = '2.3' + if args.report_format and (args.report_format == 'spdxjson@2.3'): + args.report_format = 'spdxjson' + args.spdxv = '2.3' if args.report_format: return generate_format( - images, args.report_format, args.print_inclusive) - return generate_format(images, 'default', args.print_inclusive) + images, args.report_format, args.print_inclusive, args.spdxv) + return generate_format(images, 'default', args.print_inclusive, args.spdxv) -def generate_format(images, format_string, print_inclusive): +def generate_format(images, format_string, print_inclusive, spdxv): '''Generate a report in the format of format_string given one or more image objects. Here we will load the required module and run the generate function to get back a report''' @@ -53,7 +60,7 @@ def generate_format(images, format_string, print_inclusive): name=format_string, invoke_on_load=True, ) - return mgr.driver.generate(images, print_inclusive) + return mgr.driver.generate(images, print_inclusive, spdxv) except NoMatches: return None