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  }