Skip to content

Commit

Permalink
feat: add auth support for downloading OCI artifacts (#3915)
Browse files Browse the repository at this point in the history
  • Loading branch information
knqyf263 committed Mar 30, 2023
1 parent 1ee0518 commit f14bed4
Show file tree
Hide file tree
Showing 22 changed files with 280 additions and 215 deletions.
38 changes: 38 additions & 0 deletions docs/docs/vulnerability/db.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Database
Trivy uses two types of databases for vulnerability detection:

- Vulnerability Database
- Java Index Database

This page provides detailed information about these databases.

## Vulnerability Database
Trivy utilizes a database containing vulnerability information.
This database is built every six hours on [GitHub](https://github.com/aquasecurity/trivy-db) and is distributed via [GitHub Container registry (GHCR)](https://ghcr.io/aquasecurity/trivy-db).
The database is cached and updated as needed.
As Trivy updates the database automatically during execution, users don't need to be concerned about it.

For CLI flags related to the database, please refer to [this page](./examples/db.md).

### Private Hosting
If you host the database on your own OCI registry, you can specify a different repository with the `--db-repository` flag.
The default is `ghcr.io/aquasecurity/trivy-db`.

```shell
$ trivy image --db-repository YOUR_REPO YOUR_IMAGE
```

If authentication is required, it can be configured in the same way as for private images.
Please refer to [the documentation](../advanced/private-registries/index.md) for more details.

## Java Index Database
This database is only downloaded when scanning JAR files so that Trivy can identify the groupId, artifactId, and version of JAR files.
It is built once a day on [GitHub](https://github.com/aquasecurity/trivy-java-db) and distributed via [GitHub Container registry (GHCR)](https://ghcr.io/aquasecurity/trivy-java-db).
Like the vulnerability database, it is automatically downloaded and updated when needed, so users don't need to worry about it.

### Private Hosting
If you host the database on your own OCI registry, you can specify a different repository with the `--java-db-repository` flag.
The default is `ghcr.io/aquasecurity/trivy-java-db`.

If authentication is required, you need to run `docker login YOUR_REGISTRY`.
Currently, specifying a username and password is not supported.
4 changes: 1 addition & 3 deletions docs/docs/vulnerability/examples/db.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# Vulnerability DB

## Skip update of vulnerability DB
`Trivy` downloads its vulnerability database every 12 hours when it starts operating.
This is usually fast, as the size of the DB is only 10~30MB.
But if you want to skip even that, use the `--skip-db-update` option.
If you want to skip downloading the vulnerability database, use the `--skip-db-update` option.

```
$ trivy image --skip-db-update python:3.4-alpine3.9
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ nav:
- OS Packages: docs/vulnerability/detection/os.md
- Language-specific Packages: docs/vulnerability/detection/language.md
- Data Sources: docs/vulnerability/detection/data-source.md
- Database: docs/vulnerability/db.md
- Examples:
- Vulnerability Filtering: docs/vulnerability/examples/filter.md
- Report Formats: docs/vulnerability/examples/report.md
Expand Down
2 changes: 1 addition & 1 deletion pkg/commands/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,7 @@ func NewModuleCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
if err != nil {
return xerrors.Errorf("flag error: %w", err)
}
return module.Install(cmd.Context(), opts.ModuleDir, repo, opts.Quiet, opts.Insecure)
return module.Install(cmd.Context(), opts.ModuleDir, repo, opts.Quiet, opts.Remote())
},
},
&cobra.Command{
Expand Down
15 changes: 4 additions & 11 deletions pkg/commands/artifact/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/config"
"github.com/aquasecurity/trivy/pkg/fanal/artifact"
"github.com/aquasecurity/trivy/pkg/fanal/cache"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/javadb"
"github.com/aquasecurity/trivy/pkg/log"
Expand Down Expand Up @@ -315,7 +314,7 @@ func (r *runner) initDB(opts flag.Options) error {

// download the database file
noProgress := opts.Quiet || opts.NoProgress
if err := operation.DownloadDB(opts.AppVersion, opts.CacheDir, opts.DBRepository, noProgress, opts.Insecure, opts.SkipDBUpdate); err != nil {
if err := operation.DownloadDB(opts.AppVersion, opts.CacheDir, opts.DBRepository, noProgress, opts.SkipDBUpdate, opts.Remote()); err != nil {
return err
}

Expand Down Expand Up @@ -610,13 +609,7 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi
}
}

remoteOption := ftypes.RemoteOptions{
Credentials: opts.Credentials,
RegistryToken: opts.RegistryToken,
Insecure: opts.Insecure,
Platform: opts.Platform,
AWSRegion: opts.AWSOptions.Region,
}
remoteOpts := opts.Remote()

return ScannerConfig{
Target: target,
Expand All @@ -643,8 +636,8 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi
Slow: opts.Slow,
AWSRegion: opts.Region,

// For container registries
RemoteOptions: remoteOption,
// For OCI registries
RemoteOptions: remoteOpts,

// For misconfiguration scanning
MisconfScannerOption: configScannerOptions,
Expand Down
9 changes: 5 additions & 4 deletions pkg/commands/operation/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/aquasecurity/trivy-db/pkg/metadata"
"github.com/aquasecurity/trivy/pkg/db"
"github.com/aquasecurity/trivy/pkg/fanal/cache"
"github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/policy"
Expand Down Expand Up @@ -101,8 +102,8 @@ func (c Cache) ClearArtifacts() error {
}

// DownloadDB downloads the DB
func DownloadDB(appVersion, cacheDir, dbRepository string, quiet, insecure, skipUpdate bool) error {
client := db.NewClient(cacheDir, quiet, insecure, db.WithDBRepository(dbRepository))
func DownloadDB(appVersion, cacheDir, dbRepository string, quiet, skipUpdate bool, opt types.RemoteOptions) error {
client := db.NewClient(cacheDir, quiet, db.WithDBRepository(dbRepository))
ctx := context.Background()
needsUpdate, err := client.NeedsUpdate(appVersion, skipUpdate)
if err != nil {
Expand All @@ -113,7 +114,7 @@ func DownloadDB(appVersion, cacheDir, dbRepository string, quiet, insecure, skip
log.Logger.Info("Need to update DB")
log.Logger.Infof("DB Repository: %s", dbRepository)
log.Logger.Info("Downloading DB...")
if err = client.Download(ctx, cacheDir); err != nil {
if err = client.Download(ctx, cacheDir, opt); err != nil {
return xerrors.Errorf("failed to download vulnerability DB: %w", err)
}
}
Expand Down Expand Up @@ -145,7 +146,7 @@ func InitBuiltinPolicies(ctx context.Context, cacheDir string, quiet, skipUpdate

needsUpdate := false
if !skipUpdate {
needsUpdate, err = client.NeedsUpdate()
needsUpdate, err = client.NeedsUpdate(ctx)
if err != nil {
return nil, xerrors.Errorf("unable to check if built-in policies need to be updated: %w", err)
}
Expand Down
7 changes: 4 additions & 3 deletions pkg/commands/server/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func Run(ctx context.Context, opts flag.Options) (err error) {

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

Expand All @@ -57,6 +57,7 @@ func Run(ctx context.Context, opts flag.Options) (err error) {
}
m.Register()

server := rpcServer.NewServer(opts.AppVersion, opts.Listen, opts.CacheDir, opts.Token, opts.TokenHeader, opts.DBRepository)
return server.ListenAndServe(cache, opts.Insecure, opts.SkipDBUpdate)
server := rpcServer.NewServer(opts.AppVersion, opts.Listen, opts.CacheDir, opts.Token, opts.TokenHeader,
opts.DBRepository, opts.Remote())
return server.ListenAndServe(cache, opts.SkipDBUpdate)
}
31 changes: 15 additions & 16 deletions pkg/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy-db/pkg/metadata"
"github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/oci"
)
Expand All @@ -24,7 +25,7 @@ const (
// 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, opt types.RemoteOptions) (err error)
}

type options struct {
Expand Down Expand Up @@ -61,14 +62,13 @@ func WithClock(clock clock.Clock) Option {
type Client struct {
*options

cacheDir string
metadata metadata.Client
quiet bool
insecureSkipTLSVerify bool
cacheDir string
metadata metadata.Client
quiet bool
}

// NewClient is the factory method for DB client
func NewClient(cacheDir string, quiet, insecure bool, opts ...Option) *Client {
func NewClient(cacheDir string, quiet bool, opts ...Option) *Client {
o := &options{
clock: clock.RealClock{},
dbRepository: defaultDBRepository,
Expand All @@ -79,11 +79,10 @@ func NewClient(cacheDir string, quiet, insecure bool, opts ...Option) *Client {
}

return &Client{
options: o,
cacheDir: cacheDir,
metadata: metadata.NewClient(cacheDir),
quiet: quiet,
insecureSkipTLSVerify: insecure, // insecure skip for download DB
options: o,
cacheDir: cacheDir,
metadata: metadata.NewClient(cacheDir),
quiet: quiet,
}
}

Expand Down Expand Up @@ -144,18 +143,18 @@ 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, opt types.RemoteOptions) 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")
}

art, err := c.initOCIArtifact()
art, err := c.initOCIArtifact(opt)
if err != nil {
return xerrors.Errorf("OCI artifact error: %w", err)
}

if err = art.Download(ctx, db.Dir(dst)); err != nil {
if err = art.Download(ctx, db.Dir(dst), oci.DownloadOption{MediaType: dbMediaType}); err != nil {
return xerrors.Errorf("database download error: %w", err)
}

Expand Down Expand Up @@ -184,13 +183,13 @@ func (c *Client) updateDownloadedAt(dst string) error {
return nil
}

func (c *Client) initOCIArtifact() (*oci.Artifact, error) {
func (c *Client) initOCIArtifact(opt types.RemoteOptions) (*oci.Artifact, error) {
if c.artifact != nil {
return c.artifact, nil
}

repo := fmt.Sprintf("%s:%d", c.dbRepository, db.SchemaVersion)
art, err := oci.NewArtifact(repo, dbMediaType, "", c.quiet, c.insecureSkipTLSVerify)
art, err := oci.NewArtifact(repo, c.quiet, opt)
if err != nil {
var terr *transport.Error
if errors.As(err, &terr) {
Expand Down
12 changes: 8 additions & 4 deletions pkg/db/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
tdb "github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy-db/pkg/metadata"
"github.com/aquasecurity/trivy/pkg/db"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/oci"
)

Expand Down Expand Up @@ -152,7 +153,7 @@ func TestClient_NeedsUpdate(t *testing.T) {
require.NoError(t, err)
}

client := db.NewClient(cacheDir, true, false, db.WithClock(tt.clock))
client := db.NewClient(cacheDir, true, db.WithClock(tt.clock))
needsUpdate, err := client.NeedsUpdate("test", tt.skip)

switch {
Expand Down Expand Up @@ -218,11 +219,14 @@ func TestClient_Download(t *testing.T) {
}, nil)

// Mock OCI artifact
art, err := oci.NewArtifact("db", mediaType, "", true, false, oci.WithImage(img))
opt := ftypes.RemoteOptions{
Insecure: false,
}
art, err := oci.NewArtifact("db", true, opt, oci.WithImage(img))
require.NoError(t, err)

client := db.NewClient(cacheDir, true, false, db.WithOCIArtifact(art), db.WithClock(timeDownloadedAt))
err = client.Download(context.Background(), cacheDir)
client := db.NewClient(cacheDir, true, db.WithOCIArtifact(art), db.WithClock(timeDownloadedAt))
err = client.Download(context.Background(), cacheDir, opt)
if tt.wantErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
Expand Down
6 changes: 4 additions & 2 deletions pkg/db/mock_operation.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pkg/fanal/artifact/artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type Option struct {
Slow bool // Lower CPU and memory
AWSRegion string

// For container registries
// For OCI registries
types.RemoteOptions

MisconfScannerOption misconf.ScannerOption
Expand Down
7 changes: 5 additions & 2 deletions pkg/fanal/artifact/image/remote_sbom.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func (a Artifact) inspectOCIReferrerSBOM(ctx context.Context) (ftypes.ArtifactRe
func (a Artifact) parseReferrer(ctx context.Context, repo string, desc v1.Descriptor) (ftypes.ArtifactReference, error) {
const fileName string = "referrer.sbom"
repoName := fmt.Sprintf("%s@%s", repo, desc.Digest)
referrer, err := oci.NewArtifact(repoName, desc.ArtifactType, fileName, true, a.artifactOption.Insecure)
referrer, err := oci.NewArtifact(repoName, true, a.artifactOption.RemoteOptions)
if err != nil {
return ftypes.ArtifactReference{}, xerrors.Errorf("OCI error: %w", err)
}
Expand All @@ -93,7 +93,10 @@ func (a Artifact) parseReferrer(ctx context.Context, repo string, desc v1.Descri
defer os.RemoveAll(tmpDir)

// Download SBOM to local filesystem
if err = referrer.Download(ctx, tmpDir); err != nil {
if err = referrer.Download(ctx, tmpDir, oci.DownloadOption{
MediaType: desc.ArtifactType,
Filename: fileName,
}); err != nil {
return ftypes.ArtifactReference{}, xerrors.Errorf("SBOM download error: %w", err)
}

Expand Down
12 changes: 12 additions & 0 deletions pkg/flag/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"golang.org/x/xerrors"

"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/report"
)
Expand Down Expand Up @@ -121,6 +122,17 @@ func (o *Options) Align() {
}
}

// Remote returns options for OCI registries
func (o *Options) Remote() ftypes.RemoteOptions {
return ftypes.RemoteOptions{
Credentials: o.Credentials,
RegistryToken: o.RegistryToken,
Insecure: o.Insecure,
Platform: o.Platform,
AWSRegion: o.AWSOptions.Region,
}
}

func addFlag(cmd *cobra.Command, flag *Flag) {
if flag == nil || flag.Name == "" {
return
Expand Down
6 changes: 4 additions & 2 deletions pkg/javadb/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/aquasecurity/go-dep-parser/pkg/java/jar"
"github.com/aquasecurity/trivy-java-db/pkg/db"
"github.com/aquasecurity/trivy-java-db/pkg/types"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/oci"
)
Expand Down Expand Up @@ -51,11 +52,12 @@ func (u *Updater) Update() error {
log.Logger.Infof("Java DB Repository: %s", u.repo)
log.Logger.Info("Downloading the Java DB...")

// TODO: support remote options
var a *oci.Artifact
if a, err = oci.NewArtifact(u.repo, mediaType, "", u.quiet, u.insecure); err != nil {
if a, err = oci.NewArtifact(u.repo, u.quiet, ftypes.RemoteOptions{}); err != nil {
return xerrors.Errorf("oci error: %w", err)
}
if err = a.Download(context.Background(), dbDir); err != nil {
if err = a.Download(context.Background(), dbDir, oci.DownloadOption{MediaType: mediaType}); err != nil {
return xerrors.Errorf("DB download error: %w", err)
}

Expand Down

0 comments on commit f14bed4

Please sign in to comment.