Skip to content

Commit 0856043

Browse files
committed
deptool: added support for generating CycloneDX JSON SBOMs
Signed-off-by: jakub-nt <175944085+jakub-nt@users.noreply.github.com>
1 parent 17ef29d commit 0856043

File tree

2 files changed

+83
-5
lines changed

2 files changed

+83
-5
lines changed

scripts/README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
`deptool.py` is a script which can be used to enumerate dependencies of CFEngine.
44
It supports printing to stdout in the Markdown table format, and printing to file in the JSON format (see `--to-json`).
5+
It can also generate basic SBOMs in the CycloneDX JSON format (see [below](https://github.com/cfengine/buildscripts/tree/master/scripts#generating-cyclonedx-json-sboms) for usage instructions).
56
It can be used as a replacement for the [cf-bottom](https://github.com/cfengine/cf-bottom/) `depstable` command.
67

78
`deptool.py` works on a local buildscripts repository. By default, the repository is assumed to be current working directory (i.e. `.`).
@@ -123,8 +124,20 @@ $ python deptool.py --compare 3.21.5 3.21.6 3.24.0 3.24.1 --no-info --skip-uncha
123124
124125
```
125126

126-
## Using a copy of the repository
127+
### Using a copy of the repository
127128

128129
```
129130
python deptool.py --root ../../buildscripts-copy
130131
```
132+
133+
### Generating CycloneDX JSON SBOMs
134+
135+
```
136+
python deptool.py --to-cdx-sbom
137+
```
138+
139+
A separate CycloneDX JSON SBOM is generated for each version. Optionally, a path template can be specified using `{}` as a substitute for the version:
140+
141+
```
142+
python deptool.py --to-cdx-sbom my-sbom-{}.cdx.json
143+
```

scripts/deptool.py

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,13 @@ def deps_versions(self, ref):
311311
...
312312
}
313313
```
314+
The ref is checked out during the execution of this function.
314315
"""
316+
self.buildscripts_repo.checkout(ref)
317+
# also support checking out refs that are not necessarily branches, such as tags
318+
if self.buildscripts_repo.is_git_branch(ref):
319+
self.buildscripts_repo.run_command("pull")
320+
315321
deps_versions = {}
316322
deps_list = self.deps_list(ref)
317323
for dep in deps_list:
@@ -326,10 +332,6 @@ def deps_dict(self, refs):
326332

327333
for ref in refs:
328334
ref_column_widths[ref] = len(ref)
329-
self.buildscripts_repo.checkout(ref)
330-
# also support checking out refs that are not necessarily branches, such as tags
331-
if self.buildscripts_repo.is_git_branch(ref):
332-
self.buildscripts_repo.run_command("pull")
333335
deps_versions = self.deps_versions(ref)
334336

335337
for dep in deps_versions:
@@ -463,6 +465,58 @@ def write_deps_json(self, json_path, refs):
463465
deps_data, _ = self.deps_dict(refs)
464466
write_json_file(json_path, deps_data)
465467

468+
def write_cdx_sboms(self, cdx_sbom_path_template, refs, fake_rpm=True):
469+
for ref in refs:
470+
cdx_dict = collections.OrderedDict()
471+
cdx_dict["bomFormat"] = "CycloneDX"
472+
cdx_dict["specVersion"] = "1.6"
473+
474+
metadata_dict = collections.OrderedDict()
475+
application_bomref = "northerntech:cfengine"
476+
metadata_dict["component"] = {
477+
"type": "application",
478+
"bom-ref": application_bomref,
479+
"name": "CFEngine",
480+
"version": ref,
481+
}
482+
cdx_dict["metadata"] = metadata_dict
483+
484+
components = []
485+
deps_bomrefs = []
486+
deps_versions = self.deps_versions(ref)
487+
for dep_name, dep_version in deps_versions.items():
488+
c_bomref = f"northerntech:{dep_name}"
489+
component = {
490+
"type": "library",
491+
"bom-ref": c_bomref,
492+
"name": dep_name,
493+
"version": dep_version,
494+
}
495+
purl = "pkg:rpm/" + dep_name + "@" + dep_version
496+
if fake_rpm:
497+
component["purl"] = purl
498+
components.append(component)
499+
deps_bomrefs.append(c_bomref)
500+
501+
cdx_dict["components"] = components
502+
503+
dependencies = [{"ref": application_bomref, "dependsOn": deps_bomrefs}]
504+
cdx_dict["dependencies"] = dependencies
505+
506+
if "{}" in cdx_sbom_path_template:
507+
cdx_sbom_path = cdx_sbom_path_template.replace("{}", ref)
508+
else:
509+
log.warning(
510+
"Substring {} not found in the CycloneDX SBOM path template, continuing using paths which might be unexpected"
511+
)
512+
if len(refs) == 1:
513+
cdx_sbom_path = cdx_sbom_path_template
514+
else:
515+
cdx_sbom_path = (
516+
cdx_sbom_path_template[:-9] + "-" + ref + ".cdx.json"
517+
)
518+
write_json_file(cdx_sbom_path, cdx_dict)
519+
466520
def comparison_md_table(self, refs, skip_unchanged=False):
467521
"""Column headers of B refs are always bolded. Row headers are never bolded."""
468522
deps_data, _ = self.deps_dict(refs)
@@ -612,6 +666,14 @@ def parse_args():
612666
default=None,
613667
dest="json_path",
614668
)
669+
parser.add_argument(
670+
"--to-cdx-sbom",
671+
help="Output to a CycloneDX SBOM JSON file (or files, for multiple refs) with an optionally specified path template",
672+
nargs="?",
673+
const="sbom-{}.cdx.json",
674+
default=None,
675+
dest="cdx_sbom_path_template",
676+
)
615677
parser.add_argument(
616678
"--compare",
617679
action="store_true",
@@ -652,6 +714,9 @@ def main():
652714
if args.json_path:
653715
dr.write_deps_json(args.json_path, args.refs)
654716

717+
if args.cdx_sbom_path_template:
718+
dr.write_cdx_sboms(args.cdx_sbom_path_template, args.refs)
719+
655720
if args.patch or not args.compare:
656721
updated_readme, updated_agent_table, updated_hub_table = (
657722
dr.updated_deps_markdown_table(args.refs)

0 commit comments

Comments
 (0)