diff --git a/docs/vulnerability/examples/cache.md b/docs/vulnerability/examples/cache.md index f5118fde12..2b3e4d63fc 100644 --- a/docs/vulnerability/examples/cache.md +++ b/docs/vulnerability/examples/cache.md @@ -41,3 +41,14 @@ Two options: ``` $ trivy server --cache-backend redis://localhost:6379 ``` + +Trivy also support for connecting to Redis using TLS, you only need to specify `--redis-ca` , `--redis-cert` , and `--redis-key` option. + +``` +$ trivy server --cache-backend redis://localhost:6379 \ + --redis-ca /path/to/ca-cert.pem \ + --redis-cert /path/to/cert.pem \ + --redis-key /path/to/key.pem +``` + +TLS option for redis is hidden from Trivy command-line flag, but you still can use it. \ No newline at end of file diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 4c06ed23de..f7971015a1 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -166,6 +166,27 @@ var ( EnvVars: []string{"TRIVY_CACHE_BACKEND"}, } + redisBackendCACert = cli.StringFlag{ + Name: "redis-ca", + Usage: "redis ca file location, if using redis as cache backend", + EnvVars: []string{"TRIVY_REDIS_BACKEND_CA"}, + Hidden: true, + } + + redisBackendCert = cli.StringFlag{ + Name: "redis-cert", + Usage: "redis certificate file location, if using redis as cache backend", + EnvVars: []string{"TRIVY_REDIS_BACKEND_CERT"}, + Hidden: true, + } + + redisBackendKey = cli.StringFlag{ + Name: "redis-key", + Usage: "redis key file location, if using redis as cache backend", + EnvVars: []string{"TRIVY_REDIS_BACKEND_KEY"}, + Hidden: true, + } + ignoreFileFlag = cli.StringFlag{ Name: "ignorefile", Value: result.DefaultIgnoreFile, @@ -407,6 +428,9 @@ func NewImageCommand() *cli.Command { &ignorePolicy, &listAllPackages, &cacheBackendFlag, + &redisBackendCACert, + &redisBackendCert, + &redisBackendKey, &offlineScan, &insecureFlag, stringSliceFlag(skipFiles), @@ -437,6 +461,9 @@ func NewFilesystemCommand() *cli.Command { &securityChecksFlag, &ignoreFileFlag, &cacheBackendFlag, + &redisBackendCACert, + &redisBackendCert, + &redisBackendKey, &timeoutFlag, &noProgressFlag, &ignorePolicy, @@ -472,6 +499,9 @@ func NewRootfsCommand() *cli.Command { &securityChecksFlag, &ignoreFileFlag, &cacheBackendFlag, + &redisBackendCACert, + &redisBackendCert, + &redisBackendKey, &timeoutFlag, &noProgressFlag, &ignorePolicy, @@ -510,6 +540,9 @@ func NewRepositoryCommand() *cli.Command { &securityChecksFlag, &ignoreFileFlag, &cacheBackendFlag, + &redisBackendCACert, + &redisBackendCert, + &redisBackendKey, &timeoutFlag, &noProgressFlag, &ignorePolicy, @@ -582,6 +615,9 @@ func NewServerCommand() *cli.Command { &downloadDBOnlyFlag, &resetFlag, &cacheBackendFlag, + &redisBackendCACert, + &redisBackendCert, + &redisBackendKey, // original flags &token, diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index fca707547c..8addf5ca29 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -94,7 +94,7 @@ func runWithTimeout(ctx context.Context, opt Option, initializeScanner Initializ func initFSCache(c Option) (cache.Cache, error) { utils.SetCacheDir(c.CacheDir) - cache, err := operation.NewCache(c.CacheBackend) + cache, err := operation.NewCache(c.CacheOption) if err != nil { return operation.Cache{}, xerrors.Errorf("unable to initialize the cache: %w", err) } diff --git a/pkg/commands/operation/operation.go b/pkg/commands/operation/operation.go index 8863bfecf8..87a1afa685 100644 --- a/pkg/commands/operation/operation.go +++ b/pkg/commands/operation/operation.go @@ -2,6 +2,7 @@ package operation import ( "context" + "crypto/tls" "os" "strings" @@ -11,6 +12,7 @@ import ( "github.com/aquasecurity/fanal/cache" "github.com/aquasecurity/trivy-db/pkg/metadata" + "github.com/aquasecurity/trivy/pkg/commands/option" "github.com/aquasecurity/trivy/pkg/db" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/policy" @@ -30,13 +32,27 @@ type Cache struct { } // NewCache is the factory method for Cache -func NewCache(backend string) (Cache, error) { - if strings.HasPrefix(backend, "redis://") { - log.Logger.Infof("Redis cache: %s", backend) - options, err := redis.ParseURL(backend) +func NewCache(c option.CacheOption) (Cache, error) { + if strings.HasPrefix(c.CacheBackend, "redis://") { + log.Logger.Infof("Redis cache: %s", c.CacheBackend) + options, err := redis.ParseURL(c.CacheBackend) if err != nil { return Cache{}, err } + + if (option.RedisOption{}) != c.RedisOption { + caCert, cert, err := utils.GetTLSConfig(c.RedisCACert, c.RedisCert, c.RedisKey) + if err != nil { + return Cache{}, err + } + + options.TLSConfig = &tls.Config{ + RootCAs: caCert, + Certificates: []tls.Certificate{cert}, + MinVersion: tls.VersionTLS12, + } + } + redisCache := cache.NewRedisCache(options) return Cache{Cache: redisCache}, nil } diff --git a/pkg/commands/option/cache.go b/pkg/commands/option/cache.go index 2ee23d0379..ecaafefa64 100644 --- a/pkg/commands/option/cache.go +++ b/pkg/commands/option/cache.go @@ -10,12 +10,25 @@ import ( // CacheOption holds the options for cache type CacheOption struct { CacheBackend string + RedisOption +} + +// RedisOption holds the options for redis cache +type RedisOption struct { + RedisCACert string + RedisCert string + RedisKey string } // NewCacheOption returns an instance of CacheOption func NewCacheOption(c *cli.Context) CacheOption { return CacheOption{ CacheBackend: c.String("cache-backend"), + RedisOption: RedisOption{ + RedisCACert: c.String("redis-ca"), + RedisCert: c.String("redis-cert"), + RedisKey: c.String("redis-key"), + }, } } @@ -27,5 +40,11 @@ func (c *CacheOption) Init() error { c.CacheBackend != "fs" && c.CacheBackend != "" { return xerrors.Errorf("unsupported cache backend: %s", c.CacheBackend) } + // if one of redis option not nil, make sure CA, cert, and key provided + if (RedisOption{}) != c.RedisOption { + if c.RedisCACert == "" || c.RedisCert == "" || c.RedisKey == "" { + return xerrors.Errorf("you must provide CA, cert and key file path when using tls") + } + } return nil } diff --git a/pkg/commands/server/run.go b/pkg/commands/server/run.go index 0b43d8e5aa..430a0c57ab 100644 --- a/pkg/commands/server/run.go +++ b/pkg/commands/server/run.go @@ -28,7 +28,7 @@ func run(c Config) (err error) { // configure cache dir utils.SetCacheDir(c.CacheDir) - cache, err := operation.NewCache(c.CacheBackend) + cache, err := operation.NewCache(c.CacheOption) if err != nil { return xerrors.Errorf("server cache error: %w", err) } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 4369b1b3ff..89f17d1c8a 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -1,6 +1,8 @@ package utils import ( + "crypto/tls" + "crypto/x509" "fmt" "io" "os" @@ -65,3 +67,21 @@ func CopyFile(src, dst string) (int64, error) { n, err := io.Copy(destination, source) return n, err } + +// getTLSConfig get 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 +}