Skip to content

Commit

Permalink
fix: update cpe generation and add unit test
Browse files Browse the repository at this point in the history
Signed-off-by: Christopher Phillips <[email protected]>
  • Loading branch information
spiffcs committed Oct 6, 2023
1 parent 305c465 commit a0dce54
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 17 deletions.
60 changes: 43 additions & 17 deletions syft/pkg/cataloger/golang/cataloger.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ package golang

import (
"fmt"
"regexp"
"strings"

"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/event/monitor"
Expand All @@ -17,6 +17,9 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)

// this regex is used
var versionCandidateGroups = regexp.MustCompile(`(?P<version>\d+(\.\d+)?(\.\d+)?)(?P<candidate>\w*)`)

// NewGoModFileCataloger returns a new Go module cataloger object.
func NewGoModFileCataloger(opts GoCatalogerOpts) pkg.Cataloger {
c := goModCataloger{
Expand Down Expand Up @@ -56,14 +59,16 @@ func (p *progressingCataloger) Catalog(resolver file.Resolver) ([]pkg.Package, [
goCompilerPkgs := []pkg.Package{}
totalLocations := file.NewLocationSet()
for _, goPkg := range pkgs {
mValue, ok := goPkg.Metadata.(pkg.GolangBinMetadata)
if !ok {
continue
}
// go binary packages should only contain a single location
for _, location := range goPkg.Locations.ToSlice() {
if !totalLocations.Contains(location) {
if mValue, ok := goPkg.Metadata.(pkg.GolangBinMetadata); ok {
stdLibPkg := newGoStdLib(mValue.GoCompiledVersion, goPkg.Locations)
if stdLibPkg != nil {
goCompilerPkgs = append(goCompilerPkgs, *stdLibPkg)
}
stdLibPkg := newGoStdLib(mValue.GoCompiledVersion, goPkg.Locations)
if stdLibPkg != nil {
goCompilerPkgs = append(goCompilerPkgs, *stdLibPkg)
totalLocations.Add(location)
}
}
Expand All @@ -73,22 +78,15 @@ func (p *progressingCataloger) Catalog(resolver file.Resolver) ([]pkg.Package, [
return pkgs, relationships, err
}
func newGoStdLib(version string, location file.LocationSet) *pkg.Package {
// for matching we need to strip the go prefix
// this can be preserved for metadata purposes
matchVersion := strings.TrimPrefix(version, "go")
cpes := make([]cpe.CPE, 0)
compilerCPE, err := cpe.New(fmt.Sprintf("cpe:2.3:a:golang:go:%s:-:*:*:*:*:*:*", matchVersion))
stdlibCpe, err := generateStdlibCpe(version)
if err != nil {
log.Warn("could not build cpe for given compiler version: %s", version)
return nil
}

cpes = append(cpes, compilerCPE)
goCompilerPkg := &pkg.Package{
Name: "Golang Standard Library",
Name: "stdlib",
Version: version,
PURL: packageURL("stdlib", matchVersion),
CPEs: cpes,
PURL: packageURL("stdlib", strings.TrimPrefix(version, "go")),
CPEs: []cpe.CPE{stdlibCpe},
Locations: location,
Language: pkg.Go,
Type: pkg.GoModulePkg,
Expand All @@ -101,3 +99,31 @@ func newGoStdLib(version string, location file.LocationSet) *pkg.Package {

return goCompilerPkg
}

func generateStdlibCpe(version string) (stdlibCpe cpe.CPE, err error) {
// GoCompiledVersion when pulled from a binary is prefixed by go
version = strings.TrimPrefix(version, "go")

// we also need to trim starting from the first +<metadata> to
// correctly extract potential rc candidate information for cpe generation
// ex: 2.0.0-rc.1+build.123 -> 2.0.0-rc.1; if no + is found then + is returned
after, _, found := strings.Cut("+", version)
if found {
version = after
}

// extracting <version> and <candidate>
// https://regex101.com/r/985GsI/1
captureGroups := internal.MatchNamedCaptureGroups(versionCandidateGroups, version)
vr, ok := captureGroups["version"]
if !ok || vr == "" {
return stdlibCpe, fmt.Errorf("could not match candidate version for: %s", version)
}

cpeString := fmt.Sprintf("cpe:2.3:a:golang:go:%s:-:*:*:*:*:*:*", captureGroups["version"])
if candidate, ok := captureGroups["candidate"]; ok && candidate != "" {
cpeString = fmt.Sprintf("cpe:2.3:a:golang:go:%s:%s:*:*:*:*:*:*", vr, candidate)
}

return cpe.New(cpeString)
}
30 changes: 30 additions & 0 deletions syft/pkg/cataloger/golang/cataloger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package golang
import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
)

Expand Down Expand Up @@ -56,3 +59,30 @@ func Test_Binary_Cataloger_Globs(t *testing.T) {
})
}
}

func Test_Binary_Cataloger_Stdlib_Cpe(t *testing.T) {
tests := []struct {
name string
candidate string
want string
}{
{
name: "generateStdlibCpe generates a cpe with a - for a major version",
candidate: "go1.21.0",
want: "cpe:2.3:a:golang:go:1.21.0:-:*:*:*:*:*:*",
},
{
name: "generateStdlibCpe generates a cpe with an rc candidate for a major rc version",
candidate: "go1.21rc2",
want: "cpe:2.3:a:golang:go:1.21:rc2:*:*:*:*:*:*",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := generateStdlibCpe(tc.candidate)
assert.NoError(t, err, "expected no err; got %v", err)
assert.Equal(t, cpe.String(got), tc.want)
})
}
}

0 comments on commit a0dce54

Please sign in to comment.