Skip to content

Commit

Permalink
feat(cache): add memory backend
Browse files Browse the repository at this point in the history
Signed-off-by: knqyf263 <knqyf263@gmail.com>
  • Loading branch information
knqyf263 committed Jun 27, 2024
1 parent 4f8b399 commit 0888384
Show file tree
Hide file tree
Showing 6 changed files with 561 additions and 22 deletions.
50 changes: 40 additions & 10 deletions docs/docs/configuration/cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The cache option is common to all scanners.
## Clear Caches
`trivy clean` subcommand removes caches.

```
```bash
$ trivy clean --scan-cache
```

Expand All @@ -31,31 +31,59 @@ See `trivy clean --help` for details.
## Cache Directory
Specify where the cache is stored with `--cache-dir`.

```
```bash
$ trivy --cache-dir /tmp/trivy/ image python:3.4-alpine3.9
```

## Cache Backend
## Scan Cache Backend
!!! warning "EXPERIMENTAL"
This feature might change without preserving backwards compatibility.

Trivy supports local filesystem and Redis as the cache backend. This option is useful especially for client/server mode.

Two options:
Trivy utilizes a scan cache to store analysis results, such as package lists.
It supports three types of backends for this cache:

- `fs`
- the cache path can be specified by `--cache-dir`
- `redis://`
- Local File System (`fs`)
- The cache path can be specified by `--cache-dir`
- Memory (`memory`)
- Redis (`redis://`)
- `redis://[HOST]:[PORT]`
- TTL can be configured via `--cache-ttl`

### Local File System
The local file system backend is the default choice for container and VM image scans.
When scanning container images, it stores analysis results on a per-layer basis, using layer IDs as keys.
This approach enables faster scans of the same container image or different images that share layers.

!!! note
Internally, this backend uses [BoltDB][boltdb], which has an important limitation: only one process can access the cache at a time.
Subsequent processes attempting to access the cache will be locked.
For more details on this limitation, refer to the [troubleshooting guide][parallel-run].

### Memory
The memory backend stores analysis results in memory, which means the cache is discarded when the process ends.
This makes it useful in scenarios where caching is not required or desired.
It serves as the default for repository, filesystem and SBOM scans and can also be employed for container image scans when caching is unnecessary.

To use the memory backend for a container image scan, you can use the following command:

```bash
$ trivy image debian:11 --cache-backend memory
```

### Redis

The Redis backend is particularly useful when you need to share the cache across multiple Trivy instances.
You can set up Trivy to use a Redis backend with a command like this:

```bash
$ trivy server --cache-backend redis://localhost:6379
```

This approach allows for centralized caching, which can be beneficial in distributed or high-concurrency environments.

If you want to use TLS with Redis, you can enable it by specifying the `--redis-tls` flag.

