Skip to content

Use GitLab's advisory DB with cluster image scanning

Why are we doing this work

Similar to Use GitLab's advisory DB with container scanning (#349160 - closed), we want Cluster Image Scanning to use the latest advisories from gemnasium-db.

Relevant links

Non-functional requirements

  • Documentation:
  • Feature flag:
  • Performance:
  • Testing:

Implementation plan

We need to make changes to three components, top-down:

  1. The starboard-operator Helm chart needs to accept a dbRepository value and write it to the starboard-trivy-config ConfigMap.

Toggle diff
diff --git a/deploy/helm/templates/config.yaml b/deploy/helm/templates/config.yaml
@@ -57,6 +57,7 @@ data:
   trivy.nonSslRegistry.{{ $key }}: {{ $registry | quote }}
   {{- end }}
   trivy.severity: {{ .severity | quote }}
+  trivy.dbRepository: {{ .dbRepository | quote }}
   {{- if .ignoreUnfixed }}
   trivy.ignoreUnfixed: {{ .ignoreUnfixed | quote }}
   {{- end }}

diff --git a/deploy/helm/values.yaml b/deploy/helm/values.yaml
@@ -166,6 +166,9 @@ trivy:
   #
   # serverCustomHeaders: "foo=bar"

+  # OCI registry to pull advisory databases from.
+  dbRepository: "ghcr.io/aquasecurity/trivy-db"
+
 kubeBench:
   imageRef: docker.io/aquasec/kube-bench:v0.6.5

  1. The starboard-operator program's Trivy plugin needs to read the dbRepository value from the ConfigMap and pass it along as a parameter to the trivy-download-db initContainers it spawns. This initContainer precedes every container scan and retrieves the advisory database.

