diff --git a/docs/docs/references/cli/fs.md b/docs/docs/references/cli/fs.md index 93a4dfc746..cebcfbb5c5 100644 --- a/docs/docs/references/cli/fs.md +++ b/docs/docs/references/cli/fs.md @@ -15,6 +15,7 @@ OPTIONS: --exit-code value Exit code when vulnerabilities were found (default: 0) [$TRIVY_EXIT_CODE] --skip-db-update, --skip-update skip updating vulnerability database (default: false) [$TRIVY_SKIP_UPDATE, $TRIVY_SKIP_DB_UPDATE] --skip-policy-update skip updating built-in policies (default: false) [$TRIVY_SKIP_POLICY_UPDATE] + --insecure allow insecure server connections when using SSL (default: false) [$TRIVY_INSECURE] --clear-cache, -c clear image caches without scanning (default: false) [$TRIVY_CLEAR_CACHE] --ignore-unfixed display only fixed vulnerabilities (default: false) [$TRIVY_IGNORE_UNFIXED] --vuln-type value comma-separated list of vulnerability types (os,library) (default: "os,library") [$TRIVY_VULN_TYPE] diff --git a/docs/docs/references/cli/rootfs.md b/docs/docs/references/cli/rootfs.md index 5bb941080d..643548f75c 100644 --- a/docs/docs/references/cli/rootfs.md +++ b/docs/docs/references/cli/rootfs.md @@ -14,6 +14,7 @@ OPTIONS: --output value, -o value output file name [$TRIVY_OUTPUT] --exit-code value Exit code when vulnerabilities were found (default: 0) [$TRIVY_EXIT_CODE] --skip-db-update, --skip-update skip updating vulnerability database (default: false) [$TRIVY_SKIP_UPDATE, $TRIVY_SKIP_DB_UPDATE] + --insecure allow insecure server connections when using SSL (default: false) [$TRIVY_INSECURE] --skip-policy-update skip updating built-in policies (default: false) [$TRIVY_SKIP_POLICY_UPDATE] --clear-cache, -c clear image caches without scanning (default: false) [$TRIVY_CLEAR_CACHE] --ignore-unfixed display only fixed vulnerabilities (default: false) [$TRIVY_IGNORE_UNFIXED] diff --git a/docs/docs/references/cli/sbom.md b/docs/docs/references/cli/sbom.md index 5e5c9be4d9..6c2fc99eae 100644 --- a/docs/docs/references/cli/sbom.md +++ b/docs/docs/references/cli/sbom.md @@ -18,6 +18,7 @@ OPTIONS: --severity value, -s value severities of vulnerabilities to be displayed (comma separated) (default: "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL") [$TRIVY_SEVERITY] --offline-scan do not issue API requests to identify dependencies (default: false) [$TRIVY_OFFLINE_SCAN] --db-repository value OCI repository to retrieve trivy-db from (default: "ghcr.io/aquasecurity/trivy-db") [$TRIVY_DB_REPOSITORY] + --insecure allow insecure server connections when using SSL (default: false) [$TRIVY_INSECURE] --skip-files value specify the file paths to skip traversal (accepts multiple inputs) [$TRIVY_SKIP_FILES] --skip-dirs value specify the directories where the traversal is skipped (accepts multiple inputs) [$TRIVY_SKIP_DIRS] --artifact-type value, --type value input artifact type (image, fs, repo, archive) (default: "image") [$TRIVY_ARTIFACT_TYPE] diff --git a/docs/docs/references/cli/server.md b/docs/docs/references/cli/server.md index 200bb9d0d8..c6ad8cac62 100644 --- a/docs/docs/references/cli/server.md +++ b/docs/docs/references/cli/server.md @@ -10,6 +10,7 @@ USAGE: OPTIONS: --skip-db-update, --skip-update skip updating vulnerability database (default: false) [$TRIVY_SKIP_UPDATE, $TRIVY_SKIP_DB_UPDATE] --download-db-only download/update vulnerability database but don't run a scan (default: false) [$TRIVY_DOWNLOAD_DB_ONLY] + --insecure allow insecure server connections when using SSL (default: false) [$TRIVY_INSECURE] --reset remove all caches and database (default: false) [$TRIVY_RESET] --cache-backend value cache backend (e.g. redis://localhost:6379) (default: "fs") [$TRIVY_CACHE_BACKEND] --cache-ttl value cache TTL when using redis as cache backend (default: 0s) [$TRIVY_CACHE_TTL] diff --git a/pkg/commands/app.go b/pkg/commands/app.go index cddf32504f..a7aee7ff50 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -516,6 +516,7 @@ func NewFilesystemCommand() *cli.Command { &exitCodeFlag, &skipDBUpdateFlag, &skipPolicyUpdateFlag, + &insecureFlag, &clearCacheFlag, &ignoreUnfixedFlag, &vulnTypeFlag, @@ -564,6 +565,7 @@ func NewRootfsCommand() *cli.Command { &outputFlag, &exitCodeFlag, &skipDBUpdateFlag, + &insecureFlag, &skipPolicyUpdateFlag, &clearCacheFlag, &ignoreUnfixedFlag, @@ -695,6 +697,7 @@ func NewServerCommand() *cli.Command { Flags: []cli.Flag{ &skipDBUpdateFlag, &downloadDBOnlyFlag, + &insecureFlag, &resetFlag, &cacheBackendFlag, &cacheTTL, @@ -828,6 +831,7 @@ func NewK8sCommand() *cli.Command { &severityFlag, &exitCodeFlag, &skipDBUpdateFlag, + &insecureFlag, &skipPolicyUpdateFlag, &clearCacheFlag, &ignoreUnfixedFlag, @@ -887,6 +891,7 @@ func NewSbomCommand() *cli.Command { &severityFlag, &offlineScan, &dbRepositoryFlag, + &insecureFlag, stringSliceFlag(skipFiles), stringSliceFlag(skipDirs), diff --git a/pkg/commands/artifact/option.go b/pkg/commands/artifact/option.go index 4d7e5c5ef2..1ec4ea20fb 100644 --- a/pkg/commands/artifact/option.go +++ b/pkg/commands/artifact/option.go @@ -21,6 +21,7 @@ type Option struct { option.SbomOption option.SecretOption option.KubernetesOption + option.OtherOption // We don't want to allow disabled analyzers to be passed by users, // but it differs depending on scanning modes. @@ -46,6 +47,7 @@ func NewOption(c *cli.Context) (Option, error) { SbomOption: option.NewSbomOption(c), SecretOption: option.NewSecretOption(c), KubernetesOption: option.NewKubernetesOption(c), + OtherOption: option.NewOtherOption(c), }, nil } diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 18c1ca99e4..cee88b3421 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -230,7 +230,7 @@ func (r *Runner) initDB(c Option) error { // download the database file noProgress := c.Quiet || c.NoProgress - if err := operation.DownloadDB(c.AppVersion, c.CacheDir, c.DBRepository, noProgress, c.SkipDBUpdate); err != nil { + if err := operation.DownloadDB(c.AppVersion, c.CacheDir, c.DBRepository, noProgress, c.Insecure, c.SkipDBUpdate); err != nil { return err } diff --git a/pkg/commands/operation/operation.go b/pkg/commands/operation/operation.go index 43bec8e7c3..7f7e8b1e2b 100644 --- a/pkg/commands/operation/operation.go +++ b/pkg/commands/operation/operation.go @@ -98,8 +98,8 @@ func (c Cache) ClearArtifacts() error { } // DownloadDB downloads the DB -func DownloadDB(appVersion, cacheDir, dbRepository string, quiet, skipUpdate bool) error { - client := db.NewClient(cacheDir, quiet, db.WithDBRepository(dbRepository)) +func DownloadDB(appVersion, cacheDir, dbRepository string, quiet, insecure, skipUpdate bool) error { + client := db.NewClient(cacheDir, quiet, insecure, db.WithDBRepository(dbRepository)) ctx := context.Background() needsUpdate, err := client.NeedsUpdate(appVersion, skipUpdate) if err != nil { diff --git a/pkg/commands/option/artifact.go b/pkg/commands/option/artifact.go index 63afba5b15..6903594803 100644 --- a/pkg/commands/option/artifact.go +++ b/pkg/commands/option/artifact.go @@ -14,7 +14,6 @@ type ArtifactOption struct { Input string Timeout time.Duration ClearCache bool - Insecure bool SkipDirs []string SkipFiles []string @@ -33,13 +32,11 @@ func NewArtifactOption(c *cli.Context) ArtifactOption { SkipFiles: c.StringSlice("skip-files"), SkipDirs: c.StringSlice("skip-dirs"), OfflineScan: c.Bool("offline-scan"), - Insecure: c.Bool("insecure"), } } // Init initialize the CLI context for artifact scanning func (c *ArtifactOption) Init(ctx *cli.Context, logger *zap.SugaredLogger) (err error) { - // kubernetes subcommand doesn't require any argument if ctx.Command.Name == "kubernetes" { return nil diff --git a/pkg/commands/option/others.go b/pkg/commands/option/others.go new file mode 100644 index 0000000000..2343ffec7f --- /dev/null +++ b/pkg/commands/option/others.go @@ -0,0 +1,14 @@ +package option + +import "github.com/urfave/cli/v2" + +type OtherOption struct { + Insecure bool +} + +// NewOtherOption is the factory method to return other option +func NewOtherOption(c *cli.Context) OtherOption { + return OtherOption{ + Insecure: c.Bool("insecure"), + } +} diff --git a/pkg/commands/server/config.go b/pkg/commands/server/option.go similarity index 73% rename from pkg/commands/server/config.go rename to pkg/commands/server/option.go index 0c0f031531..9878e8730c 100644 --- a/pkg/commands/server/config.go +++ b/pkg/commands/server/option.go @@ -6,25 +6,27 @@ import ( "github.com/aquasecurity/trivy/pkg/commands/option" ) -// Config holds the Trivy config -type Config struct { +// Option holds the Trivy config +type Option struct { option.GlobalOption option.DBOption option.CacheOption + option.OtherOption Listen string Token string TokenHeader string } -// NewConfig is the factory method to return config -func NewConfig(c *cli.Context) Config { +// NewOption is the factory method to return config +func NewOption(c *cli.Context) Option { // the error is ignored because logger is unnecessary gc, _ := option.NewGlobalOption(c) // nolint: errcheck - return Config{ + return Option{ GlobalOption: gc, DBOption: option.NewDBOption(c), CacheOption: option.NewCacheOption(c), + OtherOption: option.NewOtherOption(c), Listen: c.String("listen"), Token: c.String("token"), @@ -33,7 +35,7 @@ func NewConfig(c *cli.Context) Config { } // Init initializes the config -func (c *Config) Init() (err error) { +func (c *Option) Init() (err error) { if err := c.DBOption.Init(); err != nil { return err } diff --git a/pkg/commands/server/config_test.go b/pkg/commands/server/option_test.go similarity index 95% rename from pkg/commands/server/config_test.go rename to pkg/commands/server/option_test.go index ca861eef1b..92df369335 100644 --- a/pkg/commands/server/config_test.go +++ b/pkg/commands/server/option_test.go @@ -16,12 +16,12 @@ func TestNew(t *testing.T) { tests := []struct { name string args []string - want server.Config + want server.Option }{ { name: "happy path", args: []string{"-quiet", "--no-progress", "--reset", "--skip-db-update", "--listen", "localhost:8080"}, - want: server.Config{ + want: server.Option{ GlobalOption: option.GlobalOption{ Quiet: true, }, @@ -49,7 +49,7 @@ func TestNew(t *testing.T) { tt.want.GlobalOption.Context = ctx - got := server.NewConfig(ctx) + got := server.NewOption(ctx) assert.Equal(t, tt.want.GlobalOption.Quiet, got.Quiet, tt.name) assert.Equal(t, tt.want.DBOption, got.DBOption, tt.name) assert.Equal(t, tt.want.Listen, got.Listen, tt.name) @@ -88,7 +88,7 @@ func TestConfig_Init(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := &server.Config{ + c := &server.Option{ DBOption: tt.dbConfig, } diff --git a/pkg/commands/server/run.go b/pkg/commands/server/run.go index de5b94c2a5..6dc0dc624c 100644 --- a/pkg/commands/server/run.go +++ b/pkg/commands/server/run.go @@ -13,10 +13,10 @@ import ( // Run runs the scan func Run(ctx *cli.Context) error { - return run(NewConfig(ctx)) + return run(NewOption(ctx)) } -func run(c Config) (err error) { +func run(c Option) (err error) { if err = log.InitLogger(c.Debug, c.Quiet); err != nil { return xerrors.Errorf("failed to initialize a logger: %w", err) } @@ -40,7 +40,7 @@ func run(c Config) (err error) { } // download the database file - if err = operation.DownloadDB(c.AppVersion, c.CacheDir, c.DBRepository, true, c.SkipDBUpdate); err != nil { + if err = operation.DownloadDB(c.AppVersion, c.CacheDir, c.DBRepository, true, c.Insecure, c.SkipDBUpdate); err != nil { return err } @@ -53,5 +53,5 @@ func run(c Config) (err error) { } server := rpcServer.NewServer(c.AppVersion, c.Listen, c.CacheDir, c.Token, c.TokenHeader) - return server.ListenAndServe(cache) + return server.ListenAndServe(cache, c.Insecure) } diff --git a/pkg/db/db.go b/pkg/db/db.go index e33aa11f2f..1f11dadf2e 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -60,13 +60,14 @@ func WithClock(clock clock.Clock) Option { type Client struct { *options - cacheDir string - metadata metadata.Client - quiet bool + cacheDir string + metadata metadata.Client + quiet bool + insecureSkipTLSVerify bool } // NewClient is the factory method for DB client -func NewClient(cacheDir string, quiet bool, opts ...Option) *Client { +func NewClient(cacheDir string, quiet, insecure bool, opts ...Option) *Client { o := &options{ clock: clock.RealClock{}, dbRepository: defaultDBRepository, @@ -77,10 +78,11 @@ func NewClient(cacheDir string, quiet bool, opts ...Option) *Client { } return &Client{ - options: o, - cacheDir: cacheDir, - metadata: metadata.NewClient(cacheDir), - quiet: quiet, + options: o, + cacheDir: cacheDir, + metadata: metadata.NewClient(cacheDir), + quiet: quiet, + insecureSkipTLSVerify: insecure, // insecure skip for download DB } } @@ -183,7 +185,7 @@ func (c *Client) updateDownloadedAt(dst string) error { func (c *Client) populateOCIArtifact() error { if c.artifact == nil { repo := fmt.Sprintf("%s:%d", c.dbRepository, db.SchemaVersion) - art, err := oci.NewArtifact(repo, dbMediaType, c.quiet) + art, err := oci.NewArtifact(repo, dbMediaType, c.quiet, c.insecureSkipTLSVerify) if err != nil { return xerrors.Errorf("OCI artifact error: %w", err) } diff --git a/pkg/db/db_test.go b/pkg/db/db_test.go index 9ed0c56b6f..13cd4a4b36 100644 --- a/pkg/db/db_test.go +++ b/pkg/db/db_test.go @@ -152,7 +152,7 @@ func TestClient_NeedsUpdate(t *testing.T) { require.NoError(t, err) } - client := db.NewClient(cacheDir, true, db.WithClock(tt.clock)) + client := db.NewClient(cacheDir, true, false, db.WithClock(tt.clock)) needsUpdate, err := client.NeedsUpdate("test", tt.skip) switch { @@ -203,10 +203,10 @@ func TestClient_Download(t *testing.T) { img.LayersReturns([]v1.Layer{newFakeLayer(t, tt.input)}, nil) // Mock OCI artifact - art, err := oci.NewArtifact("db", mediaType, true, oci.WithImage(img)) + art, err := oci.NewArtifact("db", mediaType, true, false, oci.WithImage(img)) require.NoError(t, err) - client := db.NewClient(cacheDir, true, db.WithOCIArtifact(art), db.WithClock(timeDownloadedAt)) + client := db.NewClient(cacheDir, true, false, db.WithOCIArtifact(art), db.WithClock(timeDownloadedAt)) err = client.Download(context.Background(), cacheDir) if tt.wantErr != "" { require.Error(t, err) diff --git a/pkg/oci/artifact.go b/pkg/oci/artifact.go index 1cbedcd895..62cabe66c0 100644 --- a/pkg/oci/artifact.go +++ b/pkg/oci/artifact.go @@ -2,7 +2,9 @@ package oci import ( "context" + "crypto/tls" "io" + "net/http" "os" "github.com/cheggaaa/pb/v3" @@ -37,7 +39,7 @@ type Artifact struct { } // NewArtifact returns a new artifact -func NewArtifact(repo, mediaType string, quiet bool, opts ...Option) (*Artifact, error) { +func NewArtifact(repo, mediaType string, quiet, insecure bool, opts ...Option) (*Artifact, error) { o := &options{} for _, opt := range opts { @@ -50,7 +52,15 @@ func NewArtifact(repo, mediaType string, quiet bool, opts ...Option) (*Artifact, return nil, xerrors.Errorf("repository name error (%s): %w", repo, err) } - o.img, err = remote.Image(ref) + var remoteOpts []remote.Option + if insecure { + t := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + remoteOpts = append(remoteOpts, remote.WithTransport(t)) + } + + o.img, err = remote.Image(ref, remoteOpts...) if err != nil { return nil, xerrors.Errorf("OCI repository error: %w", err) } diff --git a/pkg/oci/artifact_test.go b/pkg/oci/artifact_test.go index bf2c947f19..8c21d69a90 100644 --- a/pkg/oci/artifact_test.go +++ b/pkg/oci/artifact_test.go @@ -83,7 +83,7 @@ func TestNewArtifact(t *testing.T) { img := new(fakei.FakeImage) img.LayersReturns(tt.layersReturns.layers, tt.layersReturns.err) - _, err = oci.NewArtifact("repo", tt.mediaType, true, oci.WithImage(img)) + _, err = oci.NewArtifact("repo", tt.mediaType, true, false, oci.WithImage(img)) if tt.wantErr != "" { require.Error(t, err) assert.Contains(t, err.Error(), tt.wantErr) @@ -128,7 +128,7 @@ func TestArtifact_Download(t *testing.T) { img.LayersReturns([]v1.Layer{flayer}, nil) mediaType := "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip" - artifact, err := oci.NewArtifact("repo", mediaType, true, oci.WithImage(img)) + artifact, err := oci.NewArtifact("repo", mediaType, true, false, oci.WithImage(img)) require.NoError(t, err) err = artifact.Download(context.Background(), tempDir) diff --git a/pkg/rpc/server/listen.go b/pkg/rpc/server/listen.go index 5af2751a9c..c77482b445 100644 --- a/pkg/rpc/server/listen.go +++ b/pkg/rpc/server/listen.go @@ -45,12 +45,12 @@ func NewServer(appVersion, addr, cacheDir, token, tokenHeader string) Server { } // ListenAndServe starts Trivy server -func (s Server) ListenAndServe(serverCache cache.Cache) error { +func (s Server) ListenAndServe(serverCache cache.Cache, insecure bool) error { requestWg := &sync.WaitGroup{} dbUpdateWg := &sync.WaitGroup{} go func() { - worker := newDBWorker(dbc.NewClient(s.cacheDir, true)) + worker := newDBWorker(dbc.NewClient(s.cacheDir, true, insecure)) ctx := context.Background() for { time.Sleep(updateInterval) @@ -130,7 +130,7 @@ func (w dbWorker) update(ctx context.Context, appVersion, cacheDir string, log.Logger.Info("Updating DB...") if err = w.hotUpdate(ctx, cacheDir, dbUpdateWg, requestWg); err != nil { - return xerrors.Errorf("failed DB hot update") + return xerrors.Errorf("failed DB hot update: %w", err) } return nil }