Adding the ability to retrieve remote licenses from package.lock (#2708)

Signed-off-by: Colm O hEigeartaigh <coheigea@apache.org>
This commit is contained in:
Colm O hEigeartaigh 2024-03-21 17:20:04 +00:00 committed by GitHub
parent 0d5ebed74a
commit f4e18961b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 57 additions and 15 deletions

View File

@ -17,8 +17,9 @@ func NewPackageCataloger() pkg.Cataloger {
// NewLockCataloger returns a new cataloger object for NPM (and NPM-adjacent, such as yarn) lock files. // NewLockCataloger returns a new cataloger object for NPM (and NPM-adjacent, such as yarn) lock files.
func NewLockCataloger(cfg CatalogerConfig) pkg.Cataloger { func NewLockCataloger(cfg CatalogerConfig) pkg.Cataloger {
yarnLockAdapter := newGenericYarnLockAdapter(cfg) yarnLockAdapter := newGenericYarnLockAdapter(cfg)
packageLockAdapter := newGenericPackageLockAdapter(cfg)
return generic.NewCataloger("javascript-lock-cataloger"). return generic.NewCataloger("javascript-lock-cataloger").
WithParserByGlobs(parsePackageLock, "**/package-lock.json"). WithParserByGlobs(packageLockAdapter.parsePackageLock, "**/package-lock.json").
WithParserByGlobs(yarnLockAdapter.parseYarnLock, "**/yarn.lock"). WithParserByGlobs(yarnLockAdapter.parseYarnLock, "**/yarn.lock").
WithParserByGlobs(parsePnpmLock, "**/pnpm-lock.yaml") WithParserByGlobs(parsePnpmLock, "**/pnpm-lock.yaml")
} }

View File

@ -47,7 +47,7 @@ func newPackageJSONPackage(u packageJSON, indexLocation file.Location) pkg.Packa
return p return p
} }
func newPackageLockV1Package(resolver file.Resolver, location file.Location, name string, u lockDependency) pkg.Package { func newPackageLockV1Package(cfg CatalogerConfig, resolver file.Resolver, location file.Location, name string, u lockDependency) pkg.Package {
version := u.Version version := u.Version
const aliasPrefixPackageLockV1 = "npm:" const aliasPrefixPackageLockV1 = "npm:"
@ -63,12 +63,26 @@ func newPackageLockV1Package(resolver file.Resolver, location file.Location, nam
version = canonicalPackageAndVersion[versionSeparator+1:] version = canonicalPackageAndVersion[versionSeparator+1:]
} }
var licenseSet pkg.LicenseSet
if cfg.SearchRemoteLicenses {
license, err := getLicenseFromNpmRegistry(cfg.NPMBaseURL, name, version)
if err == nil && license != "" {
licenses := pkg.NewLicensesFromValues(license)
licenseSet = pkg.NewLicenseSet(licenses...)
}
if err != nil {
log.Warnf("unable to extract licenses from javascript yarn.lock for package %s:%s: %+v", name, version, err)
}
}
return finalizeLockPkg( return finalizeLockPkg(
resolver, resolver,
location, location,
pkg.Package{ pkg.Package{
Name: name, Name: name,
Version: version, Version: version,
Licenses: licenseSet,
Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
PURL: packageURL(name, version), PURL: packageURL(name, version),
Language: pkg.JavaScript, Language: pkg.JavaScript,
@ -78,7 +92,22 @@ func newPackageLockV1Package(resolver file.Resolver, location file.Location, nam
) )
} }
func newPackageLockV2Package(resolver file.Resolver, location file.Location, name string, u lockPackage) pkg.Package { func newPackageLockV2Package(cfg CatalogerConfig, resolver file.Resolver, location file.Location, name string, u lockPackage) pkg.Package {
var licenseSet pkg.LicenseSet
if u.License != nil {
licenseSet = pkg.NewLicenseSet(pkg.NewLicensesFromLocation(location, u.License...)...)
} else if cfg.SearchRemoteLicenses {
license, err := getLicenseFromNpmRegistry(cfg.NPMBaseURL, name, u.Version)
if err == nil && license != "" {
licenses := pkg.NewLicensesFromValues(license)
licenseSet = pkg.NewLicenseSet(licenses...)
}
if err != nil {
log.Warnf("unable to extract licenses from javascript yarn.lock for package %s:%s: %+v", name, u.Version, err)
}
}
return finalizeLockPkg( return finalizeLockPkg(
resolver, resolver,
location, location,
@ -86,7 +115,7 @@ func newPackageLockV2Package(resolver file.Resolver, location file.Location, nam
Name: name, Name: name,
Version: u.Version, Version: u.Version,
Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(location, u.License...)...), Licenses: licenseSet,
PURL: packageURL(name, u.Version), PURL: packageURL(name, u.Version),
Language: pkg.JavaScript, Language: pkg.JavaScript,
Type: pkg.NpmPkg, Type: pkg.NpmPkg,

View File

@ -15,9 +15,6 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/generic" "github.com/anchore/syft/syft/pkg/cataloger/generic"
) )
// integrity check
var _ generic.Parser = parsePackageLock
// packageLock represents a JavaScript package.lock json file // packageLock represents a JavaScript package.lock json file
type packageLock struct { type packageLock struct {
Requires bool `json:"requires"` Requires bool `json:"requires"`
@ -44,8 +41,18 @@ type lockPackage struct {
// packageLockLicense // packageLockLicense
type packageLockLicense []string type packageLockLicense []string
type genericPackageLockAdapter struct {
cfg CatalogerConfig
}
func newGenericPackageLockAdapter(cfg CatalogerConfig) genericPackageLockAdapter {
return genericPackageLockAdapter{
cfg: cfg,
}
}
// parsePackageLock parses a package-lock.json and returns the discovered JavaScript packages. // parsePackageLock parses a package-lock.json and returns the discovered JavaScript packages.
func parsePackageLock(_ context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { func (a genericPackageLockAdapter) parsePackageLock(_ context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
// in the case we find package-lock.json files in the node_modules directories, skip those // in the case we find package-lock.json files in the node_modules directories, skip those
// as the whole purpose of the lock file is for the specific dependencies of the root project // as the whole purpose of the lock file is for the specific dependencies of the root project
if pathContainsNodeModulesDirectory(reader.Path()) { if pathContainsNodeModulesDirectory(reader.Path()) {
@ -66,7 +73,7 @@ func parsePackageLock(_ context.Context, resolver file.Resolver, _ *generic.Envi
if lock.LockfileVersion == 1 { if lock.LockfileVersion == 1 {
for name, pkgMeta := range lock.Dependencies { for name, pkgMeta := range lock.Dependencies {
pkgs = append(pkgs, newPackageLockV1Package(resolver, reader.Location, name, pkgMeta)) pkgs = append(pkgs, newPackageLockV1Package(a.cfg, resolver, reader.Location, name, pkgMeta))
} }
} }
@ -86,7 +93,7 @@ func parsePackageLock(_ context.Context, resolver file.Resolver, _ *generic.Envi
pkgs = append( pkgs = append(
pkgs, pkgs,
newPackageLockV2Package(resolver, reader.Location, getNameFromPath(name), pkgMeta), newPackageLockV2Package(a.cfg, resolver, reader.Location, getNameFromPath(name), pkgMeta),
) )
} }
} }

View File

@ -106,7 +106,8 @@ func TestParsePackageLock(t *testing.T) {
expectedPkgs[i].Locations.Add(file.NewLocation(fixture)) expectedPkgs[i].Locations.Add(file.NewLocation(fixture))
} }
pkgtest.TestFileParser(t, fixture, parsePackageLock, expectedPkgs, expectedRelationships) adapter := newGenericPackageLockAdapter(CatalogerConfig{})
pkgtest.TestFileParser(t, fixture, adapter.parsePackageLock, expectedPkgs, expectedRelationships)
} }
func TestParsePackageLockV2(t *testing.T) { func TestParsePackageLockV2(t *testing.T) {
@ -169,7 +170,8 @@ func TestParsePackageLockV2(t *testing.T) {
for i := range expectedPkgs { for i := range expectedPkgs {
expectedPkgs[i].Locations.Add(file.NewLocation(fixture)) expectedPkgs[i].Locations.Add(file.NewLocation(fixture))
} }
pkgtest.TestFileParser(t, fixture, parsePackageLock, expectedPkgs, expectedRelationships) adapter := newGenericPackageLockAdapter(CatalogerConfig{})
pkgtest.TestFileParser(t, fixture, adapter.parsePackageLock, expectedPkgs, expectedRelationships)
} }
func TestParsePackageLockV3(t *testing.T) { func TestParsePackageLockV3(t *testing.T) {
@ -220,7 +222,8 @@ func TestParsePackageLockV3(t *testing.T) {
for i := range expectedPkgs { for i := range expectedPkgs {
expectedPkgs[i].Locations.Add(file.NewLocation(fixture)) expectedPkgs[i].Locations.Add(file.NewLocation(fixture))
} }
pkgtest.TestFileParser(t, fixture, parsePackageLock, expectedPkgs, expectedRelationships) adapter := newGenericPackageLockAdapter(CatalogerConfig{})
pkgtest.TestFileParser(t, fixture, adapter.parsePackageLock, expectedPkgs, expectedRelationships)
} }
func TestParsePackageLockAlias(t *testing.T) { func TestParsePackageLockAlias(t *testing.T) {
@ -279,7 +282,8 @@ func TestParsePackageLockAlias(t *testing.T) {
for i := range expected { for i := range expected {
expected[i].Locations.Add(file.NewLocation(pl)) expected[i].Locations.Add(file.NewLocation(pl))
} }
pkgtest.TestFileParser(t, pl, parsePackageLock, expected, expectedRelationships) adapter := newGenericPackageLockAdapter(CatalogerConfig{})
pkgtest.TestFileParser(t, pl, adapter.parsePackageLock, expected, expectedRelationships)
} }
} }
@ -326,5 +330,6 @@ func TestParsePackageLockLicenseWithArray(t *testing.T) {
for i := range expectedPkgs { for i := range expectedPkgs {
expectedPkgs[i].Locations.Add(file.NewLocation(fixture)) expectedPkgs[i].Locations.Add(file.NewLocation(fixture))
} }
pkgtest.TestFileParser(t, fixture, parsePackageLock, expectedPkgs, expectedRelationships) adapter := newGenericPackageLockAdapter(CatalogerConfig{})
pkgtest.TestFileParser(t, fixture, adapter.parsePackageLock, expectedPkgs, expectedRelationships)
} }