Toggle diff
diff --git a/pkg/plugin/trivy/plugin.go b/pkg/plugin/trivy/plugin.go
@@ -43,6 +43,7 @@ const (
 	keyTrivyGitHubToken            = "trivy.githubToken"
 	keyTrivySkipFiles              = "trivy.skipFiles"
 	keyTrivySkipDirs               = "trivy.skipDirs"
+	keyTrivyDBRepository           = "trivy.dbRepository"
 
 	keyTrivyServerURL           = "trivy.serverURL"
 	keyTrivyServerTokenHeader   = "trivy.serverTokenHeader"
@@ -205,6 +206,11 @@ func (c Config) setResourceLimit(configKey string, k8sResourceList *corev1.Resou
 	return nil
 }
 
+func (c Config) GetDBRepository() string {
+	dbRepository, _ := c.GetRequiredData(keyTrivyDBRepository)
+	return dbRepository
+}
+
 type plugin struct {
 	clock          ext.Clock
 	idGenerator    ext.IDGenerator
@@ -238,6 +244,7 @@ func (p *plugin) Init(ctx starboard.PluginContext) error {
 			keyTrivySeverity: "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL",
 			keyTrivyMode:     string(Standalone),
 			keyTrivyTimeout:  "5m0s",
+			keyTrivyDBRepository: "ghcr.io/aquasecurity/trivy-db",
 
 			keyResourcesRequestsCPU:    "100m",
 			keyResourcesRequestsMemory: "100M",
@@ -405,6 +412,8 @@ func (p *plugin) getPodSpecForStandaloneMode(ctx starboard.PluginContext, config
 			"/tmp/trivy/.cache",
 			"image",
 			"--download-db-only",
+			"--db-repository",
+			config.GetDBRepository(),
 		},
 		Resources: requirements,
 		VolumeMounts: []corev1.VolumeMount{
@@ -1033,6 +1042,8 @@ func (p *plugin) getPodSpecForStandaloneFSMode(ctx starboard.PluginContext, conf
 			"--download-db-only",
 			"--cache-dir",
 			"/var/starboard/trivy-db",
+			"--db-repository",
+			config.GetDBRepository(),
 		},
 		Resources:    requirements,
 		VolumeMounts: volumeMounts,

  1. The trivy program needs to accept a new command-line flag --db-repository which determines the advisory database OCI registry.

Toggle diff
diff --git a/pkg/commands/app.go b/pkg/commands/app.go
@@ -313,6 +313,13 @@ var (
                EnvVars: []string{"TRIVY_INSECURE"},
        }

+       dbRepositoryFlag = cli.StringFlag{
+               Name:    "db-repository",
+               Usage:   "OCI repository to retrieve trivy-db from",
+               Value:   "ghcr.io/aquasecurity/trivy-db",
+               EnvVars: []string{"TRIVY_DB_REPOSITORY"},
+       }
+
        // Global flags
        globalFlags = []cli.Flag{
                &quietFlag,
@@ -434,6 +441,7 @@ func NewImageCommand() *cli.Command {
                        &redisBackendKey,
                        &offlineScan,
                        &insecureFlag,
+                       &dbRepositoryFlag,
                        stringSliceFlag(skipFiles),
                        stringSliceFlag(skipDirs),
                },
@@ -470,6 +478,7 @@ func NewFilesystemCommand() *cli.Command {
                        &ignorePolicy,
                        &listAllPackages,
                        &offlineScan,
+                       &dbRepositoryFlag,
                        stringSliceFlag(skipFiles),
                        stringSliceFlag(skipDirs),
                        stringSliceFlag(configPolicy),
diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go
@@ -120,7 +120,7 @@ func initFSCache(c Option) (cache.Cache, error) {
 func initDB(c Option) error {
        // download the database file
        noProgress := c.Quiet || c.NoProgress
-       if err := operation.DownloadDB(c.AppVersion, c.CacheDir, noProgress, c.SkipDBUpdate); err != nil {
+       if err := operation.DownloadDB(c.AppVersion, c.CacheDir, noProgress, c.SkipDBUpdate, c.DBRepository); err != nil {
                return err
        }

diff --git a/pkg/commands/operation/operation.go b/pkg/commands/operation/operation.go
@@ -93,7 +93,7 @@ func (c Cache) ClearArtifacts() error {
 }

 // DownloadDB downloads the DB
-func DownloadDB(appVersion, cacheDir string, quiet, skipUpdate bool) error {
+func DownloadDB(appVersion, cacheDir string, quiet, skipUpdate bool, dbRepository string) error {
        client := db.NewClient(cacheDir, quiet)
        ctx := context.Background()
        needsUpdate, err := client.NeedsUpdate(appVersion, skipUpdate)
@@ -104,7 +104,8 @@ func DownloadDB(appVersion, cacheDir string, quiet, skipUpdate bool) error {
        if needsUpdate {
                log.Logger.Info("Need to update DB")
                log.Logger.Info("Downloading DB...")
-               if err = client.Download(ctx, cacheDir); err != nil {
+               log.Logger.Infof("Repository: %s", dbRepository)
+               if err = client.Download(ctx, cacheDir, dbRepository); err != nil {
                        return xerrors.Errorf("failed to download vulnerability DB: %w", err)
                }
        }
diff --git a/pkg/commands/option/db.go b/pkg/commands/option/db.go
@@ -14,6 +14,7 @@ type DBOption struct {
        SkipDBUpdate   bool
        Light          bool
        NoProgress     bool
+       DBRepository   string
 }

 // NewDBOption is the factory method to return the DBOption
@@ -24,6 +25,7 @@ func NewDBOption(c *cli.Context) DBOption {
                SkipDBUpdate:   c.Bool("skip-db-update"),
                Light:          c.Bool("light"),
                NoProgress:     c.Bool("no-progress"),
+               DBRepository:   c.String("db-repository"),
        }
 }

diff --git a/pkg/commands/server/run.go b/pkg/commands/server/run.go
@@ -40,7 +40,7 @@ func run(c Config) (err error) {
        }

        // download the database file
-       if err = operation.DownloadDB(c.AppVersion, c.CacheDir, true, c.SkipDBUpdate); err != nil {
+       if err = operation.DownloadDB(c.AppVersion, c.CacheDir, true, c.SkipDBUpdate, c.DBRepository); err != nil {
                return err
        }

diff --git a/pkg/db/db.go b/pkg/db/db.go
@@ -15,15 +15,12 @@ import (
        "github.com/aquasecurity/trivy/pkg/log"
 )

-const (
-       dbRepository = "ghcr.io/aquasecurity/trivy-db"
-       dbMediaType  = "application/vnd.aquasec.trivy.db.layer.v1.tar+gzip"
-)
+const dbMediaType = "application/vnd.aquasec.trivy.db.layer.v1.tar+gzip"

 // Operation defines the DB operations
 type Operation interface {
        NeedsUpdate(cliVersion string, skip bool) (need bool, err error)
-       Download(ctx context.Context, dst string) (err error)
+       Download(ctx context.Context, dst string, dbRepository string) (err error)
 }

 type options struct {
@@ -129,13 +126,13 @@ func (c *Client) isNewDB(meta metadata.Metadata) bool {
 }

 // Download downloads the DB file
-func (c *Client) Download(ctx context.Context, dst string) error {
+func (c *Client) Download(ctx context.Context, dst string, dbRepository string) error {
        // Remove the metadata file under the cache directory before downloading DB
        if err := c.metadata.Delete(); err != nil {
                log.Logger.Debug("no metadata file")
        }

-       if err := c.populateOCIArtifact(); err != nil {
+       if err := c.populateOCIArtifact(dbRepository); err != nil {
                return xerrors.Errorf("OCI artifact error: %w", err)
        }

@@ -168,7 +165,7 @@ func (c *Client) updateDownloadedAt(dst string) error {
        return nil
 }

-func (c *Client) populateOCIArtifact() error {
+func (c *Client) populateOCIArtifact(dbRepository string) error {
        if c.artifact == nil {
                repo := fmt.Sprintf("%s:%d", dbRepository, db.SchemaVersion)
                art, err := oci.NewArtifact(repo, dbMediaType, c.quiet)

diff --git a/pkg/rpc/server/listen.go b/pkg/rpc/server/listen.go
@@ -142,7 +142,7 @@ func (w dbWorker) hotUpdate(ctx context.Context, cacheDir string, dbUpdateWg, re
        }
        defer os.RemoveAll(tmpDir)

-       if err = w.dbClient.Download(ctx, tmpDir); err != nil {
+       if err = w.dbClient.Download(ctx, tmpDir, "ghcr.io/aquasecurity/trivy-db"); err != nil {
                return xerrors.Errorf("failed to download vulnerability DB: %w", err)
        }

Edited by Dominic Bauer