Analysis of a cyclonedx-gomod generated SBOM
We generated an SBOM with cyclonedx-gomod in a previous post. In this post, we perform an initial analysis of the contents of the generated SBOM.
The focus of this analysis is on [email protected]
.
Punchline
Run cyclonedx-gomod with the following flags:
cyclonedx-gomod app --files --licenses --std
Opening
<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.3" serialNumber="urn:uuid:072ddef2-5299-4131-8554-b143b41f6b86" version="1">
We begin with an initial XML version and encoding of UTF-8. The bom
tag represents the root bill of materials type defined in the schema listed in the xmlns
parameter. The serial number is a Universal Resource Name with a unique UUID that identifies the document’s identity
. This UUID is likely randomly generated.
Metadata
A metadata tag has three components:
Timestamp
<timestamp>2021-11-11T18:48:25-08:00</timestamp>
The timestamp
tag describes the SBOM’s creation time.
Tools
<tools>
<tool>
<vendor>CycloneDX</vendor>
<name>cyclonedx-gomod</name>
<version>v0.0.0-unset</version>
<hashes>
<hash alg="MD5">0738f1e7bc821d0bb630f3ad00ba0b52</hash>
<hash alg="SHA-1">b2cdc16eb791d7d2c6387e9ff566abf687518132</hash>
<hash alg="SHA-256">c82f6cb776191aea89bad6513f0fde4654df6e78befc07cdaee73e3c5c9daa30</hash>
<hash alg="SHA-384">f91edd9b60b9f73a22bc479daac7638e4e6f1bbb3bd49a5bac68f7ca3a632e07ddef253c70d235fa00602b992e7ea7c0</hash>
<hash alg="SHA-512">133e9467c92948cd250cb71d24aa048cd6533c1f126baad59ff1983990a2bbfc02f7d3bc560a74f99eee7d5b10e57c20c0e8e50d6cd0e2697a7f3d795457b373</hash>
</hashes>
</tool>
</tools>
The tools
tag states that we are using cyclonedx-gomod to create the SBOM. Interestingly, the version is set to v0.0.0-unset
despite installing from a specific tag:
go install github.com/CycloneDX/[email protected]
Five hashes are provided, which represent the hashes of the tool’s binary. Each of the hashes matches what is in the SBOM.
% m5sum cyclonedx-gomod
0738f1e7bc821d0bb630f3ad00ba0b52 cyclonedx-gomod
% sha1sum cyclonedx-gomod
b2cdc16eb791d7d2c6387e9ff566abf687518132 cyclonedx-gomod
% ha256sum cyclonedx-gomod
c82f6cb776191aea89bad6513f0fde4654df6e78befc07cdaee73e3c5c9daa30 cyclonedx-gomod
% sha384sum cyclonedx-gomod
f91edd9b60b9f73a22bc479daac7638e4e6f1bbb3bd49a5bac68f7ca3a632e07ddef253c70d235fa00602b992e7ea7c0 cyclonedx-gomod
% sha512sum cyclonedx-gomod
133e9467c92948cd250cb71d24aa048cd6533c1f126baad59ff1983990a2bbfc02f7d3bc560a74f99eee7d5b10e57c20c0e8e50d6cd0e2697a7f3d795457b373 cyclonedx-gomod
Component
<component bom-ref="pkg:golang/github.com/fkautz/[email protected]" type="application">
<name>github.com/fkautz/serve</name>
<version>v1.0.0</version>
<purl>pkg:golang/github.com/fkautz/[email protected]</purl>
<externalReferences>
<reference type="vcs">
<url>https://github.com/fkautz/serve</url>
</reference>
</externalReferences>
<properties>
<property name="cdx:gomod:build:env:CGO_ENABLED">1</property>
<property name="cdx:gomod:build:env:GOARCH">amd64</property>
<property name="cdx:gomod:build:env:GOOS">linux</property>
<property name="cdx:gomod:build:env:GOVERSION">go1.17.3</property>
</properties>
</component>
The component lists the name and version as expected. In the generated output, we receive a purl
tag. A purl is a package URL that follows the specification listed at https://github.com/package-url/purl-spec.
The component may also be listed in a variety of other formats enumerated in https://cyclonedx.org/specification/overview/#components
The external references property was automatically populated by the tool to include information introspected from .git/config. In this case, it fetched the upstream origin.
Properties show interesting, relevant information about how the application is compiled. E.g., CGO_ENABLED, GOARCH, GOOS, and GOVERSION are all properties that affect the result’s binary and functional output. E.g., in go, you can name add _linux
to the name of the file, which will cause that file to only be compiled on a Linux build. e.g., syscalls_linux.go
might include system calls only available on Linux systems.
Components
<component bom-ref="pkg:golang/github.com/cpuguy83/go-md2man/[email protected]" type="library">
The tool generated a components
tag after the metadata
tag. Each component contains a bom-ref which describes a reference to where you can find other BOMs. Presumably, you can derive an SBOM location from bom-ref
and recursively pull in additional SBOMs as necessary.
Every component also has a name
, version
, scope
, hashes
, and externalReferences
defined using the same format as we saw in the metadata. The scope
is included explicitly here since some build systems may have optional or excluded dependencies. In gomod’s version, all scopes appear to be required.
Dependencies
A list of dependencies is listed for each package. The ref
for each package matches the bom-ref
we saw in each component. The components represent a graph of dependencies and their associated dependencies recursively. The reference helps identify the import chain that leads to a specific package being pulled into a build.
What’s missing in the default?
Files
The dependency metadata is stored in the default settings for an SBOM, but the file references and contents are explicitly excluded. For this test, I modified main.go as follows:
diff --git a/main.go b/main.go
index 13c0a7f..7cea2a4 100644
--- a/main.go
+++ b/main.go
@@ -27,7 +27,7 @@ import (
func main() {
app := cli.NewApp()
- app.Name = "serve"
+ app.Name = "serve2"
app.Usage = "Simple HTTP Server"
app.Flags = []cli.Flag{
cli.StringFlag{
This resulted in the following changes
2c2
< <bom xmlns="http://cyclonedx.org/schema/bom/1.3" serialNumber="urn:uuid:6d002c1b-9a00-4656-9d6f-da4fac202409" version="1">
---
> <bom xmlns="http://cyclonedx.org/schema/bom/1.3" serialNumber="urn:uuid:420928ac-a7b7-411e-b044-b41535bba92d" version="1">
4c4
< <timestamp>2021-11-11T18:10:43-08:00</timestamp>
---
> <timestamp>2021-11-12T18:19:09-08:00</timestamp>
Notice how the SBOM did not change despite the contents of main.go changing. This can be remedied using the -files
flag when calling cyclonedx-gomod
.
cyclonedx-gomod app -files > sbom.xml
results in
<components>
<component type="file">
<name>main.go</name>
<version>v0.0.0-3fb2132aec9c</version>
<scope>required</scope>
<hashes>
<hash alg="MD5">f3ac7a60cd038e207b329d1c8568b293</hash>
<hash alg="SHA-1">3fb2132aec9cf639f00409b508e27bdbf0c0527f</hash>
<hash alg="SHA-256">39f3f4d5985c290926d957c689ba08486773e153f464c1d00f46bf4efd1ccd20</hash>
<hash alg="SHA-384">eabc498f20af1247f83d5dbee2ead38a5ba476e2c0069448975a88bf7dbd2e4d6f91e36a482ee0e445ab1bb777270f7a</hash>
<hash alg="SHA-512">d335ffab199b3ac2982580659a16a5a3e5e651b6094ff0cdc98c642da12073367e2bc671243d8fc05a6b42f75a5edff50e21897a4fb358506fc4472e773b2b13</hash>
</hashes>
</component>
</components>
The <version>
tag maps to a truncated SHA-1
sum, rather than the version listed in git.
The hashes are sufficient to tell whether a file has been modified, as long as you can trust the tool, build environment, and SBOM provider.
The -files
flag will also cause every file in every dependency included in the application’s build to be included in the manifest. This can lead to very large SBOMs when generating SBOMs for large projects. However, this behavior should help increase the overall accuracy of the SBOM itself.
Golang STD inclusion
<component bom-ref="pkg:golang/[email protected]" type="library">
<name>std</name>
<version>1.17.3</version>
<description>The Go standard library</description>
<scope>required</scope>
<purl>pkg:golang/[email protected]</purl>
<externalReferences>
<reference type="documentation">
<url>https://golang.org/pkg/</url>
</reference>
<reference type="vcs">
<url>https://go.googlesource.com/go</url>
</reference>
<reference type="website">
<url>https://golang.org/</url>
</reference>
</externalReferences>
</component>
Adding the --std
flag results in an std component tag. Unfortunately, there is no way to determine whether the std was modified from the information provided by the SBOM. Hashes and files are not included in the result.
Go version
The tool indicates what version of golang was used as a property, but does not include any hashes or any other identifying information which may be used to identify custom compiler builds.
Licenses
The tool will also analyze licenses for you and place the license in an evidence
tag for each component.
<evidence>
<licenses>
<license>
<id>Apache-2.0</id>
</license>
</licenses>
</evidence>
The evidence
tag appears to be present only at the component
level. If a specific file is under a separate license, the tool may miss the file’s license. Hopefully, a future version of cyclonedx-gomod will solve this issue. One possible incomplete solution would be to look for SPDX-License-Identifier
tags in the comments. There may also be other tags I am unaware of, which may help simplify this process.
An example in main.go:
// Copyright (c) 2014-2021 Frederick F. Kautz IV
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
Minimum Elements of an SBOM
- Supplier name - missing, but can be added after creation of the XML document and before signing in CI/CD.
- Component Name - present
- Version of the component - present
- Other unique identifiers - present,
bom-ref
is useful here. - Dependency relationship - present at a component level. Some file level dependency is present at the global level, but intra-file dependency does not appear to be modelled.
- Author of the SBOM - missing, but can be added after creation of the XML document and before signing in CI/CD.
- Timestamp - present
Conclusion
Overall, generating a golang SBOM in cyclonedx-gomod is trivial. I highly recommend running cyclonedx-gomod with the following flags to increase the overall utility of the generated SBOM and to help identify whether the contents of the inputs has been tampered with or whether the compiler is customized:
cyclonedx-gomod app --files --licenses --std
Some modification of the SBOM may be necessary to add the supplier name and SBOM author. Ideally, this would occur immediately after the XML generation and before the SBOM was signed by sigstore or an equivalent system. The SBOM results do not include sufficient Golang std support other than what version of the std was used.