refactor: use google/wire for cache (#7024)

Signed-off-by: knqyf263 <knqyf263@gmail.com>
This commit is contained in:
Teppei Fukuda
2024-06-27 11:04:01 +04:00
committed by GitHub
parent e9fc3e3397
commit 4be02bab8c
22 changed files with 525 additions and 447 deletions

2
pkg/cache/cache.go vendored
View File

@@ -5,7 +5,7 @@ import (
) )
const ( const (
cacheDirName = "fanal" scanCacheDirName = "fanal"
// artifactBucket stores artifact information with artifact ID such as image ID // artifactBucket stores artifact information with artifact ID such as image ID
artifactBucket = "artifact" artifactBucket = "artifact"

157
pkg/cache/client.go vendored
View File

@@ -1,21 +1,14 @@
package cache package cache
import ( import (
"crypto/tls"
"crypto/x509"
"fmt"
"os"
"strings" "strings"
"time" "time"
"github.com/go-redis/redis/v8"
"github.com/samber/lo"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/log"
) )
const ( const (
TypeUnknown Type = "unknown"
TypeFS Type = "fs" TypeFS Type = "fs"
TypeRedis Type = "redis" TypeRedis Type = "redis"
) )
@@ -23,144 +16,50 @@ const (
type Type string type Type string
type Options struct { type Options struct {
Type Type
TTL time.Duration
Redis RedisOptions
}
func NewOptions(backend, redisCACert, redisCert, redisKey string, redisTLS bool, ttl time.Duration) (Options, error) {
t, err := NewType(backend)
if err != nil {
return Options{}, xerrors.Errorf("cache type error: %w", err)
}
var redisOpts RedisOptions
if t == TypeRedis {
redisTLSOpts, err := NewRedisTLSOptions(redisCACert, redisCert, redisKey)
if err != nil {
return Options{}, xerrors.Errorf("redis TLS option error: %w", err)
}
redisOpts = RedisOptions{
Backend: backend,
TLS: redisTLS,
TLSOptions: redisTLSOpts,
}
} else if ttl != 0 {
log.Warn("'--cache-ttl' is only available with Redis cache backend")
}
return Options{
Type: t,
TTL: ttl,
Redis: redisOpts,
}, nil
}
type RedisOptions struct {
Backend string Backend string
TLS bool CacheDir string
TLSOptions RedisTLSOptions RedisCACert string
RedisCert string
RedisKey string
RedisTLS bool
TTL time.Duration
} }
// BackendMasked returns the redis connection string masking credentials func NewType(backend string) Type {
func (o *RedisOptions) BackendMasked() string {
endIndex := strings.Index(o.Backend, "@")
if endIndex == -1 {
return o.Backend
}
startIndex := strings.Index(o.Backend, "//")
return fmt.Sprintf("%s****%s", o.Backend[:startIndex+2], o.Backend[endIndex:])
}
// RedisTLSOptions holds the options for redis cache
type RedisTLSOptions struct {
CACert string
Cert string
Key string
}
func NewRedisTLSOptions(caCert, cert, key string) (RedisTLSOptions, error) {
opts := RedisTLSOptions{
CACert: caCert,
Cert: cert,
Key: key,
}
// If one of redis option not nil, make sure CA, cert, and key provided
if !lo.IsEmpty(opts) {
if opts.CACert == "" || opts.Cert == "" || opts.Key == "" {
return RedisTLSOptions{}, xerrors.Errorf("you must provide Redis CA, cert and key file path when using TLS")
}
}
return opts, nil
}
func NewType(backend string) (Type, error) {
// "redis://" or "fs" are allowed for now // "redis://" or "fs" are allowed for now
// An empty value is also allowed for testability // An empty value is also allowed for testability
switch { switch {
case strings.HasPrefix(backend, "redis://"): case strings.HasPrefix(backend, "redis://"):
return TypeRedis, nil return TypeRedis
case backend == "fs", backend == "": case backend == "fs", backend == "":
return TypeFS, nil return TypeFS
default: default:
return "", xerrors.Errorf("unknown cache backend: %s", backend) return TypeUnknown
} }
} }
// New returns a new cache client // New returns a new cache client
func New(dir string, opts Options) (Cache, error) { func New(opts Options) (Cache, func(), error) {
if opts.Type == TypeRedis { cleanup := func() {} // To avoid panic
log.Info("Redis cache", log.String("url", opts.Redis.BackendMasked()))
options, err := redis.ParseURL(opts.Redis.Backend) var cache Cache
t := NewType(opts.Backend)
switch t {
case TypeRedis:
redisCache, err := NewRedisCache(opts.Backend, opts.RedisCACert, opts.RedisCert, opts.RedisKey, opts.RedisTLS, opts.TTL)
if err != nil { if err != nil {
return nil, err return nil, cleanup, xerrors.Errorf("unable to initialize redis cache: %w", err)
} }
cache = redisCache
if tlsOpts := opts.Redis.TLSOptions; !lo.IsEmpty(tlsOpts) { case TypeFS:
caCert, cert, err := GetTLSConfig(tlsOpts.CACert, tlsOpts.Cert, tlsOpts.Key)
if err != nil {
return nil, err
}
options.TLSConfig = &tls.Config{
RootCAs: caCert,
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
}
} else if opts.Redis.TLS {
options.TLSConfig = &tls.Config{
MinVersion: tls.VersionTLS12,
}
}
return NewRedisCache(options, opts.TTL), nil
}
// standalone mode // standalone mode
fsCache, err := NewFSCache(dir) fsCache, err := NewFSCache(opts.CacheDir)
if err != nil { if err != nil {
return nil, xerrors.Errorf("unable to initialize fs cache: %w", err) return nil, cleanup, xerrors.Errorf("unable to initialize fs cache: %w", err)
} }
return fsCache, nil cache = fsCache
default:
return nil, cleanup, xerrors.Errorf("unknown cache type: %s", t)
} }
return cache, func() { _ = cache.Close() }, nil
// GetTLSConfig gets tls config from CA, Cert and Key file
func GetTLSConfig(caCertPath, certPath, keyPath string) (*x509.CertPool, tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, tls.Certificate{}, err
}
caCert, err := os.ReadFile(caCertPath)
if err != nil {
return nil, tls.Certificate{}, err
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
return caCertPool, cert, nil
} }

View File

@@ -2,7 +2,6 @@ package cache_test
import ( import (
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -10,120 +9,113 @@ import (
"github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/cache"
) )
func TestNewOptions(t *testing.T) { func TestNew(t *testing.T) {
type args struct {
backend string
redisCACert string
redisCert string
redisKey string
redisTLS bool
ttl time.Duration
}
tests := []struct { tests := []struct {
name string name string
args args opts cache.Options
want cache.Options wantType any
assertion require.ErrorAssertionFunc wantErr string
}{ }{
{ {
name: "fs", name: "fs backend",
args: args{backend: "fs"}, opts: cache.Options{
want: cache.Options{Type: cache.TypeFS}, Backend: "fs",
assertion: require.NoError, CacheDir: "/tmp/cache",
},
wantType: cache.FSCache{},
}, },
{ {
name: "redis", name: "redis backend",
args: args{backend: "redis://localhost:6379"}, opts: cache.Options{
want: cache.Options{
Type: cache.TypeRedis,
Redis: cache.RedisOptions{Backend: "redis://localhost:6379"},
},
assertion: require.NoError,
},
{
name: "redis tls",
args: args{
backend: "redis://localhost:6379",
redisCACert: "ca-cert.pem",
redisCert: "cert.pem",
redisKey: "key.pem",
},
want: cache.Options{
Type: cache.TypeRedis,
Redis: cache.RedisOptions{
Backend: "redis://localhost:6379", Backend: "redis://localhost:6379",
TLSOptions: cache.RedisTLSOptions{
CACert: "ca-cert.pem",
Cert: "cert.pem",
Key: "key.pem",
}, },
}, wantType: cache.RedisCache{},
},
assertion: require.NoError,
},
{
name: "redis tls with public certificates",
args: args{
backend: "redis://localhost:6379",
redisTLS: true,
},
want: cache.Options{
Type: cache.TypeRedis,
Redis: cache.RedisOptions{
Backend: "redis://localhost:6379",
TLS: true,
},
},
assertion: require.NoError,
}, },
{ {
name: "unknown backend", name: "unknown backend",
args: args{backend: "unknown"}, opts: cache.Options{
assertion: func(t require.TestingT, err error, msgs ...any) { Backend: "unknown",
require.ErrorContains(t, err, "unknown cache backend")
}, },
wantErr: "unknown cache type",
}, },
{ {
name: "sad redis tls", name: "invalid redis URL",
args: args{ opts: cache.Options{
backend: "redis://localhost:6379", Backend: "redis://invalid-url:foo/bar",
redisCACert: "ca-cert.pem",
}, },
assertion: func(t require.TestingT, err error, msgs ...any) { wantErr: "failed to parse Redis URL",
require.ErrorContains(t, err, "you must provide Redis CA, cert and key file path when using TLS")
}, },
{
name: "incomplete TLS options",
opts: cache.Options{
Backend: "redis://localhost:6379",
RedisCACert: "testdata/ca-cert.pem",
RedisTLS: true,
},
wantErr: "you must provide Redis CA, cert and key file path when using TLS",
},
{
name: "invalid TLS file paths",
opts: cache.Options{
Backend: "redis://localhost:6379",
RedisCACert: "testdata/non-existent-ca-cert.pem",
RedisCert: "testdata/non-existent-cert.pem",
RedisKey: "testdata/non-existent-key.pem",
RedisTLS: true,
},
wantErr: "failed to get TLS config",
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := cache.NewOptions(tt.args.backend, tt.args.redisCACert, tt.args.redisCert, tt.args.redisKey, tt.args.redisTLS, tt.args.ttl) c, cleanup, err := cache.New(tt.opts)
tt.assertion(t, err) defer cleanup()
assert.Equal(t, tt.want, got)
if tt.wantErr != "" {
assert.ErrorContains(t, err, tt.wantErr)
return
}
require.NoError(t, err)
assert.NotNil(t, c)
assert.IsType(t, tt.wantType, c)
}) })
} }
} }
func TestRedisOptions_BackendMasked(t *testing.T) { func TestNewType(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
fields cache.RedisOptions backend string
want string wantType cache.Type
}{ }{
{ {
name: "redis cache backend masked", name: "redis backend",
fields: cache.RedisOptions{Backend: "redis://root:password@localhost:6379"}, backend: "redis://localhost:6379",
want: "redis://****@localhost:6379", wantType: cache.TypeRedis,
}, },
{ {
name: "redis cache backend masked does nothing", name: "fs backend",
fields: cache.RedisOptions{Backend: "redis://localhost:6379"}, backend: "fs",
want: "redis://localhost:6379", wantType: cache.TypeFS,
},
{
name: "empty backend",
backend: "",
wantType: cache.TypeFS,
},
{
name: "unknown backend",
backend: "unknown",
wantType: cache.TypeUnknown,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, tt.fields.BackendMasked()) got := cache.NewType(tt.backend)
assert.Equal(t, tt.wantType, got)
}) })
} }
} }

