Skip to content

Commit d4c1c5a

Browse files
authored
Merge pull request #1659 from jakub-nt/ENT-12608
Add support for generating CycloneDX JSON SBOMs
2 parents bfb2f55 + 0856043 commit d4c1c5a

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. `.`).
@@ -122,8 +123,20 @@ $ python scripts/deptool.py --compare 3.21.5 3.21.6 3.24.0 3.24.1 --no-info --sk
122123
123124
```
124125

125-
## Using a copy of the repository
126+
### Using a copy of the repository
126127

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

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:
@@ -465,6 +467,58 @@ def write_deps_json(self, json_path, refs):
465467
deps_data, _ = self.deps_dict(refs)
466468
write_json_file(json_path, deps_data)
467469

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

721+
if args.cdx_sbom_path_template:
722+
dr.write_cdx_sboms(args.cdx_sbom_path_template, args.refs)
723+
659724
if args.patch or not args.compare:
660725
updated_readme, updated_agent_table, updated_hub_table = (
661726
dr.updated_deps_markdown_table(args.refs)

0 commit comments

Comments
 (0)