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...) + } +}