7
pkg/cache/fs.go vendored
View File

@@ -20,7 +20,7 @@ type FSCache struct {
} }
func NewFSCache(cacheDir string) (FSCache, error) { func NewFSCache(cacheDir string) (FSCache, error) {
dir := filepath.Join(cacheDir, cacheDirName) dir := filepath.Join(cacheDir, scanCacheDirName)
if err := os.MkdirAll(dir, 0700); err != nil { if err := os.MkdirAll(dir, 0700); err != nil {
return FSCache{}, xerrors.Errorf("failed to create cache dir: %w", err) return FSCache{}, xerrors.Errorf("failed to create cache dir: %w", err)
} }
@@ -31,7 +31,10 @@ func NewFSCache(cacheDir string) (FSCache, error) {
} }
err = db.Update(func(tx *bolt.Tx) error { err = db.Update(func(tx *bolt.Tx) error {
for _, bucket := range []string{artifactBucket, blobBucket} { for _, bucket := range []string{
artifactBucket,
blobBucket,
} {
if _, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil { if _, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
return xerrors.Errorf("unable to create %s bucket: %w", bucket, err) return xerrors.Errorf("unable to create %s bucket: %w", bucket, err)
} }

View File

@@ -373,7 +373,7 @@ func TestFSCache_PutArtifact(t *testing.T) {
require.NoError(t, err, tt.name) require.NoError(t, err, tt.name)
} }
fs.db.View(func(tx *bolt.Tx) error { err = fs.db.View(func(tx *bolt.Tx) error {
// check decompressedDigestBucket // check decompressedDigestBucket
imageBucket := tx.Bucket([]byte(artifactBucket)) imageBucket := tx.Bucket([]byte(artifactBucket))
b := imageBucket.Get([]byte(tt.args.imageID)) b := imageBucket.Get([]byte(tt.args.imageID))
@@ -381,6 +381,7 @@ func TestFSCache_PutArtifact(t *testing.T) {
return nil return nil
}) })
require.NoError(t, err)
}) })
} }
} }

117
pkg/cache/redis.go vendored
View File

@@ -2,33 +2,118 @@ package cache
import ( import (
"context" "context"
"crypto/tls"
"crypto/x509"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os"
"strings"
"time" "time"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/samber/lo"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
) )
var _ Cache = &RedisCache{} var _ Cache = (*RedisCache)(nil)
const ( const redisPrefix = "fanal"
redisPrefix = "fanal"
) type RedisOptions struct {
Backend string
TLS bool
TLSOptions RedisTLSOptions
}
func NewRedisOptions(backend, caCert, cert, key string, enableTLS bool) (RedisOptions, error) {
tlsOpts, err := NewRedisTLSOptions(caCert, cert, key)
if err != nil {
return RedisOptions{}, xerrors.Errorf("redis TLS option error: %w", err)
}
return RedisOptions{
Backend: backend,
TLS: enableTLS,
TLSOptions: tlsOpts,
}, nil
}
// BackendMasked returns the redis connection string masking credentials
func (o *RedisOptions) BackendMasked() string {
endIndex := strings.Index(o.Backend, "@")
if endIndex == -1 {
return o.Backend
}
startIndex := strings.Index(o.Backend, "//")
return fmt.Sprintf("%s****%s", o.Backend[:startIndex+2], o.Backend[endIndex:])
}
// RedisTLSOptions holds the options for redis cache
type RedisTLSOptions struct {
CACert string
Cert string
Key string
}
func NewRedisTLSOptions(caCert, cert, key string) (RedisTLSOptions, error) {
opts := RedisTLSOptions{
CACert: caCert,
Cert: cert,
Key: key,
}
// If one of redis option not nil, make sure CA, cert, and key provided
if !lo.IsEmpty(opts) {
if opts.CACert == "" || opts.Cert == "" || opts.Key == "" {
return RedisTLSOptions{}, xerrors.Errorf("you must provide Redis CA, cert and key file path when using TLS")
}
}
return opts, nil
}
type RedisCache struct { type RedisCache struct {
client *redis.Client client *redis.Client
expiration time.Duration expiration time.Duration
} }
func NewRedisCache(options *redis.Options, expiration time.Duration) RedisCache { func NewRedisCache(backend, caCertPath, certPath, keyPath string, enableTLS bool, ttl time.Duration) (RedisCache, error) {
opts, err := NewRedisOptions(backend, caCertPath, certPath, keyPath, enableTLS)
if err != nil {
return RedisCache{}, xerrors.Errorf("failed to create Redis options: %w", err)
}
log.Info("Redis scan cache", log.String("url", opts.BackendMasked()))
options, err := redis.ParseURL(opts.Backend)
if err != nil {
return RedisCache{}, xerrors.Errorf("failed to parse Redis URL: %w", err)
}
if tlsOpts := opts.TLSOptions; !lo.IsEmpty(tlsOpts) {
caCert, cert, err := GetTLSConfig(tlsOpts.CACert, tlsOpts.Cert, tlsOpts.Key)
if err != nil {
return RedisCache{}, xerrors.Errorf("failed to get TLS config: %w", err)
}
options.TLSConfig = &tls.Config{
RootCAs: caCert,
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
}
} else if opts.TLS {
options.TLSConfig = &tls.Config{
MinVersion: tls.VersionTLS12,
}
}
return RedisCache{ return RedisCache{
client: redis.NewClient(options), client: redis.NewClient(options),
expiration: expiration, expiration: ttl,
} }, nil
} }
func (c RedisCache) PutArtifact(artifactID string, artifactConfig types.ArtifactInfo) error { func (c RedisCache) PutArtifact(artifactID string, artifactConfig types.ArtifactInfo) error {
@@ -145,3 +230,21 @@ func (c RedisCache) Clear() error {
} }
return nil return nil
} }
// GetTLSConfig gets tls config from CA, Cert and Key file
func GetTLSConfig(caCertPath, certPath, keyPath string) (*x509.CertPool, tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, tls.Certificate{}, err
}
caCert, err := os.ReadFile(caCertPath)
if err != nil {
return nil, tls.Certificate{}, err
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
return caCertPool, cert, nil
}

View File

