mirror of
https://github.com/project-zot/zot.git
synced 2026-06-16 20:38:08 +08:00
chore(trivy): update trivy version and enforce OCI compliant repo names in local image storage (#1068)
1. chore(trivy): update trivy library version The trivy team switched github.com/urfave/cli for viper so there are some other code changes as well. Since we don't use github.com/urfave/cli directly in our software we needed to add a tools.go in order for "go mod tidy" to not delete it. See this pattern explained in: - https://github.com/99designs/gqlgen#quick-start - https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module - https://github.com/go-modules-by-example/index/blob/master/010_tools/README.md#walk-through The jobs using "go get -u" have been updated to use "go install", since go get modifies the go.mod by upgrading some of the packages, but downgrading trivy to an older version with broken dependencies 2. fix(storage) Update local storage to ignore folder names not compliant with dist spec Also updated trivy to download the DB and cache results under the rootDir/_trivy folder 3. fix(s3): one of the s3 tests was missing the skipIt call This caused a failure when running locally without s3 being available 4. make sure the offline scanning is enabled, and zot only downloads the trivy DB on the regular schedule, and doesn't download the DB on every image scan ci: increase build and test timeout as tests are reaching the limit more often Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
This commit is contained in:
@@ -555,8 +555,13 @@ func TestRepoListWithNewestImage(t *testing.T) {
|
||||
ShouldBeGreaterThan,
|
||||
0,
|
||||
)
|
||||
// This really depends on the test data, but with the current test images it's CRITICAL
|
||||
So(vulnerabilities.MaxSeverity, ShouldEqual, "CRITICAL")
|
||||
if repo.Name == "zot-cve-test" {
|
||||
// This really depends on the test data, but with the current test image it's HIGH
|
||||
So(vulnerabilities.MaxSeverity, ShouldEqual, "HIGH")
|
||||
} else if repo.Name == "zot-test" {
|
||||
// This really depends on the test data, but with the current test image it's CRITICAL
|
||||
So(vulnerabilities.MaxSeverity, ShouldEqual, "CRITICAL")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
package trivy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/artifact"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/operation"
|
||||
"github.com/aquasecurity/trivy/pkg/flag"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
regTypes "github.com/google/go-containerregistry/pkg/v1/types"
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/extensions/search/common"
|
||||
@@ -24,51 +23,44 @@ import (
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
)
|
||||
|
||||
type trivyCtx struct {
|
||||
Input string
|
||||
Ctx *cli.Context
|
||||
}
|
||||
const dbRepository = "ghcr.io/aquasecurity/trivy-db"
|
||||
|
||||
// newTrivyContext set some trivy configuration value and return a context.
|
||||
func newTrivyContext(dir string) *trivyCtx {
|
||||
tCtx := &trivyCtx{}
|
||||
// getNewScanOptions sets trivy configuration values for our scans and returns them as
|
||||
// a trivy Options structure.
|
||||
func getNewScanOptions(dir string) *flag.Options {
|
||||
scanOptions := flag.Options{
|
||||
GlobalOptions: flag.GlobalOptions{
|
||||
CacheDir: dir,
|
||||
},
|
||||
ScanOptions: flag.ScanOptions{
|
||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||
OfflineScan: true,
|
||||
},
|
||||
VulnerabilityOptions: flag.VulnerabilityOptions{
|
||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
||||
},
|
||||
DBOptions: flag.DBOptions{
|
||||
DBRepository: dbRepository,
|
||||
SkipDBUpdate: true,
|
||||
},
|
||||
ReportOptions: flag.ReportOptions{
|
||||
Format: "table",
|
||||
Severities: []dbTypes.Severity{
|
||||
dbTypes.SeverityUnknown,
|
||||
dbTypes.SeverityLow,
|
||||
dbTypes.SeverityMedium,
|
||||
dbTypes.SeverityHigh,
|
||||
dbTypes.SeverityCritical,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app := &cli.App{}
|
||||
|
||||
flagSet := &flag.FlagSet{}
|
||||
|
||||
var cacheDir string
|
||||
|
||||
flagSet.StringVar(&cacheDir, "cache-dir", dir, "")
|
||||
|
||||
var vuln string
|
||||
|
||||
flagSet.StringVar(&vuln, "vuln-type", strings.Join([]string{types.VulnTypeOS, types.VulnTypeLibrary}, ","), "")
|
||||
|
||||
var severity string
|
||||
|
||||
flagSet.StringVar(&severity, "severity", strings.Join(dbTypes.SeverityNames, ","), "")
|
||||
|
||||
flagSet.StringVar(&tCtx.Input, "input", "", "")
|
||||
|
||||
var securityCheck string
|
||||
|
||||
flagSet.StringVar(&securityCheck, "security-checks", types.SecurityCheckVulnerability, "")
|
||||
|
||||
var reportFormat string
|
||||
|
||||
flagSet.StringVar(&reportFormat, "format", "table", "")
|
||||
|
||||
ctx := cli.NewContext(app, flagSet, nil)
|
||||
|
||||
tCtx.Ctx = ctx
|
||||
|
||||
return tCtx
|
||||
return &scanOptions
|
||||
}
|
||||
|
||||
type cveTrivyController struct {
|
||||
DefaultCveConfig *trivyCtx
|
||||
SubCveConfig map[string]*trivyCtx
|
||||
DefaultCveConfig *flag.Options
|
||||
SubCveConfig map[string]*flag.Options
|
||||
}
|
||||
|
||||
type Scanner struct {
|
||||
@@ -85,25 +77,27 @@ func NewScanner(storeController storage.StoreController,
|
||||
) *Scanner {
|
||||
cveController := cveTrivyController{}
|
||||
|
||||
subCveConfig := make(map[string]*trivyCtx)
|
||||
subCveConfig := make(map[string]*flag.Options)
|
||||
|
||||
if storeController.DefaultStore != nil {
|
||||
imageStore := storeController.DefaultStore
|
||||
|
||||
rootDir := imageStore.RootDir()
|
||||
|
||||
ctx := newTrivyContext(rootDir)
|
||||
cacheDir := path.Join(rootDir, "_trivy")
|
||||
opts := getNewScanOptions(cacheDir)
|
||||
|
||||
cveController.DefaultCveConfig = ctx
|
||||
cveController.DefaultCveConfig = opts
|
||||
}
|
||||
|
||||
if storeController.SubStore != nil {
|
||||
for route, storage := range storeController.SubStore {
|
||||
rootDir := storage.RootDir()
|
||||
|
||||
ctx := newTrivyContext(rootDir)
|
||||
cacheDir := path.Join(rootDir, "_trivy")
|
||||
opts := getNewScanOptions(cacheDir)
|
||||
|
||||
subCveConfig[route] = ctx
|
||||
subCveConfig[route] = opts
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,33 +113,58 @@ func NewScanner(storeController storage.StoreController,
|
||||
}
|
||||
}
|
||||
|
||||
func (scanner Scanner) getTrivyContext(image string) *trivyCtx {
|
||||
func (scanner Scanner) getTrivyOptions(image string) flag.Options {
|
||||
// Split image to get route prefix
|
||||
prefixName := common.GetRoutePrefix(image)
|
||||
|
||||
var tCtx *trivyCtx
|
||||
var opts flag.Options
|
||||
|
||||
var ok bool
|
||||
|
||||
var rootDir string
|
||||
|
||||
// Get corresponding CVE trivy config, if no sub cve config present that means its default
|
||||
tCtx, ok = scanner.cveController.SubCveConfig[prefixName]
|
||||
_, ok = scanner.cveController.SubCveConfig[prefixName]
|
||||
if ok {
|
||||
opts = *scanner.cveController.SubCveConfig[prefixName]
|
||||
|
||||
imgStore := scanner.storeController.SubStore[prefixName]
|
||||
|
||||
rootDir = imgStore.RootDir()
|
||||
} else {
|
||||
tCtx = scanner.cveController.DefaultCveConfig
|
||||
opts = *scanner.cveController.DefaultCveConfig
|
||||
|
||||
imgStore := scanner.storeController.DefaultStore
|
||||
|
||||
rootDir = imgStore.RootDir()
|
||||
}
|
||||
|
||||
tCtx.Input = path.Join(rootDir, image)
|
||||
opts.ScanOptions.Target = path.Join(rootDir, image)
|
||||
opts.ImageOptions.Input = path.Join(rootDir, image)
|
||||
|
||||
return tCtx
|
||||
return opts
|
||||
}
|
||||
|
||||
func (scanner Scanner) runTrivy(opts flag.Options) (types.Report, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
runner, err := artifact.NewRunner(ctx, opts)
|
||||
if err != nil {
|
||||
return types.Report{}, err
|
||||
}
|
||||
defer runner.Close(ctx)
|
||||
|
||||
report, err := runner.ScanImage(ctx, opts)
|
||||
if err != nil {
|
||||
return types.Report{}, err
|
||||
}
|
||||
|
||||
report, err = runner.Filter(ctx, opts, report)
|
||||
if err != nil {
|
||||
return types.Report{}, err
|
||||
}
|
||||
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func (scanner Scanner) IsImageFormatScannable(image string) (bool, error) {
|
||||
@@ -208,10 +227,9 @@ func (scanner Scanner) ScanImage(image string) (map[string]cvemodel.CVE, error)
|
||||
|
||||
scanner.log.Debug().Str("image", image).Msg("scanning image")
|
||||
|
||||
tCtx := scanner.getTrivyContext(image)
|
||||
|
||||
scanner.dbLock.Lock()
|
||||
report, err := artifact.TrivyImageRun(tCtx.Ctx)
|
||||
opts := scanner.getTrivyOptions(image)
|
||||
report, err := scanner.runTrivy(opts)
|
||||
scanner.dbLock.Unlock()
|
||||
|
||||
if err != nil { //nolint: wsl
|
||||
@@ -288,7 +306,7 @@ func (scanner Scanner) UpdateDB() error {
|
||||
defer scanner.dbLock.Unlock()
|
||||
|
||||
if scanner.storeController.DefaultStore != nil {
|
||||
dbDir := scanner.storeController.DefaultStore.RootDir()
|
||||
dbDir := path.Join(scanner.storeController.DefaultStore.RootDir(), "_trivy")
|
||||
|
||||
err := scanner.updateDB(dbDir)
|
||||
if err != nil {
|
||||
@@ -298,7 +316,9 @@ func (scanner Scanner) UpdateDB() error {
|
||||
|
||||
if scanner.storeController.SubStore != nil {
|
||||
for _, storage := range scanner.storeController.SubStore {
|
||||
err := scanner.updateDB(storage.RootDir())
|
||||
dbDir := path.Join(storage.RootDir(), "_trivy")
|
||||
|
||||
err := scanner.updateDB(dbDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -313,7 +333,7 @@ func (scanner Scanner) UpdateDB() error {
|
||||
func (scanner Scanner) updateDB(dbDir string) error {
|
||||
scanner.log.Debug().Msgf("Download Trivy DB to destination dir: %s", dbDir)
|
||||
|
||||
err := operation.DownloadDB("dev", dbDir, false, false, false)
|
||||
err := operation.DownloadDB("dev", dbDir, dbRepository, false, false, false)
|
||||
if err != nil {
|
||||
scanner.log.Error().Err(err).Msgf("Error downloading Trivy DB to destination dir: %s", dbDir)
|
||||
|
||||
|
||||
@@ -102,19 +102,23 @@ func TestMultipleStoragePath(t *testing.T) {
|
||||
img1 := "a/test/image1:tag1"
|
||||
img2 := "b/test/image2:tag2"
|
||||
|
||||
ctx := scanner.getTrivyContext(img0)
|
||||
So(ctx.Input, ShouldEqual, path.Join(firstStore.RootDir(), img0))
|
||||
opts := scanner.getTrivyOptions(img0)
|
||||
So(opts.ScanOptions.Target, ShouldEqual, path.Join(firstStore.RootDir(), img0))
|
||||
|
||||
ctx = scanner.getTrivyContext(img1)
|
||||
So(ctx.Input, ShouldEqual, path.Join(secondStore.RootDir(), img1))
|
||||
opts = scanner.getTrivyOptions(img1)
|
||||
So(opts.ScanOptions.Target, ShouldEqual, path.Join(secondStore.RootDir(), img1))
|
||||
|
||||
ctx = scanner.getTrivyContext(img2)
|
||||
So(ctx.Input, ShouldEqual, path.Join(thirdStore.RootDir(), img2))
|
||||
opts = scanner.getTrivyOptions(img2)
|
||||
So(opts.ScanOptions.Target, ShouldEqual, path.Join(thirdStore.RootDir(), img2))
|
||||
|
||||
generateTestImage(storeController, img0)
|
||||
generateTestImage(storeController, img1)
|
||||
generateTestImage(storeController, img2)
|
||||
|
||||
// Download DB since DB download on scan is disabled
|
||||
err = scanner.UpdateDB()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// Scanning image in default store
|
||||
cveMap, err := scanner.ScanImage(img0)
|
||||
So(err, ShouldBeNil)
|
||||
@@ -152,3 +156,65 @@ func TestMultipleStoragePath(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTrivyLibraryErrors(t *testing.T) {
|
||||
Convey("Test trivy API errors", t, func() {
|
||||
// Create temporary directory
|
||||
rootDir := t.TempDir()
|
||||
|
||||
err := test.CopyFiles("../../../../../test/data/zot-test", path.Join(rootDir, "zot-test"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
log := log.NewLogger("debug", "")
|
||||
metrics := monitoring.NewMetricsServer(false, log)
|
||||
|
||||
conf := config.New()
|
||||
conf.Extensions = &extconf.ExtensionConfig{}
|
||||
conf.Extensions.Lint = &extconf.LintConfig{}
|
||||
|
||||
// Create ImageStore
|
||||
store := local.NewImageStore(rootDir, false, storage.DefaultGCDelay, false, false, log, metrics, nil, nil)
|
||||
|
||||
storeController := storage.StoreController{}
|
||||
storeController.DefaultStore = store
|
||||
|
||||
repoDB, err := bolt.NewBoltDBWrapper(bolt.DBParameters{
|
||||
RootDir: rootDir,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = repodb.SyncRepoDB(repoDB, storeController, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
scanner := NewScanner(storeController, repoDB, log)
|
||||
|
||||
// Download DB since DB download on scan is disabled
|
||||
err = scanner.UpdateDB()
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
img := "zot-test:0.0.1"
|
||||
|
||||
// Scanning image with correct options
|
||||
opts := scanner.getTrivyOptions(img)
|
||||
_, err = scanner.runTrivy(opts)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// Scanning image with incorrect cache options
|
||||
// to trigger runner initialization errors
|
||||
opts.CacheOptions.CacheBackend = "redis://asdf!$%&!*)("
|
||||
_, err = scanner.runTrivy(opts)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
// Scanning image with invalid input to trigger a scanner error
|
||||
opts = scanner.getTrivyOptions("nonexisting_image:0.0.1")
|
||||
_, err = scanner.runTrivy(opts)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
// Scanning image with incorrect report options
|
||||
// to trigger report filtering errors
|
||||
opts = scanner.getTrivyOptions(img)
|
||||
opts.ReportOptions.IgnorePolicy = "invalid file path"
|
||||
_, err = scanner.runTrivy(opts)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user