diff --git a/e2e/testdata/snapshots/TestAppCmdSimple b/e2e/testdata/snapshots/TestAppCmdSimple
index 5f7737d8..dbeb2e5e 100644
--- a/e2e/testdata/snapshots/TestAppCmdSimple
+++ b/e2e/testdata/snapshots/TestAppCmdSimple
@@ -5,6 +5,9 @@
testmod-simple
v0.0.0-20210716183230-c7ea7c975ab8
pkg:golang/testmod-simple@v0.0.0-20210716183230-c7ea7c975ab8
+
+ 001
+
diff --git a/e2e/testdata/snapshots/TestAppCmdSimpleMultiCommandPURL b/e2e/testdata/snapshots/TestAppCmdSimpleMultiCommandPURL
index a41e2944..e8bd7e63 100644
--- a/e2e/testdata/snapshots/TestAppCmdSimpleMultiCommandPURL
+++ b/e2e/testdata/snapshots/TestAppCmdSimpleMultiCommandPURL
@@ -1,10 +1,13 @@
-
+
testmod-simple
v0.0.0-20210901192510-dc2d14d2351d
- pkg:golang/testmod-simple@v0.0.0-20210901192510-dc2d14d2351d
+ pkg:golang/testmod-simple@v0.0.0-20210901192510-dc2d14d2351d#cmd/purl
+
+ purl
+
@@ -24,7 +27,7 @@
-
+
diff --git a/e2e/testdata/snapshots/TestAppCmdSimpleMultiCommandUUID b/e2e/testdata/snapshots/TestAppCmdSimpleMultiCommandUUID
index d4050312..cd105872 100644
--- a/e2e/testdata/snapshots/TestAppCmdSimpleMultiCommandUUID
+++ b/e2e/testdata/snapshots/TestAppCmdSimpleMultiCommandUUID
@@ -1,10 +1,13 @@
-
+
testmod-simple
v0.0.0-20210901192510-dc2d14d2351d
- pkg:golang/testmod-simple@v0.0.0-20210901192510-dc2d14d2351d
+ pkg:golang/testmod-simple@v0.0.0-20210901192510-dc2d14d2351d#cmd/uuid
+
+ uuid
+
@@ -31,7 +34,7 @@
-
+
diff --git a/e2e/testdata/snapshots/TestAppCmdSimpleWithFiles b/e2e/testdata/snapshots/TestAppCmdSimpleWithFiles
index 54131739..a33ea4d9 100644
--- a/e2e/testdata/snapshots/TestAppCmdSimpleWithFiles
+++ b/e2e/testdata/snapshots/TestAppCmdSimpleWithFiles
@@ -5,6 +5,9 @@
testmod-simple
v0.0.0-20210716183230-c7ea7c975ab8
pkg:golang/testmod-simple@v0.0.0-20210716183230-c7ea7c975ab8
+
+ 001
+
main.go
diff --git a/e2e/testdata/snapshots/TestAppCmdVendored b/e2e/testdata/snapshots/TestAppCmdVendored
index 6b1452d5..c83ac164 100644
--- a/e2e/testdata/snapshots/TestAppCmdVendored
+++ b/e2e/testdata/snapshots/TestAppCmdVendored
@@ -5,6 +5,9 @@
testmod-vendored
v0.0.0-20210716185931-5c9f3d791930
pkg:golang/testmod-vendored@v0.0.0-20210716185931-5c9f3d791930
+
+ 001
+
diff --git a/e2e/testdata/snapshots/TestAppCmdVendoredWithFiles b/e2e/testdata/snapshots/TestAppCmdVendoredWithFiles
index c682d10c..847a6d5c 100644
--- a/e2e/testdata/snapshots/TestAppCmdVendoredWithFiles
+++ b/e2e/testdata/snapshots/TestAppCmdVendoredWithFiles
@@ -5,6 +5,9 @@
testmod-vendored
v0.0.0-20210716185931-5c9f3d791930
pkg:golang/testmod-vendored@v0.0.0-20210716185931-5c9f3d791930
+
+ 001
+
main.go
diff --git a/examples/app_minikube-v1.23.1.bom.json b/examples/app_minikube-v1.23.1.bom.json
index 0eb90849..e4a2a973 100644
--- a/examples/app_minikube-v1.23.1.bom.json
+++ b/examples/app_minikube-v1.23.1.bom.json
@@ -1,46 +1,50 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.3",
- "serialNumber": "urn:uuid:26ccca90-9735-44e8-bad6-59967428dec0",
+ "serialNumber": "urn:uuid:2df66088-ff65-4895-b7ed-8b980e6c677f",
"version": 1,
"metadata": {
- "timestamp": "2021-09-19T16:20:21Z",
+ "timestamp": "2021-09-25T19:52:57Z",
"tools": [
{
"vendor": "CycloneDX",
"name": "cyclonedx-gomod",
- "version": "v0.0.0-20210919172430-96bd0c538cce",
+ "version": "v0.0.0-20210925214913-cdaccfe30cb0",
"hashes": [
{
"alg": "MD5",
- "content": "f09d9d5ec8a3070ef06c8a716ef58206"
+ "content": "d6c2bf92f7d13841ed21d9c409cf7cf4"
},
{
"alg": "SHA-1",
- "content": "6a76da80a00b8ce762019cb132c576e1dd485e6e"
+ "content": "cf9246e05f2416552edbf4b40ef8c3df259ed751"
},
{
"alg": "SHA-256",
- "content": "b796ac65d8c21b987c2b4a88b329fa56a9b9e79859292701d1767f627c926724"
+ "content": "890a85574c7f68b950f5bc5c08e5a3c32c06e8103de168d5721ec2dcc49fd3b9"
},
{
"alg": "SHA-384",
- "content": "6814ac9dace7d2aeec9a2d9fe11e0c877e71e2852d47a84560db16b5d1a351371773bfa09ba952a4a5b18f78c7d53cd9"
+ "content": "04d28e89523d14fc7dd32c54c4bdbbe99ac12fee427ff69d56ff3f9d98bde3880ab2b77520ff19fd620ab84d39920380"
},
{
"alg": "SHA-512",
- "content": "e73d97ccaac30c039428e688a095efc1c525a8476e12ceb69b26a425defbc786ef09a80a241b7a6de52fb8c38bf9f06a23b971b2344b93e61f88597b4d34d743"
+ "content": "ddfce73b8949a2d7878f1e9154cfc1228cb16cf55c6a7e57583cdc9ac286e653e8e285a64fda677bd8bc5d83dc85e1c1c54b630f2ab31c69881b1c09bf68538b"
}
]
}
],
"component": {
- "bom-ref": "pkg:golang/k8s.io/minikube@v1.23.1",
+ "bom-ref": "pkg:golang/k8s.io/minikube@v1.23.1#cmd/minikube",
"type": "application",
"name": "k8s.io/minikube",
"version": "v1.23.1",
- "purl": "pkg:golang/k8s.io/minikube@v1.23.1",
+ "purl": "pkg:golang/k8s.io/minikube@v1.23.1#cmd/minikube",
"properties": [
+ {
+ "name": "cdx:gomod:application:name",
+ "value": "minikube"
+ },
{
"name": "cdx:gomod:build:env:CGO_ENABLED",
"value": "1"
@@ -3728,7 +3732,7 @@
],
"dependencies": [
{
- "ref": "pkg:golang/k8s.io/minikube@v1.23.1",
+ "ref": "pkg:golang/k8s.io/minikube@v1.23.1#cmd/minikube",
"dependsOn": [
"pkg:golang/cloud.google.com/go@v0.93.3",
"pkg:golang/cloud.google.com/go/storage@v1.16.1",
diff --git a/examples/bin_minikube-v1.23.1.bom.json b/examples/bin_minikube-v1.23.1.bom.json
index 41847003..f7796c4a 100644
--- a/examples/bin_minikube-v1.23.1.bom.json
+++ b/examples/bin_minikube-v1.23.1.bom.json
@@ -1,35 +1,35 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.3",
- "serialNumber": "urn:uuid:6cc44ff9-1816-4343-8ac9-5bcab5a8d691",
+ "serialNumber": "urn:uuid:5171defc-4ecb-4275-b1ef-bf5ff059ed56",
"version": 1,
"metadata": {
- "timestamp": "2021-09-19T16:20:48Z",
+ "timestamp": "2021-09-25T19:53:42Z",
"tools": [
{
"vendor": "CycloneDX",
"name": "cyclonedx-gomod",
- "version": "v0.0.0-20210919172430-96bd0c538cce",
+ "version": "v0.0.0-20210925214913-cdaccfe30cb0",
"hashes": [
{
"alg": "MD5",
- "content": "f09d9d5ec8a3070ef06c8a716ef58206"
+ "content": "d6c2bf92f7d13841ed21d9c409cf7cf4"
},
{
"alg": "SHA-1",
- "content": "6a76da80a00b8ce762019cb132c576e1dd485e6e"
+ "content": "cf9246e05f2416552edbf4b40ef8c3df259ed751"
},
{
"alg": "SHA-256",
- "content": "b796ac65d8c21b987c2b4a88b329fa56a9b9e79859292701d1767f627c926724"
+ "content": "890a85574c7f68b950f5bc5c08e5a3c32c06e8103de168d5721ec2dcc49fd3b9"
},
{
"alg": "SHA-384",
- "content": "6814ac9dace7d2aeec9a2d9fe11e0c877e71e2852d47a84560db16b5d1a351371773bfa09ba952a4a5b18f78c7d53cd9"
+ "content": "04d28e89523d14fc7dd32c54c4bdbbe99ac12fee427ff69d56ff3f9d98bde3880ab2b77520ff19fd620ab84d39920380"
},
{
"alg": "SHA-512",
- "content": "e73d97ccaac30c039428e688a095efc1c525a8476e12ceb69b26a425defbc786ef09a80a241b7a6de52fb8c38bf9f06a23b971b2344b93e61f88597b4d34d743"
+ "content": "ddfce73b8949a2d7878f1e9154cfc1228cb16cf55c6a7e57583cdc9ac286e653e8e285a64fda677bd8bc5d83dc85e1c1c54b630f2ab31c69881b1c09bf68538b"
}
]
}
diff --git a/examples/mod_minikube-v1.23.1.bom.json b/examples/mod_minikube-v1.23.1.bom.json
index b79ac81c..fcee462b 100644
--- a/examples/mod_minikube-v1.23.1.bom.json
+++ b/examples/mod_minikube-v1.23.1.bom.json
@@ -1,35 +1,35 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.3",
- "serialNumber": "urn:uuid:e1470263-f40c-4ce7-9beb-a3c02ffcc7d3",
+ "serialNumber": "urn:uuid:5edc5a19-e6c2-4d78-b023-2ee41a536e9c",
"version": 1,
"metadata": {
- "timestamp": "2021-09-19T16:20:37Z",
+ "timestamp": "2021-09-25T19:53:18Z",
"tools": [
{
"vendor": "CycloneDX",
"name": "cyclonedx-gomod",
- "version": "v0.0.0-20210919172430-96bd0c538cce",
+ "version": "v0.0.0-20210925214913-cdaccfe30cb0",
"hashes": [
{
"alg": "MD5",
- "content": "f09d9d5ec8a3070ef06c8a716ef58206"
+ "content": "d6c2bf92f7d13841ed21d9c409cf7cf4"
},
{
"alg": "SHA-1",
- "content": "6a76da80a00b8ce762019cb132c576e1dd485e6e"
+ "content": "cf9246e05f2416552edbf4b40ef8c3df259ed751"
},
{
"alg": "SHA-256",
- "content": "b796ac65d8c21b987c2b4a88b329fa56a9b9e79859292701d1767f627c926724"
+ "content": "890a85574c7f68b950f5bc5c08e5a3c32c06e8103de168d5721ec2dcc49fd3b9"
},
{
"alg": "SHA-384",
- "content": "6814ac9dace7d2aeec9a2d9fe11e0c877e71e2852d47a84560db16b5d1a351371773bfa09ba952a4a5b18f78c7d53cd9"
+ "content": "04d28e89523d14fc7dd32c54c4bdbbe99ac12fee427ff69d56ff3f9d98bde3880ab2b77520ff19fd620ab84d39920380"
},
{
"alg": "SHA-512",
- "content": "e73d97ccaac30c039428e688a095efc1c525a8476e12ceb69b26a425defbc786ef09a80a241b7a6de52fb8c38bf9f06a23b971b2344b93e61f88597b4d34d743"
+ "content": "ddfce73b8949a2d7878f1e9154cfc1228cb16cf55c6a7e57583cdc9ac286e653e8e285a64fda677bd8bc5d83dc85e1c1c54b630f2ab31c69881b1c09bf68538b"
}
]
}
diff --git a/internal/cli/cmd/app/app.go b/internal/cli/cmd/app/app.go
index c0589017..767843ca 100644
--- a/internal/cli/cmd/app/app.go
+++ b/internal/cli/cmd/app/app.go
@@ -20,6 +20,7 @@ package app
import (
"context"
"flag"
+ "path/filepath"
"strings"
cdx "github.com/CycloneDX/cyclonedx-go"
@@ -161,6 +162,8 @@ func Exec(options Options) error {
bom.Components = &components
bom.Dependencies = &dependencies
+ enrichWithApplicationDetails(bom, options.ModuleDir, options.Main)
+
if options.IncludeStd {
err = cliutil.AddStdComponent(bom)
if err != nil {
@@ -193,11 +196,9 @@ func createBuildProperties() (properties []cdx.Property, err error) {
continue
}
- if buildEnvVal == "" {
- continue
+ if buildEnvVal != "" {
+ properties = append(properties, sbom.NewProperty("build:env:"+buildEnvKey, buildEnvVal))
}
-
- properties = append(properties, sbom.NewProperty("build:env:"+buildEnvKey, buildEnvVal))
}
goflags, ok := env["GOFLAGS"]
@@ -225,3 +226,74 @@ func parseTagsFromGoFlags(goflags string) (tags []string) {
return
}
+
+// enrichWithApplicationDetails determines the application name as well as
+// the path to the application (path to mainFile's parent dir) relative to moduleDir.
+// If the application path is not equal to moduleDir, it is added to the main component's
+// package URL as sub path. For example:
+//
+// + moduleDir <- application name
+// |-+ main.go
+//
+// + moduleDir
+// |-+ cmd
+// |-+ app <- application name
+// |-+ main.go
+//
+// The package URLs for the above examples would look like this:
+// 1. pkg:golang/../module@version (untouched)
+// 2. pkg:golang/../module@version#cmd/app (with sub path)
+//
+// If the package URL is updated, the BOM reference is as well.
+// All places within the BOM that reference the main component will be updated accordingly.
+func enrichWithApplicationDetails(bom *cdx.BOM, moduleDir, mainFile string) {
+ // Resolve absolute paths to moduleDir and mainFile.
+ // Both may contain traversals or similar elements we don't care about.
+ // This procedure is done during options validation already,
+ // which is why we don't check for errors here.
+ moduleDirAbs, _ := filepath.Abs(moduleDir)
+ mainFileAbs, _ := filepath.Abs(filepath.Join(moduleDirAbs, mainFile))
+
+ // Construct path to mainFile relative to moduleDir
+ mainFileRel := strings.TrimPrefix(mainFileAbs, moduleDirAbs)
+ mainFileRel = strings.TrimPrefix(mainFileRel, "/")
+
+ // The application name is the name of the directory that contains
+ // the main file. There may be cases where this is not true.
+ // We could add a -name flag to override this in the future.
+ var applicationName string
+
+ if mainDir, _ := filepath.Split(mainFileRel); mainDir != "" {
+ mainDir = strings.TrimSuffix(mainDir, "/")
+ applicationName = filepath.Base(mainDir)
+
+ oldPURL := bom.Metadata.Component.PackageURL
+ newPURL := oldPURL + "#" + mainDir
+
+ log.Debug().
+ Str("old", oldPURL).
+ Str("new", newPURL).
+ Msg("updating purl of main component")
+
+ // Update PURL of main component
+ bom.Metadata.Component.BOMRef = newPURL
+ bom.Metadata.Component.PackageURL = newPURL
+
+ // Update PURL in dependency graph
+ for i, dep := range *bom.Dependencies {
+ if dep.Ref == oldPURL {
+ (*bom.Dependencies)[i].Ref = newPURL
+ break
+ }
+ }
+ } else {
+ applicationName = filepath.Base(moduleDirAbs)
+ }
+
+ applicationNameProperty := sbom.NewProperty("application:name", applicationName)
+ if bom.Metadata.Component.Properties == nil {
+ bom.Metadata.Component.Properties = &[]cdx.Property{applicationNameProperty}
+ } else {
+ *bom.Metadata.Component.Properties = append([]cdx.Property{applicationNameProperty}, *bom.Metadata.Component.Properties...)
+ }
+}