@@ -7,7 +7,6 @@ import (
"time" "time"
"github.com/alicebob/miniredis/v2" "github.com/alicebob/miniredis/v2"
"github.com/go-redis/redis/v8"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -67,18 +66,15 @@ func TestRedisCache_PutArtifact(t *testing.T) {
addr = "dummy:16379" addr = "dummy:16379"
} }
c := cache.NewRedisCache(&redis.Options{ c, err := cache.NewRedisCache(fmt.Sprintf("redis://%s", addr), "", "", "", false, 0)
Addr: addr, require.NoError(t, err)
}, 0)
err = c.PutArtifact(tt.args.artifactID, tt.args.artifactConfig) err = c.PutArtifact(tt.args.artifactID, tt.args.artifactConfig)
if tt.wantErr != "" { if tt.wantErr != "" {
require.Error(t, err) require.ErrorContains(t, err, tt.wantErr)
assert.Contains(t, err.Error(), tt.wantErr)
return return
} else {
require.NoError(t, err)
} }
require.NoError(t, err)
got, err := s.Get(tt.wantKey) got, err := s.Get(tt.wantKey)
require.NoError(t, err) require.NoError(t, err)
@@ -156,18 +152,15 @@ func TestRedisCache_PutBlob(t *testing.T) {
addr = "dummy:16379" addr = "dummy:16379"
} }
c := cache.NewRedisCache(&redis.Options{ c, err := cache.NewRedisCache(fmt.Sprintf("redis://%s", addr), "", "", "", false, 0)
Addr: addr, require.NoError(t, err)
}, 0)
err = c.PutBlob(tt.args.blobID, tt.args.blobConfig) err = c.PutBlob(tt.args.blobID, tt.args.blobConfig)
if tt.wantErr != "" { if tt.wantErr != "" {
require.Error(t, err) require.ErrorContains(t, err, tt.wantErr)
assert.Contains(t, err.Error(), tt.wantErr)
return return
} else {
require.NoError(t, err)
} }
require.NoError(t, err)
got, err := s.Get(tt.wantKey) got, err := s.Get(tt.wantKey)
require.NoError(t, err) require.NoError(t, err)
@@ -241,18 +234,15 @@ func TestRedisCache_GetArtifact(t *testing.T) {
addr = "dummy:16379" addr = "dummy:16379"
} }
c := cache.NewRedisCache(&redis.Options{ c, err := cache.NewRedisCache(fmt.Sprintf("redis://%s", addr), "", "", "", false, 0)
Addr: addr, require.NoError(t, err)
}, 0)
got, err := c.GetArtifact(tt.artifactID) got, err := c.GetArtifact(tt.artifactID)
if tt.wantErr != "" { if tt.wantErr != "" {
require.Error(t, err) require.ErrorContains(t, err, tt.wantErr)
assert.Contains(t, err.Error(), tt.wantErr)
return return
} else {
require.NoError(t, err)
} }
require.NoError(t, err)
assert.Equal(t, tt.want, got) assert.Equal(t, tt.want, got)
}) })
@@ -334,14 +324,12 @@ func TestRedisCache_GetBlob(t *testing.T) {
addr = "dummy:16379" addr = "dummy:16379"
} }
c := cache.NewRedisCache(&redis.Options{ c, err := cache.NewRedisCache(fmt.Sprintf("redis://%s", addr), "", "", "", false, 0)
Addr: addr, require.NoError(t, err)
}, 0)
got, err := c.GetBlob(tt.blobID) got, err := c.GetBlob(tt.blobID)
if tt.wantErr != "" { if tt.wantErr != "" {
require.Error(t, err) require.ErrorContains(t, err, tt.wantErr)
assert.Contains(t, err.Error(), tt.wantErr)
return return
} }
@@ -445,14 +433,12 @@ func TestRedisCache_MissingBlobs(t *testing.T) {
addr = "dummy:6379" addr = "dummy:6379"
} }
c := cache.NewRedisCache(&redis.Options{ c, err := cache.NewRedisCache(fmt.Sprintf("redis://%s", addr), "", "", "", false, 0)
Addr: addr, require.NoError(t, err)
}, 0)
missingArtifact, missingBlobIDs, err := c.MissingBlobs(tt.args.artifactID, tt.args.blobIDs) missingArtifact, missingBlobIDs, err := c.MissingBlobs(tt.args.artifactID, tt.args.blobIDs)
if tt.wantErr != "" { if tt.wantErr != "" {
require.Error(t, err) require.ErrorContains(t, err, tt.wantErr)
assert.Contains(t, err.Error(), tt.wantErr)
return return
} }
@@ -470,9 +456,9 @@ func TestRedisCache_Close(t *testing.T) {
defer s.Close() defer s.Close()
t.Run("close", func(t *testing.T) { t.Run("close", func(t *testing.T) {
c := cache.NewRedisCache(&redis.Options{ c, err := cache.NewRedisCache(fmt.Sprintf("redis://%s", s.Addr()), "", "", "", false, 0)
Addr: s.Addr(), require.NoError(t, err)
}, 0)
closeErr := c.Close() closeErr := c.Close()
require.NoError(t, closeErr) require.NoError(t, closeErr)
time.Sleep(3 * time.Second) // give it some time time.Sleep(3 * time.Second) // give it some time
@@ -492,9 +478,9 @@ func TestRedisCache_Clear(t *testing.T) {
s.Set("foo", "bar") s.Set("foo", "bar")
t.Run("clear", func(t *testing.T) { t.Run("clear", func(t *testing.T) {
c := cache.NewRedisCache(&redis.Options{ c, err := cache.NewRedisCache(fmt.Sprintf("redis://%s", s.Addr()), "", "", "", false, 0)
Addr: s.Addr(), require.NoError(t, err)
}, 0)
require.NoError(t, c.Clear()) require.NoError(t, c.Clear())
for i := 0; i < 200; i++ { for i := 0; i < 200; i++ {
assert.False(t, s.Exists(fmt.Sprintf("fanal::key%d", i))) assert.False(t, s.Exists(fmt.Sprintf("fanal::key%d", i)))
@@ -546,9 +532,8 @@ func TestRedisCache_DeleteBlobs(t *testing.T) {
addr = "dummy:16379" addr = "dummy:16379"
} }
c := cache.NewRedisCache(&redis.Options{ c, err := cache.NewRedisCache(fmt.Sprintf("redis://%s", addr), "", "", "", false, 0)
Addr: addr, require.NoError(t, err)
}, 0)
err = c.DeleteBlobs(tt.args.blobIDs) err = c.DeleteBlobs(tt.args.blobIDs)
if tt.wantErr != "" { if tt.wantErr != "" {
@@ -560,3 +545,27 @@ func TestRedisCache_DeleteBlobs(t *testing.T) {
}) })
} }
} }
func TestRedisOptions_BackendMasked(t *testing.T) {
tests := []struct {
name string
fields cache.RedisOptions
want string
}{
{
name: "redis cache backend masked",
fields: cache.RedisOptions{Backend: "redis://root:password@localhost:6379"},
want: "redis://****@localhost:6379",
},
{
name: "redis cache backend masked does nothing",
fields: cache.RedisOptions{Backend: "redis://localhost:6379"},
want: "redis://localhost:6379",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, tt.fields.BackendMasked())
})
}
}

16
pkg/cache/remote.go vendored
View File

@@ -13,6 +13,14 @@ import (
rpcCache "github.com/aquasecurity/trivy/rpc/cache" rpcCache "github.com/aquasecurity/trivy/rpc/cache"
) )
var _ ArtifactCache = (*RemoteCache)(nil)
type RemoteOptions struct {
ServerAddr string
CustomHeaders http.Header
Insecure bool
}
// RemoteCache implements remote cache // RemoteCache implements remote cache
type RemoteCache struct { type RemoteCache struct {
ctx context.Context // for custom header ctx context.Context // for custom header
@@ -20,18 +28,18 @@ type RemoteCache struct {
} }
// NewRemoteCache is the factory method for RemoteCache // NewRemoteCache is the factory method for RemoteCache
func NewRemoteCache(url string, customHeaders http.Header, insecure bool) ArtifactCache { func NewRemoteCache(opts RemoteOptions) *RemoteCache {
ctx := client.WithCustomHeaders(context.Background(), customHeaders) ctx := client.WithCustomHeaders(context.Background(), opts.CustomHeaders)
httpClient := &http.Client{ httpClient := &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
InsecureSkipVerify: insecure, InsecureSkipVerify: opts.Insecure,
}, },
}, },
} }
c := rpcCache.NewCacheProtobufClient(url, httpClient) c := rpcCache.NewCacheProtobufClient(opts.ServerAddr, httpClient)
return &RemoteCache{ return &RemoteCache{
ctx: ctx, ctx: ctx,
client: c, client: c,

View File

@@ -145,7 +145,11 @@ func TestRemoteCache_PutArtifact(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) {
c := cache.NewRemoteCache(ts.URL, tt.args.customHeaders, false) c := cache.NewRemoteCache(cache.RemoteOptions{
ServerAddr: ts.URL,
CustomHeaders: tt.args.customHeaders,
Insecure: false,
})
err := c.PutArtifact(tt.args.imageID, tt.args.imageInfo) err := c.PutArtifact(tt.args.imageID, tt.args.imageInfo)
if tt.wantErr != "" { if tt.wantErr != "" {
require.Error(t, err, tt.name) require.Error(t, err, tt.name)
@@ -206,7 +210,11 @@ func TestRemoteCache_PutBlob(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) {
c := cache.NewRemoteCache(ts.URL, tt.args.customHeaders, false) c := cache.NewRemoteCache(cache.RemoteOptions{
ServerAddr: ts.URL,
CustomHeaders: tt.args.customHeaders,
Insecure: false,
})
err := c.PutBlob(tt.args.diffID, tt.args.layerInfo) err := c.PutBlob(tt.args.diffID, tt.args.layerInfo)
if tt.wantErr != "" { if tt.wantErr != "" {
require.Error(t, err, tt.name) require.Error(t, err, tt.name)
@@ -284,7 +292,11 @@ func TestRemoteCache_MissingBlobs(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) {
c := cache.NewRemoteCache(ts.URL, tt.args.customHeaders, false) c := cache.NewRemoteCache(cache.RemoteOptions{
ServerAddr: ts.URL,
CustomHeaders: tt.args.customHeaders,
Insecure: false,
})
gotMissingImage, gotMissingLayerIDs, err := c.MissingBlobs(tt.args.imageID, tt.args.layerIDs) gotMissingImage, gotMissingLayerIDs, err := c.MissingBlobs(tt.args.imageID, tt.args.layerIDs)
if tt.wantErr != "" { if tt.wantErr != "" {
require.Error(t, err, tt.name) require.Error(t, err, tt.name)
@@ -334,7 +346,11 @@ func TestRemoteCache_PutArtifactInsecure(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) {
c := cache.NewRemoteCache(ts.URL, nil, tt.args.insecure) c := cache.NewRemoteCache(cache.RemoteOptions{
ServerAddr: ts.URL,
CustomHeaders: nil,
Insecure: tt.args.insecure,
})
err := c.PutArtifact(tt.args.imageID, tt.args.imageInfo) err := c.PutArtifact(tt.args.imageID, tt.args.imageInfo)
if tt.wantErr != "" { if tt.wantErr != "" {
require.Error(t, err) require.Error(t, err)

View File

@@ -21,8 +21,7 @@ import (
// initializeImageScanner is for container image scanning in standalone mode // initializeImageScanner is for container image scanning in standalone mode
// e.g. dockerd, container registry, podman, etc. // e.g. dockerd, container registry, podman, etc.
func initializeImageScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, func initializeImageScanner(ctx context.Context, imageName string, imageOpt types.ImageOptions, cacheOptions cache.Options, artifactOption artifact.Option) (
localArtifactCache cache.LocalArtifactCache, imageOpt types.ImageOptions, artifactOption artifact.Option) (
scanner.Scanner, func(), error) { scanner.Scanner, func(), error) {
wire.Build(scanner.StandaloneDockerSet) wire.Build(scanner.StandaloneDockerSet)
return scanner.Scanner{}, nil, nil return scanner.Scanner{}, nil, nil
@@ -30,33 +29,29 @@ func initializeImageScanner(ctx context.Context, imageName string, artifactCache
// initializeArchiveScanner is for container image archive scanning in standalone mode // initializeArchiveScanner is for container image archive scanning in standalone mode
// e.g. docker save -o alpine.tar alpine:3.15 // e.g. docker save -o alpine.tar alpine:3.15
func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, func initializeArchiveScanner(ctx context.Context, filePath string, cacheOptions cache.Options, artifactOption artifact.Option) (
localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, error) { scanner.Scanner, func(), error) {
wire.Build(scanner.StandaloneArchiveSet) wire.Build(scanner.StandaloneArchiveSet)
return scanner.Scanner{}, nil return scanner.Scanner{}, nil, nil
} }
// initializeFilesystemScanner is for filesystem scanning in standalone mode // initializeFilesystemScanner is for filesystem scanning in standalone mode
func initializeFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, func initializeFilesystemScanner(ctx context.Context, path string, cacheOptions cache.Options, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
wire.Build(scanner.StandaloneFilesystemSet) wire.Build(scanner.StandaloneFilesystemSet)
return scanner.Scanner{}, nil, nil return scanner.Scanner{}, nil, nil
} }
func initializeRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, func initializeRepositoryScanner(ctx context.Context, url string, cacheOptions cache.Options, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
wire.Build(scanner.StandaloneRepositorySet) wire.Build(scanner.StandaloneRepositorySet)
return scanner.Scanner{}, nil, nil return scanner.Scanner{}, nil, nil
} }
func initializeSBOMScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, func initializeSBOMScanner(ctx context.Context, filePath string, cacheOptions cache.Options, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
wire.Build(scanner.StandaloneSBOMSet) wire.Build(scanner.StandaloneSBOMSet)
return scanner.Scanner{}, nil, nil return scanner.Scanner{}, nil, nil
} }
func initializeVMScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, func initializeVMScanner(ctx context.Context, filePath string, cacheOptions cache.Options, artifactOption artifact.Option) (
localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (
scanner.Scanner, func(), error) { scanner.Scanner, func(), error) {
wire.Build(scanner.StandaloneVMSet) wire.Build(scanner.StandaloneVMSet)
return scanner.Scanner{}, nil, nil return scanner.Scanner{}, nil, nil
@@ -68,7 +63,7 @@ func initializeVMScanner(ctx context.Context, filePath string, artifactCache cac
// initializeRemoteImageScanner is for container image scanning in client/server mode // initializeRemoteImageScanner is for container image scanning in client/server mode
// e.g. dockerd, container registry, podman, etc. // e.g. dockerd, container registry, podman, etc.
func initializeRemoteImageScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, func initializeRemoteImageScanner(ctx context.Context, imageName string, remoteCacheOptions cache.RemoteOptions,
remoteScanOptions client.ScannerOption, imageOpt types.ImageOptions, artifactOption artifact.Option) ( remoteScanOptions client.ScannerOption, imageOpt types.ImageOptions, artifactOption artifact.Option) (
scanner.Scanner, func(), error) { scanner.Scanner, func(), error) {
wire.Build(scanner.RemoteDockerSet) wire.Build(scanner.RemoteDockerSet)
@@ -77,21 +72,21 @@ func initializeRemoteImageScanner(ctx context.Context, imageName string, artifac
// initializeRemoteArchiveScanner is for container image archive scanning in client/server mode // initializeRemoteArchiveScanner is for container image archive scanning in client/server mode
// e.g. docker save -o alpine.tar alpine:3.15 // e.g. docker save -o alpine.tar alpine:3.15
func initializeRemoteArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, func initializeRemoteArchiveScanner(ctx context.Context, filePath string, remoteCacheOptions cache.RemoteOptions,
remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, error) { remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
wire.Build(scanner.RemoteArchiveSet) wire.Build(scanner.RemoteArchiveSet)
return scanner.Scanner{}, nil return scanner.Scanner{}, nil, nil
} }
// initializeRemoteFilesystemScanner is for filesystem scanning in client/server mode // initializeRemoteFilesystemScanner is for filesystem scanning in client/server mode
func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, func initializeRemoteFilesystemScanner(ctx context.Context, path string, remoteCacheOptions cache.RemoteOptions,
remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
wire.Build(scanner.RemoteFilesystemSet) wire.Build(scanner.RemoteFilesystemSet)
return scanner.Scanner{}, nil, nil return scanner.Scanner{}, nil, nil
} }
// initializeRemoteRepositoryScanner is for repository scanning in client/server mode // initializeRemoteRepositoryScanner is for repository scanning in client/server mode
func initializeRemoteRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, func initializeRemoteRepositoryScanner(ctx context.Context, url string, remoteCacheOptions cache.RemoteOptions,
remoteScanOptions client.ScannerOption, artifactOption artifact.Option) ( remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (
scanner.Scanner, func(), error) { scanner.Scanner, func(), error) {
wire.Build(scanner.RemoteRepositorySet) wire.Build(scanner.RemoteRepositorySet)
@@ -99,14 +94,14 @@ func initializeRemoteRepositoryScanner(ctx context.Context, url string, artifact
} }
// initializeRemoteSBOMScanner is for sbom scanning in client/server mode // initializeRemoteSBOMScanner is for sbom scanning in client/server mode
func initializeRemoteSBOMScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, func initializeRemoteSBOMScanner(ctx context.Context, path string, remoteCacheOptions cache.RemoteOptions,
remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
wire.Build(scanner.RemoteSBOMSet) wire.Build(scanner.RemoteSBOMSet)
return scanner.Scanner{}, nil, nil return scanner.Scanner{}, nil, nil
} }
// initializeRemoteVMScanner is for vm scanning in client/server mode // initializeRemoteVMScanner is for vm scanning in client/server mode
func initializeRemoteVMScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, func initializeRemoteVMScanner(ctx context.Context, path string, remoteCacheOptions cache.RemoteOptions,
remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
wire.Build(scanner.RemoteVMSet) wire.Build(scanner.RemoteVMSet)
return scanner.Scanner{}, nil, nil return scanner.Scanner{}, nil, nil

View File

@@ -57,8 +57,8 @@ type ScannerConfig struct {
Target string Target string
// Cache // Cache
ArtifactCache cache.ArtifactCache CacheOptions cache.Options
LocalArtifactCache cache.LocalArtifactCache RemoteCacheOptions cache.RemoteOptions
// Client/Server options // Client/Server options
ServerOption client.ScannerOption ServerOption client.ScannerOption
@@ -89,37 +89,31 @@ type Runner interface {
} }
type runner struct { type runner struct {
cache cache.ArtifactCache initializeScanner InitializeScanner
localCache cache.LocalArtifactCache
dbOpen bool dbOpen bool
// WASM modules // WASM modules
module *module.Manager module *module.Manager
} }
type runnerOption func(*runner) type RunnerOption func(*runner)
// WithCacheClient takes a custom cache implementation // WithInitializeScanner takes a custom scanner initialization function.
// It is useful when Trivy is imported as a library. // It is useful when Trivy is imported as a library.
func WithCacheClient(c cache.Cache) runnerOption { func WithInitializeScanner(f InitializeScanner) RunnerOption {
return func(r *runner) { return func(r *runner) {
r.cache = c r.initializeScanner = f
r.localCache = c
} }
} }
// NewRunner initializes Runner that provides scanning functionalities. // NewRunner initializes Runner that provides scanning functionalities.
// It is possible to return SkipScan and it must be handled by caller. // It is possible to return SkipScan and it must be handled by caller.
func NewRunner(ctx context.Context, cliOptions flag.Options, opts ...runnerOption) (Runner, error) { func NewRunner(ctx context.Context, cliOptions flag.Options, opts ...RunnerOption) (Runner, error) {
r := &runner{} r := &runner{}
for _, opt := range opts { for _, opt := range opts {
opt(r) opt(r)
} }
if err := r.initCache(cliOptions); err != nil {
return nil, xerrors.Errorf("cache error: %w", err)
}
// Update the vulnerability database if needed. // Update the vulnerability database if needed.
if err := r.initDB(ctx, cliOptions); err != nil { if err := r.initDB(ctx, cliOptions); err != nil {
return nil, xerrors.Errorf("DB error: %w", err) return nil, xerrors.Errorf("DB error: %w", err)
@@ -142,10 +136,6 @@ func NewRunner(ctx context.Context, cliOptions flag.Options, opts ...runnerOptio
// Close closes everything // Close closes everything
func (r *runner) Close(ctx context.Context) error { func (r *runner) Close(ctx context.Context) error {
var errs error var errs error
if err := r.localCache.Close(); err != nil {
errs = multierror.Append(errs, err)
}
if r.dbOpen { if r.dbOpen {
if err := db.Close(); err != nil { if err := db.Close(); err != nil {
errs = multierror.Append(errs, err) errs = multierror.Append(errs, err)
@@ -258,6 +248,9 @@ func (r *runner) ScanVM(ctx context.Context, opts flag.Options) (types.Report, e
} }
func (r *runner) scanArtifact(ctx context.Context, opts flag.Options, initializeScanner InitializeScanner) (types.Report, error) { func (r *runner) scanArtifact(ctx context.Context, opts flag.Options, initializeScanner InitializeScanner) (types.Report, error) {
if r.initializeScanner != nil {
initializeScanner = r.initializeScanner
}
report, err := r.scan(ctx, opts, initializeScanner) report, err := r.scan(ctx, opts, initializeScanner)
if err != nil { if err != nil {
return types.Report{}, xerrors.Errorf("scan error: %w", err) return types.Report{}, xerrors.Errorf("scan error: %w", err)
@@ -335,31 +328,6 @@ func (r *runner) initJavaDB(opts flag.Options) error {
return nil return nil
} }
func (r *runner) initCache(opts flag.Options) error {
// Skip initializing cache when custom cache is passed
if r.cache != nil {
return nil
}
// client/server mode
if opts.ServerAddr != "" {
r.cache = cache.NewRemoteCache(opts.ServerAddr, opts.CustomHeaders, opts.Insecure)
r.localCache = cache.NewNopCache() // No need to use local cache in client/server mode
return nil
}
// standalone mode
cacheClient, err := cache.New(opts.CacheDir, opts.CacheOptions.CacheBackendOptions)
if err != nil {
return xerrors.Errorf("unable to initialize the cache: %w", err)
}
log.Debug("Cache dir", log.String("dir", opts.CacheDir))
r.cache = cacheClient
r.localCache = cacheClient
return nil
}
// Run performs artifact scanning // Run performs artifact scanning
func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err error) { func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err error) {
ctx, cancel := context.WithTimeout(ctx, opts.Timeout) ctx, cancel := context.WithTimeout(ctx, opts.Timeout)
@@ -588,8 +556,8 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan
return ScannerConfig{ return ScannerConfig{
Target: target, Target: target,
ArtifactCache: r.cache, CacheOptions: opts.CacheOpts(),
LocalArtifactCache: r.localCache, RemoteCacheOptions: opts.RemoteCacheOpts(),
ServerOption: client.ScannerOption{ ServerOption: client.ScannerOption{
RemoteURL: opts.ServerAddr, RemoteURL: opts.ServerAddr,
CustomHeaders: opts.CustomHeaders, CustomHeaders: opts.CustomHeaders,
@@ -607,7 +575,6 @@ func (r *runner) initScannerConfig(opts flag.Options) (ScannerConfig, types.Scan
RepoTag: opts.RepoTag, RepoTag: opts.RepoTag,
SBOMSources: opts.SBOMSources, SBOMSources: opts.SBOMSources,
RekorURL: opts.RekorURL, RekorURL: opts.RekorURL,
//Platform: opts.Platform,
AWSRegion: opts.Region, AWSRegion: opts.Region,
AWSEndpoint: opts.Endpoint, AWSEndpoint: opts.Endpoint,
FileChecksum: fileChecksum, FileChecksum: fileChecksum,

View File

@@ -11,8 +11,7 @@ import (
// imageStandaloneScanner initializes a container image scanner in standalone mode // imageStandaloneScanner initializes a container image scanner in standalone mode
// $ trivy image alpine:3.15 // $ trivy image alpine:3.15
func imageStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { func imageStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeImageScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, s, cleanup, err := initializeImageScanner(ctx, conf.Target, conf.ArtifactOption.ImageOption, conf.CacheOptions, conf.ArtifactOption)
conf.ArtifactOption.ImageOption, conf.ArtifactOption)
if err != nil { if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize an image scanner: %w", err) return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize an image scanner: %w", err)
} }
@@ -22,18 +21,18 @@ func imageStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Sc
// archiveStandaloneScanner initializes an image archive scanner in standalone mode // archiveStandaloneScanner initializes an image archive scanner in standalone mode
// $ trivy image --input alpine.tar // $ trivy image --input alpine.tar
func archiveStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { func archiveStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, err := initializeArchiveScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption) s, cleanup, err := initializeArchiveScanner(ctx, conf.Target, conf.CacheOptions, conf.ArtifactOption)
if err != nil { if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize the archive scanner: %w", err) return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize the archive scanner: %w", err)
} }
return s, func() {}, nil return s, cleanup, nil
} }
// imageRemoteScanner initializes a container image scanner in client/server mode // imageRemoteScanner initializes a container image scanner in client/server mode
// $ trivy image --server localhost:4954 alpine:3.15 // $ trivy image --server localhost:4954 alpine:3.15
func imageRemoteScanner(ctx context.Context, conf ScannerConfig) ( func imageRemoteScanner(ctx context.Context, conf ScannerConfig) (
scanner.Scanner, func(), error) { scanner.Scanner, func(), error) {
s, cleanup, err := initializeRemoteImageScanner(ctx, conf.Target, conf.ArtifactCache, conf.ServerOption, s, cleanup, err := initializeRemoteImageScanner(ctx, conf.Target, conf.RemoteCacheOptions, conf.ServerOption,
conf.ArtifactOption.ImageOption, conf.ArtifactOption) conf.ArtifactOption.ImageOption, conf.ArtifactOption)
if err != nil { if err != nil {
return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize a remote image scanner: %w", err) return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize a remote image scanner: %w", err)
@@ -45,16 +44,16 @@ func imageRemoteScanner(ctx context.Context, conf ScannerConfig) (
// $ trivy image --server localhost:4954 --input alpine.tar // $ trivy image --server localhost:4954 --input alpine.tar
func archiveRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { func archiveRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
// Scan tar file // Scan tar file
s, err := initializeRemoteArchiveScanner(ctx, conf.Target, conf.ArtifactCache, conf.ServerOption, conf.ArtifactOption) s, cleanup, err := initializeRemoteArchiveScanner(ctx, conf.Target, conf.RemoteCacheOptions, conf.ServerOption, conf.ArtifactOption)
if err != nil { if err != nil {
return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize the remote archive scanner: %w", err) return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize the remote archive scanner: %w", err)
} }
return s, func() {}, nil return s, cleanup, nil
} }
// filesystemStandaloneScanner initializes a filesystem scanner in standalone mode // filesystemStandaloneScanner initializes a filesystem scanner in standalone mode
func filesystemStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { func filesystemStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption) s, cleanup, err := initializeFilesystemScanner(ctx, conf.Target, conf.CacheOptions, conf.ArtifactOption)
if err != nil { if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err) return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
} }
@@ -63,7 +62,7 @@ func filesystemStandaloneScanner(ctx context.Context, conf ScannerConfig) (scann
// filesystemRemoteScanner initializes a filesystem scanner in client/server mode // filesystemRemoteScanner initializes a filesystem scanner in client/server mode
func filesystemRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { func filesystemRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeRemoteFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.ServerOption, conf.ArtifactOption) s, cleanup, err := initializeRemoteFilesystemScanner(ctx, conf.Target, conf.RemoteCacheOptions, conf.ServerOption, conf.ArtifactOption)
if err != nil { if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a remote filesystem scanner: %w", err) return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a remote filesystem scanner: %w", err)
} }
@@ -72,7 +71,7 @@ func filesystemRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.S
// repositoryStandaloneScanner initializes a repository scanner in standalone mode // repositoryStandaloneScanner initializes a repository scanner in standalone mode
func repositoryStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { func repositoryStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeRepositoryScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption) s, cleanup, err := initializeRepositoryScanner(ctx, conf.Target, conf.CacheOptions, conf.ArtifactOption)
if err != nil { if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a repository scanner: %w", err) return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a repository scanner: %w", err)
} }
@@ -81,7 +80,7 @@ func repositoryStandaloneScanner(ctx context.Context, conf ScannerConfig) (scann
// repositoryRemoteScanner initializes a repository scanner in client/server mode // repositoryRemoteScanner initializes a repository scanner in client/server mode
func repositoryRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { func repositoryRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeRemoteRepositoryScanner(ctx, conf.Target, conf.ArtifactCache, conf.ServerOption, s, cleanup, err := initializeRemoteRepositoryScanner(ctx, conf.Target, conf.RemoteCacheOptions, conf.ServerOption,
conf.ArtifactOption) conf.ArtifactOption)
if err != nil { if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a remote repository scanner: %w", err) return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a remote repository scanner: %w", err)
@@ -91,7 +90,7 @@ func repositoryRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.S
// sbomStandaloneScanner initializes a SBOM scanner in standalone mode // sbomStandaloneScanner initializes a SBOM scanner in standalone mode
func sbomStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { func sbomStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeSBOMScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption) s, cleanup, err := initializeSBOMScanner(ctx, conf.Target, conf.CacheOptions, conf.ArtifactOption)
if err != nil { if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a cycloneDX scanner: %w", err) return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a cycloneDX scanner: %w", err)
} }
@@ -100,7 +99,7 @@ func sbomStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Sca
// sbomRemoteScanner initializes a SBOM scanner in client/server mode // sbomRemoteScanner initializes a SBOM scanner in client/server mode
func sbomRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { func sbomRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeRemoteSBOMScanner(ctx, conf.Target, conf.ArtifactCache, conf.ServerOption, conf.ArtifactOption) s, cleanup, err := initializeRemoteSBOMScanner(ctx, conf.Target, conf.RemoteCacheOptions, conf.ServerOption, conf.ArtifactOption)
if err != nil { if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a remote cycloneDX scanner: %w", err) return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a remote cycloneDX scanner: %w", err)
} }
@@ -109,7 +108,7 @@ func sbomRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner
// vmStandaloneScanner initializes a VM scanner in standalone mode // vmStandaloneScanner initializes a VM scanner in standalone mode
func vmStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { func vmStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeVMScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption) s, cleanup, err := initializeVMScanner(ctx, conf.Target, conf.CacheOptions, conf.ArtifactOption)
if err != nil { if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a vm scanner: %w", err) return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a vm scanner: %w", err)
} }
@@ -118,7 +117,7 @@ func vmStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scann
// vmRemoteScanner initializes a VM scanner in client/server mode // vmRemoteScanner initializes a VM scanner in client/server mode
func vmRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { func vmRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeRemoteVMScanner(ctx, conf.Target, conf.ArtifactCache, conf.ServerOption, conf.ArtifactOption) s, cleanup, err := initializeRemoteVMScanner(ctx, conf.Target, conf.RemoteCacheOptions, conf.ServerOption, conf.ArtifactOption)
if err != nil { if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a remote vm scanner: %w", err) return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a remote vm scanner: %w", err)
} }

View File

@@ -9,6 +9,7 @@ package artifact
import ( import (
"context" "context"
"github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy/pkg/cache"
"github.com/aquasecurity/trivy/pkg/fanal/applier" "github.com/aquasecurity/trivy/pkg/fanal/applier"
"github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/artifact"
image2 "github.com/aquasecurity/trivy/pkg/fanal/artifact/image" image2 "github.com/aquasecurity/trivy/pkg/fanal/artifact/image"
@@ -16,7 +17,6 @@ import (
"github.com/aquasecurity/trivy/pkg/fanal/artifact/repo" "github.com/aquasecurity/trivy/pkg/fanal/artifact/repo"
"github.com/aquasecurity/trivy/pkg/fanal/artifact/sbom" "github.com/aquasecurity/trivy/pkg/fanal/artifact/sbom"
"github.com/aquasecurity/trivy/pkg/fanal/artifact/vm" "github.com/aquasecurity/trivy/pkg/fanal/artifact/vm"
"github.com/aquasecurity/trivy/pkg/cache"
"github.com/aquasecurity/trivy/pkg/fanal/image" "github.com/aquasecurity/trivy/pkg/fanal/image"
"github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/fanal/walker" "github.com/aquasecurity/trivy/pkg/fanal/walker"
@@ -32,32 +32,43 @@ import (
// initializeImageScanner is for container image scanning in standalone mode // initializeImageScanner is for container image scanning in standalone mode
// e.g. dockerd, container registry, podman, etc. // e.g. dockerd, container registry, podman, etc.
func initializeImageScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, imageOpt types.ImageOptions, artifactOption artifact.Option) (scanner.Scanner, func(), error) { func initializeImageScanner(ctx context.Context, imageName string, imageOpt types.ImageOptions, cacheOptions cache.Options, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
applierApplier := applier.NewApplier(localArtifactCache) cacheCache, cleanup, err := cache.New(cacheOptions)
if err != nil {
return scanner.Scanner{}, nil, err
}
applierApplier := applier.NewApplier(cacheCache)
ospkgScanner := ospkg.NewScanner() ospkgScanner := ospkg.NewScanner()
langpkgScanner := langpkg.NewScanner() langpkgScanner := langpkg.NewScanner()
config := db.Config{} config := db.Config{}
client := vulnerability.NewClient(config) client := vulnerability.NewClient(config)
localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client) localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client)
typesImage, cleanup, err := image.NewContainerImage(ctx, imageName, imageOpt) typesImage, cleanup2, err := image.NewContainerImage(ctx, imageName, imageOpt)
if err != nil { if err != nil {
cleanup()
return scanner.Scanner{}, nil, err return scanner.Scanner{}, nil, err
} }
artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption) artifactArtifact, err := image2.NewArtifact(typesImage, cacheCache, artifactOption)
if err != nil { if err != nil {
cleanup2()
cleanup() cleanup()
return scanner.Scanner{}, nil, err return scanner.Scanner{}, nil, err
} }
scannerScanner := scanner.NewScanner(localScanner, artifactArtifact) scannerScanner := scanner.NewScanner(localScanner, artifactArtifact)
return scannerScanner, func() { return scannerScanner, func() {
cleanup2()
cleanup() cleanup()
}, nil }, nil
} }
// initializeArchiveScanner is for container image archive scanning in standalone mode // initializeArchiveScanner is for container image archive scanning in standalone mode
// e.g. docker save -o alpine.tar alpine:3.15 // e.g. docker save -o alpine.tar alpine:3.15
func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, error) { func initializeArchiveScanner(ctx context.Context, filePath string, cacheOptions cache.Options, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
applierApplier := applier.NewApplier(localArtifactCache) cacheCache, cleanup, err := cache.New(cacheOptions)
if err != nil {
return scanner.Scanner{}, nil, err
}
applierApplier := applier.NewApplier(cacheCache)
ospkgScanner := ospkg.NewScanner() ospkgScanner := ospkg.NewScanner()
langpkgScanner := langpkg.NewScanner() langpkgScanner := langpkg.NewScanner()
config := db.Config{} config := db.Config{}
@@ -65,44 +76,12 @@ func initializeArchiveScanner(ctx context.Context, filePath string, artifactCach
localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client) localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client)
typesImage, err := image.NewArchiveImage(filePath) typesImage, err := image.NewArchiveImage(filePath)
if err != nil { if err != nil {
return scanner.Scanner{}, err cleanup()
}
artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption)
if err != nil {
return scanner.Scanner{}, err
}
scannerScanner := scanner.NewScanner(localScanner, artifactArtifact)
return scannerScanner, nil
}
// initializeFilesystemScanner is for filesystem scanning in standalone mode
func initializeFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
applierApplier := applier.NewApplier(localArtifactCache)
ospkgScanner := ospkg.NewScanner()
langpkgScanner := langpkg.NewScanner()
config := db.Config{}
client := vulnerability.NewClient(config)
localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client)
fs := walker.NewFS()
artifactArtifact, err := local2.NewArtifact(path, artifactCache, fs, artifactOption)
if err != nil {
return scanner.Scanner{}, nil, err return scanner.Scanner{}, nil, err
} }
scannerScanner := scanner.NewScanner(localScanner, artifactArtifact) artifactArtifact, err := image2.NewArtifact(typesImage, cacheCache, artifactOption)
return scannerScanner, func() {
}, nil
}
func initializeRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
applierApplier := applier.NewApplier(localArtifactCache)
ospkgScanner := ospkg.NewScanner()
langpkgScanner := langpkg.NewScanner()
config := db.Config{}
client := vulnerability.NewClient(config)
localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client)
fs := walker.NewFS()
artifactArtifact, cleanup, err := repo.NewArtifact(url, artifactCache, fs, artifactOption)
if err != nil { if err != nil {
cleanup()
return scanner.Scanner{}, nil, err return scanner.Scanner{}, nil, err
} }
scannerScanner := scanner.NewScanner(localScanner, artifactArtifact) scannerScanner := scanner.NewScanner(localScanner, artifactArtifact)
@@ -111,49 +90,110 @@ func initializeRepositoryScanner(ctx context.Context, url string, artifactCache
}, nil }, nil
} }
func initializeSBOMScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { // initializeFilesystemScanner is for filesystem scanning in standalone mode
applierApplier := applier.NewApplier(localArtifactCache) func initializeFilesystemScanner(ctx context.Context, path string, cacheOptions cache.Options, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
cacheCache, cleanup, err := cache.New(cacheOptions)
if err != nil {
return scanner.Scanner{}, nil, err
}
applierApplier := applier.NewApplier(cacheCache)
ospkgScanner := ospkg.NewScanner() ospkgScanner := ospkg.NewScanner()
langpkgScanner := langpkg.NewScanner() langpkgScanner := langpkg.NewScanner()
config := db.Config{} config := db.Config{}
client := vulnerability.NewClient(config) client := vulnerability.NewClient(config)
localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client) localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client)
artifactArtifact, err := sbom.NewArtifact(filePath, artifactCache, artifactOption) fs := walker.NewFS()
artifactArtifact, err := local2.NewArtifact(path, cacheCache, fs, artifactOption)
if err != nil { if err != nil {
cleanup()
return scanner.Scanner{}, nil, err return scanner.Scanner{}, nil, err
} }
scannerScanner := scanner.NewScanner(localScanner, artifactArtifact) scannerScanner := scanner.NewScanner(localScanner, artifactArtifact)
return scannerScanner, func() { return scannerScanner, func() {
cleanup()
}, nil }, nil
} }
func initializeVMScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { func initializeRepositoryScanner(ctx context.Context, url string, cacheOptions cache.Options, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
applierApplier := applier.NewApplier(localArtifactCache) cacheCache, cleanup, err := cache.New(cacheOptions)
if err != nil {
return scanner.Scanner{}, nil, err
}
applierApplier := applier.NewApplier(cacheCache)
ospkgScanner := ospkg.NewScanner()
langpkgScanner := langpkg.NewScanner()
config := db.Config{}
client := vulnerability.NewClient(config)
localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client)
fs := walker.NewFS()
artifactArtifact, cleanup2, err := repo.NewArtifact(url, cacheCache, fs, artifactOption)
if err != nil {
cleanup()
return scanner.Scanner{}, nil, err
}
scannerScanner := scanner.NewScanner(localScanner, artifactArtifact)
return scannerScanner, func() {
cleanup2()
cleanup()
}, nil
}
func initializeSBOMScanner(ctx context.Context, filePath string, cacheOptions cache.Options, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
cacheCache, cleanup, err := cache.New(cacheOptions)
if err != nil {
return scanner.Scanner{}, nil, err
}
applierApplier := applier.NewApplier(cacheCache)
ospkgScanner := ospkg.NewScanner()
langpkgScanner := langpkg.NewScanner()
config := db.Config{}
client := vulnerability.NewClient(config)
localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client)
artifactArtifact, err := sbom.NewArtifact(filePath, cacheCache, artifactOption)
if err != nil {
cleanup()
return scanner.Scanner{}, nil, err
}
scannerScanner := scanner.NewScanner(localScanner, artifactArtifact)
return scannerScanner, func() {
cleanup()
}, nil
}
func initializeVMScanner(ctx context.Context, filePath string, cacheOptions cache.Options, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
cacheCache, cleanup, err := cache.New(cacheOptions)
if err != nil {
return scanner.Scanner{}, nil, err
}
applierApplier := applier.NewApplier(cacheCache)
ospkgScanner := ospkg.NewScanner() ospkgScanner := ospkg.NewScanner()
langpkgScanner := langpkg.NewScanner() langpkgScanner := langpkg.NewScanner()
config := db.Config{} config := db.Config{}
client := vulnerability.NewClient(config) client := vulnerability.NewClient(config)
localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client) localScanner := local.NewScanner(applierApplier, ospkgScanner, langpkgScanner, client)
walkerVM := walker.NewVM() walkerVM := walker.NewVM()
artifactArtifact, err := vm.NewArtifact(filePath, artifactCache, walkerVM, artifactOption) artifactArtifact, err := vm.NewArtifact(filePath, cacheCache, walkerVM, artifactOption)
if err != nil { if err != nil {
cleanup()
return scanner.Scanner{}, nil, err return scanner.Scanner{}, nil, err
} }
scannerScanner := scanner.NewScanner(localScanner, artifactArtifact) scannerScanner := scanner.NewScanner(localScanner, artifactArtifact)
return scannerScanner, func() { return scannerScanner, func() {
cleanup()
}, nil }, nil
} }
// initializeRemoteImageScanner is for container image scanning in client/server mode // initializeRemoteImageScanner is for container image scanning in client/server mode
// e.g. dockerd, container registry, podman, etc. // e.g. dockerd, container registry, podman, etc.
func initializeRemoteImageScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, imageOpt types.ImageOptions, artifactOption artifact.Option) (scanner.Scanner, func(), error) { func initializeRemoteImageScanner(ctx context.Context, imageName string, remoteCacheOptions cache.RemoteOptions, remoteScanOptions client.ScannerOption, imageOpt types.ImageOptions, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
v := _wireValue v := _wireValue
clientScanner := client.NewScanner(remoteScanOptions, v...) clientScanner := client.NewScanner(remoteScanOptions, v...)
typesImage, cleanup, err := image.NewContainerImage(ctx, imageName, imageOpt) typesImage, cleanup, err := image.NewContainerImage(ctx, imageName, imageOpt)
if err != nil { if err != nil {
return scanner.Scanner{}, nil, err return scanner.Scanner{}, nil, err
} }
artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption) remoteCache := cache.NewRemoteCache(remoteCacheOptions)
artifactArtifact, err := image2.NewArtifact(typesImage, remoteCache, artifactOption)
if err != nil { if err != nil {
cleanup() cleanup()
return scanner.Scanner{}, nil, err return scanner.Scanner{}, nil, err
@@ -170,27 +210,30 @@ var (
// initializeRemoteArchiveScanner is for container image archive scanning in client/server mode // initializeRemoteArchiveScanner is for container image archive scanning in client/server mode
// e.g. docker save -o alpine.tar alpine:3.15 // e.g. docker save -o alpine.tar alpine:3.15
func initializeRemoteArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, error) { func initializeRemoteArchiveScanner(ctx context.Context, filePath string, remoteCacheOptions cache.RemoteOptions, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
v := _wireValue v := _wireValue
clientScanner := client.NewScanner(remoteScanOptions, v...) clientScanner := client.NewScanner(remoteScanOptions, v...)
typesImage, err := image.NewArchiveImage(filePath) typesImage, err := image.NewArchiveImage(filePath)
if err != nil { if err != nil {
return scanner.Scanner{}, err return scanner.Scanner{}, nil, err
} }
artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption) remoteCache := cache.NewRemoteCache(remoteCacheOptions)
artifactArtifact, err := image2.NewArtifact(typesImage, remoteCache, artifactOption)
if err != nil { if err != nil {
return scanner.Scanner{}, err return scanner.Scanner{}, nil, err
} }
scannerScanner := scanner.NewScanner(clientScanner, artifactArtifact) scannerScanner := scanner.NewScanner(clientScanner, artifactArtifact)
return scannerScanner, nil return scannerScanner, func() {
}, nil
} }
// initializeRemoteFilesystemScanner is for filesystem scanning in client/server mode // initializeRemoteFilesystemScanner is for filesystem scanning in client/server mode
func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { func initializeRemoteFilesystemScanner(ctx context.Context, path string, remoteCacheOptions cache.RemoteOptions, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
v := _wireValue v := _wireValue
clientScanner := client.NewScanner(remoteScanOptions, v...) clientScanner := client.NewScanner(remoteScanOptions, v...)
remoteCache := cache.NewRemoteCache(remoteCacheOptions)
fs := walker.NewFS() fs := walker.NewFS()
artifactArtifact, err := local2.NewArtifact(path, artifactCache, fs, artifactOption) artifactArtifact, err := local2.NewArtifact(path, remoteCache, fs, artifactOption)
if err != nil { if err != nil {
return scanner.Scanner{}, nil, err return scanner.Scanner{}, nil, err
} }
@@ -200,11 +243,12 @@ func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifac
} }
// initializeRemoteRepositoryScanner is for repository scanning in client/server mode // initializeRemoteRepositoryScanner is for repository scanning in client/server mode
func initializeRemoteRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { func initializeRemoteRepositoryScanner(ctx context.Context, url string, remoteCacheOptions cache.RemoteOptions, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
v := _wireValue v := _wireValue
clientScanner := client.NewScanner(remoteScanOptions, v...) clientScanner := client.NewScanner(remoteScanOptions, v...)
remoteCache := cache.NewRemoteCache(remoteCacheOptions)
fs := walker.NewFS() fs := walker.NewFS()
artifactArtifact, cleanup, err := repo.NewArtifact(url, artifactCache, fs, artifactOption) artifactArtifact, cleanup, err := repo.NewArtifact(url, remoteCache, fs, artifactOption)
if err != nil { if err != nil {
return scanner.Scanner{}, nil, err return scanner.Scanner{}, nil, err
} }
@@ -215,10 +259,11 @@ func initializeRemoteRepositoryScanner(ctx context.Context, url string, artifact
} }
// initializeRemoteSBOMScanner is for sbom scanning in client/server mode // initializeRemoteSBOMScanner is for sbom scanning in client/server mode
func initializeRemoteSBOMScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { func initializeRemoteSBOMScanner(ctx context.Context, path string, remoteCacheOptions cache.RemoteOptions, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
v := _wireValue v := _wireValue
clientScanner := client.NewScanner(remoteScanOptions, v...) clientScanner := client.NewScanner(remoteScanOptions, v...)
artifactArtifact, err := sbom.NewArtifact(path, artifactCache, artifactOption) remoteCache := cache.NewRemoteCache(remoteCacheOptions)
artifactArtifact, err := sbom.NewArtifact(path, remoteCache, artifactOption)
if err != nil { if err != nil {
return scanner.Scanner{}, nil, err return scanner.Scanner{}, nil, err
} }
@@ -228,11 +273,12 @@ func initializeRemoteSBOMScanner(ctx context.Context, path string, artifactCache
} }
// initializeRemoteVMScanner is for vm scanning in client/server mode // initializeRemoteVMScanner is for vm scanning in client/server mode
func initializeRemoteVMScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { func initializeRemoteVMScanner(ctx context.Context, path string, remoteCacheOptions cache.RemoteOptions, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) {
v := _wireValue v := _wireValue
clientScanner := client.NewScanner(remoteScanOptions, v...) clientScanner := client.NewScanner(remoteScanOptions, v...)
remoteCache := cache.NewRemoteCache(remoteCacheOptions)
walkerVM := walker.NewVM() walkerVM := walker.NewVM()
artifactArtifact, err := vm.NewArtifact(path, artifactCache, walkerVM, artifactOption) artifactArtifact, err := vm.NewArtifact(path, remoteCache, walkerVM, artifactOption)
if err != nil { if err != nil {
return scanner.Scanner{}, nil, err return scanner.Scanner{}, nil, err
} }

View File

@@ -62,10 +62,12 @@ func cleanAll(ctx context.Context, opts flag.Options) error {
func cleanScanCache(ctx context.Context, opts flag.Options) error { func cleanScanCache(ctx context.Context, opts flag.Options) error {
log.InfoContext(ctx, "Removing scan cache...") log.InfoContext(ctx, "Removing scan cache...")
c, err := cache.New(opts.CacheDir, opts.CacheBackendOptions) c, cleanup, err := cache.New(opts.CacheOpts())
if err != nil { if err != nil {
return xerrors.Errorf("failed to instantiate cache client: %w", err) return xerrors.Errorf("failed to instantiate cache client: %w", err)
} }
defer cleanup()
if err = c.Clear(); err != nil { if err = c.Clear(); err != nil {
return xerrors.Errorf("clear scan cache: %w", err) return xerrors.Errorf("clear scan cache: %w", err)
} }

View File

@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/cache"
"github.com/aquasecurity/trivy/pkg/commands/clean" "github.com/aquasecurity/trivy/pkg/commands/clean"
"github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/flag"
) )
@@ -100,6 +101,9 @@ func TestRun(t *testing.T) {
GlobalOptions: flag.GlobalOptions{ GlobalOptions: flag.GlobalOptions{
CacheDir: tempDir, CacheDir: tempDir,
}, },
CacheOptions: flag.CacheOptions{
CacheBackend: string(cache.TypeFS),
},
CleanOptions: tt.cleanOpts, CleanOptions: tt.cleanOpts,
} }

View File

@@ -19,12 +19,11 @@ func Run(ctx context.Context, opts flag.Options) (err error) {
log.InitLogger(opts.Debug, opts.Quiet) log.InitLogger(opts.Debug, opts.Quiet)
// configure cache dir // configure cache dir
cacheClient, err := cache.New(opts.CacheDir, opts.CacheOptions.CacheBackendOptions) cacheClient, cleanup, err := cache.New(opts.CacheOpts())
if err != nil { if err != nil {
return xerrors.Errorf("server cache error: %w", err) return xerrors.Errorf("server cache error: %w", err)
} }
defer cacheClient.Close() defer cleanup()
log.Debug("Cache", log.String("dir", opts.CacheDir))
// download the database file // download the database file
if err = operation.DownloadDB(ctx, opts.AppVersion, opts.CacheDir, opts.DBRepository, if err = operation.DownloadDB(ctx, opts.AppVersion, opts.CacheDir, opts.DBRepository,

View File

@@ -2,10 +2,6 @@ package flag
import ( import (
"time" "time"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/cache"
) )
// e.g. config yaml: // e.g. config yaml:
@@ -72,13 +68,18 @@ type CacheFlagGroup struct {
type CacheOptions struct { type CacheOptions struct {
ClearCache bool ClearCache bool
CacheBackendOptions cache.Options
CacheBackend string
CacheTTL time.Duration
RedisTLS bool
RedisCACert string
RedisCert string
RedisKey string
} }
// NewCacheFlagGroup returns a default CacheFlagGroup // NewCacheFlagGroup returns a default CacheFlagGroup
func NewCacheFlagGroup() *CacheFlagGroup { func NewCacheFlagGroup() *CacheFlagGroup {
return &CacheFlagGroup{ return &CacheFlagGroup{
ClearCache: ClearCacheFlag.Clone(),
CacheBackend: CacheBackendFlag.Clone(), CacheBackend: CacheBackendFlag.Clone(),
CacheTTL: CacheTTLFlag.Clone(), CacheTTL: CacheTTLFlag.Clone(),
RedisTLS: RedisTLSFlag.Clone(), RedisTLS: RedisTLSFlag.Clone(),
@@ -109,14 +110,12 @@ func (fg *CacheFlagGroup) ToOptions() (CacheOptions, error) {
return CacheOptions{}, err return CacheOptions{}, err
} }
backendOpts, err := cache.NewOptions(fg.CacheBackend.Value(), fg.RedisCACert.Value(), fg.RedisCert.Value(),
fg.RedisKey.Value(), fg.RedisTLS.Value(), fg.CacheTTL.Value())
if err != nil {
return CacheOptions{}, xerrors.Errorf("failed to initialize cache options: %w", err)
}
return CacheOptions{ return CacheOptions{
ClearCache: fg.ClearCache.Value(), CacheBackend: fg.CacheBackend.Value(),
CacheBackendOptions: backendOpts, CacheTTL: fg.CacheTTL.Value(),
RedisTLS: fg.RedisTLS.Value(),
RedisCACert: fg.RedisCACert.Value(),
RedisCert: fg.RedisCert.Value(),
RedisKey: fg.RedisKey.Value(),
}, nil }, nil
} }

View File

@@ -7,6 +7,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/cache"
"github.com/aquasecurity/trivy/pkg/log"
) )
var ( var (
@@ -144,6 +145,8 @@ func (f *GlobalFlagGroup) ToOptions() (GlobalOptions, error) {
// Keep TRIVY_NON_SSL for backward compatibility // Keep TRIVY_NON_SSL for backward compatibility
insecure := f.Insecure.Value() || os.Getenv("TRIVY_NON_SSL") != "" insecure := f.Insecure.Value() || os.Getenv("TRIVY_NON_SSL") != ""
log.Debug("Cache dir", log.String("dir", f.CacheDir.Value()))
return GlobalOptions{ return GlobalOptions{
ConfigFile: f.ConfigFile.Value(), ConfigFile: f.ConfigFile.Value(),
ShowVersion: f.ShowVersion.Value(), ShowVersion: f.ShowVersion.Value(),

View File

@@ -17,6 +17,7 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/cache"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/log"
@@ -448,6 +449,28 @@ func (o *Options) FilterOpts() result.FilterOption {
} }
} }
// CacheOpts returns options for scan cache
func (o *Options) CacheOpts() cache.Options {
return cache.Options{
Backend: o.CacheBackend,
CacheDir: o.CacheDir,
RedisCACert: o.RedisCACert,
RedisCert: o.RedisCert,
RedisKey: o.RedisKey,
RedisTLS: o.RedisTLS,
TTL: o.CacheTTL,
}
}
// RemoteCacheOpts returns options for remote scan cache
func (o *Options) RemoteCacheOpts() cache.RemoteOptions {
return cache.RemoteOptions{
ServerAddr: o.ServerAddr,
CustomHeaders: o.CustomHeaders,
Insecure: o.Insecure,
}
}
// SetOutputWriter sets an output writer. // SetOutputWriter sets an output writer.
func (o *Options) SetOutputWriter(w io.Writer) { func (o *Options) SetOutputWriter(w io.Writer) {
o.outputWriter = w o.outputWriter = w

View File

@@ -8,8 +8,8 @@ package k8s
import ( import (
"github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy/pkg/fanal/applier"
"github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/cache"
"github.com/aquasecurity/trivy/pkg/fanal/applier"
"github.com/aquasecurity/trivy/pkg/scanner/langpkg" "github.com/aquasecurity/trivy/pkg/scanner/langpkg"
"github.com/aquasecurity/trivy/pkg/scanner/local" "github.com/aquasecurity/trivy/pkg/scanner/local"
"github.com/aquasecurity/trivy/pkg/scanner/ospkg" "github.com/aquasecurity/trivy/pkg/scanner/ospkg"

View File

@@ -8,8 +8,8 @@ package server
import ( import (
"github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy/pkg/fanal/applier"
"github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/cache"
"github.com/aquasecurity/trivy/pkg/fanal/applier"
"github.com/aquasecurity/trivy/pkg/scanner/langpkg" "github.com/aquasecurity/trivy/pkg/scanner/langpkg"
"github.com/aquasecurity/trivy/pkg/scanner/local" "github.com/aquasecurity/trivy/pkg/scanner/local"
"github.com/aquasecurity/trivy/pkg/scanner/ospkg" "github.com/aquasecurity/trivy/pkg/scanner/ospkg"

View File

@@ -6,6 +6,7 @@ import (
"github.com/google/wire" "github.com/google/wire"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/cache"
"github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/clock"
"github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/artifact"
aimage "github.com/aquasecurity/trivy/pkg/fanal/artifact/image" aimage "github.com/aquasecurity/trivy/pkg/fanal/artifact/image"
@@ -28,6 +29,11 @@ import (
// StandaloneSuperSet is used in the standalone mode // StandaloneSuperSet is used in the standalone mode
var StandaloneSuperSet = wire.NewSet( var StandaloneSuperSet = wire.NewSet(
// Cache
cache.New,
wire.Bind(new(cache.ArtifactCache), new(cache.Cache)),
wire.Bind(new(cache.LocalArtifactCache), new(cache.Cache)),
local.SuperSet, local.SuperSet,
wire.Bind(new(Driver), new(local.Scanner)), wire.Bind(new(Driver), new(local.Scanner)),
NewScanner, NewScanner,
@@ -77,6 +83,10 @@ var StandaloneVMSet = wire.NewSet(
// RemoteSuperSet is used in the client mode // RemoteSuperSet is used in the client mode
var RemoteSuperSet = wire.NewSet( var RemoteSuperSet = wire.NewSet(
// Cache
cache.NewRemoteCache,
wire.Bind(new(cache.ArtifactCache), new(*cache.RemoteCache)), // No need for LocalArtifactCache
client.NewScanner, client.NewScanner,
wire.Value([]client.Option(nil)), wire.Value([]client.Option(nil)),
wire.Bind(new(Driver), new(client.Scanner)), wire.Bind(new(Driver), new(client.Scanner)),