Add Linux Kernel cataloger (#1694)

* add kernel handler

Signed-off-by: Avi Deitcher <avi@deitcher.net>

* [wip] combine kernel and kernel module cataloging

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

* [wip] combine kernel and kernel module cataloging

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Avi Deitcher <avi@deitcher.net>

* rename Kernel package to LinuxKernel package

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

* split kernel and module packages within cataloger

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

* wire up application configuration with kernel cataloger options

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

* dont use references for packages on relationships

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

* fix linting and tests

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

* kernel cataloger should be resistent to partial failure

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

* log upon kernel module metadata missing

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

* add tests for linux kernel cataloger

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

* update integration tests

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

* update cli package test counts

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

* add evidence annotations for kernel packages

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

* reduce noise in cli test output

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

* missed cli test to reduce noise for

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

* fix package counts

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

* update docs with linux kernel cataloging refs

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

* bump json schema with new metadata fields

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

---------

Signed-off-by: Avi Deitcher <avi@deitcher.net>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: <>
Co-authored-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
Avi Deitcher 2023-04-14 21:33:36 +03:00 committed by GitHub
parent 5d156b8241
commit cc731c7b19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 2516 additions and 78 deletions

View File

@ -45,6 +45,8 @@ For commercial support options with Syft or Grype, please [contact Anchore](http
- Java (jar, ear, war, par, sar, nar, native-image) - Java (jar, ear, war, par, sar, nar, native-image)
- JavaScript (npm, yarn) - JavaScript (npm, yarn)
- Jenkins Plugins (jpi, hpi) - Jenkins Plugins (jpi, hpi)
- Linux kernel archives (vmlinz)
- Linux kernel modules (ko)
- Nix (outputs in /nix/store) - Nix (outputs in /nix/store)
- PHP (composer) - PHP (composer)
- Python (wheel, egg, poetry, requirements.txt) - Python (wheel, egg, poetry, requirements.txt)
@ -513,6 +515,11 @@ golang:
# SYFT_GOLANG_LOCAL_MOD_CACHE_DIR env var # SYFT_GOLANG_LOCAL_MOD_CACHE_DIR env var
local-mod-cache-dir: "" local-mod-cache-dir: ""
linux-kernel:
# whether to catalog linux kernel modules found within lib/modules/** directories
# SYFT_LINUX_KERNEL_CATALOG_MODULES env var
catalog-modules: true
# cataloging file contents is exposed through the power-user subcommand # cataloging file contents is exposed through the power-user subcommand
file-contents: file-contents:
cataloger: cataloger:

1
go.mod
View File

@ -53,6 +53,7 @@ require (
github.com/Masterminds/sprig/v3 v3.2.3 github.com/Masterminds/sprig/v3 v3.2.3
github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8 github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8
github.com/anchore/stereoscope v0.0.0-20230406143206-e95d60a265e3 github.com/anchore/stereoscope v0.0.0-20230406143206-e95d60a265e3
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
github.com/docker/docker v23.0.3+incompatible github.com/docker/docker v23.0.3+incompatible
github.com/google/go-containerregistry v0.14.0 github.com/google/go-containerregistry v0.14.0
github.com/google/licensecheck v0.3.1 github.com/google/licensecheck v0.3.1

2
go.sum
View File

@ -145,6 +145,8 @@ github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M=
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk=
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
github.com/docker/cli v23.0.1+incompatible h1:LRyWITpGzl2C9e9uGxzisptnxAn1zfZKXy13Ul2Q5oM= github.com/docker/cli v23.0.1+incompatible h1:LRyWITpGzl2C9e9uGxzisptnxAn1zfZKXy13Ul2Q5oM=
github.com/docker/cli v23.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v23.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=

View File

@ -19,6 +19,7 @@ import (
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg/cataloger" "github.com/anchore/syft/syft/pkg/cataloger"
golangCataloger "github.com/anchore/syft/syft/pkg/cataloger/golang" golangCataloger "github.com/anchore/syft/syft/pkg/cataloger/golang"
"github.com/anchore/syft/syft/pkg/cataloger/kernel"
) )
var ( var (
@ -50,6 +51,7 @@ type Application struct {
Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"` Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"`
Package pkg `yaml:"package" json:"package" mapstructure:"package"` Package pkg `yaml:"package" json:"package" mapstructure:"package"`
Golang golang `yaml:"golang" json:"golang" mapstructure:"golang"` Golang golang `yaml:"golang" json:"golang" mapstructure:"golang"`
LinuxKernel linuxKernel `yaml:"linux-kernel" json:"linux-kernel" mapstructure:"linux-kernel"`
Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"` Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"`
FileMetadata FileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"` FileMetadata FileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"`
FileClassification fileClassification `yaml:"file-classification" json:"file-classification" mapstructure:"file-classification"` FileClassification fileClassification `yaml:"file-classification" json:"file-classification" mapstructure:"file-classification"`
@ -76,6 +78,9 @@ func (cfg Application) ToCatalogerConfig() cataloger.Config {
SearchLocalModCacheLicenses: cfg.Golang.SearchLocalModCacheLicenses, SearchLocalModCacheLicenses: cfg.Golang.SearchLocalModCacheLicenses,
LocalModCacheDir: cfg.Golang.LocalModCacheDir, LocalModCacheDir: cfg.Golang.LocalModCacheDir,
}, },
LinuxKernel: kernel.LinuxCatalogerConfig{
CatalogModules: cfg.LinuxKernel.CatalogModules,
},
} }
} }

View File

@ -0,0 +1,11 @@
package config
import "github.com/spf13/viper"
type linuxKernel struct {
CatalogModules bool `json:"catalog-modules" yaml:"catalog-modules" mapstructure:"catalog-modules"`
}
func (cfg linuxKernel) loadDefaultValues(v *viper.Viper) {
v.SetDefault("linux-kernel.catalog-modules", true)
}

View File

@ -6,5 +6,5 @@ const (
// JSONSchemaVersion is the current schema version output by the JSON encoder // 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. // 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.1.2" JSONSchemaVersion = "7.1.3"
) )

View File

@ -45,6 +45,8 @@ type artifactMetadataContainer struct {
Hackage pkg.HackageMetadata Hackage pkg.HackageMetadata
Java pkg.JavaMetadata Java pkg.JavaMetadata
KbPackage pkg.KbPackageMetadata KbPackage pkg.KbPackageMetadata
LinuxKernel pkg.LinuxKernelMetadata
LinuxKernelModule pkg.LinuxKernelModuleMetadata
Nix pkg.NixStoreMetadata Nix pkg.NixStoreMetadata
NpmPackage pkg.NpmPackageJSONMetadata NpmPackage pkg.NpmPackageJSONMetadata
NpmPackageLock pkg.NpmPackageLockJSONMetadata NpmPackageLock pkg.NpmPackageLockJSONMetadata

File diff suppressed because it is too large Load Diff

View File

@ -6,6 +6,7 @@ import (
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
) )
//nolint:funlen
func SourceInfo(p pkg.Package) string { func SourceInfo(p pkg.Package) string {
answer := "" answer := ""
switch p.Type { switch p.Type {
@ -45,6 +46,10 @@ func SourceInfo(p pkg.Package) string {
answer = "acquired package info from cabal or stack manifest files" answer = "acquired package info from cabal or stack manifest files"
case pkg.HexPkg: case pkg.HexPkg:
answer = "acquired package info from rebar3 or mix manifest file" answer = "acquired package info from rebar3 or mix manifest file"
case pkg.LinuxKernelPkg:
answer = "acquired package info from linux kernel archive"
case pkg.LinuxKernelModulePkg:
answer = "acquired package info from linux kernel module files"
case pkg.NixPkg: case pkg.NixPkg:
answer = "acquired package info from nix store path" answer = "acquired package info from nix store path"
default: default:

View File

@ -199,6 +199,22 @@ func Test_SourceInfo(t *testing.T) {
"from rebar3 or mix manifest file", "from rebar3 or mix manifest file",
}, },
}, },
{
input: pkg.Package{
Type: pkg.LinuxKernelPkg,
},
expected: []string{
"from linux kernel archive",
},
},
{
input: pkg.Package{
Type: pkg.LinuxKernelModulePkg,
},
expected: []string{
"from linux kernel module files",
},
},
{ {
input: pkg.Package{ input: pkg.Package{
Type: pkg.NixPkg, Type: pkg.NixPkg,

View File

@ -89,7 +89,7 @@
} }
}, },
"schema": { "schema": {
"version": "7.1.2", "version": "7.1.3",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.2.json" "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.3.json"
} }
} }

View File

@ -185,7 +185,7 @@
} }
}, },
"schema": { "schema": {
"version": "7.1.2", "version": "7.1.3",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.2.json" "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.3.json"
} }
} }

View File

@ -112,7 +112,7 @@
} }
}, },
"schema": { "schema": {
"version": "7.1.2", "version": "7.1.3",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.2.json" "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.3.json"
} }
} }

View File

@ -23,6 +23,7 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/haskell" "github.com/anchore/syft/syft/pkg/cataloger/haskell"
"github.com/anchore/syft/syft/pkg/cataloger/java" "github.com/anchore/syft/syft/pkg/cataloger/java"
"github.com/anchore/syft/syft/pkg/cataloger/javascript" "github.com/anchore/syft/syft/pkg/cataloger/javascript"
"github.com/anchore/syft/syft/pkg/cataloger/kernel"
"github.com/anchore/syft/syft/pkg/cataloger/nix" "github.com/anchore/syft/syft/pkg/cataloger/nix"
"github.com/anchore/syft/syft/pkg/cataloger/php" "github.com/anchore/syft/syft/pkg/cataloger/php"
"github.com/anchore/syft/syft/pkg/cataloger/portage" "github.com/anchore/syft/syft/pkg/cataloger/portage"
@ -55,6 +56,7 @@ func ImageCatalogers(cfg Config) []pkg.Cataloger {
nix.NewStoreCataloger(), nix.NewStoreCataloger(),
sbom.NewSBOMCataloger(), sbom.NewSBOMCataloger(),
binary.NewCataloger(), binary.NewCataloger(),
kernel.NewLinuxKernelCataloger(cfg.Kernel()),
}, cfg.Catalogers) }, cfg.Catalogers)
} }
@ -88,6 +90,7 @@ func DirectoryCatalogers(cfg Config) []pkg.Cataloger {
binary.NewCataloger(), binary.NewCataloger(),
elixir.NewMixLockCataloger(), elixir.NewMixLockCataloger(),
erlang.NewRebarLockCataloger(), erlang.NewRebarLockCataloger(),
kernel.NewLinuxKernelCataloger(cfg.Kernel()),
nix.NewStoreCataloger(), nix.NewStoreCataloger(),
}, cfg.Catalogers) }, cfg.Catalogers)
} }
@ -126,6 +129,7 @@ func AllCatalogers(cfg Config) []pkg.Cataloger {
binary.NewCataloger(), binary.NewCataloger(),
elixir.NewMixLockCataloger(), elixir.NewMixLockCataloger(),
erlang.NewRebarLockCataloger(), erlang.NewRebarLockCataloger(),
kernel.NewLinuxKernelCataloger(cfg.Kernel()),
nix.NewStoreCataloger(), nix.NewStoreCataloger(),
}, cfg.Catalogers) }, cfg.Catalogers)
} }

View File

@ -3,11 +3,15 @@ package cataloger
import ( import (
"github.com/anchore/syft/syft/pkg/cataloger/golang" "github.com/anchore/syft/syft/pkg/cataloger/golang"
"github.com/anchore/syft/syft/pkg/cataloger/java" "github.com/anchore/syft/syft/pkg/cataloger/java"
"github.com/anchore/syft/syft/pkg/cataloger/kernel"
) )
// TODO: these field naming vs helper function naming schemes are inconsistent.
type Config struct { type Config struct {
Search SearchConfig Search SearchConfig
Golang golang.GoCatalogerOpts Golang golang.GoCatalogerOpts
LinuxKernel kernel.LinuxCatalogerConfig
Catalogers []string Catalogers []string
Parallelism int Parallelism int
} }
@ -16,6 +20,7 @@ func DefaultConfig() Config {
return Config{ return Config{
Search: DefaultSearchConfig(), Search: DefaultSearchConfig(),
Parallelism: 1, Parallelism: 1,
LinuxKernel: kernel.DefaultLinuxCatalogerConfig(),
} }
} }
@ -29,3 +34,7 @@ func (c Config) Java() java.Config {
func (c Config) Go() golang.GoCatalogerOpts { func (c Config) Go() golang.GoCatalogerOpts {
return c.Golang return c.Golang
} }
func (c Config) Kernel() kernel.LinuxCatalogerConfig {
return c.LinuxKernel
}

View File

@ -0,0 +1,127 @@
/*
Package kernel provides a concrete Cataloger implementation for linux kernel and module files.
*/
package kernel
import (
"github.com/hashicorp/go-multierror"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
"github.com/anchore/syft/syft/source"
)
var _ pkg.Cataloger = (*LinuxKernelCataloger)(nil)
type LinuxCatalogerConfig struct {
CatalogModules bool
}
type LinuxKernelCataloger struct {
cfg LinuxCatalogerConfig
}
func DefaultLinuxCatalogerConfig() LinuxCatalogerConfig {
return LinuxCatalogerConfig{
CatalogModules: true,
}
}
var kernelArchiveGlobs = []string{
"**/kernel",
"**/kernel-*",
"**/vmlinux",
"**/vmlinux-*",
"**/vmlinuz",
"**/vmlinuz-*",
}
var kernelModuleGlobs = []string{
"**/lib/modules/**/*.ko",
}
// NewLinuxKernelCataloger returns a new kernel files cataloger object.
func NewLinuxKernelCataloger(cfg LinuxCatalogerConfig) *LinuxKernelCataloger {
return &LinuxKernelCataloger{
cfg: cfg,
}
}
func (l LinuxKernelCataloger) Name() string {
return "linux-kernel-cataloger"
}
func (l LinuxKernelCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
var allPackages []pkg.Package
var allRelationships []artifact.Relationship
var errs error
kernelPackages, kernelRelationships, err := generic.NewCataloger(l.Name()).WithParserByGlobs(parseLinuxKernelFile, kernelArchiveGlobs...).Catalog(resolver)
if err != nil {
errs = multierror.Append(errs, err)
}
allRelationships = append(allRelationships, kernelRelationships...)
allPackages = append(allPackages, kernelPackages...)
if l.cfg.CatalogModules {
modulePackages, moduleRelationships, err := generic.NewCataloger(l.Name()).WithParserByGlobs(parseLinuxKernelModuleFile, kernelModuleGlobs...).Catalog(resolver)
if err != nil {
errs = multierror.Append(errs, err)
}
allPackages = append(allPackages, modulePackages...)
moduleToKernelRelationships := createKernelToModuleRelationships(kernelPackages, modulePackages)
allRelationships = append(allRelationships, moduleRelationships...)
allRelationships = append(allRelationships, moduleToKernelRelationships...)
}
return allPackages, allRelationships, errs
}
func createKernelToModuleRelationships(kernelPackages, modulePackages []pkg.Package) []artifact.Relationship {
// organize kernel and module packages by kernel version
kernelPackagesByVersion := make(map[string][]*pkg.Package)
for idx, p := range kernelPackages {
kernelPackagesByVersion[p.Version] = append(kernelPackagesByVersion[p.Version], &kernelPackages[idx])
}
modulesByKernelVersion := make(map[string][]*pkg.Package)
for idx, p := range modulePackages {
m, ok := p.Metadata.(pkg.LinuxKernelModuleMetadata)
if !ok {
log.Debug("linux-kernel-module package found without metadata: %s@%s", p.Name, p.Version)
continue
}
modulesByKernelVersion[m.KernelVersion] = append(modulesByKernelVersion[m.KernelVersion], &modulePackages[idx])
}
// create relationships between kernel and modules: [module] --(depends on)--> [kernel]
// since we try to use singular directions for relationships, we'll use "dependency of" here instead:
// [kernel] --(dependency of)--> [module]
var moduleToKernelRelationships []artifact.Relationship
for kernelVersion, modules := range modulesByKernelVersion {
kps, ok := kernelPackagesByVersion[kernelVersion]
if !ok {
// it's ok if there is a module that has no installed kernel...
continue
}
// we don't know which kernel is the "right" one, so we'll create a relationship for each one
for _, kp := range kps {
for _, mp := range modules {
moduleToKernelRelationships = append(moduleToKernelRelationships, artifact.Relationship{
// note: relationships should have Package objects, not pointers
From: *kp,
To: *mp,
Type: artifact.DependencyOfRelationship,
})
}
}
}
return moduleToKernelRelationships
}

View File

@ -0,0 +1,92 @@
package kernel
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 Test_JavascriptCataloger(t *testing.T) {
kernelPkg := pkg.Package{
Name: "linux-kernel",
Version: "6.2.9-200.fc37.x86_64",
FoundBy: "linux-kernel-cataloger",
Locations: source.NewLocationSet(
source.NewVirtualLocation(
"/lib/modules/6.2.9-200.fc37.x86_64/vmlinuz",
"/lib/modules/6.2.9-200.fc37.x86_64/vmlinuz",
),
),
Type: pkg.LinuxKernelPkg,
PURL: "pkg:generic/linux-kernel@6.2.9-200.fc37.x86_64",
MetadataType: pkg.LinuxKernelMetadataType,
Metadata: pkg.LinuxKernelMetadata{
Name: "",
Architecture: "x86",
Version: "6.2.9-200.fc37.x86_64",
ExtendedVersion: "6.2.9-200.fc37.x86_64 (mockbuild@bkernel02.iad2.fedoraproject.org) #1 SMP PREEMPT_DYNAMIC Thu Mar 30 22:31:57 UTC 2023",
BuildTime: "",
Author: "",
Format: "bzImage",
RWRootFS: false,
SwapDevice: 0,
RootDevice: 0,
VideoMode: "Video mode 65535",
},
}
kernelModulePkg := pkg.Package{
Name: "fsa4480",
Version: "",
FoundBy: "linux-kernel-cataloger",
Locations: source.NewLocationSet(
source.NewVirtualLocation("/lib/modules/6.2.9-200.fc37.x86_64/kernel/drivers/usb/typec/mux/fsa4480.ko",
"/lib/modules/6.2.9-200.fc37.x86_64/kernel/drivers/usb/typec/mux/fsa4480.ko",
),
),
Licenses: []string{
"GPL v2",
},
Type: pkg.LinuxKernelModulePkg,
PURL: "pkg:generic/fsa4480",
MetadataType: pkg.LinuxKernelModuleMetadataType,
Metadata: pkg.LinuxKernelModuleMetadata{
Name: "fsa4480",
Version: "",
SourceVersion: "",
License: "GPL v2",
Path: "/lib/modules/6.2.9-200.fc37.x86_64/kernel/drivers/usb/typec/mux/fsa4480.ko",
Description: "ON Semiconductor FSA4480 driver",
KernelVersion: "6.2.9-200.fc37.x86_64",
VersionMagic: "6.2.9-200.fc37.x86_64 SMP preempt mod_unload ",
Parameters: map[string]pkg.LinuxKernelModuleParameter{},
},
}
expectedPkgs := []pkg.Package{
kernelPkg,
kernelModulePkg,
}
expectedRelationships := []artifact.Relationship{
{
From: kernelPkg,
To: kernelModulePkg,
Type: artifact.DependencyOfRelationship,
},
}
pkgtest.NewCatalogTester().
WithImageResolver(t, "image-kernel-and-modules").
IgnoreLocationLayer().
Expects(expectedPkgs, expectedRelationships).
TestCataloger(t,
NewLinuxKernelCataloger(
LinuxCatalogerConfig{
CatalogModules: true,
},
),
)
}

View File

@ -0,0 +1,71 @@
package kernel
import (
"strings"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
const linuxKernelPackageName = "linux-kernel"
func newLinuxKernelPackage(metadata pkg.LinuxKernelMetadata, locations ...source.Location) pkg.Package {
p := pkg.Package{
Name: linuxKernelPackageName,
Version: metadata.Version,
Locations: source.NewLocationSet(locations...),
PURL: packageURL(linuxKernelPackageName, metadata.Version),
Type: pkg.LinuxKernelPkg,
MetadataType: pkg.LinuxKernelMetadataType,
Metadata: metadata,
}
p.SetID()
return p
}
func newLinuxKernelModulePackage(metadata pkg.LinuxKernelModuleMetadata, locations ...source.Location) pkg.Package {
var licenses []string
if metadata.License != "" {
licenses = []string{metadata.License}
} else {
licenses = []string{}
}
p := pkg.Package{
Name: metadata.Name,
Version: metadata.Version,
Locations: source.NewLocationSet(locations...),
Licenses: licenses,
PURL: packageURL(metadata.Name, metadata.Version),
Type: pkg.LinuxKernelModulePkg,
MetadataType: pkg.LinuxKernelModuleMetadataType,
Metadata: metadata,
}
p.SetID()
return p
}
// packageURL returns the PURL for the specific Kernel package (see https://github.com/package-url/purl-spec)
func packageURL(name, version string) string {
var namespace string
fields := strings.SplitN(name, "/", 2)
if len(fields) > 1 {
namespace = fields[0]
name = fields[1]
}
return packageurl.NewPackageURL(
packageurl.TypeGeneric,
namespace,
name,
version,
nil,
"",
).ToString()
}

View File

@ -0,0 +1,92 @@
package kernel
import (
"fmt"
"strconv"
"strings"
"github.com/deitch/magic/pkg/magic"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
"github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader"
"github.com/anchore/syft/syft/source"
)
const linuxKernelMagicName = "Linux kernel"
func parseLinuxKernelFile(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
unionReader, err := unionreader.GetUnionReader(reader)
if err != nil {
return nil, nil, fmt.Errorf("unable to get union reader for file: %w", err)
}
magicType, err := magic.GetType(unionReader)
if err != nil {
return nil, nil, fmt.Errorf("unable to get magic type for file: %w", err)
}
if len(magicType) < 1 || magicType[0] != linuxKernelMagicName {
return nil, nil, nil
}
metadata := parseLinuxKernelMetadata(magicType)
if metadata.Version == "" {
return nil, nil, nil
}
return []pkg.Package{
newLinuxKernelPackage(
metadata,
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
),
}, nil, nil
}
func parseLinuxKernelMetadata(magicType []string) (p pkg.LinuxKernelMetadata) {
// Linux kernel x86 boot executable bzImage,
// version 5.10.121-linuxkit (root@buildkitsandbox) #1 SMP Fri Dec 2 10:35:42 UTC 2022,
// RO-rootFS,
// swap_dev 0XA,
// Normal VGA
for _, t := range magicType {
switch {
case strings.HasPrefix(t, "x86 "):
p.Architecture = "x86"
case strings.Contains(t, "ARM64 "):
p.Architecture = "arm64"
case strings.Contains(t, "ARM "):
p.Architecture = "arm"
case t == "bzImage":
p.Format = "bzImage"
case t == "zImage":
p.Format = "zImage"
case strings.HasPrefix(t, "version "):
p.ExtendedVersion = strings.TrimPrefix(t, "version ")
fields := strings.Fields(p.ExtendedVersion)
if len(fields) > 0 {
p.Version = fields[0]
}
case strings.Contains(t, "rootFS") && strings.HasPrefix(t, "RW-"):
p.RWRootFS = true
case strings.HasPrefix(t, "swap_dev "):
swapDevStr := strings.TrimPrefix(t, "swap_dev ")
swapDev, err := strconv.ParseInt(swapDevStr, 16, 32)
if err != nil {
log.Warnf("unable to parse swap device: %s", err)
continue
}
p.SwapDevice = int(swapDev)
case strings.HasPrefix(t, "root_dev "):
rootDevStr := strings.TrimPrefix(t, "root_dev ")
rootDev, err := strconv.ParseInt(rootDevStr, 16, 32)
if err != nil {
log.Warnf("unable to parse root device: %s", err)
continue
}
p.SwapDevice = int(rootDev)
case strings.Contains(t, "VGA") || strings.Contains(t, "Video"):
p.VideoMode = t
}
}
return p
}

View File

@ -0,0 +1,157 @@
package kernel
import (
"debug/elf"
"fmt"
"strings"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
"github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader"
"github.com/anchore/syft/syft/source"
)
const modinfoName = ".modinfo"
func parseLinuxKernelModuleFile(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
unionReader, err := unionreader.GetUnionReader(reader)
if err != nil {
return nil, nil, fmt.Errorf("unable to get union reader for file: %w", err)
}
metadata, err := parseLinuxKernelModuleMetadata(unionReader)
if err != nil {
return nil, nil, fmt.Errorf("unable to parse kernel module metadata: %w", err)
}
if metadata == nil || metadata.KernelVersion == "" {
return nil, nil, nil
}
metadata.Path = reader.Location.RealPath
return []pkg.Package{
newLinuxKernelModulePackage(
*metadata,
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
),
}, nil, nil
}
func parseLinuxKernelModuleMetadata(r unionreader.UnionReader) (p *pkg.LinuxKernelModuleMetadata, err error) {
// filename: /lib/modules/5.15.0-1031-aws/kernel/zfs/zzstd.ko
// version: 1.4.5a
// license: Dual BSD/GPL
// description: ZSTD Compression for ZFS
// srcversion: F1F818A6E016499AB7F826E
// depends: spl
// retpoline: Y
// name: zzstd
// vermagic: 5.15.0-1031-aws SMP mod_unload modversions
// sig_id: PKCS#7
// signer: Build time autogenerated kernel key
// sig_key: 49:A9:55:87:90:5B:33:41:AF:C0:A7:BE:2A:71:6C:D2:CA:34:E0:AE
// sig_hashalgo: sha512
//
// OR
//
// filename: /home/ubuntu/eve/rootfs/lib/modules/5.10.121-linuxkit/kernel/drivers/net/wireless/realtek/rtl8821cu/8821cu.ko
// version: v5.4.1_28754.20180921_COEX20180712-3232
// author: Realtek Semiconductor Corp.
// description: Realtek Wireless Lan Driver
// license: GPL
// srcversion: 960CCC648A0E0369171A2C9
// depends: cfg80211
// retpoline: Y
// name: 8821cu
// vermagic: 5.10.121-linuxkit SMP mod_unload
p = &pkg.LinuxKernelModuleMetadata{
Parameters: make(map[string]pkg.LinuxKernelModuleParameter),
}
f, err := elf.NewFile(r)
if err != nil {
return nil, err
}
defer f.Close()
modinfo := f.Section(modinfoName)
if modinfo == nil {
return nil, fmt.Errorf("no section %s", modinfoName)
}
b, err := modinfo.Data()
if err != nil {
return nil, fmt.Errorf("error reading secion %s: %w", modinfoName, err)
}
var (
entry []byte
)
for _, b2 := range b {
if b2 == 0 {
if err := addLinuxKernelModuleEntry(p, entry); err != nil {
return nil, fmt.Errorf("error parsing entry %s: %w", string(entry), err)
}
entry = []byte{}
continue
}
entry = append(entry, b2)
}
if err := addLinuxKernelModuleEntry(p, entry); err != nil {
return nil, fmt.Errorf("error parsing entry %s: %w", string(entry), err)
}
return p, nil
}
func addLinuxKernelModuleEntry(k *pkg.LinuxKernelModuleMetadata, entry []byte) error {
if len(entry) == 0 {
return nil
}
var key, value string
parts := strings.SplitN(string(entry), "=", 2)
if len(parts) > 0 {
key = parts[0]
}
if len(parts) > 1 {
value = parts[1]
}
switch key {
case "version":
k.Version = value
case "license":
k.License = value
case "author":
k.Author = value
case "name":
k.Name = value
case "vermagic":
k.VersionMagic = value
fields := strings.Fields(value)
if len(fields) > 0 {
k.KernelVersion = fields[0]
}
case "srcversion":
k.SourceVersion = value
case "description":
k.Description = value
case "parm":
parts := strings.SplitN(value, ":", 2)
if len(parts) != 2 {
return fmt.Errorf("invalid parm entry: %s", value)
}
if m, ok := k.Parameters[parts[0]]; !ok {
k.Parameters[parts[0]] = pkg.LinuxKernelModuleParameter{Description: parts[1]}
} else {
m.Description = parts[1]
}
case "parmtype":
parts := strings.SplitN(value, ":", 2)
if len(parts) != 2 {
return fmt.Errorf("invalid parmtype entry: %s", value)
}
if m, ok := k.Parameters[parts[0]]; !ok {
k.Parameters[parts[0]] = pkg.LinuxKernelModuleParameter{Type: parts[1]}
} else {
m.Type = parts[1]
}
}
return nil
}

View File

@ -0,0 +1,17 @@
FROM fedora:37@sha256:3f987b7657e944cf87a129cc262982d4f80e38bd98f7db313ccaf90ca7069dd2
RUN dnf install 'dnf-command(download)' cpio xz -y
RUN dnf download kernel-core kernel-modules-core -y
RUN rpm2cpio kernel-core-*.rpm | cpio -t && \
rpm2cpio kernel-core-*.rpm | cpio -idmv ./lib/modules/6.2.9-200.fc37.x86_64/vmlinuz
RUN rpm2cpio kernel-modules-core-*.rpm | cpio -t && \
rpm2cpio kernel-modules-core-*.rpm | cpio -idmv ./lib/modules/6.2.9-200.fc37.x86_64/kernel/drivers/usb/typec/mux/fsa4480.ko.xz
RUN unxz /lib/modules/6.2.9-200.fc37.x86_64/kernel/drivers/usb/typec/mux/fsa4480.ko.xz
FROM scratch
COPY --from=0 /lib/modules/6.2.9-200.fc37.x86_64/vmlinuz /lib/modules/6.2.9-200.fc37.x86_64/vmlinuz
COPY --from=0 /lib/modules/6.2.9-200.fc37.x86_64/kernel/drivers/usb/typec/mux/fsa4480.ko /lib/modules/6.2.9-200.fc37.x86_64/kernel/drivers/usb/typec/mux/fsa4480.ko

View File

@ -0,0 +1,34 @@
package pkg
// LinuxKernelMetadata represents all captured data for a Linux kernel
type LinuxKernelMetadata struct {
Name string `mapstructure:"name" json:"name" cyclonedx:"name"`
Architecture string `mapstructure:"architecture" json:"architecture" cyclonedx:"architecture"`
Version string `mapstructure:"version" json:"version" cyclonedx:"version"`
ExtendedVersion string `mapstructure:"extendedVersion" json:"extendedVersion,omitempty" cyclonedx:"extendedVersion"`
BuildTime string `mapstructure:"buildTime" json:"buildTime,omitempty" cyclonedx:"buildTime"`
Author string `mapstructure:"author" json:"author,omitempty" cyclonedx:"author"`
Format string `mapstructure:"format" json:"format,omitempty" cyclonedx:"format"`
RWRootFS bool `mapstructure:"rwRootFS" json:"rwRootFS,omitempty" cyclonedx:"rwRootFS"`
SwapDevice int `mapstructure:"swapDevice" json:"swapDevice,omitempty" cyclonedx:"swapDevice"`
RootDevice int `mapstructure:"rootDevice" json:"rootDevice,omitempty" cyclonedx:"rootDevice"`
VideoMode string `mapstructure:"videoMode" json:"videoMode,omitempty" cyclonedx:"videoMode"`
}
type LinuxKernelModuleMetadata struct {
Name string `mapstructure:"name" json:"name,omitempty" cyclonedx:"name"`
Version string `mapstructure:"version" json:"version,omitempty" cyclonedx:"version"`
SourceVersion string `mapstructure:"sourceVersion" json:"sourceVersion,omitempty" cyclonedx:"sourceVersion"`
Path string `mapstructure:"path" json:"path,omitempty" cyclonedx:"path"`
Description string `mapstructure:"description" json:"description,omitempty" cyclonedx:"description"`
Author string `mapstructure:"author" json:"author,omitempty" cyclonedx:"author"`
License string `mapstructure:"license" json:"license,omitempty" cyclonedx:"license"`
KernelVersion string `mapstructure:"kernelVersion" json:"kernelVersion,omitempty" cyclonedx:"kernelVersion"`
VersionMagic string `mapstructure:"versionMagic" json:"versionMagic,omitempty" cyclonedx:"versionMagic"`
Parameters map[string]LinuxKernelModuleParameter `mapstructure:"parameters" json:"parameters,omitempty" cyclonedx:"parameters"`
}
type LinuxKernelModuleParameter struct {
Type string `mapstructure:"type" json:"type,omitempty" cyclonedx:"type"`
Description string `mapstructure:"description" json:"description,omitempty" cyclonedx:"description"`
}

View File

@ -26,6 +26,8 @@ const (
HackageMetadataType MetadataType = "HackageMetadataType" HackageMetadataType MetadataType = "HackageMetadataType"
JavaMetadataType MetadataType = "JavaMetadata" JavaMetadataType MetadataType = "JavaMetadata"
KbPackageMetadataType MetadataType = "KbPackageMetadata" KbPackageMetadataType MetadataType = "KbPackageMetadata"
LinuxKernelMetadataType MetadataType = "LinuxKernelMetadata"
LinuxKernelModuleMetadataType MetadataType = "LinuxKernelModuleMetadata"
MixLockMetadataType MetadataType = "MixLockMetadataType" MixLockMetadataType MetadataType = "MixLockMetadataType"
NixStoreMetadataType MetadataType = "NixStoreMetadata" NixStoreMetadataType MetadataType = "NixStoreMetadata"
NpmPackageJSONMetadataType MetadataType = "NpmPackageJsonMetadata" NpmPackageJSONMetadataType MetadataType = "NpmPackageJsonMetadata"
@ -55,6 +57,8 @@ var AllMetadataTypes = []MetadataType{
HackageMetadataType, HackageMetadataType,
JavaMetadataType, JavaMetadataType,
KbPackageMetadataType, KbPackageMetadataType,
LinuxKernelMetadataType,
LinuxKernelModuleMetadataType,
MixLockMetadataType, MixLockMetadataType,
NixStoreMetadataType, NixStoreMetadataType,
NpmPackageJSONMetadataType, NpmPackageJSONMetadataType,
@ -84,6 +88,8 @@ var MetadataTypeByName = map[MetadataType]reflect.Type{
HackageMetadataType: reflect.TypeOf(HackageMetadata{}), HackageMetadataType: reflect.TypeOf(HackageMetadata{}),
JavaMetadataType: reflect.TypeOf(JavaMetadata{}), JavaMetadataType: reflect.TypeOf(JavaMetadata{}),
KbPackageMetadataType: reflect.TypeOf(KbPackageMetadata{}), KbPackageMetadataType: reflect.TypeOf(KbPackageMetadata{}),
LinuxKernelMetadataType: reflect.TypeOf(LinuxKernelMetadata{}),
LinuxKernelModuleMetadataType: reflect.TypeOf(LinuxKernelModuleMetadata{}),
MixLockMetadataType: reflect.TypeOf(MixLockMetadata{}), MixLockMetadataType: reflect.TypeOf(MixLockMetadata{}),
NixStoreMetadataType: reflect.TypeOf(NixStoreMetadata{}), NixStoreMetadataType: reflect.TypeOf(NixStoreMetadata{}),
NpmPackageJSONMetadataType: reflect.TypeOf(NpmPackageJSONMetadata{}), NpmPackageJSONMetadataType: reflect.TypeOf(NpmPackageJSONMetadata{}),

View File

@ -26,6 +26,8 @@ const (
JavaPkg Type = "java-archive" JavaPkg Type = "java-archive"
JenkinsPluginPkg Type = "jenkins-plugin" JenkinsPluginPkg Type = "jenkins-plugin"
KbPkg Type = "msrc-kb" KbPkg Type = "msrc-kb"
LinuxKernelPkg Type = "linux-kernel"
LinuxKernelModulePkg Type = "linux-kernel-module"
NixPkg Type = "nix" NixPkg Type = "nix"
NpmPkg Type = "npm" NpmPkg Type = "npm"
PhpComposerPkg Type = "php-composer" PhpComposerPkg Type = "php-composer"
@ -52,6 +54,8 @@ var AllPkgs = []Type{
JavaPkg, JavaPkg,
JenkinsPluginPkg, JenkinsPluginPkg,
KbPkg, KbPkg,
LinuxKernelPkg,
LinuxKernelModulePkg,
NixPkg, NixPkg,
NpmPkg, NpmPkg,
PhpComposerPkg, PhpComposerPkg,
@ -88,6 +92,10 @@ func (t Type) PackageURLType() string {
return packageurl.TypeHackage return packageurl.TypeHackage
case JavaPkg, JenkinsPluginPkg: case JavaPkg, JenkinsPluginPkg:
return packageurl.TypeMaven return packageurl.TypeMaven
case LinuxKernelPkg:
return "generic/linux-kernel"
case LinuxKernelModulePkg:
return packageurl.TypeGeneric
case PhpComposerPkg: case PhpComposerPkg:
return packageurl.TypeComposer return packageurl.TypeComposer
case PythonPkg: case PythonPkg:
@ -114,7 +122,11 @@ func TypeFromPURL(p string) Type {
return UnknownPkg return UnknownPkg
} }
return TypeByName(purl.Type) ptype := purl.Type
if ptype == "generic" {
ptype = purl.Name
}
return TypeByName(ptype)
} }
func TypeByName(name string) Type { func TypeByName(name string) Type {
@ -155,6 +167,10 @@ func TypeByName(name string) Type {
return PortagePkg return PortagePkg
case packageurl.TypeHex: case packageurl.TypeHex:
return HexPkg return HexPkg
case "linux-kernel":
return LinuxKernelPkg
case "linux-kernel-module":
return LinuxKernelModulePkg
case "nix": case "nix":
return NixPkg return NixPkg
default: default:

View File

@ -83,6 +83,10 @@ func TestTypeFromPURL(t *testing.T) {
purl: "pkg:hex/hpax/hpax@0.1.1", purl: "pkg:hex/hpax/hpax@0.1.1",
expected: HexPkg, expected: HexPkg,
}, },
{
purl: "pkg:generic/linux-kernel@5.10.15",
expected: LinuxKernelPkg,
},
{ {
purl: "pkg:nix/glibc@2.34?hash=h0cnbmfcn93xm5dg2x27ixhag1cwndga", purl: "pkg:nix/glibc@2.34?hash=h0cnbmfcn93xm5dg2x27ixhag1cwndga",
expected: NixPkg, expected: NixPkg,
@ -101,6 +105,7 @@ func TestTypeFromPURL(t *testing.T) {
expectedTypes.Remove(string(JenkinsPluginPkg)) expectedTypes.Remove(string(JenkinsPluginPkg))
expectedTypes.Remove(string(PortagePkg)) expectedTypes.Remove(string(PortagePkg))
expectedTypes.Remove(string(BinaryPkg)) expectedTypes.Remove(string(BinaryPkg))
expectedTypes.Remove(string(LinuxKernelModulePkg))
for _, test := range tests { for _, test := range tests {
t.Run(string(test.expected), func(t *testing.T) { t.Run(string(test.expected), func(t *testing.T) {

View File

@ -55,11 +55,7 @@ func TestAllFormatsConvertable(t *testing.T) {
for _, traitFn := range assertions { for _, traitFn := range assertions {
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
} }
if t.Failed() { logOutputOnFailure(t, cmd, stdout, stderr)
t.Log("STDOUT:\n", stdout)
t.Log("STDERR:\n", stderr)
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
}
}) })
} }
} }

View File

@ -2,7 +2,6 @@ package cli
import ( import (
"fmt" "fmt"
"strings"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -34,11 +33,7 @@ func TestAllFormatsExpressible(t *testing.T) {
for _, traitFn := range commonAssertions { for _, traitFn := range commonAssertions {
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
} }
if t.Failed() { logOutputOnFailure(t, cmd, stdout, stderr)
t.Log("STDOUT:\n", stdout)
t.Log("STDERR:\n", stderr)
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
}
}) })
} }
} }

View File

@ -40,11 +40,7 @@ func TestConvertCmd(t *testing.T) {
for _, traitFn := range assertions { for _, traitFn := range assertions {
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
} }
if t.Failed() { logOutputOnFailure(t, cmd, stdout, stderr)
t.Log("STDOUT:\n", stdout)
t.Log("STDERR:\n", stderr)
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
}
}) })
} }
} }

View File

@ -52,11 +52,7 @@ func TestValidCycloneDX(t *testing.T) {
for _, traitFn := range test.assertions { for _, traitFn := range test.assertions {
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
} }
if t.Failed() { logOutputOnFailure(t, cmd, stdout, stderr)
t.Log("STDOUT:\n", stdout)
t.Log("STDERR:\n", stderr)
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
}
validateCycloneDXJSON(t, stdout) validateCycloneDXJSON(t, stdout)
}) })
@ -95,11 +91,7 @@ func assertValidCycloneDX(tb testing.TB, stdout, stderr string, rc int) {
tb.Errorf("expected no validation failures for cyclonedx-cli but got rc=%d", rc) tb.Errorf("expected no validation failures for cyclonedx-cli but got rc=%d", rc)
} }
if tb.Failed() { logOutputOnFailure(tb, cmd, stdout, stderr)
tb.Log("STDOUT:\n", stdout)
tb.Log("STDERR:\n", stderr)
tb.Log("COMMAND:", strings.Join(cmd.Args, " "))
}
} }
// validate --input-format json --input-version v1_4 --input-file bom.json // validate --input-format json --input-version v1_4 --input-file bom.json
@ -134,9 +126,5 @@ func validateCycloneDXJSON(t *testing.T, stdout string) {
t.Errorf("expected no validation failures for cyclonedx-cli but found invalid BOM") t.Errorf("expected no validation failures for cyclonedx-cli but found invalid BOM")
} }
if t.Failed() { logOutputOnFailure(t, cmd, stdout, stderr)
t.Log("STDOUT:\n", stdout)
t.Log("STDERR:\n", stderr)
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
}
} }

View File

@ -2,7 +2,6 @@ package cli
import ( import (
"os/exec" "os/exec"
"strings"
"testing" "testing"
"time" "time"
) )
@ -37,10 +36,6 @@ func TestDirectoryScanCompletesWithinTimeout(t *testing.T) {
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
} }
if t.Failed() { logOutputOnFailure(t, cmd, stdout, stderr)
t.Log("STDOUT:\n", stdout)
t.Log("STDERR:\n", stderr)
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
}
} }

View File

@ -3,7 +3,6 @@ package cli
import ( import (
"fmt" "fmt"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
) )
@ -97,7 +96,7 @@ func TestPackagesCmdFlags(t *testing.T) {
name: "squashed-scope-flag", name: "squashed-scope-flag",
args: []string{"packages", "-o", "json", "-s", "squashed", coverageImage}, args: []string{"packages", "-o", "json", "-s", "squashed", coverageImage},
assertions: []traitAssertion{ assertions: []traitAssertion{
assertPackageCount(35), assertPackageCount(37),
assertSuccessfulReturnCode, assertSuccessfulReturnCode,
}, },
}, },
@ -214,7 +213,7 @@ func TestPackagesCmdFlags(t *testing.T) {
// the application config in the log matches that of what we expect to have been configured. // the application config in the log matches that of what we expect to have been configured.
assertInOutput("parallelism: 2"), assertInOutput("parallelism: 2"),
assertInOutput("parallelism=2"), assertInOutput("parallelism=2"),
assertPackageCount(35), assertPackageCount(37),
assertSuccessfulReturnCode, assertSuccessfulReturnCode,
}, },
}, },
@ -225,7 +224,7 @@ func TestPackagesCmdFlags(t *testing.T) {
// the application config in the log matches that of what we expect to have been configured. // the application config in the log matches that of what we expect to have been configured.
assertInOutput("parallelism: 1"), assertInOutput("parallelism: 1"),
assertInOutput("parallelism=1"), assertInOutput("parallelism=1"),
assertPackageCount(35), assertPackageCount(37),
assertSuccessfulReturnCode, assertSuccessfulReturnCode,
}, },
}, },
@ -239,7 +238,7 @@ func TestPackagesCmdFlags(t *testing.T) {
assertions: []traitAssertion{ assertions: []traitAssertion{
assertNotInOutput("secret_password"), assertNotInOutput("secret_password"),
assertNotInOutput("secret_key_path"), assertNotInOutput("secret_key_path"),
assertPackageCount(35), assertPackageCount(37),
assertSuccessfulReturnCode, assertSuccessfulReturnCode,
}, },
}, },
@ -251,11 +250,7 @@ func TestPackagesCmdFlags(t *testing.T) {
for _, traitFn := range test.assertions { for _, traitFn := range test.assertions {
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
} }
if t.Failed() { logOutputOnFailure(t, cmd, stdout, stderr)
t.Log("STDOUT:\n", stdout)
t.Log("STDERR:\n", stderr)
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
}
}) })
} }
} }
@ -337,11 +332,7 @@ func TestRegistryAuth(t *testing.T) {
for _, traitAssertionFn := range test.assertions { for _, traitAssertionFn := range test.assertions {
traitAssertionFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) traitAssertionFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
} }
if t.Failed() { logOutputOnFailure(t, cmd, stdout, stderr)
t.Log("STDOUT:\n", stdout)
t.Log("STDERR:\n", stderr)
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
}
}) })
} }
} }

View File

@ -1,7 +1,6 @@
package cli package cli
import ( import (
"strings"
"testing" "testing"
) )
@ -96,11 +95,7 @@ func TestPowerUserCmdFlags(t *testing.T) {
for _, traitFn := range test.assertions { for _, traitFn := range test.assertions {
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
} }
if t.Failed() { logOutputOnFailure(t, cmd, stdout, stderr)
t.Log("STDOUT:\n", stdout)
t.Log("STDERR:\n", stderr)
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
}
}) })
} }
} }

View File

@ -3,7 +3,6 @@ package cli
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"github.com/sergi/go-diff/diffmatchpatch" "github.com/sergi/go-diff/diffmatchpatch"
@ -109,11 +108,7 @@ func TestPersistentFlags(t *testing.T) {
for _, traitFn := range test.assertions { for _, traitFn := range test.assertions {
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
} }
if t.Failed() { logOutputOnFailure(t, cmd, stdout, stderr)
t.Log("STDOUT:\n", stdout)
t.Log("STDERR:\n", stderr)
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
}
}) })
} }
} }
@ -151,11 +146,7 @@ func TestLogFile(t *testing.T) {
for _, traitFn := range test.assertions { for _, traitFn := range test.assertions {
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
} }
if t.Failed() { logOutputOnFailure(t, cmd, stdout, stderr)
t.Log("STDOUT:\n", stdout)
t.Log("STDERR:\n", stderr)
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
}
}) })
} }
} }

View File

@ -3,6 +3,7 @@ package cli
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"flag"
"fmt" "fmt"
"io" "io"
"math" "math"
@ -21,6 +22,16 @@ import (
"github.com/anchore/stereoscope/pkg/imagetest" "github.com/anchore/stereoscope/pkg/imagetest"
) )
var showOutput = flag.Bool("show-output", false, "show stdout and stderr for failing tests")
func logOutputOnFailure(t testing.TB, cmd *exec.Cmd, stdout, stderr string) {
if t.Failed() && showOutput != nil && *showOutput {
t.Log("STDOUT:\n", stdout)
t.Log("STDERR:\n", stderr)
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
}
}
func runAndShow(t *testing.T, cmd *exec.Cmd) { func runAndShow(t *testing.T, cmd *exec.Cmd) {
t.Helper() t.Helper()

View File

@ -11,6 +11,20 @@ type testCase struct {
} }
var imageOnlyTestCases = []testCase{ var imageOnlyTestCases = []testCase{
{
name: "find kernel packages",
pkgType: pkg.LinuxKernelPkg,
pkgInfo: map[string]string{
"linux-kernel": "6.2.9-200.fc37.x86_64",
},
},
{
name: "find kernel module packages",
pkgType: pkg.LinuxKernelModulePkg,
pkgInfo: map[string]string{
"fsa4480": "",
},
},
{ {
name: "find gemspec packages", name: "find gemspec packages",
pkgType: pkg.GemPkg, pkgType: pkg.GemPkg,

View File

@ -221,6 +221,8 @@ func TestPkgCoverageDirectory(t *testing.T) {
observedPkgs.Remove(string(pkg.UnknownPkg)) observedPkgs.Remove(string(pkg.UnknownPkg))
definedPkgs.Remove(string(pkg.BinaryPkg)) definedPkgs.Remove(string(pkg.BinaryPkg))
definedPkgs.Remove(string(pkg.UnknownPkg)) definedPkgs.Remove(string(pkg.UnknownPkg))
definedPkgs.Remove(string(pkg.LinuxKernelPkg))
definedPkgs.Remove(string(pkg.LinuxKernelModulePkg))
// for directory scans we should not expect to see any of the following package types // for directory scans we should not expect to see any of the following package types
definedPkgs.Remove(string(pkg.KbPkg)) definedPkgs.Remove(string(pkg.KbPkg))

View File

@ -1,4 +1,21 @@
FROM fedora:37@sha256:3f987b7657e944cf87a129cc262982d4f80e38bd98f7db313ccaf90ca7069dd2
RUN dnf install 'dnf-command(download)' cpio xz -y
RUN dnf download kernel-core kernel-modules-core -y
RUN rpm2cpio kernel-core-*.rpm | cpio -t && \
rpm2cpio kernel-core-*.rpm | cpio -idmv ./lib/modules/6.2.9-200.fc37.x86_64/vmlinuz
RUN rpm2cpio kernel-modules-core-*.rpm | cpio -t && \
rpm2cpio kernel-modules-core-*.rpm | cpio -idmv ./lib/modules/6.2.9-200.fc37.x86_64/kernel/drivers/usb/typec/mux/fsa4480.ko.xz
RUN unxz /lib/modules/6.2.9-200.fc37.x86_64/kernel/drivers/usb/typec/mux/fsa4480.ko.xz
FROM scratch FROM scratch
COPY --from=0 /lib/modules/6.2.9-200.fc37.x86_64/vmlinuz /lib/modules/6.2.9-200.fc37.x86_64/vmlinuz
COPY --from=0 /lib/modules/6.2.9-200.fc37.x86_64/kernel/drivers/usb/typec/mux/fsa4480.ko /lib/modules/6.2.9-200.fc37.x86_64/kernel/drivers/usb/typec/mux/fsa4480.ko
COPY pkgs/ . COPY pkgs/ .
# we duplicate to show a package count difference between all-layers and squashed scopes # we duplicate to show a package count difference between all-layers and squashed scopes
COPY lib lib COPY lib lib