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