mirror of
https://github.com/anchore/syft.git
synced 2026-10-14 15:17:18 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7845381331 | ||
|
|
7464079a09 | ||
|
|
8a574c9ed9 | ||
|
|
681d250fdc | ||
|
|
2022ffa0e5 |
@@ -33,7 +33,7 @@ jobs:
|
||||
OUTPUT="${OUTPUT//'%'/'%25'}" # URL encode all '%' characters
|
||||
OUTPUT="${OUTPUT//$'\n'/'%0A'}" # URL encode all '\n' characters
|
||||
OUTPUT="${OUTPUT//$'\r'/'%0D'}" # URL encode all '\r' characters
|
||||
echo "::set-output name=result::$OUTPUT"
|
||||
echo "result=$OUTPUT" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
go mod tidy
|
||||
|
||||
# export the version for use with create-pull-request
|
||||
echo "::set-output name=LATEST_VERSION::$LATEST_VERSION"
|
||||
echo "LATEST_VERSION=$LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
id: latest-version
|
||||
|
||||
- uses: tibdex/github-app-token@v1
|
||||
|
||||
@@ -45,6 +45,7 @@ For commercial support options with Syft or Grype, please [contact Anchore](http
|
||||
- Java (jar, ear, war, par, sar, native-image)
|
||||
- JavaScript (npm, yarn)
|
||||
- Jenkins Plugins (jpi, hpi)
|
||||
- Nix (outputs in /nix/store)
|
||||
- PHP (composer)
|
||||
- Python (wheel, egg, poetry, requirements.txt)
|
||||
- Red Hat (rpm)
|
||||
|
||||
@@ -31,7 +31,7 @@ require (
|
||||
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e
|
||||
github.com/sergi/go-diff v1.3.1
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spdx/tools-golang v0.5.0-rc1
|
||||
github.com/spdx/tools-golang v0.5.0
|
||||
github.com/spf13/afero v1.9.5
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
|
||||
@@ -258,7 +258,6 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-containerregistry v0.14.0 h1:z58vMqHxuwvAsVwvKEkmVBz2TlgBgH5k6koEXBtlYkw=
|
||||
@@ -519,8 +518,8 @@ github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM=
|
||||
github.com/spdx/tools-golang v0.5.0-rc1 h1:ooCSe48QatlidqEFd+nSI308tyeNTR6NJvauUj3ApX8=
|
||||
github.com/spdx/tools-golang v0.5.0-rc1/go.mod h1:LI6onw172PdO57Ob/hgnLDD4Y2PMnroeNT3wO/2WJJI=
|
||||
github.com/spdx/tools-golang v0.5.0 h1:/fqihV2Jna7fmow65dHpgKNsilgLK7ICpd2tkCnPEyY=
|
||||
github.com/spdx/tools-golang v0.5.0/go.mod h1:kkGlrSXXfHwuSzHQZJRV3aKu9ZXCq/MSf2+xyiJH1lM=
|
||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
||||
|
||||
@@ -6,5 +6,5 @@ const (
|
||||
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "7.0.1"
|
||||
JSONSchemaVersion = "7.1.1"
|
||||
)
|
||||
|
||||
@@ -45,6 +45,7 @@ type artifactMetadataContainer struct {
|
||||
Hackage pkg.HackageMetadata
|
||||
Java pkg.JavaMetadata
|
||||
KbPackage pkg.KbPackageMetadata
|
||||
Nix pkg.NixStoreMetadata
|
||||
NpmPackage pkg.NpmPackageJSONMetadata
|
||||
NpmPackageLock pkg.NpmPackageLockJSONMetadata
|
||||
MixLock pkg.MixLockMetadata
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -45,6 +45,8 @@ func SourceInfo(p pkg.Package) string {
|
||||
answer = "acquired package info from cabal or stack manifest files"
|
||||
case pkg.HexPkg:
|
||||
answer = "acquired package info from rebar3 or mix manifest file"
|
||||
case pkg.NixPkg:
|
||||
answer = "acquired package info from nix store path"
|
||||
default:
|
||||
answer = "acquired package info from the following paths"
|
||||
}
|
||||
|
||||
@@ -199,6 +199,14 @@ func Test_SourceInfo(t *testing.T) {
|
||||
"from rebar3 or mix manifest file",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.NixPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from nix store path",
|
||||
},
|
||||
},
|
||||
}
|
||||
var pkgTypes []pkg.Type
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "6.2.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-6.2.0.json"
|
||||
"version": "7.1.1",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.1.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "6.2.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-6.2.0.json"
|
||||
"version": "7.1.1",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.1.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "6.2.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-6.2.0.json"
|
||||
"version": "7.1.1",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.1.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/haskell"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/java"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/javascript"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/nix"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/php"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/portage"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/python"
|
||||
@@ -51,6 +52,7 @@ func ImageCatalogers(cfg Config) []pkg.Cataloger {
|
||||
golang.NewGoModuleBinaryCataloger(cfg.Go()),
|
||||
dotnet.NewDotnetDepsCataloger(),
|
||||
portage.NewPortageCataloger(),
|
||||
nix.NewStoreCataloger(),
|
||||
sbom.NewSBOMCataloger(),
|
||||
binary.NewCataloger(),
|
||||
}, cfg.Catalogers)
|
||||
@@ -85,6 +87,7 @@ func DirectoryCatalogers(cfg Config) []pkg.Cataloger {
|
||||
binary.NewCataloger(),
|
||||
elixir.NewMixLockCataloger(),
|
||||
erlang.NewRebarLockCataloger(),
|
||||
nix.NewStoreCataloger(),
|
||||
}, cfg.Catalogers)
|
||||
}
|
||||
|
||||
@@ -121,6 +124,7 @@ func AllCatalogers(cfg Config) []pkg.Cataloger {
|
||||
binary.NewCataloger(),
|
||||
elixir.NewMixLockCataloger(),
|
||||
erlang.NewRebarLockCataloger(),
|
||||
nix.NewStoreCataloger(),
|
||||
}, cfg.Catalogers)
|
||||
}
|
||||
|
||||
|
||||
@@ -28,13 +28,14 @@ func newPackageJSONPackage(u packageJSON, locations ...source.Location) pkg.Pack
|
||||
Type: pkg.NpmPkg,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: u.Name,
|
||||
Version: u.Version,
|
||||
Author: u.Author.AuthorString(),
|
||||
Homepage: u.Homepage,
|
||||
URL: u.Repository.URL,
|
||||
Licenses: licenses,
|
||||
Private: u.Private,
|
||||
Name: u.Name,
|
||||
Version: u.Version,
|
||||
Author: u.Author.AuthorString(),
|
||||
Homepage: u.Homepage,
|
||||
URL: u.Repository.URL,
|
||||
Licenses: licenses,
|
||||
Private: u.Private,
|
||||
Description: u.Description,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -26,12 +26,13 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -46,12 +47,13 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"ISC"},
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"ISC"},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -66,12 +68,13 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"MIT", "Apache-2.0"},
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"MIT", "Apache-2.0"},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -86,12 +89,13 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: nil,
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: nil,
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -106,12 +110,13 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{},
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -126,12 +131,13 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -146,12 +152,13 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "function-bind",
|
||||
Version: "1.1.1",
|
||||
Author: "Raynos <raynos2@gmail.com>",
|
||||
Homepage: "https://github.com/Raynos/function-bind",
|
||||
URL: "git://github.com/Raynos/function-bind.git",
|
||||
Licenses: []string{"MIT"},
|
||||
Name: "function-bind",
|
||||
Version: "1.1.1",
|
||||
Author: "Raynos <raynos2@gmail.com>",
|
||||
Homepage: "https://github.com/Raynos/function-bind",
|
||||
URL: "git://github.com/Raynos/function-bind.git",
|
||||
Licenses: []string{"MIT"},
|
||||
Description: "Implementation of Function.prototype.bind",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -166,13 +173,14 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Private: true,
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Private: true,
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package nix
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bmatcuk/doublestar/v4"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const (
|
||||
catalogerName = "nix-store-cataloger"
|
||||
nixStoreGlob = "**/nix/store/*"
|
||||
)
|
||||
|
||||
// StoreCataloger finds package outputs installed in the Nix store location (/nix/store/*).
|
||||
type StoreCataloger struct{}
|
||||
|
||||
func NewStoreCataloger() *StoreCataloger {
|
||||
return &StoreCataloger{}
|
||||
}
|
||||
|
||||
func (c *StoreCataloger) Name() string {
|
||||
return catalogerName
|
||||
}
|
||||
|
||||
func (c *StoreCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
// we want to search for only directories, which isn't possible via the stereoscope API, so we need to apply the glob manually on all returned paths
|
||||
var pkgs []pkg.Package
|
||||
var filesByPath = make(map[string]*source.LocationSet)
|
||||
for location := range resolver.AllLocations() {
|
||||
matchesStorePath, err := doublestar.Match(nixStoreGlob, location.RealPath)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to match nix store path: %w", err)
|
||||
}
|
||||
|
||||
parentStorePath := findParentNixStorePath(location.RealPath)
|
||||
if parentStorePath != "" {
|
||||
if _, ok := filesByPath[parentStorePath]; !ok {
|
||||
s := source.NewLocationSet()
|
||||
filesByPath[parentStorePath] = &s
|
||||
}
|
||||
filesByPath[parentStorePath].Add(location)
|
||||
}
|
||||
|
||||
if !matchesStorePath {
|
||||
continue
|
||||
}
|
||||
|
||||
storePath := parseNixStorePath(location.RealPath)
|
||||
|
||||
if storePath == nil || !storePath.isValidPackage() {
|
||||
continue
|
||||
}
|
||||
|
||||
p := newNixStorePackage(*storePath, location)
|
||||
pkgs = append(pkgs, p)
|
||||
}
|
||||
|
||||
// add file sets to packages
|
||||
for i := range pkgs {
|
||||
p := &pkgs[i]
|
||||
locations := p.Locations.ToSlice()
|
||||
if len(locations) == 0 {
|
||||
log.WithFields("package", p.Name).Warn("nix package has no evidence locations associated")
|
||||
continue
|
||||
}
|
||||
parentStorePath := locations[0].RealPath
|
||||
files, ok := filesByPath[parentStorePath]
|
||||
if !ok {
|
||||
log.WithFields("path", parentStorePath, "nix-store-path", parentStorePath).Warn("found a nix store file for a non-existent package")
|
||||
continue
|
||||
}
|
||||
appendFiles(p, files.ToSlice()...)
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
}
|
||||
|
||||
func appendFiles(p *pkg.Package, location ...source.Location) {
|
||||
metadata, ok := p.Metadata.(pkg.NixStoreMetadata)
|
||||
if !ok {
|
||||
log.WithFields("package", p.Name).Warn("nix package metadata missing")
|
||||
return
|
||||
}
|
||||
|
||||
for _, l := range location {
|
||||
metadata.Files = append(metadata.Files, l.RealPath)
|
||||
}
|
||||
|
||||
if metadata.Files == nil {
|
||||
// note: we always have an allocated collection for output
|
||||
metadata.Files = []string{}
|
||||
}
|
||||
|
||||
p.Metadata = metadata
|
||||
p.SetID()
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package nix
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func TestCataloger_Catalog(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
fixture string
|
||||
wantPkgs []pkg.Package
|
||||
wantRel []artifact.Relationship
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/fixture-1",
|
||||
wantPkgs: []pkg.Package{
|
||||
{
|
||||
Name: "glibc",
|
||||
Version: "2.34-210",
|
||||
PURL: "pkg:nix/glibc@2.34-210?output=bin&outputhash=h0cnbmfcn93xm5dg2x27ixhag1cwndga",
|
||||
Locations: source.NewLocationSet(source.NewLocation("nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin")),
|
||||
FoundBy: catalogerName,
|
||||
Type: pkg.NixPkg,
|
||||
MetadataType: pkg.NixStoreMetadataType,
|
||||
Metadata: pkg.NixStoreMetadata{
|
||||
OutputHash: "h0cnbmfcn93xm5dg2x27ixhag1cwndga",
|
||||
Output: "bin",
|
||||
Files: []string{
|
||||
"nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin/lib",
|
||||
"nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin/lib/glibc.so",
|
||||
"nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin/share",
|
||||
"nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin/share/man",
|
||||
"nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin/share/man/glibc.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.fixture, func(t *testing.T) {
|
||||
c := NewStoreCataloger()
|
||||
|
||||
pkgtest.NewCatalogTester().
|
||||
FromDirectory(t, tt.fixture).
|
||||
Expects(tt.wantPkgs, tt.wantRel).
|
||||
TestCataloger(t, c)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package nix
|
||||
|
||||
import (
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func newNixStorePackage(storePath nixStorePath, locations ...source.Location) pkg.Package {
|
||||
p := pkg.Package{
|
||||
Name: storePath.name,
|
||||
Version: storePath.version,
|
||||
FoundBy: catalogerName,
|
||||
Locations: source.NewLocationSet(locations...),
|
||||
Type: pkg.NixPkg,
|
||||
PURL: packageURL(storePath),
|
||||
MetadataType: pkg.NixStoreMetadataType,
|
||||
Metadata: pkg.NixStoreMetadata{
|
||||
OutputHash: storePath.outputHash,
|
||||
Output: storePath.output,
|
||||
},
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func packageURL(storePath nixStorePath) string {
|
||||
var qualifiers packageurl.Qualifiers
|
||||
if storePath.output != "" {
|
||||
// since there is no nix pURL type yet, this is a guess, however, it is reasonable to assume that
|
||||
// if only a single output is installed the pURL should be able to express this.
|
||||
qualifiers = append(qualifiers,
|
||||
packageurl.Qualifier{
|
||||
Key: "output",
|
||||
Value: storePath.output,
|
||||
},
|
||||
)
|
||||
}
|
||||
if storePath.outputHash != "" {
|
||||
// it's not immediately clear if the hash found in the store path should be encoded in the pURL
|
||||
qualifiers = append(qualifiers,
|
||||
packageurl.Qualifier{
|
||||
Key: "outputhash",
|
||||
Value: storePath.outputHash,
|
||||
},
|
||||
)
|
||||
}
|
||||
pURL := packageurl.NewPackageURL(
|
||||
// TODO: nix pURL type has not been accepted yet (only proposed at this time)
|
||||
"nix",
|
||||
"",
|
||||
storePath.name,
|
||||
storePath.version,
|
||||
qualifiers,
|
||||
"")
|
||||
return pURL.ToString()
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package nix
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_packageURL(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
storePath nixStorePath
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "name + version",
|
||||
storePath: nixStorePath{
|
||||
name: "glibc",
|
||||
version: "2.34",
|
||||
},
|
||||
want: "pkg:nix/glibc@2.34",
|
||||
},
|
||||
{
|
||||
name: "hash qualifier",
|
||||
storePath: nixStorePath{
|
||||
name: "glibc",
|
||||
version: "2.34",
|
||||
outputHash: "h0cnbmfcn93xm5dg2x27ixhag1cwndga",
|
||||
},
|
||||
want: "pkg:nix/glibc@2.34?outputhash=h0cnbmfcn93xm5dg2x27ixhag1cwndga",
|
||||
},
|
||||
{
|
||||
name: "output qualifier",
|
||||
storePath: nixStorePath{
|
||||
name: "glibc",
|
||||
version: "2.34",
|
||||
outputHash: "h0cnbmfcn93xm5dg2x27ixhag1cwndga",
|
||||
output: "bin",
|
||||
},
|
||||
want: "pkg:nix/glibc@2.34?output=bin&outputhash=h0cnbmfcn93xm5dg2x27ixhag1cwndga",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, packageURL(tt.storePath))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package nix
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
numericPattern = regexp.MustCompile(`\d`)
|
||||
|
||||
// attempts to find the right-most example of something that appears to be a version (semver or otherwise)
|
||||
// example input: h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin
|
||||
// example output:
|
||||
// version: "2.34-210"
|
||||
// major: "2"
|
||||
// minor: "34"
|
||||
// patch: "210"
|
||||
// (there are other capture groups, but they can be ignored)
|
||||
rightMostVersionIshPattern = regexp.MustCompile(`-(?P<version>(?P<major>[0-9][a-zA-Z0-9]*)(\.(?P<minor>[0-9][a-zA-Z0-9]*))?(\.(?P<patch>0|[1-9][a-zA-Z0-9]*)){0,3}(?:-(?P<prerelease>\d*[.a-zA-Z-][.0-9a-zA-Z-]*)*)?(?:\+(?P<metadata>[.0-9a-zA-Z-]+(?:\.[.0-9a-zA-Z-]+)*))?)`)
|
||||
|
||||
unstableVersion = regexp.MustCompile(`-(?P<version>unstable-\d{4}-\d{2}-\d{2})$`)
|
||||
)
|
||||
|
||||
// checkout the package naming conventions here: https://nixos.org/manual/nixpkgs/stable/#sec-package-naming
|
||||
|
||||
type nixStorePath struct {
|
||||
outputHash string
|
||||
name string
|
||||
version string
|
||||
output string
|
||||
}
|
||||
|
||||
func (p nixStorePath) isValidPackage() bool {
|
||||
return p.name != "" && p.version != ""
|
||||
}
|
||||
|
||||
func findParentNixStorePath(source string) string {
|
||||
source = strings.TrimRight(source, "/")
|
||||
indicator := "nix/store/"
|
||||
start := strings.Index(source, indicator)
|
||||
if start == -1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
startOfHash := start + len(indicator)
|
||||
nextField := strings.Index(source[startOfHash:], "/")
|
||||
if nextField == -1 {
|
||||
return ""
|
||||
}
|
||||
startOfSubPath := startOfHash + nextField
|
||||
|
||||
return source[0:startOfSubPath]
|
||||
}
|
||||
|
||||
func parseNixStorePath(source string) *nixStorePath {
|
||||
if strings.HasSuffix(source, ".drv") {
|
||||
// ignore derivations
|
||||
return nil
|
||||
}
|
||||
|
||||
source = path.Base(source)
|
||||
|
||||
versionStartIdx, versionIsh, prerelease := findVersionIsh(source)
|
||||
if versionStartIdx == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
hashName := strings.TrimSuffix(source[0:versionStartIdx], "-")
|
||||
hashNameFields := strings.Split(hashName, "-")
|
||||
if len(hashNameFields) < 2 {
|
||||
return nil
|
||||
}
|
||||
hash, name := hashNameFields[0], strings.Join(hashNameFields[1:], "-")
|
||||
|
||||
prereleaseFields := strings.Split(prerelease, "-")
|
||||
lastPrereleaseField := prereleaseFields[len(prereleaseFields)-1]
|
||||
|
||||
var version = versionIsh
|
||||
var output string
|
||||
if !hasNumeric(lastPrereleaseField) {
|
||||
// this last prerelease field is probably a nix output
|
||||
version = strings.TrimSuffix(versionIsh, fmt.Sprintf("-%s", lastPrereleaseField))
|
||||
output = lastPrereleaseField
|
||||
}
|
||||
|
||||
return &nixStorePath{
|
||||
outputHash: hash,
|
||||
name: name,
|
||||
version: version,
|
||||
output: output,
|
||||
}
|
||||
}
|
||||
|
||||
func hasNumeric(s string) bool {
|
||||
return numericPattern.MatchString(s)
|
||||
}
|
||||
|
||||
func findVersionIsh(input string) (int, string, string) {
|
||||
// we want to return the index of the start of the "version" group (the first capture group).
|
||||
// note that the match indices are in the form of [start, end, start, end, ...]. Also note that the
|
||||
// capture group for version in both regexes are the same index, but if the regexes are changed
|
||||
// this code will start to fail.
|
||||
versionGroup := 1
|
||||
|
||||
match := unstableVersion.FindAllStringSubmatchIndex(input, -1)
|
||||
if len(match) > 0 && len(match[0]) > 0 {
|
||||
return match[0][versionGroup*2], input[match[0][versionGroup*2]:match[0][(versionGroup*2)+1]], ""
|
||||
}
|
||||
|
||||
match = rightMostVersionIshPattern.FindAllStringSubmatchIndex(input, -1)
|
||||
if len(match) == 0 || len(match[0]) == 0 {
|
||||
return -1, "", ""
|
||||
}
|
||||
|
||||
var version string
|
||||
versionStart, versionStop := match[0][versionGroup*2], match[0][(versionGroup*2)+1]
|
||||
if versionStart != -1 || versionStop != -1 {
|
||||
version = input[versionStart:versionStop]
|
||||
}
|
||||
|
||||
prereleaseGroup := 7
|
||||
|
||||
var prerelease string
|
||||
prereleaseStart, prereleaseStop := match[0][prereleaseGroup*2], match[0][(prereleaseGroup*2)+1]
|
||||
if prereleaseStart != -1 && prereleaseStop != -1 {
|
||||
prerelease = input[prereleaseStart:prereleaseStop]
|
||||
}
|
||||
|
||||
return versionStart,
|
||||
version,
|
||||
prerelease
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
package nix
|
||||
|
||||
import (
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_findVersionIsh(t *testing.T) {
|
||||
// note: only the package version fields are tested here, the name is tested in parseNixStorePath below.
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantIdx int
|
||||
wantVersion string
|
||||
wantPreRelease string
|
||||
}{
|
||||
{
|
||||
name: "no version",
|
||||
input: "5q7vxm9lc4b9hifc3br4sr8dy7f2h0qa-source",
|
||||
wantIdx: -1,
|
||||
wantVersion: "",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
{
|
||||
name: "semver with overbite into output",
|
||||
input: "/nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin",
|
||||
wantIdx: 50,
|
||||
wantVersion: "2.34-210-bin",
|
||||
wantPreRelease: "210-bin",
|
||||
},
|
||||
{
|
||||
name: "multiple versions",
|
||||
input: "5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33",
|
||||
wantIdx: 53,
|
||||
wantVersion: "2.33",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
{
|
||||
name: "name ends with number",
|
||||
input: "55nswyz8335lk954y1ccx6as2jbq1z8f-libfido2-1.10.0",
|
||||
wantIdx: 42,
|
||||
wantVersion: "1.10.0",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
{
|
||||
name: "major-minor only",
|
||||
input: "q8gnp7r8475p52k9gmdzsrcddw5hirbn-gdbm-1.23",
|
||||
wantIdx: 38,
|
||||
wantVersion: "1.23",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
{
|
||||
name: "0-prefixed version field",
|
||||
input: "r705jm2icczpnmfccby3fzfrckfjakx3-perl5.34.1-URI-5.05",
|
||||
wantIdx: 48,
|
||||
wantVersion: "5.05",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
{
|
||||
name: "prerelease with alpha prefix",
|
||||
input: "v48s6iddb518j9lc1pk3rcn3x8c2ff0j-bash-interactive-5.1-p16",
|
||||
wantIdx: 50,
|
||||
wantVersion: "5.1-p16",
|
||||
wantPreRelease: "p16",
|
||||
},
|
||||
{
|
||||
|
||||
name: "0-major version",
|
||||
input: "x2f9x5q6qrs6cssx09ylxqyg9q2isi1z-aws-c-http-0.6.15",
|
||||
wantIdx: 44,
|
||||
wantVersion: "0.6.15",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
{
|
||||
|
||||
name: "several version fields",
|
||||
// note: this package version is fictitious
|
||||
input: "z24qs6f5d1mmwdp73n1jfc3swj4v2c5s-krb5-1.19.3.9.10",
|
||||
wantIdx: 38,
|
||||
wantVersion: "1.19.3.9.10",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
{
|
||||
|
||||
name: "skip drv + major only version",
|
||||
input: "z0fqylhisz47krxv8fd0izm1i2qbswfr-readline63-006.drv",
|
||||
wantIdx: 44,
|
||||
wantVersion: "006",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
{
|
||||
|
||||
name: "prerelease with multiple dashes",
|
||||
input: "zkgyp2vra0bgqm0dv1qi514l5fd0aksx-bash-interactive-5.1-p16-man",
|
||||
wantIdx: 50,
|
||||
wantVersion: "5.1-p16-man",
|
||||
wantPreRelease: "p16-man",
|
||||
},
|
||||
{
|
||||
|
||||
name: "date as major version",
|
||||
input: "0amf0d1dymv9gqcyhhjb9j0l8sn00c56-libedit-20210910-3.1",
|
||||
wantIdx: 41,
|
||||
wantVersion: "20210910-3.1",
|
||||
wantPreRelease: "3.1",
|
||||
},
|
||||
{
|
||||
|
||||
name: "long name",
|
||||
input: "0296qxvn30z9b2ah1g5p97k5wr9k8y78-busybox-static-x86_64-unknown-linux-musl-1.35.0",
|
||||
wantIdx: 74,
|
||||
wantVersion: "1.35.0",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
{
|
||||
// this accounts for https://nixos.org/manual/nixpkgs/stable/#sec-package-naming
|
||||
// > If a package is not a release but a commit from a repository, then the version attribute must
|
||||
// > be the date of that (fetched) commit. The date must be in "unstable-YYYY-MM-DD" format.
|
||||
// example: https://github.com/NixOS/nixpkgs/blob/798e23beab9b5cba4d6f05e8b243e1d4535770f3/pkgs/servers/webdav-server-rs/default.nix#L14
|
||||
name: "unstable version",
|
||||
input: "q5dhwzcn82by5ndc7g0q83wsnn13qkqw-webdav-server-rs-unstable-2021-08-16",
|
||||
wantIdx: 50,
|
||||
wantVersion: "unstable-2021-08-16",
|
||||
wantPreRelease: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotIdx, gotVersion, gotPreRelease := findVersionIsh(tt.input)
|
||||
assert.Equal(t, tt.wantIdx, gotIdx)
|
||||
assert.Equal(t, tt.wantVersion, gotVersion)
|
||||
assert.Equal(t, tt.wantPreRelease, gotPreRelease)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseNixStorePath(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
source string
|
||||
want *nixStorePath
|
||||
}{
|
||||
{
|
||||
source: "/nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin",
|
||||
want: &nixStorePath{
|
||||
outputHash: "h0cnbmfcn93xm5dg2x27ixhag1cwndga",
|
||||
name: "glibc",
|
||||
version: "2.34-210",
|
||||
output: "bin",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "/nix/store/0296qxvn30z9b2ah1g5p97k5wr9k8y78-busybox-static-x86_64-unknown-linux-musl-1.35.0",
|
||||
want: &nixStorePath{
|
||||
outputHash: "0296qxvn30z9b2ah1g5p97k5wr9k8y78",
|
||||
name: "busybox-static-x86_64-unknown-linux-musl",
|
||||
version: "1.35.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "/nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33",
|
||||
want: &nixStorePath{
|
||||
outputHash: "5zzrvdmlkc5rh3k5862krd3wfb3pqhyf",
|
||||
name: "perl5.34.1-TimeDate",
|
||||
version: "2.33",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "/nix/store/q38q8ng57zwjg1h15ry5zx0lb0xyax4b-libcap-2.63-lib",
|
||||
want: &nixStorePath{
|
||||
outputHash: "q38q8ng57zwjg1h15ry5zx0lb0xyax4b",
|
||||
name: "libcap",
|
||||
version: "2.63",
|
||||
output: "lib",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "/nix/store/p0y8fbpbqr2jm5zfrdll0rgyg2lvp5g2-util-linux-minimal-2.37.4-bin",
|
||||
want: &nixStorePath{
|
||||
outputHash: "p0y8fbpbqr2jm5zfrdll0rgyg2lvp5g2",
|
||||
name: "util-linux-minimal",
|
||||
version: "2.37.4",
|
||||
output: "bin",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "/nix/store/z24qs6f5d1mmwdp73n1jfc3swj4v2c5s-krb5-1.19.3.9.10",
|
||||
want: &nixStorePath{
|
||||
outputHash: "z24qs6f5d1mmwdp73n1jfc3swj4v2c5s",
|
||||
name: "krb5",
|
||||
version: "1.19.3.9.10",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "/nix/store/zkgyp2vra0bgqm0dv1qi514l5fd0aksx-bash-interactive-5.1-p16-man",
|
||||
want: &nixStorePath{
|
||||
outputHash: "zkgyp2vra0bgqm0dv1qi514l5fd0aksx",
|
||||
name: "bash-interactive",
|
||||
version: "5.1-p16",
|
||||
output: "man",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "/nix/store/nwf2y0nc48ybim56308cr5ccvwkabcqc-openssl-1.1.1q",
|
||||
want: &nixStorePath{
|
||||
outputHash: "nwf2y0nc48ybim56308cr5ccvwkabcqc",
|
||||
name: "openssl",
|
||||
version: "1.1.1q",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "/nix/store/nwv742f1bxv6g78hy9yc6slxdbxlmqhb-kmod-29",
|
||||
want: &nixStorePath{
|
||||
outputHash: "nwv742f1bxv6g78hy9yc6slxdbxlmqhb",
|
||||
name: "kmod",
|
||||
version: "29",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "/nix/store/n83qx7m848kg51lcjchwbkmlgdaxfckf-tzdata-2022a",
|
||||
want: &nixStorePath{
|
||||
outputHash: "n83qx7m848kg51lcjchwbkmlgdaxfckf",
|
||||
name: "tzdata",
|
||||
version: "2022a",
|
||||
},
|
||||
},
|
||||
{
|
||||
source: "'/nix/store/q5dhwzcn82by5ndc7g0q83wsnn13qkqw-webdav-server-rs-unstable-2021-08-16",
|
||||
want: &nixStorePath{
|
||||
outputHash: "q5dhwzcn82by5ndc7g0q83wsnn13qkqw",
|
||||
name: "webdav-server-rs",
|
||||
version: "unstable-2021-08-16",
|
||||
},
|
||||
},
|
||||
// negative cases...
|
||||
{
|
||||
source: "'z33yk02rsr6b4rb56lgb80bnvxx6yw39-?id=21ee35dde73aec5eba35290587d479218c6dd824.drv'",
|
||||
},
|
||||
{
|
||||
source: "/nix/store/yzahni8aig6mdrvcsccgwm2515lcpi5q-git-minimal-2.36.0.drv",
|
||||
},
|
||||
{
|
||||
source: "/nix/store/z9yvxs0s3xdkp5jgmzis4g50bfq3dgvm-0018-pkg-config-derive-prefix-from-prefix.patch",
|
||||
},
|
||||
{
|
||||
source: "/nix/store/w3hl7zrmc9qvzadc0k7cp9ysxiyz88j6-base-system",
|
||||
},
|
||||
{
|
||||
source: "/nix/store/zz1lc28x25fcx6al6xwk3dk8kp7wx47y-Test-RequiresInternet-0.05.tar.gz.drv",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(path.Base(tt.source), func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, parseNixStorePath(tt.source))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parentNixStorePath(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
source string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "exact path from absolute root",
|
||||
source: "/nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "exact path from relative root",
|
||||
source: "nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "clean paths",
|
||||
source: "//nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33///",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "relative root with subdir file",
|
||||
source: "nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33/bin/perl-timedate",
|
||||
want: "nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33",
|
||||
},
|
||||
{
|
||||
name: "absolute root with with subdir file",
|
||||
source: "/nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33/bin/perl-timedate",
|
||||
want: "/nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33",
|
||||
},
|
||||
{
|
||||
name: "nexted root with with subdir file",
|
||||
source: "/somewhere/nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33/bin/perl-timedate",
|
||||
want: "/somewhere/nix/store/5zzrvdmlkc5rh3k5862krd3wfb3pqhyf-perl5.34.1-TimeDate-2.33",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, findParentNixStorePath(tt.source))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# this is not a real binary, just a small text file
|
||||
!nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin/lib/glibc.so
|
||||
+1
@@ -0,0 +1 @@
|
||||
the binary
|
||||
+1
@@ -0,0 +1 @@
|
||||
the man pages
|
||||
@@ -2,8 +2,23 @@ package pkg
|
||||
|
||||
// GemMetadata represents all metadata parsed from the gemspec file
|
||||
type GemMetadata struct {
|
||||
Name string `mapstructure:"name" json:"name"`
|
||||
Version string `mapstructure:"version" json:"version"`
|
||||
Name string `mapstructure:"name" json:"name"`
|
||||
Version string `mapstructure:"version" json:"version"`
|
||||
// note regarding if Files can contribute to GemMetadata being able to implement FileOwner: this list is a
|
||||
// "logical" list of files, not a list of paths that can be used to find the files without additional processing.
|
||||
//
|
||||
// For example: The "bundler" gem has a file entry of:
|
||||
// "lib/bundler/vendor/uri/lib/uri/ldap.rb"
|
||||
// but the actual file is located at:
|
||||
// "/usr/local/lib/ruby/3.2.0/bundler/vendor/uri/lib/uri/ldap.rb"
|
||||
// which do not match (the "lib" prefix is missing even for relative processing).
|
||||
//
|
||||
// without additional information about:
|
||||
// - the gem installation path
|
||||
// - the ruby installation path
|
||||
// - the ruby version
|
||||
// - environment variables (e.g. GEM_HOME) that may affect the gem installation path
|
||||
// ... we can't reliably determine the full path to the file on disk, thus cannot implement FileOwner (...yet...).
|
||||
Files []string `mapstructure:"files" json:"files,omitempty"`
|
||||
Authors []string `mapstructure:"authors" json:"authors,omitempty"`
|
||||
Licenses []string `mapstructure:"licenses" json:"licenses,omitempty"`
|
||||
|
||||
@@ -9,6 +9,7 @@ type MetadataType string
|
||||
|
||||
const (
|
||||
// this is the full set of data shapes that can be represented within the pkg.Package.Metadata field
|
||||
|
||||
UnknownMetadataType MetadataType = "UnknownMetadata"
|
||||
AlpmMetadataType MetadataType = "AlpmMetadata"
|
||||
ApkMetadataType MetadataType = "ApkMetadata"
|
||||
@@ -26,6 +27,7 @@ const (
|
||||
JavaMetadataType MetadataType = "JavaMetadata"
|
||||
KbPackageMetadataType MetadataType = "KbPackageMetadata"
|
||||
MixLockMetadataType MetadataType = "MixLockMetadataType"
|
||||
NixStoreMetadataType MetadataType = "NixStoreMetadata"
|
||||
NpmPackageJSONMetadataType MetadataType = "NpmPackageJsonMetadata"
|
||||
NpmPackageLockJSONMetadataType MetadataType = "NpmPackageLockJsonMetadata"
|
||||
PhpComposerJSONMetadataType MetadataType = "PhpComposerJsonMetadata"
|
||||
@@ -54,6 +56,7 @@ var AllMetadataTypes = []MetadataType{
|
||||
JavaMetadataType,
|
||||
KbPackageMetadataType,
|
||||
MixLockMetadataType,
|
||||
NixStoreMetadataType,
|
||||
NpmPackageJSONMetadataType,
|
||||
NpmPackageLockJSONMetadataType,
|
||||
PhpComposerJSONMetadataType,
|
||||
@@ -82,6 +85,7 @@ var MetadataTypeByName = map[MetadataType]reflect.Type{
|
||||
JavaMetadataType: reflect.TypeOf(JavaMetadata{}),
|
||||
KbPackageMetadataType: reflect.TypeOf(KbPackageMetadata{}),
|
||||
MixLockMetadataType: reflect.TypeOf(MixLockMetadata{}),
|
||||
NixStoreMetadataType: reflect.TypeOf(NixStoreMetadata{}),
|
||||
NpmPackageJSONMetadataType: reflect.TypeOf(NpmPackageJSONMetadata{}),
|
||||
NpmPackageLockJSONMetadataType: reflect.TypeOf(NpmPackageLockJSONMetadata{}),
|
||||
PhpComposerJSONMetadataType: reflect.TypeOf(PhpComposerJSONMetadata{}),
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
)
|
||||
|
||||
type NixStoreMetadata struct {
|
||||
// OutputHash is the prefix of the nix store basename path
|
||||
OutputHash string `mapstructure:"outputHash" json:"outputHash"`
|
||||
|
||||
// Output allows for optionally specifying the specific nix package output this package represents (for packages that support multiple outputs).
|
||||
// Note: the default output for a package is an empty string, so will not be present in the output.
|
||||
Output string `mapstructure:"output" json:"output,omitempty"`
|
||||
|
||||
// Files is a listing a files that are under the nix/store path for this package
|
||||
Files []string `mapstructure:"files" json:"files"`
|
||||
}
|
||||
|
||||
func (m NixStoreMetadata) OwnedFiles() (result []string) {
|
||||
result = strset.New(m.Files...).List()
|
||||
sort.Strings(result)
|
||||
return
|
||||
}
|
||||
@@ -4,7 +4,6 @@ package pkg
|
||||
type NpmPackageJSONMetadata struct {
|
||||
Name string `mapstructure:"name" json:"name"`
|
||||
Version string `mapstructure:"version" json:"version"`
|
||||
Files []string `mapstructure:"files" json:"files,omitempty"`
|
||||
Author string `mapstructure:"author" json:"author"`
|
||||
Licenses []string `mapstructure:"licenses" json:"licenses"`
|
||||
Homepage string `mapstructure:"homepage" json:"homepage"`
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
var _ FileOwner = (*PortageMetadata)(nil)
|
||||
|
||||
// PortageMetadata represents all captured data for a Package package DB entry.
|
||||
type PortageMetadata struct {
|
||||
InstalledSize int `mapstructure:"InstalledSize" json:"installedSize" cyclonedx:"installedSize"`
|
||||
@@ -15,3 +21,15 @@ type PortageFileRecord struct {
|
||||
Path string `json:"path"`
|
||||
Digest *file.Digest `json:"digest,omitempty"`
|
||||
}
|
||||
|
||||
func (m PortageMetadata) OwnedFiles() (result []string) {
|
||||
s := strset.New()
|
||||
for _, f := range m.Files {
|
||||
if f.Path != "" {
|
||||
s.Add(f.Path)
|
||||
}
|
||||
}
|
||||
result = s.List()
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ const (
|
||||
JavaPkg Type = "java-archive"
|
||||
JenkinsPluginPkg Type = "jenkins-plugin"
|
||||
KbPkg Type = "msrc-kb"
|
||||
NixPkg Type = "nix"
|
||||
NpmPkg Type = "npm"
|
||||
PhpComposerPkg Type = "php-composer"
|
||||
PortagePkg Type = "portage"
|
||||
@@ -51,6 +52,7 @@ var AllPkgs = []Type{
|
||||
JavaPkg,
|
||||
JenkinsPluginPkg,
|
||||
KbPkg,
|
||||
NixPkg,
|
||||
NpmPkg,
|
||||
PhpComposerPkg,
|
||||
PortagePkg,
|
||||
@@ -92,6 +94,8 @@ func (t Type) PackageURLType() string {
|
||||
return packageurl.TypePyPi
|
||||
case PortagePkg:
|
||||
return "portage"
|
||||
case NixPkg:
|
||||
return "nix"
|
||||
case NpmPkg:
|
||||
return packageurl.TypeNPM
|
||||
case RpmPkg:
|
||||
@@ -151,6 +155,8 @@ func TypeByName(name string) Type {
|
||||
return PortagePkg
|
||||
case packageurl.TypeHex:
|
||||
return HexPkg
|
||||
case "nix":
|
||||
return NixPkg
|
||||
default:
|
||||
return UnknownPkg
|
||||
}
|
||||
|
||||
@@ -83,6 +83,10 @@ func TestTypeFromPURL(t *testing.T) {
|
||||
purl: "pkg:hex/hpax/hpax@0.1.1",
|
||||
expected: HexPkg,
|
||||
},
|
||||
{
|
||||
purl: "pkg:nix/glibc@2.34?hash=h0cnbmfcn93xm5dg2x27ixhag1cwndga",
|
||||
expected: NixPkg,
|
||||
},
|
||||
}
|
||||
|
||||
var pkgTypes []string
|
||||
|
||||
@@ -97,7 +97,7 @@ func TestPackagesCmdFlags(t *testing.T) {
|
||||
name: "squashed-scope-flag",
|
||||
args: []string{"packages", "-o", "json", "-s", "squashed", coverageImage},
|
||||
assertions: []traitAssertion{
|
||||
assertPackageCount(34),
|
||||
assertPackageCount(35),
|
||||
assertSuccessfulReturnCode,
|
||||
},
|
||||
},
|
||||
@@ -214,7 +214,7 @@ func TestPackagesCmdFlags(t *testing.T) {
|
||||
// the application config in the log matches that of what we expect to have been configured.
|
||||
assertInOutput("parallelism: 2"),
|
||||
assertInOutput("parallelism=2"),
|
||||
assertPackageCount(34),
|
||||
assertPackageCount(35),
|
||||
assertSuccessfulReturnCode,
|
||||
},
|
||||
},
|
||||
@@ -225,7 +225,7 @@ func TestPackagesCmdFlags(t *testing.T) {
|
||||
// the application config in the log matches that of what we expect to have been configured.
|
||||
assertInOutput("parallelism: 1"),
|
||||
assertInOutput("parallelism=1"),
|
||||
assertPackageCount(34),
|
||||
assertPackageCount(35),
|
||||
assertSuccessfulReturnCode,
|
||||
},
|
||||
},
|
||||
@@ -239,7 +239,7 @@ func TestPackagesCmdFlags(t *testing.T) {
|
||||
assertions: []traitAssertion{
|
||||
assertNotInOutput("secret_password"),
|
||||
assertNotInOutput("secret_key_path"),
|
||||
assertPackageCount(34),
|
||||
assertPackageCount(35),
|
||||
assertSuccessfulReturnCode,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -10,82 +10,53 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
)
|
||||
|
||||
func TestSpdxValidationTooling(t *testing.T) {
|
||||
img := imagetest.GetFixtureImage(t, "docker-archive", "image-java-spdx-tools")
|
||||
require.NotEmpty(t, img.Metadata.Tags)
|
||||
imgTag := img.Metadata.Tags[0]
|
||||
|
||||
images := []string{
|
||||
"alpine:3.17.3@sha256:b6ca290b6b4cdcca5b3db3ffa338ee0285c11744b4a6abaa9627746ee3291d8d",
|
||||
"photon:3.0@sha256:888675e193418d924feea262cf639c46532b63c2027a39fd3ac75383b3c1130e",
|
||||
"debian:stable@sha256:729c2433e196207749a86f1d86e0106822041bb280b4200cf7a4db97608f6d3a",
|
||||
}
|
||||
|
||||
env := map[string]string{
|
||||
"SYFT_FILE_METADATA_CATALOGER_ENABLED": "true",
|
||||
"SYFT_FILE_CONTENTS_CATALOGER_ENABLED": "true",
|
||||
"SYFT_FILE_METADATA_DIGESTS": "sha1",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
syftArgs []string
|
||||
images []string
|
||||
setup func(t *testing.T)
|
||||
env map[string]string
|
||||
assertions []traitAssertion
|
||||
name string
|
||||
syftArgs []string
|
||||
images []string
|
||||
setup func(t *testing.T)
|
||||
env map[string]string
|
||||
}{
|
||||
{
|
||||
name: "spdx validation tooling tag value",
|
||||
syftArgs: []string{"packages", "-o", "spdx"},
|
||||
images: []string{"alpine:latest", "photon:3.0", "debian:latest"},
|
||||
env: map[string]string{
|
||||
"SYFT_FILE_METADATA_CATALOGER_ENABLED": "true",
|
||||
"SYFT_FILE_CONTENTS_CATALOGER_ENABLED": "true",
|
||||
"SYFT_FILE_METADATA_DIGESTS": "sha1",
|
||||
},
|
||||
setup: func(t *testing.T) {
|
||||
cwd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
fixturesPath := filepath.Join(cwd, "test-fixtures", "image-java-spdx-tools")
|
||||
buildCmd := exec.Command("make", "build")
|
||||
buildCmd.Dir = fixturesPath
|
||||
buildCmd.Stdout = os.Stdout
|
||||
buildCmd.Stderr = os.Stderr
|
||||
err = buildCmd.Run()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertions: []traitAssertion{
|
||||
assertSuccessfulReturnCode,
|
||||
},
|
||||
images: images,
|
||||
env: env,
|
||||
},
|
||||
{
|
||||
name: "spdx validation tooling json",
|
||||
syftArgs: []string{"packages", "-o", "spdx-json"},
|
||||
images: []string{"alpine:latest", "photon:3.0", "debian:latest"},
|
||||
env: map[string]string{
|
||||
"SYFT_FILE_METADATA_CATALOGER_ENABLED": "true",
|
||||
"SYFT_FILE_CONTENTS_CATALOGER_ENABLED": "true",
|
||||
"SYFT_FILE_METADATA_DIGESTS": "sha1",
|
||||
},
|
||||
setup: func(t *testing.T) {
|
||||
cwd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
fixturesPath := filepath.Join(cwd, "test-fixtures", "image-java-spdx-tools")
|
||||
buildCmd := exec.Command("make", "build")
|
||||
buildCmd.Dir = fixturesPath
|
||||
err = buildCmd.Run()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
assertions: []traitAssertion{
|
||||
assertSuccessfulReturnCode,
|
||||
},
|
||||
images: images,
|
||||
env: env,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// build the validation image
|
||||
test.setup(t)
|
||||
dir := t.TempDir()
|
||||
for _, image := range test.images {
|
||||
for _, image := range test.images {
|
||||
t.Run(test.name+"_"+image, func(t *testing.T) {
|
||||
|
||||
args := append(test.syftArgs, image)
|
||||
cmd, stdout, stderr := runSyft(t, test.env, args...)
|
||||
for _, traitFn := range test.assertions {
|
||||
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
|
||||
}
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
f, err := os.CreateTemp(dir, "temp")
|
||||
require.NoError(t, err)
|
||||
|
||||
var suffix string
|
||||
if strings.Contains(test.name, "json") {
|
||||
@@ -94,23 +65,28 @@ func TestSpdxValidationTooling(t *testing.T) {
|
||||
suffix = ".spdx"
|
||||
}
|
||||
|
||||
// spdx tooling only takes a file with suffix spdx
|
||||
rename := path.Join(path.Dir(f.Name()), fmt.Sprintf("%s%s", path.Base(f.Name()), suffix))
|
||||
err = os.Rename(f.Name(), rename)
|
||||
require.NoError(t, err)
|
||||
dir := t.TempDir()
|
||||
sbomPath := filepath.Join(dir, fmt.Sprintf("sbom%s", suffix))
|
||||
|
||||
// write file for validation
|
||||
_, err = f.Write([]byte(stdout))
|
||||
args = append(args, "--file", sbomPath)
|
||||
|
||||
cmd, _, stderr := runSyft(t, test.env, args...)
|
||||
if cmd.ProcessState.ExitCode() != 0 {
|
||||
t.Fatalf("failed to run syft: %s", stderr)
|
||||
}
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
// validate against spdx java tooling
|
||||
fileArg := fmt.Sprintf("FILE=%s", rename)
|
||||
mountArg := fmt.Sprintf("BASE=%s", path.Base(rename))
|
||||
fileArg := fmt.Sprintf("DIR=%s", dir)
|
||||
mountArg := fmt.Sprintf("BASE=%s", path.Base(sbomPath))
|
||||
imageArg := fmt.Sprintf("IMAGE=%s", imgTag)
|
||||
|
||||
validateCmd := exec.Command("make", "validate", fileArg, mountArg)
|
||||
validateCmd := exec.Command("make", "validate", fileArg, mountArg, imageArg)
|
||||
validateCmd.Dir = filepath.Join(cwd, "test-fixtures", "image-java-spdx-tools")
|
||||
runAndShow(t, validateCmd)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
all: build validate
|
||||
|
||||
IMAGE := "spdx-java-tools:latest"
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
docker build -t spdx-java-tools:latest .
|
||||
|
||||
validate:
|
||||
docker run --rm -v ${FILE}:/home/build/${BASE} spdx-java-tools:latest Verify ${BASE}
|
||||
docker run --rm -v $(DIR):/home/build/ $(IMAGE) Verify /home/build/$(BASE)
|
||||
|
||||
@@ -390,4 +390,11 @@ var commonTestCases = []testCase{
|
||||
"example-jenkins-plugin": "1.0-SNAPSHOT",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "find nix store packages",
|
||||
pkgType: pkg.NixPkg,
|
||||
pkgInfo: map[string]string{
|
||||
"glibc": "2.34-210",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user