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