```shell
```bash
$ trivy server --cache-backend redis://localhost:6379 --redis-tls
```

Expand All @@ -72,6 +100,8 @@ $ trivy server --cache-backend redis://localhost:6379 \
[trivy-db]: ./db.md#vulnerability-database
[trivy-java-db]: ./db.md#java-index-database
[misconf-checks]: ../scanner/misconfiguration/check/builtin.md
[boltdb]: https://github.com/etcd-io/bbolt
[parallel-run]: https://aquasecurity.github.io/trivy/v0.52/docs/references/troubleshooting/#running-in-parallel-takes-same-time-as-series-run

[^1]: Downloaded when scanning for vulnerabilities
[^2]: Downloaded when scanning `jar/war/par/ear` files
Expand Down
8 changes: 8 additions & 0 deletions pkg/cache/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import (
"time"

"golang.org/x/xerrors"

"github.com/aquasecurity/trivy/pkg/log"
)

const (
TypeUnknown Type = "unknown"
TypeFS Type = "fs"
TypeRedis Type = "redis"
TypeMemory Type = "memory"
)

type Type string
Expand All @@ -33,6 +36,8 @@ func NewType(backend string) Type {
return TypeRedis
case backend == "fs", backend == "":
return TypeFS
case backend == "memory":
return TypeMemory
default:
return TypeUnknown
}
Expand All @@ -44,6 +49,7 @@ func New(opts Options) (Cache, func(), error) {

var cache Cache
t := NewType(opts.Backend)
log.Debug("Initializing scan cache...", log.String("type", string(t)))
switch t {
case TypeRedis:
redisCache, err := NewRedisCache(opts.Backend, opts.RedisCACert, opts.RedisCert, opts.RedisKey, opts.RedisTLS, opts.TTL)
Expand All @@ -58,6 +64,8 @@ func New(opts Options) (Cache, func(), error) {
return nil, cleanup, xerrors.Errorf("unable to initialize fs cache: %w", err)
}
cache = fsCache
case TypeMemory:
cache = NewMemoryCache()
default:
return nil, cleanup, xerrors.Errorf("unknown cache type: %s", t)
}
Expand Down
98 changes: 98 additions & 0 deletions pkg/cache/memory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package cache

import (
"sync"

"golang.org/x/xerrors"

"github.com/aquasecurity/trivy/pkg/fanal/types"
)

var _ Cache = &MemoryCache{}

type MemoryCache struct {
artifacts sync.Map // Map to store artifact information
blobs sync.Map // Map to store blob information
}

func NewMemoryCache() *MemoryCache {
return &MemoryCache{}
}

// PutArtifact stores the artifact information in the memory cache
func (c *MemoryCache) PutArtifact(artifactID string, artifactInfo types.ArtifactInfo) error {
c.artifacts.Store(artifactID, artifactInfo)
return nil
}

// PutBlob stores the blob information in the memory cache
func (c *MemoryCache) PutBlob(blobID string, blobInfo types.BlobInfo) error {
c.blobs.Store(blobID, blobInfo)
return nil
}

// DeleteBlobs removes the specified blobs from the memory cache
func (c *MemoryCache) DeleteBlobs(blobIDs []string) error {
for _, blobID := range blobIDs {
c.blobs.Delete(blobID)
}
return nil
}

// GetArtifact retrieves the artifact information from the memory cache
func (c *MemoryCache) GetArtifact(artifactID string) (types.ArtifactInfo, error) {
info, ok := c.artifacts.Load(artifactID)
if !ok {
return types.ArtifactInfo{}, xerrors.Errorf("artifact (%s) not found in memory cache", artifactID)
}
artifactInfo, ok := info.(types.ArtifactInfo)
if !ok {
return types.ArtifactInfo{}, xerrors.Errorf("invalid type for artifact (%s) in memory cache", artifactID)
}
return artifactInfo, nil
}

// GetBlob retrieves the blob information from the memory cache
func (c *MemoryCache) GetBlob(blobID string) (types.BlobInfo, error) {
info, ok := c.blobs.Load(blobID)
if !ok {
return types.BlobInfo{}, xerrors.Errorf("blob (%s) not found in memory cache", blobID)
}
blobInfo, ok := info.(types.BlobInfo)
if !ok {
return types.BlobInfo{}, xerrors.Errorf("invalid type for blob (%s) in memory cache", blobID)
}
return blobInfo, nil
}

// MissingBlobs determines the missing artifact and blob information in the memory cache
func (c *MemoryCache) MissingBlobs(artifactID string, blobIDs []string) (bool, []string, error) {
var missingArtifact bool
var missingBlobIDs []string

if _, err := c.GetArtifact(artifactID); err != nil {
missingArtifact = true
}

for _, blobID := range blobIDs {
if _, err := c.GetBlob(blobID); err != nil {
missingBlobIDs = append(missingBlobIDs, blobID)
}
}

return missingArtifact, missingBlobIDs, nil
}

// Close clears the artifact and blob information from the memory cache
func (c *MemoryCache) Close() error {
c.artifacts = sync.Map{}
c.blobs = sync.Map{}
return nil
}

// Clear clears the artifact and blob information from the memory cache
func (c *MemoryCache) Clear() error {
c.artifacts = sync.Map{}
c.blobs = sync.Map{}
return nil
}
Loading

0 comments on commit 0888384

Please sign in to comment.