fix(plugin): respect --insecure (#7022)

Signed-off-by: knqyf263 <knqyf263@gmail.com>
This commit is contained in:
Teppei Fukuda
2024-06-26 14:23:00 +04:00
committed by GitHub
parent 8d618e48a2
commit 3d02a31b44
8 changed files with 108 additions and 19 deletions

View File

@@ -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

View File

@@ -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,

View File

@@ -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))
})
}
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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")

View File

@@ -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)
}

View File

@@ -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