Compare commits

..
5 Commits
Author SHA1 Message Date
Keith Zantow 7845381331 chore: update tools-golang to v0.5.0 (#1717)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-04-05 13:59:52 -04:00
7464079a09 Add Nix cataloger (#1696)
* Add Basic Nix Cataloger

Signed-off-by: Julio Tain Sueiras <juliosueiras@gmail.com>

* Update nix def for the latest syft definition

Signed-off-by: Julio Tain Sueiras <juliosueiras@gmail.com>

* capture nix package files on pkg.NixStoreMetadata

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* fix unit tests and linting

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update JSON schema

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* address review comments

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* Update syft/pkg/cataloger/nix/parse_nix_store_path_test.go

Co-authored-by: Florian Klink <flokli@flokli.de>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* support unstable version conventions

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update json schema relative to main branch

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update syft json with v7.1.1 schema

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* fix CLI tests

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* remove extra continue statement

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add Nix to list of supported ecosystems

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

---------

Signed-off-by: Julio Tain Sueiras <juliosueiras@gmail.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Co-authored-by: Julio Tain Sueiras <juliosueiras@gmail.com>
Co-authored-by: Florian Klink <flokli@flokli.de>
2023-04-04 10:53:56 -04:00
Alex Goodman 8a574c9ed9 refactor spdx tooling test to reduce intermittent failures (#1707)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2023-04-03 18:43:28 +00:00
Alex Goodman 681d250fdc Capture file ownership relationships from portage ecosystem (#1702)
* add portage as file owners

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* fix tests

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* fix linting

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update json schema with NPM files

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

---------

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2023-04-03 09:46:18 -04:00
Keith Zantow 2022ffa0e5 chore: update deprecated set-output calls (#1705)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-04-03 09:36:11 -04:00
38 changed files with 4236 additions and 147 deletions
+1 -1
View File
@@ -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
+1
View File
@@ -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)
+1 -1
View File
@@ -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
+2 -3
View File
@@ -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=
+1 -1
View File
@@ -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"
)
+1
View File
@@ -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"
}
}
+4
View File
@@ -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)
}
+8 -7
View File
@@ -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",
},
},
},
+101
View File
@@ -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()
}
+55
View File
@@ -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)
})
}
}
+59
View File
@@ -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()
}
+49
View File
@@ -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
+17 -2
View File
@@ -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"`
+4
View File
@@ -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{}),
+25
View File
@@ -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
}
-1
View File
@@ -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"`
+18
View File
@@ -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
}
+6
View File
@@ -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
}
+4
View File
@@ -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
+4 -4
View File
@@ -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,
},
},
+46 -70
View File
@@ -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",
},
},
}