github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/pkg/cataloger/golang/cataloger.go (about) 1 /* 2 Package golang provides a concrete Cataloger implementation relating to packages within the Go language ecosystem. 3 */ 4 package golang 5 6 import ( 7 "context" 8 "fmt" 9 "regexp" 10 "strings" 11 12 "github.com/anchore/syft/internal" 13 "github.com/anchore/syft/internal/mimetype" 14 "github.com/anchore/syft/syft/artifact" 15 "github.com/anchore/syft/syft/cpe" 16 "github.com/anchore/syft/syft/file" 17 "github.com/anchore/syft/syft/pkg" 18 "github.com/anchore/syft/syft/pkg/cataloger/generic" 19 ) 20 21 var versionCandidateGroups = regexp.MustCompile(`(?P<version>\d+(\.\d+)?(\.\d+)?)(?P<candidate>\w*)`) 22 23 const ( 24 modFileCatalogerName = "go-module-file-cataloger" 25 binaryCatalogerName = "go-module-binary-cataloger" 26 ) 27 28 // NewGoModuleFileCataloger returns a new cataloger object that searches within go.mod files. 29 func NewGoModuleFileCataloger(opts CatalogerConfig) pkg.Cataloger { 30 c := goModCataloger{ 31 licenses: newGoLicenses(modFileCatalogerName, opts), 32 } 33 return &progressingCataloger{ 34 cataloger: generic.NewCataloger(modFileCatalogerName). 35 WithParserByGlobs(c.parseGoModFile, "**/go.mod"), 36 } 37 } 38 39 // NewGoModuleBinaryCataloger returns a new cataloger object that searches within binaries built by the go compiler. 40 func NewGoModuleBinaryCataloger(opts CatalogerConfig) pkg.Cataloger { 41 return &progressingCataloger{ 42 cataloger: generic.NewCataloger(binaryCatalogerName). 43 WithParserByMimeTypes( 44 newGoBinaryCataloger(opts).parseGoBinary, 45 mimetype.ExecutableMIMETypeSet.List()..., 46 ), 47 } 48 } 49 50 type progressingCataloger struct { 51 cataloger *generic.Cataloger 52 } 53 54 func (p *progressingCataloger) Name() string { 55 return p.cataloger.Name() 56 } 57 58 func (p *progressingCataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { 59 pkgs, relationships, err := p.cataloger.Catalog(ctx, resolver) 60 goCompilerPkgs := []pkg.Package{} 61 totalLocations := file.NewLocationSet() 62 for _, goPkg := range pkgs { 63 mValue, ok := goPkg.Metadata.(pkg.GolangBinaryBuildinfoEntry) 64 if !ok { 65 continue 66 } 67 // go binary packages should only contain a single location 68 for _, location := range goPkg.Locations.ToSlice() { 69 if !totalLocations.Contains(location) { 70 stdLibPkg := newGoStdLib(mValue.GoCompiledVersion, goPkg.Locations) 71 if stdLibPkg != nil { 72 goCompilerPkgs = append(goCompilerPkgs, *stdLibPkg) 73 totalLocations.Add(location) 74 } 75 } 76 } 77 } 78 pkgs = append(pkgs, goCompilerPkgs...) 79 return pkgs, relationships, err 80 } 81 82 func newGoStdLib(version string, location file.LocationSet) *pkg.Package { 83 stdlibCpe, err := generateStdlibCpe(version) 84 if err != nil { 85 return nil 86 } 87 goCompilerPkg := &pkg.Package{ 88 Name: "stdlib", 89 Version: version, 90 PURL: packageURL("stdlib", strings.TrimPrefix(version, "go")), 91 CPEs: []cpe.CPE{stdlibCpe}, 92 Locations: location, 93 Licenses: pkg.NewLicenseSet(pkg.NewLicense("BSD-3-Clause")), 94 Language: pkg.Go, 95 Type: pkg.GoModulePkg, 96 Metadata: pkg.GolangBinaryBuildinfoEntry{ 97 GoCompiledVersion: version, 98 }, 99 } 100 goCompilerPkg.SetID() 101 102 return goCompilerPkg 103 } 104 105 func generateStdlibCpe(version string) (stdlibCpe cpe.CPE, err error) { 106 // GoCompiledVersion when pulled from a binary is prefixed by go 107 version = strings.TrimPrefix(version, "go") 108 109 // we also need to trim starting from the first +<metadata> to 110 // correctly extract potential rc candidate information for cpe generation 111 // ex: 2.0.0-rc.1+build.123 -> 2.0.0-rc.1; if no + is found then + is returned 112 after, _, found := strings.Cut("+", version) 113 if found { 114 version = after 115 } 116 117 // extracting <version> and <candidate> 118 // https://regex101.com/r/985GsI/1 119 captureGroups := internal.MatchNamedCaptureGroups(versionCandidateGroups, version) 120 vr, ok := captureGroups["version"] 121 if !ok || vr == "" { 122 return stdlibCpe, fmt.Errorf("could not match candidate version for: %s", version) 123 } 124 125 cpeString := fmt.Sprintf("cpe:2.3:a:golang:go:%s:-:*:*:*:*:*:*", captureGroups["version"]) 126 if candidate, ok := captureGroups["candidate"]; ok && candidate != "" { 127 cpeString = fmt.Sprintf("cpe:2.3:a:golang:go:%s:%s:*:*:*:*:*:*", vr, candidate) 128 } 129 130 return cpe.New(cpeString, cpe.GeneratedSource) 131 }