diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 71c85aaa5933..c130deb64876 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -62,6 +62,7 @@ Use "{{.CommandPath}} [command] --help" for more information about a command.{{e // NewApp is the factory method to return Trivy CLI func NewApp() *cobra.Command { + cobra.EnableTraverseRunHooks = true // To execute persistent pre-run hooks from all parents. globalFlags := flag.NewGlobalFlagGroup() rootCmd := NewRootCommand(globalFlags) rootCmd.AddGroup( @@ -89,7 +90,7 @@ func NewApp() *cobra.Command { NewServerCommand(globalFlags), NewConfigCommand(globalFlags), NewConvertCommand(globalFlags), - NewPluginCommand(), + NewPluginCommand(globalFlags), NewModuleCommand(globalFlags), NewKubernetesCommand(globalFlags), NewSBOMCommand(globalFlags), @@ -719,7 +720,11 @@ func NewConfigCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { return cmd } -func NewPluginCommand() *cobra.Command { +func NewPluginCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + var pluginOptions flag.Options + pluginFlags := &flag.Flags{ + GlobalFlagGroup: globalFlags, + } cmd := &cobra.Command{ Use: "plugin subcommand", Aliases: []string{"p"}, @@ -727,6 +732,13 @@ func NewPluginCommand() *cobra.Command { Short: "Manage plugins", SilenceErrors: true, SilenceUsage: true, + PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { + pluginOptions, err = pluginFlags.ToOptions(args) + if err != nil { + return err + } + return nil + }, } cmd.AddCommand( &cobra.Command{ @@ -746,7 +758,7 @@ func NewPluginCommand() *cobra.Command { DisableFlagsInUseLine: true, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - if _, err := plugin.Install(cmd.Context(), args[0], plugin.Options{}); err != nil { + if _, err := plugin.Install(cmd.Context(), args[0], plugin.Options{Insecure: pluginOptions.Insecure}); err != nil { return xerrors.Errorf("plugin install error: %w", err) } return nil @@ -805,7 +817,10 @@ func NewPluginCommand() *cobra.Command { Short: "Run a plugin on the fly", Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return plugin.Run(cmd.Context(), args[0], plugin.Options{Args: args[1:]}) + return plugin.Run(cmd.Context(), args[0], plugin.Options{ + Args: args[1:], + Insecure: pluginOptions.Insecure, + }) }, }, &cobra.Command{ @@ -816,7 +831,7 @@ func NewPluginCommand() *cobra.Command { SilenceUsage: true, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { - if err := plugin.Update(cmd.Context()); err != nil { + if err := plugin.Update(cmd.Context(), plugin.Options{Insecure: pluginOptions.Insecure}); err != nil { return xerrors.Errorf("plugin update error: %w", err) } return nil diff --git a/pkg/downloader/download.go b/pkg/downloader/download.go index 9b554b6a4eb8..7190d3d3d0a3 100644 --- a/pkg/downloader/download.go +++ b/pkg/downloader/download.go @@ -10,8 +10,8 @@ import ( ) // DownloadToTempDir downloads the configured source to a temp dir. -func DownloadToTempDir(ctx context.Context, url string) (string, error) { - tempDir, err := os.MkdirTemp("", "trivy-plugin") +func DownloadToTempDir(ctx context.Context, url string, insecure bool) (string, error) { + tempDir, err := os.MkdirTemp("", "trivy-download") if err != nil { return "", xerrors.Errorf("failed to create a temp dir: %w", err) } @@ -21,7 +21,7 @@ func DownloadToTempDir(ctx context.Context, url string) (string, error) { return "", xerrors.Errorf("unable to get the current dir: %w", err) } - if err = Download(ctx, url, tempDir, pwd); err != nil { + if err = Download(ctx, url, tempDir, pwd, insecure); err != nil { return "", xerrors.Errorf("download error: %w", err) } @@ -29,11 +29,14 @@ func DownloadToTempDir(ctx context.Context, url string) (string, error) { } // Download downloads the configured source to the destination. -func Download(ctx context.Context, src, dst, pwd string) error { +func Download(ctx context.Context, src, dst, pwd string, insecure bool) error { // go-getter doesn't allow the dst directory already exists if the src is directory. _ = os.RemoveAll(dst) var opts []getter.ClientOption + if insecure { + opts = append(opts, getter.WithInsecure()) + } // Clone the global map so that it will not be accessed concurrently. getters := maps.Clone(getter.Getters) @@ -41,6 +44,14 @@ func Download(ctx context.Context, src, dst, pwd string) error { // Overwrite the file getter so that a file will be copied getters["file"] = &getter.FileGetter{Copy: true} + // Since "httpGetter" is a global pointer and the state is shared, + // once it is executed without "WithInsecure()", + // it cannot enable WithInsecure() afterwards because its state is preserved. + // cf. https://github.com/hashicorp/go-getter/blob/5a63fd9c0d5b8da8a6805e8c283f46f0dacb30b3/get.go#L63-L65 + httpGetter := &getter.HttpGetter{Netrc: true} + getters["http"] = httpGetter + getters["https"] = httpGetter + // Build the client client := &getter.Client{ Ctx: ctx, diff --git a/pkg/downloader/downloader_test.go b/pkg/downloader/downloader_test.go new file mode 100644 index 000000000000..80e7ef530310 --- /dev/null +++ b/pkg/downloader/downloader_test.go @@ -0,0 +1,61 @@ +package downloader_test + +import ( + "context" + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/downloader" +) + +func TestDownload(t *testing.T) { + // Set up a test server with a self-signed certificate + server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte("test content")) + require.NoError(t, err) + })) + defer server.Close() + + tests := []struct { + name string + insecure bool + wantErr bool + }{ + { + "Secure (should fail)", + false, + true, + }, + { + "Insecure (should succeed)", + true, + false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Set up the destination path + dst := t.TempDir() + + // Execute the download + err := downloader.Download(context.Background(), server.URL, dst, "", tt.insecure) + + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + + // Check the content of the downloaded file + content, err := os.ReadFile(dst) + require.NoError(t, err) + assert.Equal(t, "test content", string(content)) + }) + } +} diff --git a/pkg/oci/artifact.go b/pkg/oci/artifact.go index bab9ec7065ab..8cd4460ec919 100644 --- a/pkg/oci/artifact.go +++ b/pkg/oci/artifact.go @@ -188,7 +188,8 @@ func (a *Artifact) download(ctx context.Context, layer v1.Layer, fileName, dir s } // Decompress the downloaded file if it is compressed and copy it into the dst - if err = downloader.Download(ctx, f.Name(), dir, dir); err != nil { + // NOTE: it's local copying, the insecure option doesn't matter. + if err = downloader.Download(ctx, f.Name(), dir, dir, false); err != nil { return xerrors.Errorf("download error: %w", err) } diff --git a/pkg/plugin/index.go b/pkg/plugin/index.go index d0df12e2036f..58beeaa5f9c7 100644 --- a/pkg/plugin/index.go +++ b/pkg/plugin/index.go @@ -32,9 +32,9 @@ type Index struct { } `yaml:"plugins"` } -func (m *Manager) Update(ctx context.Context) error { +func (m *Manager) Update(ctx context.Context, opts Options) error { m.logger.InfoContext(ctx, "Updating the plugin index...", log.String("url", m.indexURL)) - if err := downloader.Download(ctx, m.indexURL, filepath.Dir(m.indexPath), ""); err != nil { + if err := downloader.Download(ctx, m.indexURL, filepath.Dir(m.indexPath), "", opts.Insecure); err != nil { return xerrors.Errorf("unable to download the plugin index: %w", err) } return nil @@ -69,10 +69,10 @@ func (m *Manager) Search(ctx context.Context, keyword string) error { // tryIndex returns the repository URL if the plugin name is found in the index. // Otherwise, it returns the input name. -func (m *Manager) tryIndex(ctx context.Context, name string) string { +func (m *Manager) tryIndex(ctx context.Context, name string, opts Options) string { // If the index file does not exist, download it first. if !fsutils.FileExists(m.indexPath) { - if err := m.Update(ctx); err != nil { + if err := m.Update(ctx, opts); err != nil { m.logger.ErrorContext(ctx, "Failed to update the plugin index", log.Err(err)) return name } diff --git a/pkg/plugin/index_test.go b/pkg/plugin/index_test.go index 4fbd4c9411aa..5e3f4cd017bd 100644 --- a/pkg/plugin/index_test.go +++ b/pkg/plugin/index_test.go @@ -26,7 +26,7 @@ func TestManager_Update(t *testing.T) { t.Cleanup(ts.Close) manager := plugin.NewManager(plugin.WithIndexURL(ts.URL + "/index.yaml")) - err := manager.Update(context.Background()) + err := manager.Update(context.Background(), plugin.Options{}) require.NoError(t, err) indexPath := filepath.Join(tempDir, ".trivy", "plugins", "index.yaml") diff --git a/pkg/plugin/manager.go b/pkg/plugin/manager.go index a3a806f8f42b..c0f9bf431c87 100644 --- a/pkg/plugin/manager.go +++ b/pkg/plugin/manager.go @@ -92,13 +92,13 @@ func Upgrade(ctx context.Context, names []string) error { return defaultManager( func Uninstall(ctx context.Context, name string) error { return defaultManager().Uninstall(ctx, name) } func Information(name string) error { return defaultManager().Information(name) } func List(ctx context.Context) error { return defaultManager().List(ctx) } -func Update(ctx context.Context) error { return defaultManager().Update(ctx) } +func Update(ctx context.Context, opts Options) error { return defaultManager().Update(ctx, opts) } func Search(ctx context.Context, keyword string) error { return defaultManager().Search(ctx, keyword) } // Install installs a plugin func (m *Manager) Install(ctx context.Context, arg string, opts Options) (Plugin, error) { input := m.parseArg(ctx, arg) - input.name = m.tryIndex(ctx, input.name) + input.name = m.tryIndex(ctx, input.name, opts) // If the plugin is already installed, it skips installing the plugin. if p, installed := m.isInstalled(ctx, input.name, input.version); installed { @@ -111,7 +111,7 @@ func (m *Manager) Install(ctx context.Context, arg string, opts Options) (Plugin } func (m *Manager) install(ctx context.Context, src string, opts Options) (Plugin, error) { - tempDir, err := downloader.DownloadToTempDir(ctx, src) + tempDir, err := downloader.DownloadToTempDir(ctx, src, opts.Insecure) if err != nil { return Plugin{}, xerrors.Errorf("download failed: %w", err) } diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index 68a50ae31780..56c33644f854 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -57,6 +57,7 @@ type Options struct { Args []string Stdin io.Reader // For output plugin Platform ftypes.Platform + Insecure bool } func (p *Plugin) Cmd(ctx context.Context, opts Options) (*exec.Cmd, error) { @@ -154,7 +155,7 @@ func (p *Plugin) install(ctx context.Context, dst, pwd string, opts Options) err p.Installed.Platform = lo.FromPtr(platform.Selector) log.DebugContext(ctx, "Downloading the execution file...", log.String("uri", platform.URI)) - if err = downloader.Download(ctx, platform.URI, dst, pwd); err != nil { + if err = downloader.Download(ctx, platform.URI, dst, pwd, opts.Insecure); err != nil { return xerrors.Errorf("unable to download the execution file (%s): %w", platform.URI, err) } return nil