fix(db): check schema version for image name only (#6410)

Signed-off-by: knqyf263 <knqyf263@gmail.com>
Co-authored-by: knqyf263 <knqyf263@gmail.com>
This commit is contained in:
DmitriyLewen
2024-04-02 17:22:43 +06:00
committed by GitHub
parent e75a90f2e5
commit 8baccd7909
8 changed files with 127 additions and 62 deletions

View File

@@ -9,6 +9,7 @@ import (
"sync" "sync"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/wire" "github.com/google/wire"
"github.com/samber/lo" "github.com/samber/lo"
"golang.org/x/xerrors" "golang.org/x/xerrors"
@@ -110,7 +111,8 @@ func (c Cache) ClearArtifacts() error {
} }
// DownloadDB downloads the DB // DownloadDB downloads the DB
func DownloadDB(ctx context.Context, appVersion, cacheDir, dbRepository string, quiet, skipUpdate bool, opt ftypes.RegistryOptions) error { func DownloadDB(ctx context.Context, appVersion, cacheDir string, dbRepository name.Reference, quiet, skipUpdate bool,
opt ftypes.RegistryOptions) error {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()

View File

@@ -4,9 +4,9 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote/transport" "github.com/google/go-containerregistry/pkg/v1/remote/transport"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"k8s.io/utils/clock" "k8s.io/utils/clock"
@@ -19,8 +19,13 @@ import (
) )
const ( const (
SchemaVersion = db.SchemaVersion
dbMediaType = "application/vnd.aquasec.trivy.db.layer.v1.tar+gzip" dbMediaType = "application/vnd.aquasec.trivy.db.layer.v1.tar+gzip"
defaultDBRepository = "ghcr.io/aquasecurity/trivy-db" )
var (
DefaultRepository = fmt.Sprintf("%s:%d", "ghcr.io/aquasecurity/trivy-db", db.SchemaVersion)
defaultRepository, _ = name.NewTag(DefaultRepository)
) )
// Operation defines the DB operations // Operation defines the DB operations
@@ -32,7 +37,7 @@ type Operation interface {
type options struct { type options struct {
artifact *oci.Artifact artifact *oci.Artifact
clock clock.Clock clock clock.Clock
dbRepository string dbRepository name.Reference
} }
// Option is a functional option // Option is a functional option
@@ -46,7 +51,7 @@ func WithOCIArtifact(art *oci.Artifact) Option {
} }
// WithDBRepository takes a dbRepository // WithDBRepository takes a dbRepository
func WithDBRepository(dbRepository string) Option { func WithDBRepository(dbRepository name.Reference) Option {
return func(opts *options) { return func(opts *options) {
opts.dbRepository = dbRepository opts.dbRepository = dbRepository
} }
@@ -72,19 +77,13 @@ type Client struct {
func NewClient(cacheDir string, quiet bool, opts ...Option) *Client { func NewClient(cacheDir string, quiet bool, opts ...Option) *Client {
o := &options{ o := &options{
clock: clock.RealClock{}, clock: clock.RealClock{},
dbRepository: defaultDBRepository, dbRepository: defaultRepository,
} }
for _, opt := range opts { for _, opt := range opts {
opt(o) opt(o)
} }
// Add the schema version as a tag if the tag doesn't exist.
// This is required for backward compatibility.
if !strings.Contains(o.dbRepository, ":") {
o.dbRepository = fmt.Sprintf("%s:%d", o.dbRepository, db.SchemaVersion)
}
return &Client{ return &Client{
options: o, options: o,
cacheDir: cacheDir, cacheDir: cacheDir,
@@ -195,7 +194,7 @@ func (c *Client) initOCIArtifact(opt types.RegistryOptions) (*oci.Artifact, erro
return c.artifact, nil return c.artifact, nil
} }
art, err := oci.NewArtifact(c.dbRepository, c.quiet, opt) art, err := oci.NewArtifact(c.dbRepository.String(), c.quiet, opt)
if err != nil { if err != nil {
var terr *transport.Error var terr *transport.Error
if errors.As(err, &terr) { if errors.As(err, &terr) {

View File

@@ -3,6 +3,7 @@ package analyzer_test
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/google/go-containerregistry/pkg/name"
"os" "os"
"sync" "sync"
"testing" "testing"
@@ -12,11 +13,11 @@ import (
"golang.org/x/sync/semaphore" "golang.org/x/sync/semaphore"
"golang.org/x/xerrors" "golang.org/x/xerrors"
xio "github.com/aquasecurity/trivy/pkg/x/io"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/javadb" "github.com/aquasecurity/trivy/pkg/javadb"
"github.com/aquasecurity/trivy/pkg/mapfs" "github.com/aquasecurity/trivy/pkg/mapfs"
xio "github.com/aquasecurity/trivy/pkg/x/io"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/imgconf/apk" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/imgconf/apk"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/jar" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/jar"
@@ -343,7 +344,10 @@ func TestAnalyzerGroup_AnalyzeFile(t *testing.T) {
Licenses: []string{"MIT"}, Licenses: []string{"MIT"},
Arch: "x86_64", Arch: "x86_64",
Digest: "sha1:cb2316a189ebee5282c4a9bd98794cc2477a74c6", Digest: "sha1:cb2316a189ebee5282c4a9bd98794cc2477a74c6",
InstalledFiles: []string{"lib/libc.musl-x86_64.so.1", "lib/ld-musl-x86_64.so.1"}, InstalledFiles: []string{
"lib/libc.musl-x86_64.so.1",
"lib/ld-musl-x86_64.so.1",
},
}, },
}, },
}, },
@@ -615,7 +619,9 @@ func TestAnalyzerGroup_PostAnalyze(t *testing.T) {
if tt.analyzerType == analyzer.TypeJar { if tt.analyzerType == analyzer.TypeJar {
// init java-trivy-db with skip update // init java-trivy-db with skip update
javadb.Init("./language/java/jar/testdata", "ghcr.io/aquasecurity/trivy-java-db", true, false, types.RegistryOptions{Insecure: false}) repo, err := name.NewTag(javadb.DefaultRepository)
require.NoError(t, err)
javadb.Init("./language/java/jar/testdata", repo, true, false, types.RegistryOptions{Insecure: false})
} }
ctx := context.Background() ctx := context.Background()

View File

@@ -2,6 +2,8 @@ package jar
import ( import (
"context" "context"
"github.com/google/go-containerregistry/pkg/name"
"github.com/stretchr/testify/require"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@@ -130,13 +132,15 @@ func Test_javaLibraryAnalyzer_Analyze(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
// init java-trivy-db with skip update // init java-trivy-db with skip update
javadb.Init("testdata", defaultJavaDBRepository, true, false, types.RegistryOptions{Insecure: false}) repo, err := name.NewTag(javadb.DefaultRepository)
require.NoError(t, err)
javadb.Init("testdata", repo, true, false, types.RegistryOptions{Insecure: false})
a := javaLibraryAnalyzer{} a := javaLibraryAnalyzer{}
ctx := context.Background() ctx := context.Background()
mfs := mapfs.New() mfs := mapfs.New()
err := mfs.MkdirAll(filepath.Dir(tt.inputFile), os.ModePerm) err = mfs.MkdirAll(filepath.Dir(tt.inputFile), os.ModePerm)
assert.NoError(t, err) assert.NoError(t, err)
err = mfs.WriteFile(tt.inputFile, tt.inputFile) err = mfs.WriteFile(tt.inputFile, tt.inputFile)
assert.NoError(t, err) assert.NoError(t, err)

View File

@@ -1,14 +1,17 @@
package flag package flag
import ( import (
"fmt"
"github.com/google/go-containerregistry/pkg/name"
"go.uber.org/zap"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/db"
"github.com/aquasecurity/trivy/pkg/javadb"
"github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/log"
) )
const defaultDBRepository = "ghcr.io/aquasecurity/trivy-db:2"
const defaultJavaDBRepository = "ghcr.io/aquasecurity/trivy-java-db:1"
var ( var (
ResetFlag = Flag[bool]{ ResetFlag = Flag[bool]{
Name: "reset", Name: "reset",
@@ -49,13 +52,13 @@ var (
DBRepositoryFlag = Flag[string]{ DBRepositoryFlag = Flag[string]{
Name: "db-repository", Name: "db-repository",
ConfigName: "db.repository", ConfigName: "db.repository",
Default: defaultDBRepository, Default: db.DefaultRepository,
Usage: "OCI repository to retrieve trivy-db from", Usage: "OCI repository to retrieve trivy-db from",
} }
JavaDBRepositoryFlag = Flag[string]{ JavaDBRepositoryFlag = Flag[string]{
Name: "java-db-repository", Name: "java-db-repository",
ConfigName: "db.java-repository", ConfigName: "db.java-repository",
Default: defaultJavaDBRepository, Default: javadb.DefaultRepository,
Usage: "OCI repository to retrieve trivy-java-db from", Usage: "OCI repository to retrieve trivy-java-db from",
} }
LightFlag = Flag[bool]{ LightFlag = Flag[bool]{
@@ -86,8 +89,8 @@ type DBOptions struct {
DownloadJavaDBOnly bool DownloadJavaDBOnly bool
SkipJavaDBUpdate bool SkipJavaDBUpdate bool
NoProgress bool NoProgress bool
DBRepository string DBRepository name.Reference
JavaDBRepository string JavaDBRepository name.Reference
Light bool // deprecated Light bool // deprecated
} }
@@ -145,6 +148,32 @@ func (f *DBFlagGroup) ToOptions() (DBOptions, error) {
log.Logger.Warn("'--light' option is deprecated and will be removed. See also: https://github.com/aquasecurity/trivy/discussions/1649") log.Logger.Warn("'--light' option is deprecated and will be removed. See also: https://github.com/aquasecurity/trivy/discussions/1649")
} }
var dbRepository, javaDBRepository name.Reference
var err error
if f.DBRepository != nil {
if dbRepository, err = name.ParseReference(f.DBRepository.Value(), name.WithDefaultTag("")); err != nil {
return DBOptions{}, xerrors.Errorf("invalid db repository: %w", err)
}
// Add the schema version if the tag is not specified for backward compatibility.
if t, ok := dbRepository.(name.Tag); ok && t.TagStr() == "" {
dbRepository = t.Tag(fmt.Sprint(db.SchemaVersion))
log.Logger.Infow("Adding schema version to the DB repository for backward compatibility",
zap.String("repository", dbRepository.String()))
}
}
if f.JavaDBRepository != nil {
if javaDBRepository, err = name.ParseReference(f.JavaDBRepository.Value(), name.WithDefaultTag("")); err != nil {
return DBOptions{}, xerrors.Errorf("invalid javadb repository: %w", err)
}
// Add the schema version if the tag is not specified for backward compatibility.
if t, ok := javaDBRepository.(name.Tag); ok && t.TagStr() == "" {
javaDBRepository = t.Tag(fmt.Sprint(javadb.SchemaVersion))
log.Logger.Infow("Adding schema version to the Java DB repository for backward compatibility",
zap.String("repository", javaDBRepository.String()))
}
}
return DBOptions{ return DBOptions{
Reset: f.Reset.Value(), Reset: f.Reset.Value(),
DownloadDBOnly: downloadDBOnly, DownloadDBOnly: downloadDBOnly,
@@ -153,7 +182,7 @@ func (f *DBFlagGroup) ToOptions() (DBOptions, error) {
SkipJavaDBUpdate: skipJavaDBUpdate, SkipJavaDBUpdate: skipJavaDBUpdate,
Light: light, Light: light,
NoProgress: f.NoProgress.Value(), NoProgress: f.NoProgress.Value(),
DBRepository: f.DBRepository.Value(), DBRepository: dbRepository,
JavaDBRepository: f.JavaDBRepository.Value(), JavaDBRepository: javaDBRepository,
}, nil }, nil
} }

View File

@@ -1,6 +1,7 @@
package flag_test package flag_test
import ( import (
"github.com/google/go-containerregistry/pkg/name"
"testing" "testing"
"github.com/spf13/viper" "github.com/spf13/viper"
@@ -18,6 +19,8 @@ func TestDBFlagGroup_ToOptions(t *testing.T) {
SkipDBUpdate bool SkipDBUpdate bool
DownloadDBOnly bool DownloadDBOnly bool
Light bool Light bool
DBRepository string
JavaDBRepository string
} }
tests := []struct { tests := []struct {
name string name string
@@ -31,10 +34,14 @@ func TestDBFlagGroup_ToOptions(t *testing.T) {
fields: fields{ fields: fields{
SkipDBUpdate: true, SkipDBUpdate: true,
DownloadDBOnly: false, DownloadDBOnly: false,
DBRepository: "ghcr.io/aquasecurity/trivy-db",
JavaDBRepository: "ghcr.io/aquasecurity/trivy-java-db",
}, },
want: flag.DBOptions{ want: flag.DBOptions{
SkipDBUpdate: true, SkipDBUpdate: true,
DownloadDBOnly: false, DownloadDBOnly: false,
DBRepository: name.Tag{}, // All fields are unexported
JavaDBRepository: name.Tag{}, // All fields are unexported
}, },
assertion: require.NoError, assertion: require.NoError,
}, },
@@ -42,9 +49,13 @@ func TestDBFlagGroup_ToOptions(t *testing.T) {
name: "light", name: "light",
fields: fields{ fields: fields{
Light: true, Light: true,
DBRepository: "ghcr.io/aquasecurity/trivy-db",
JavaDBRepository: "ghcr.io/aquasecurity/trivy-java-db",
}, },
want: flag.DBOptions{ want: flag.DBOptions{
Light: true, Light: true,
DBRepository: name.Tag{}, // All fields are unexported
JavaDBRepository: name.Tag{}, // All fields are unexported
}, },
wantLogs: []string{ wantLogs: []string{
"'--light' option is deprecated and will be removed. See also: https://github.com/aquasecurity/trivy/discussions/1649", "'--light' option is deprecated and will be removed. See also: https://github.com/aquasecurity/trivy/discussions/1649",
@@ -61,6 +72,17 @@ func TestDBFlagGroup_ToOptions(t *testing.T) {
require.ErrorContains(t, err, "--skip-db-update and --download-db-only options can not be specified both") require.ErrorContains(t, err, "--skip-db-update and --download-db-only options can not be specified both")
}, },
}, },
{
name: "invalid repo",
fields: fields{
SkipDBUpdate: true,
DownloadDBOnly: false,
DBRepository: "foo:bar:baz",
},
assertion: func(t require.TestingT, err error, msgs ...interface{}) {
require.ErrorContains(t, err, "invalid db repository")
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@@ -71,16 +93,20 @@ func TestDBFlagGroup_ToOptions(t *testing.T) {
viper.Set(flag.SkipDBUpdateFlag.ConfigName, tt.fields.SkipDBUpdate) viper.Set(flag.SkipDBUpdateFlag.ConfigName, tt.fields.SkipDBUpdate)
viper.Set(flag.DownloadDBOnlyFlag.ConfigName, tt.fields.DownloadDBOnly) viper.Set(flag.DownloadDBOnlyFlag.ConfigName, tt.fields.DownloadDBOnly)
viper.Set(flag.LightFlag.ConfigName, tt.fields.Light) viper.Set(flag.LightFlag.ConfigName, tt.fields.Light)
viper.Set(flag.DBRepositoryFlag.ConfigName, tt.fields.DBRepository)
viper.Set(flag.JavaDBRepositoryFlag.ConfigName, tt.fields.JavaDBRepository)
// Assert options // Assert options
f := &flag.DBFlagGroup{ f := &flag.DBFlagGroup{
DownloadDBOnly: flag.DownloadDBOnlyFlag.Clone(), DownloadDBOnly: flag.DownloadDBOnlyFlag.Clone(),
SkipDBUpdate: flag.SkipDBUpdateFlag.Clone(), SkipDBUpdate: flag.SkipDBUpdateFlag.Clone(),
Light: flag.LightFlag.Clone(), Light: flag.LightFlag.Clone(),
DBRepository: flag.DBRepositoryFlag.Clone(),
JavaDBRepository: flag.JavaDBRepositoryFlag.Clone(),
} }
got, err := f.ToOptions() got, err := f.ToOptions()
tt.assertion(t, err) tt.assertion(t, err)
assert.Equalf(t, tt.want, got, "ToOptions()") assert.EqualExportedValues(t, tt.want, got)
// Assert log messages // Assert log messages
var gotMessages []string var gotMessages []string

View File

@@ -7,10 +7,10 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
"strings"
"sync" "sync"
"time" "time"
"github.com/google/go-containerregistry/pkg/name"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/aquasecurity/trivy-java-db/pkg/db" "github.com/aquasecurity/trivy-java-db/pkg/db"
@@ -22,13 +22,16 @@ import (
) )
const ( const (
SchemaVersion = db.SchemaVersion
mediaType = "application/vnd.aquasec.trivy.javadb.layer.v1.tar+gzip" mediaType = "application/vnd.aquasec.trivy.javadb.layer.v1.tar+gzip"
) )
var DefaultRepository = fmt.Sprintf("%s:%d", "ghcr.io/aquasecurity/trivy-java-db", SchemaVersion)
var updater *Updater var updater *Updater
type Updater struct { type Updater struct {
repo string repo name.Reference
dbDir string dbDir string
skip bool skip bool
quiet bool quiet bool
@@ -50,14 +53,14 @@ func (u *Updater) Update() error {
} }
} }
if (meta.Version != db.SchemaVersion || meta.NextUpdate.Before(time.Now().UTC())) && !u.skip { if (meta.Version != SchemaVersion || meta.NextUpdate.Before(time.Now().UTC())) && !u.skip {
// Download DB // Download DB
log.Logger.Infof("Java DB Repository: %s", u.repo) log.Logger.Infof("Java DB Repository: %s", u.repo)
log.Logger.Info("Downloading the Java DB...") log.Logger.Info("Downloading the Java DB...")
// TODO: support remote options // TODO: support remote options
var a *oci.Artifact var a *oci.Artifact
if a, err = oci.NewArtifact(u.repo, u.quiet, u.registryOption); err != nil { if a, err = oci.NewArtifact(u.repo.String(), u.quiet, u.registryOption); err != nil {
return xerrors.Errorf("oci error: %w", err) return xerrors.Errorf("oci error: %w", err)
} }
if err = a.Download(context.Background(), dbDir, oci.DownloadOption{MediaType: mediaType}); err != nil { if err = a.Download(context.Background(), dbDir, oci.DownloadOption{MediaType: mediaType}); err != nil {
@@ -82,12 +85,7 @@ func (u *Updater) Update() error {
return nil return nil
} }
func Init(cacheDir, javaDBRepository string, skip, quiet bool, registryOption ftypes.RegistryOptions) { func Init(cacheDir string, javaDBRepository name.Reference, skip, quiet bool, registryOption ftypes.RegistryOptions) {
// Add the schema version as a tag if the tag doesn't exist.
// This is required for backward compatibility.
if !strings.Contains(javaDBRepository, ":") {
javaDBRepository = fmt.Sprintf("%s:%d", javaDBRepository, db.SchemaVersion)
}
updater = &Updater{ updater = &Updater{
repo: javaDBRepository, repo: javaDBRepository,
dbDir: filepath.Join(cacheDir, "java-db"), dbDir: filepath.Join(cacheDir, "java-db"),

View File

@@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/NYTimes/gziphandler" "github.com/NYTimes/gziphandler"
"github.com/google/go-containerregistry/pkg/name"
"github.com/twitchtv/twirp" "github.com/twitchtv/twirp"
"golang.org/x/xerrors" "golang.org/x/xerrors"
@@ -33,14 +34,14 @@ type Server struct {
cacheDir string cacheDir string
token string token string
tokenHeader string tokenHeader string
dbRepository string dbRepository name.Reference
// For OCI registries // For OCI registries
types.RegistryOptions types.RegistryOptions
} }
// NewServer returns an instance of Server // NewServer returns an instance of Server
func NewServer(appVersion, addr, cacheDir, token, tokenHeader, dbRepository string, opt types.RegistryOptions) Server { func NewServer(appVersion, addr, cacheDir, token, tokenHeader string, dbRepository name.Reference, opt types.RegistryOptions) Server {
return Server{ return Server{
appVersion: appVersion, appVersion: appVersion,
addr: addr, addr: addr,