From 4e1f0f267d3946d06f20c8036ae37112bb84371f Mon Sep 17 00:00:00 2001 From: Owen Rumney Date: Thu, 23 Oct 2025 11:03:42 +0100 Subject: [PATCH 01/11] refactor(cli): Rework Trivy Cloud integration logic After having discussions internally, the logic for working with the Trivy Cloud integration is going to be simplified greatly. This involves removing the Cloud specific config file and bringing the config into the existing Trivy config. The `cloud config` commands will be removed as they aren't needed anymore. As the focus is going to be on CI/CD integration with the Trivy Cloud platform, there isn't currently a need to have the Login and Logout mechanisms. --- .../references/configuration/cli/trivy.md | 3 - .../configuration/cli/trivy_image.md | 7 + go.mod | 5 +- go.sum | 2 - mkdocs.yml | 8 - pkg/cloud/config.go | 303 ++++----------- pkg/cloud/config_edit.go | 44 --- pkg/cloud/config_modify.go | 147 -------- pkg/cloud/config_modify_test.go | 244 ------------- pkg/cloud/config_test.go | 344 ++---------------- pkg/cloud/hooks/report_hook.go | 27 +- pkg/cloud/token.go | 62 ++++ pkg/cloud/token_test.go | 100 +++++ pkg/commands/app.go | 154 +------- pkg/commands/artifact/run.go | 6 + pkg/commands/cloud/run.go | 114 ++---- pkg/commands/cloud/run_test.go | 145 -------- pkg/flag/cloud_flags.go | 113 ++++-- 18 files changed, 401 insertions(+), 1427 deletions(-) delete mode 100644 pkg/cloud/config_edit.go delete mode 100644 pkg/cloud/config_modify.go delete mode 100644 pkg/cloud/config_modify_test.go create mode 100644 pkg/cloud/token.go create mode 100644 pkg/cloud/token_test.go delete mode 100644 pkg/commands/cloud/run_test.go diff --git a/docs/docs/references/configuration/cli/trivy.md b/docs/docs/references/configuration/cli/trivy.md index a22b0a937640..9776926b8386 100644 --- a/docs/docs/references/configuration/cli/trivy.md +++ b/docs/docs/references/configuration/cli/trivy.md @@ -45,14 +45,11 @@ trivy [global flags] command [flags] target ### SEE ALSO * [trivy clean](trivy_clean.md) - Remove cached files -* [trivy cloud](trivy_cloud.md) - Control Trivy Cloud platform integration settings * [trivy config](trivy_config.md) - Scan config files for misconfigurations * [trivy convert](trivy_convert.md) - Convert Trivy JSON report into a different format * [trivy filesystem](trivy_filesystem.md) - Scan local filesystem * [trivy image](trivy_image.md) - Scan a container image * [trivy kubernetes](trivy_kubernetes.md) - [EXPERIMENTAL] Scan kubernetes cluster -* [trivy login](trivy_login.md) - Log in to the Trivy Cloud platform -* [trivy logout](trivy_logout.md) - Log out of Trivy Cloud platform * [trivy module](trivy_module.md) - Manage modules * [trivy plugin](trivy_plugin.md) - Manage plugins * [trivy registry](trivy_registry.md) - Manage registry authentication diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index a00d0801ccf5..1b673b331152 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -38,6 +38,13 @@ trivy image [flags] IMAGE_NAME --cache-ttl duration cache TTL when using redis as cache backend --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") + --cloud-api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") + --cloud-download-misconfig-config Download misconfig configurations from Trivy Cloud platform (default true) + --cloud-download-secret-config Download secret configurations from Trivy Cloud platform (default true) + --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform (default true) + --cloud-token string Token used to athenticate with Trivy Cloud platform + --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform (default "https://scan.trivy.dev") + --cloud-upload-results Upload results to Trivy Cloud platform (default true) --compliance string compliance report to generate (built-in compliance's: docker-cis-1.6.0) --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded diff --git a/go.mod b/go.mod index ee84743404c8..7e5b35bba29a 100644 --- a/go.mod +++ b/go.mod @@ -111,7 +111,7 @@ require ( github.com/twitchtv/twirp v8.1.3+incompatible github.com/xeipuuv/gojsonschema v1.2.0 github.com/xlab/treeprint v1.2.0 - github.com/zalando/go-keyring v0.2.6 + github.com/zalando/go-keyring v0.2.6 // indirect github.com/zclconf/go-cty v1.17.0 github.com/zclconf/go-cty-yaml v1.1.0 go.etcd.io/bbolt v1.4.3 @@ -133,7 +133,6 @@ require ( ) require ( - al.essio.dev/pkg/shellescape v1.5.1 // indirect buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.6-20250718181942-e35f9b667443.1 // indirect buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250717185734-6c6e0d3c608e.1 // indirect buf.build/gen/go/bufbuild/registry/connectrpc/go v1.18.1-20250721151928-2b7ae473b098.1 // indirect @@ -226,7 +225,6 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 // indirect github.com/cyphar/filepath-securejoin v0.6.0 // indirect - github.com/danieljoos/wincred v1.2.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect @@ -283,7 +281,6 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.15.23 // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/gofrs/uuid v4.3.1+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect diff --git a/go.sum b/go.sum index 2bcddb97b2d6..a92fceaee8af 100644 --- a/go.sum +++ b/go.sum @@ -702,8 +702,6 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 h1:SJ+NtwL6QaZ21U+IrK7d0gGgpjGGvd2kz+FzTHVzdqI= github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2/go.mod h1:Tv1PlzqC9t8wNnpPdctvtSUOPUUg4SHeE6vR1Ir2hmg= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w= github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM= github.com/google/trillian v1.7.2 h1:EPBxc4YWY4Ak8tcuhyFleY+zYlbCDCa4Sn24e1Ka8Js= diff --git a/mkdocs.yml b/mkdocs.yml index 14c7a63eae1c..fd044aecd53c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -169,14 +169,6 @@ nav: - CLI: - Overview: docs/references/configuration/cli/trivy.md - Clean: docs/references/configuration/cli/trivy_clean.md - - Cloud: - - Cloud: docs/references/configuration/cli/trivy_cloud.md - - Cloud Config: docs/references/configuration/cli/trivy_cloud_config.md - - Cloud Config Edit: docs/references/configuration/cli/trivy_cloud_config_edit.md - - Cloud Config List: docs/references/configuration/cli/trivy_cloud_config_list.md - - Cloud Config Set: docs/references/configuration/cli/trivy_cloud_config_set.md - - Cloud Config Unset: docs/references/configuration/cli/trivy_cloud_config_unset.md - - Cloud Config Get: docs/references/configuration/cli/trivy_cloud_config_get.md - Config: docs/references/configuration/cli/trivy_config.md - Convert: docs/references/configuration/cli/trivy_convert.md - Filesystem: docs/references/configuration/cli/trivy_filesystem.md diff --git a/pkg/cloud/config.go b/pkg/cloud/config.go index 64828bb08ae9..a8a8e6bdc9c6 100644 --- a/pkg/cloud/config.go +++ b/pkg/cloud/config.go @@ -2,297 +2,126 @@ package cloud import ( "context" - "errors" + "encoding/json" "fmt" "net/http" "net/url" "os" "path/filepath" - "reflect" - "strconv" - "strings" + "time" - "github.com/samber/lo" - "github.com/zalando/go-keyring" "golang.org/x/xerrors" "gopkg.in/yaml.v3" + "github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/utils/fsutils" xhttp "github.com/aquasecurity/trivy/pkg/x/http" ) +type ConfigType string + const ( - ServiceName = "trivy-cloud" - TokenKey = "token" - DefaultApiUrl = "https://api.trivy.dev" - DefaultTrivyServerUrl = "https://scan.trivy.dev" + // Additional config types can be added here - in future custom checks etc + ConfigTypeSecret ConfigType = "secret" ) -type Api struct { - URL string `yaml:"url"` -} - -type Scanning struct { - Enabled bool `yaml:"enabled"` - UploadResults bool `yaml:"upload-results"` - SecretConfig bool `yaml:"secret-config"` - MisconfigConfig bool `yaml:"misconfig-config"` -} - -type Server struct { - URL string `yaml:"url"` - Scanning Scanning `yaml:"scanning"` -} - -type Config struct { - Api Api `yaml:"api"` - Server Server `yaml:"server"` - IsLoggedIn bool `yaml:"-"` - Token string `yaml:"-"` -} +const ( + SecretConfigPath = "/configs/secrets/secret-config.yaml" + configCacheTTL = -time.Hour +) -var defaultConfig = &Config{ - Api: Api{ - URL: DefaultApiUrl, - }, - Server: Server{ - URL: DefaultTrivyServerUrl, - Scanning: Scanning{}, - }, +var configPaths = map[ConfigType]string{ + ConfigTypeSecret: SecretConfigPath, } -func getConfigPath() string { - configFileName := fmt.Sprintf("%s.yaml", ServiceName) - return filepath.Join(fsutils.TrivyHomeDir(), configFileName) +type cloudConfigResponse struct { + Content map[string]any `json:"content"` } -func (c *Config) Save() error { - if c.Token == "" && c.Server.URL == "" && c.Api.URL == "" { - return xerrors.New("no config to save, required fields are token, server url, and api url") - } - - if err := c.initFirstLogin(); err != nil { - return err - } - - if err := keyring.Set(ServiceName, TokenKey, c.Token); err != nil { - return err - } - - configPath := getConfigPath() - if err := os.MkdirAll(filepath.Dir(configPath), 0o700); err != nil { - return err - } - - configYaml, err := yaml.Marshal(c) - if err != nil { - return err - } - - yamlWithFrontmatter := append([]byte("---\n"), configYaml...) - return os.WriteFile(configPath, yamlWithFrontmatter, 0o600) -} +func GetConfigs(ctx context.Context, opts *flag.Options, accessToken string) error { + logger := log.WithPrefix(log.PrefixCloud) + client := xhttp.ClientWithContext(ctx) -func Clear() error { - if err := keyring.Delete(ServiceName, TokenKey); err != nil { - if !errors.Is(err, keyring.ErrNotFound) { - return err + if opts.CloudOptions.SecretConfig && opts.Scanners.Enabled(types.SecretScanner) { + if opts.SecretOptions.SecretConfigPath != "" { + logger.Warn("Secret config path already set", log.String("configPath", opts.SecretOptions.SecretConfigPath)) + return nil } - } - configPath := getConfigPath() - if err := os.Remove(configPath); err != nil { - if !errors.Is(err, os.ErrNotExist) { - return err + configPath, err := getConfigFromTrivyCloud(ctx, client, opts, accessToken, ConfigTypeSecret) + if err != nil { + return xerrors.Errorf("failed to get secret config: %w", err) } - } - - return nil -} - -// initFirstLogin initializes the default scanning settings to turn them on -// after this, the user can configure in the config using the config set/unset commands -func (c *Config) initFirstLogin() error { - if c.Token == "" { - // this isn't a login save, without a token it can't login - return nil - } - - var firstLogin bool - _, err := keyring.Get(ServiceName, TokenKey) - if err != nil { - if !errors.Is(err, keyring.ErrNotFound) { - return err + if configPath != "" { + opts.SecretOptions.SecretConfigPath = configPath } - firstLogin = true - } - - if firstLogin { - // if first login, turn on all scanning options - c.Server.Scanning.Enabled = true - c.Server.Scanning.UploadResults = true - c.Server.Scanning.MisconfigConfig = true - c.Server.Scanning.SecretConfig = true } return nil } -// Load loads the Trivy Cloud config from the config file and the keychain -// If the config file does not exist the default config is returned -func Load() (*Config, error) { +// getConfigFromTrivyCloud downloads a config from Trivy Cloud and saves it to a file +// it returns the path to the config file if it was downloaded successfully, otherwise it returns an error +func getConfigFromTrivyCloud(ctx context.Context, client *http.Client, opts *flag.Options, accessToken string, configType ConfigType) (string, error) { logger := log.WithPrefix(log.PrefixCloud) - var config Config - configPath := getConfigPath() - yamlData, err := os.ReadFile(configPath) - if err != nil { - if !errors.Is(err, os.ErrNotExist) { - return nil, err - } - logger.Debug("No cloud config file found") - defaultCopy := *defaultConfig - return &defaultCopy, nil - } - if err := yaml.Unmarshal(yamlData, &config); err != nil { - return nil, err + configTypeStr := string(configType) + configDir := filepath.Join(fsutils.TrivyHomeDir(), "cloud", configTypeStr) + if err := os.MkdirAll(configDir, os.ModePerm); err != nil { + return "", xerrors.Errorf("failed to create cloud config directory: %w", err) } - token, err := keyring.Get(ServiceName, TokenKey) - if err != nil { - if !errors.Is(err, keyring.ErrNotFound) { - return nil, err - } - logger.Debug("No token found in keychain") - config.Token = "" - return &config, nil + configFilename := filepath.Join(configDir, "config.yaml") + // Return cached config if it was updated within the last hour + if stat, err := os.Stat(configFilename); err == nil && stat.ModTime().After(time.Now().Add(configCacheTTL)) { + logger.Debug("Config found in cache", log.String("configType", string(configType)), log.String("configPath", configFilename)) + return configFilename, nil } - config.Token = token - return &config, nil -} - -// Verify verifies the Trivy Cloud token and server URL and sets the global cloud config -// if the token is valid, the IsLoggedIn field is set to true and the global loggedIn variable is set to true -func (c *Config) Verify(ctx context.Context) error { - if c.Token == "" { - return xerrors.New("no token provided for verification") + logger.Debug("Config not found in cache", log.String("configType", string(configType)), log.String("configPath", configFilename)) + configPath, ok := configPaths[configType] + if !ok { + return "", xerrors.Errorf("unknown config type: %s", configType) } - - if c.Server.URL == "" { - return xerrors.New("no server URL provided for verification") - } - - logger := log.WithPrefix(log.PrefixCloud) - - client := xhttp.Client() - url, err := url.JoinPath(c.Server.URL, "verify") + configUrl, err := url.JoinPath(opts.CloudOptions.TrivyServerURL, configPath) if err != nil { - return xerrors.Errorf("failed to join server URL and verify path: %w", err) + return "", xerrors.Errorf("failed to join API URL and config path: %w", err) } - - logger.Debug("Verifying Trivy Cloud token against server", log.String("verification_url", url)) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, http.NoBody) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, configUrl, http.NoBody) if err != nil { - return xerrors.Errorf("failed to create verification request: %w", err) + return "", xerrors.Errorf("failed to create request: %w", err) } - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.Token)) + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken)) resp, err := client.Do(req) if err != nil { - return xerrors.Errorf("failed to verify token: %w", err) + return "", xerrors.Errorf("failed to get config: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return xerrors.Errorf("failed to verify token: received status code %d", resp.StatusCode) - } - - logger.Debug("Trivy Cloud token verified successfully") - return nil - -} - -// ListConfig shows the Trivy Cloud config in human readable format -func ListConfig() error { - cloudConfig, err := Load() - if err != nil { - return xerrors.Errorf("failed to load Trivy Cloud config file: %w", err) + if resp.StatusCode == http.StatusNotFound { + logger.Debug("Config not found in Trivy Cloud", log.String("configType", string(configType))) + return "", nil + } + return "", xerrors.Errorf("failed to get config: received status code %d", resp.StatusCode) } - var loggedIn bool - if cloudConfig.Verify(context.Background()) == nil { - loggedIn = true - } else { - loggedIn = false + var response cloudConfigResponse + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return "", xerrors.Errorf("failed to decode config: %w", err) } - fmt.Println() - fmt.Println("Trivy Cloud Configuration") - fmt.Println("-------------------------") - fmt.Printf("Filepath: %s\n", getConfigPath()) - fmt.Printf("Logged In: %s\n", lo.Ternary(loggedIn, "Yes", "No")) - fmt.Println() - - fields := collectConfigFields(reflect.ValueOf(cloudConfig).Elem(), "") - maxKeyLen := 0 - for _, field := range fields { - maxKeyLen = max(maxKeyLen, len(field.path)) + if response.Content == nil { + return "", xerrors.Errorf("config content is empty") } - for _, field := range fields { - fmt.Printf("%-*s %s\n", maxKeyLen, field.path, formatValue(field.value)) + configContentBytes, err := yaml.Marshal(response.Content) + if err != nil { + return "", xerrors.Errorf("failed to marshal config content: %w", err) } - fmt.Println() - - return nil -} - -type configField struct { - path string - value reflect.Value -} - -func collectConfigFields(v reflect.Value, prefix string) []configField { - var fields []configField - t := v.Type() - - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - fieldValue := v.Field(i) - - yamlTag := field.Tag.Get("yaml") - if yamlTag == "-" || yamlTag == "" { - continue - } - - tagName := strings.Split(yamlTag, ",")[0] - fullPath := tagName - if prefix != "" { - fullPath = prefix + "." + tagName - } - if fieldValue.Kind() == reflect.Struct { - fields = append(fields, collectConfigFields(fieldValue, fullPath)...) - } else { - fields = append(fields, configField{ - path: fullPath, - value: fieldValue, - }) - } + if err := os.WriteFile(configFilename, configContentBytes, 0o600); err != nil { + return "", xerrors.Errorf("failed to write config: %w", err) } - return fields -} - -func formatValue(v reflect.Value) string { - switch v.Kind() { - case reflect.Bool: - return lo.Ternary(v.Bool(), "Enabled", "Disabled") - case reflect.String: - return v.String() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return strconv.FormatInt(v.Int(), 10) - case reflect.Float32, reflect.Float64: - return fmt.Sprintf("%f", v.Float()) - default: - return fmt.Sprintf("%v", v.Interface()) - } + return configFilename, nil } diff --git a/pkg/cloud/config_edit.go b/pkg/cloud/config_edit.go deleted file mode 100644 index ac05dc0102a7..000000000000 --- a/pkg/cloud/config_edit.go +++ /dev/null @@ -1,44 +0,0 @@ -package cloud - -import ( - "os" - "os/exec" - "runtime" - - "github.com/aquasecurity/trivy/pkg/log" - "github.com/aquasecurity/trivy/pkg/utils/fsutils" -) - -// OpenConfigForEditing opens the Trivy Cloud config file for editing in the default editor specified in the EDITOR environment variable -func OpenConfigForEditing() error { - configPath := getConfigPath() - - logger := log.WithPrefix(log.PrefixCloud) - if !fsutils.FileExists(configPath) { - logger.Debug("Trivy Cloud config file does not exist", log.String("config_path", configPath)) - defaultConfig.Save() - configPath = getConfigPath() - } - - editor := getEditCommand() - - cmd := exec.Command(editor, configPath) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - return cmd.Run() -} - -func getEditCommand() string { - editor := os.Getenv("EDITOR") - if editor != "" { - return editor - } - - // fallback to notepad for windows or vi for macos/linux - if runtime.GOOS == "windows" { - return "notepad" - } - return "vi" -} diff --git a/pkg/cloud/config_modify.go b/pkg/cloud/config_modify.go deleted file mode 100644 index 9dd8bf11a93d..000000000000 --- a/pkg/cloud/config_modify.go +++ /dev/null @@ -1,147 +0,0 @@ -package cloud - -import ( - "reflect" - "strings" - - "golang.org/x/xerrors" - "gopkg.in/yaml.v3" -) - -// Set sets a nested field in the Trivy Cloud config -func Set(attribute string, value any) error { - config, err := Load() - if err != nil { - return xerrors.Errorf("failed to load Trivy Cloud config file: %w", err) - } - - if err := setNestedField(reflect.ValueOf(config).Elem(), attribute, value); err != nil { - return xerrors.Errorf("failed to set attribute %q: %w", attribute, err) - } - - return config.Save() -} - -// Unset sets a nested field in the Trivy Cloud config to its default value -func Unset(attribute string) error { - config, err := Load() - if err != nil { - return xerrors.Errorf("failed to load Trivy Cloud config file: %w", err) - } - - if err := unsetNestedField(reflect.ValueOf(config).Elem(), attribute); err != nil { - return xerrors.Errorf("failed to unset attribute %q: %w", attribute, err) - } - - return config.Save() -} - -func unsetNestedField(value reflect.Value, attribute string) error { - field, err := navigateToField(value, attribute) - if err != nil { - return err - } - - defaultField, err := navigateToField(reflect.ValueOf(defaultConfig).Elem(), attribute) - if err != nil { - return err - } - - field.Set(defaultField) - return nil -} - -// Get gets a nested field from the Trivy Cloud config -func Get(attribute string) (any, error) { - return GetWithDefault[any](attribute, nil) -} - -// GetWithDefault gets a nested field from the Trivy Cloud config with a default value -func GetWithDefault[T any](attribute string, defaultValue T) (T, error) { - config, err := Load() - if err != nil { - return defaultValue, xerrors.Errorf("failed to load Trivy Cloud config file: %w", err) - } - - field, err := navigateToField(reflect.ValueOf(config).Elem(), attribute) - if err != nil { - return defaultValue, xerrors.Errorf("failed to get attribute %q: %w", attribute, err) - } - - return field.Interface().(T), nil -} - -func setNestedField(v reflect.Value, path string, value any) error { - field, err := navigateToField(v, path) - if err != nil { - return err - } - - convertedValue, err := convertToType(value, field.Type()) - if err != nil { - return xerrors.Errorf("failed to convert value: %w", err) - } - - field.Set(convertedValue) - return nil -} - -func convertToType(value any, targetType reflect.Type) (reflect.Value, error) { - val := reflect.ValueOf(value) - if val.Type().AssignableTo(targetType) { - return val, nil - } - targetPtr := reflect.New(targetType) // *T - targetInterface := targetPtr.Interface() - data, err := yaml.Marshal(value) - if err != nil { - return reflect.Value{}, xerrors.Errorf("failed to marshal value: %w", err) - } - if err := yaml.Unmarshal(data, targetInterface); err != nil { - return reflect.Value{}, xerrors.Errorf("failed to decode into %v: %w", targetType, err) - } - return targetPtr.Elem(), nil -} - -func navigateToField(v reflect.Value, path string) (reflect.Value, error) { - parts := strings.Split(path, ".") - if len(parts) == 0 { - return reflect.Value{}, xerrors.New("empty attribute path") - } - - for i, part := range parts { - fieldName := yamlTagToFieldName(v, part) - if fieldName == "" { - return reflect.Value{}, xerrors.Errorf("field %q not found in config", part) - } - - field := v.FieldByName(fieldName) - if !field.IsValid() { - return reflect.Value{}, xerrors.Errorf("field %q not found", fieldName) - } - if !field.CanSet() { - return reflect.Value{}, xerrors.Errorf("field %q cannot be set", fieldName) - } - - if i == len(parts)-1 { - return field, nil - } - - v = field - } - - return reflect.Value{}, xerrors.New("unexpected end of path") -} - -func yamlTagToFieldName(v reflect.Value, yamlTag string) string { - t := v.Type() - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - tag := field.Tag.Get("yaml") - tagName := strings.Split(tag, ",")[0] - if tagName == yamlTag { - return field.Name - } - } - return "" -} diff --git a/pkg/cloud/config_modify_test.go b/pkg/cloud/config_modify_test.go deleted file mode 100644 index 22c79ae612d2..000000000000 --- a/pkg/cloud/config_modify_test.go +++ /dev/null @@ -1,244 +0,0 @@ -package cloud - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/zalando/go-keyring" -) - -func TestSet(t *testing.T) { - tests := []struct { - name string - configToSet map[string]any - expected *Config - expectedError string - }{ - { - name: "success with valid config", - configToSet: map[string]any{"server.scanning.enabled": true}, - expected: &Config{Api: Api{URL: "https://api.trivy.dev"}, Server: Server{URL: "https://scan.trivy.dev", Scanning: Scanning{Enabled: true, UploadResults: false, SecretConfig: false, MisconfigConfig: false}}, IsLoggedIn: false, Token: ""}, - expectedError: "", - }, - { - name: "success with valid config using off for a boolean", - configToSet: map[string]any{"server.scanning.enabled": "on"}, - expected: &Config{Api: Api{URL: "https://api.trivy.dev"}, Server: Server{URL: "https://scan.trivy.dev", Scanning: Scanning{Enabled: true, UploadResults: false, SecretConfig: false, MisconfigConfig: false}}, IsLoggedIn: false, Token: ""}, - expectedError: "", - }, - { - name: "error with invalid config", - configToSet: map[string]any{"server.scanning.foo": false}, - expected: &Config{Api: Api{URL: "https://api.trivy.dev"}, Server: Server{URL: "https://scan.trivy.dev", Scanning: Scanning{Enabled: false, UploadResults: false, SecretConfig: false, MisconfigConfig: false}}, IsLoggedIn: false, Token: ""}, - expectedError: "field \"foo\" not found in config", - }, - { - name: "error when setting boolean with yessir", - configToSet: map[string]any{"server.scanning.enabled": "yessir"}, - expected: &Config{Api: Api{URL: "https://api.trivy.dev"}, Server: Server{URL: "https://scan.trivy.dev", Scanning: Scanning{Enabled: false, UploadResults: false, SecretConfig: false, MisconfigConfig: false}}, IsLoggedIn: false, Token: ""}, - expectedError: "cannot unmarshal !!str `yessir` into bool", - }, - { - name: "error when setting boolean with invalid value", - configToSet: map[string]any{"server.scanning.enabled": "invalid"}, - expected: &Config{Api: Api{URL: "https://api.trivy.dev"}, Server: Server{URL: "https://scan.trivy.dev", Scanning: Scanning{Enabled: false, UploadResults: false, SecretConfig: false, MisconfigConfig: false}}, IsLoggedIn: false, Token: ""}, - expectedError: "cannot unmarshal !!str `invalid` into bool", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tempDir := t.TempDir() - t.Setenv("XDG_DATA_HOME", tempDir) - - keyring.MockInit() - defer keyring.DeleteAll(ServiceName) - defer Clear() - - for key, value := range tt.configToSet { - err := Set(key, value) - if tt.expectedError != "" { - require.ErrorContains(t, err, tt.expectedError) - return - } - require.NoError(t, err) - } - - config, err := Load() - require.NoError(t, err) - assert.Equal(t, tt.expected, config) - }) - } -} - -func TestGet(t *testing.T) { - tests := []struct { - name string - primeToken bool - setupConfig *Config - attribute string - defaultValue any - expected any - expectedError string - }{ - { - name: "success with default config", - setupConfig: nil, - attribute: "server.scanning.enabled", - defaultValue: false, - expected: false, - expectedError: "", - }, - { - name: "success with custom config", - primeToken: true, - setupConfig: &Config{ - Token: "test", - Server: Server{ - URL: "https://example.com", - Scanning: Scanning{ - Enabled: false, - UploadResults: true, - SecretConfig: false, - MisconfigConfig: true, - }, - }, - Api: Api{URL: "https://api.example.com"}, - }, - attribute: "server.scanning.enabled", - defaultValue: false, - expected: false, - expectedError: "", - }, - { - name: "error with invalid attribute", - setupConfig: nil, - attribute: "server.scanning.foo", - defaultValue: true, - expected: true, - expectedError: "field \"foo\" not found in config", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tempDir := t.TempDir() - t.Setenv("XDG_DATA_HOME", tempDir) - - keyring.MockInit() - defer keyring.DeleteAll(ServiceName) - defer Clear() - - if tt.primeToken { - // add the key so the custom config isn't overwritten - require.NoError(t, keyring.Set(ServiceName, TokenKey, tt.setupConfig.Token)) - } - - if tt.setupConfig != nil { - err := tt.setupConfig.Save() - require.NoError(t, err) - } - - value, err := GetWithDefault(tt.attribute, tt.defaultValue) - if tt.expectedError != "" { - require.ErrorContains(t, err, tt.expectedError) - return - } - require.NoError(t, err) - assert.Equal(t, tt.expected, value) - }) - } -} - -func TestUnset(t *testing.T) { - tests := []struct { - name string - primeToken bool - setupConfig *Config - attribute string - expectedValue any - expectedError string - }{ - { - name: "success with default config", - setupConfig: defaultConfig, - attribute: "server.scanning.enabled", - expectedValue: false, - expectedError: "", - }, - { - name: "success with custom config", - setupConfig: &Config{ - Token: "test", - Server: Server{ - URL: "https://example.com", - Scanning: Scanning{ - Enabled: false, - UploadResults: true, - SecretConfig: false, - MisconfigConfig: true, - }, - }, - Api: Api{URL: "https://api.example.com"}, - }, - attribute: "server.scanning.enabled", - expectedValue: false, - expectedError: "", - }, - { - name: "success with custom url reset", - setupConfig: &Config{ - Token: "test", - Server: Server{ - URL: "https://example.com", - Scanning: Scanning{ - Enabled: false, - UploadResults: true, - SecretConfig: false, - MisconfigConfig: true, - }, - }, - Api: Api{URL: "https://api.custom.com"}, - }, - attribute: "api.url", - expectedValue: "https://api.trivy.dev", - expectedError: "", - }, - { - name: "error with invalid attribute", - setupConfig: defaultConfig, - attribute: "server.scanning.foo", - expectedValue: true, - expectedError: "field \"foo\" not found in config", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tempDir := t.TempDir() - t.Setenv("XDG_DATA_HOME", tempDir) - - keyring.MockInit() - defer keyring.DeleteAll(ServiceName) - defer Clear() - - if tt.primeToken { - // prime the token so it doesn't get overwritten - require.NoError(t, keyring.Set(ServiceName, TokenKey, tt.setupConfig.Token)) - } - - require.NoError(t, tt.setupConfig.Save()) - err := Unset(tt.attribute) - if tt.expectedError != "" { - require.ErrorContains(t, err, tt.expectedError) - return - } - - require.NoError(t, err) - value, err := Get(tt.attribute) - require.NoError(t, err) - assert.Equal(t, tt.expectedValue, value) - }) - } -} diff --git a/pkg/cloud/config_test.go b/pkg/cloud/config_test.go index e3901a53bd76..7b3e5be2aa87 100644 --- a/pkg/cloud/config_test.go +++ b/pkg/cloud/config_test.go @@ -2,344 +2,70 @@ package cloud import ( "context" - "io" - "net/http" - "net/http/httptest" - "os" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/zalando/go-keyring" -) - -func TestSave(t *testing.T) { - tests := []struct { - name string - config *Config - wantErr bool - }{ - { - name: "empty config", - config: &Config{}, - wantErr: true, - }, - { - name: "config with all fields", - config: &Config{ - Token: "test-token-123", - Server: Server{ - URL: "https://example.com", - }, - Api: Api{ - URL: "https://api.example.com", - }, - }, - wantErr: false, - }, - { - name: "config without token", - config: &Config{ - Server: Server{ - URL: "https://example.com", - }, - Api: Api{ - URL: "https://api.example.com", - }, - }, - wantErr: false, - }, - } - tempDir := t.TempDir() - t.Setenv("XDG_DATA_HOME", tempDir) - - keyring.MockInit() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - defer keyring.DeleteAll(ServiceName) - defer Clear() - - err := tt.config.Save() - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - - config, err := Load() - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - assert.Equal(t, tt.config, config) - configPath := getConfigPath() - if tt.config.Server.URL != "" || tt.config.Api.URL != "" { - assert.FileExists(t, configPath) - } - }) - } -} - -func TestClear(t *testing.T) { - tests := []struct { - name string - createConfig bool - wantErr bool - }{ - { - name: "success when nothing to clear", - wantErr: false, - }, - { - name: "success when there is config to clear", - createConfig: true, - wantErr: false, - }, - } - - tempDir := t.TempDir() - t.Setenv("XDG_DATA_HOME", tempDir) - - keyring.MockInit() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - defer keyring.DeleteAll(ServiceName) - defer Clear() - - if tt.createConfig { - config := &Config{ - Token: "testtoken", - Server: Server{ - URL: "https://example.com", - }, - } - err := config.Save() - require.NoError(t, err) + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/types" +) - configPath := getConfigPath() - assert.FileExists(t, configPath) - } +func TestGetConfigs_Secrets(t *testing.T) { - err := Clear() - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - configPath := getConfigPath() - assert.NoFileExists(t, configPath) - }) - } -} + mockServer := &mockApiServer{} + mockServer.Start() + defer mockServer.Close() -func TestLoad(t *testing.T) { tests := []struct { name string - createConfig bool - expectDefault bool + accessToken string + serverURL string + errorContains string }{ { - name: "success when there is config to load", - createConfig: true, - expectDefault: false, + name: "incorrect api token", + accessToken: "invalid-token", + serverURL: mockServer.server.URL, + errorContains: "failed to get secret config", }, { - name: "error when there is no config to load", - expectDefault: true, - }, - } - - tempDir := t.TempDir() - t.Setenv("XDG_DATA_HOME", tempDir) - - keyring.MockInit() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - defer keyring.DeleteAll(ServiceName) - defer Clear() - - token := "testtoken" - if tt.createConfig { - config := &Config{ - Token: token, - Server: Server{ - URL: "https://example.com", - }, - Api: Api{ - URL: "https://api.example.com", - }, - } - err := config.Save() - require.NoError(t, err) - } - - config, err := Load() - if tt.expectDefault { - assert.Equal(t, defaultConfig, config) - return - } - require.NotNil(t, config) - require.NoError(t, err) - assert.Equal(t, token, config.Token) - assert.Equal(t, "https://example.com", config.Server.URL) - assert.Equal(t, "https://api.example.com", config.Api.URL) - }) - } -} - -func TestVerify(t *testing.T) { - tests := []struct { - name string - config *Config - status int - wantErr bool - }{ - { - name: "success with valid config", - config: &Config{Token: "testtoken", Server: Server{URL: "https://example.com"}, Api: Api{URL: "https://api.example.com"}}, - status: http.StatusOK, - wantErr: false, + name: "config doesn't exist", + accessToken: "valid-token", + serverURL: mockServer.server.URL + "/nonexistent", + errorContains: "failed to get secret config", }, { - name: "error with invalid config", - config: &Config{}, - status: http.StatusUnauthorized, - wantErr: true, + name: "simple config that exists", + accessToken: "valid-token", + serverURL: mockServer.server.URL, }, } - tempDir := t.TempDir() - t.Setenv("XDG_DATA_HOME", tempDir) - keyring.MockInit() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer keyring.DeleteAll(ServiceName) - defer Clear() - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodPost, r.Method) - assert.Equal(t, "/verify", r.URL.Path) - w.WriteHeader(tt.status) - })) - defer server.Close() - - tt.config.Server.URL = server.URL - - err := tt.config.Verify(context.Background()) - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - }) - } -} + tempDir := t.TempDir() + t.Setenv("XDG_DATA_HOME", tempDir) -func TestListConfig(t *testing.T) { - tests := []struct { - name string - primeToken bool - setupConfig *Config - wantErr string - wantContains []string - }{ - { - name: "success with valid config", - primeToken: true, - setupConfig: &Config{ - Token: "testtoken", - Server: Server{ - URL: "https://example.com", - Scanning: Scanning{ - Enabled: true, - UploadResults: false, - SecretConfig: true, - MisconfigConfig: false, - }, + opts := &flag.Options{ + CloudOptions: flag.CloudOptions{ + SecretConfig: true, + TrivyServerURL: tt.serverURL, + }, + ScanOptions: flag.ScanOptions{ + Scanners: types.Scanners{types.SecretScanner}, }, - Api: Api{URL: "https://api.example.com"}, - }, - wantContains: []string{ - "Trivy Cloud Configuration", - "Logged In: No", - "Filepath:", - "api.url", - "https://api.example.com", - "server.url", - "https://example.com", - "server.scanning.enabled", - "Enabled", - "server.scanning.upload-results", - "Disabled", - "server.scanning.secret-config", - "server.scanning.misconfig-config", - }, - }, - { - name: "success with default config", - setupConfig: nil, - wantContains: []string{ - "Trivy Cloud Configuration", - "Logged In: No", - "api.url", - DefaultApiUrl, - "server.url", - DefaultTrivyServerUrl, - "server.scanning.enabled", - "server.scanning.upload-results", - "server.scanning.secret-config", - "server.scanning.misconfig-config", - }, - }, - } - - tempDir := t.TempDir() - t.Setenv("XDG_DATA_HOME", tempDir) - - keyring.MockInit() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - defer keyring.DeleteAll(ServiceName) - defer Clear() - - if tt.primeToken { - // prime the token in the keyring so the custom config doesn't get overwritten - require.NoError(t, keyring.Set(ServiceName, TokenKey, tt.setupConfig.Token)) - } - - if tt.setupConfig != nil { - err := tt.setupConfig.Save() - require.NoError(t, err) } - r, w, err := os.Pipe() - require.NoError(t, err) - - originalStdout := os.Stdout - os.Stdout = w - - errChan := make(chan error, 1) - go func() { - errChan <- ListConfig() - w.Close() - }() + err := GetConfigs(context.Background(), opts, tt.accessToken) - output, _ := io.ReadAll(r) - os.Stdout = originalStdout - - err = <-errChan - if tt.wantErr != "" { - require.ErrorContains(t, err, tt.wantErr) + if tt.errorContains != "" { + require.ErrorContains(t, err, tt.errorContains) return } require.NoError(t, err) - - outputStr := string(output) - for _, want := range tt.wantContains { - assert.Contains(t, outputStr, want) - } + require.NotEmpty(t, opts.SecretOptions.SecretConfigPath) + assert.FileExists(t, opts.SecretOptions.SecretConfigPath) }) } } diff --git a/pkg/cloud/hooks/report_hook.go b/pkg/cloud/hooks/report_hook.go index 495a36c5ad07..4b9dfaffa897 100644 --- a/pkg/cloud/hooks/report_hook.go +++ b/pkg/cloud/hooks/report_hook.go @@ -10,7 +10,6 @@ import ( "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/cloud" "github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" @@ -21,32 +20,34 @@ const ( presignedUploadUrl = "/trivy-reports/upload-url" ) -type CloudPlatformResultsHook struct { +type ReportHook struct { name string - cloudConfig *cloud.Config + apiUrl string + accessToken string client *http.Client logger *log.Logger } -func NewResultsHook(cloudCfg *cloud.Config) *CloudPlatformResultsHook { - return &CloudPlatformResultsHook{ +func NewReportHook(apiUrl, accessToken string) *ReportHook { + return &ReportHook{ name: "Trivy Cloud Results Hook", - cloudConfig: cloudCfg, + apiUrl: apiUrl, + accessToken: accessToken, client: xhttp.Client(), logger: log.WithPrefix(log.PrefixCloud), } } -func (h *CloudPlatformResultsHook) Name() string { +func (h *ReportHook) Name() string { return h.name } // PreReport is not going go to be called so we return nil -func (h *CloudPlatformResultsHook) PreReport(_ context.Context, _ *types.Report, _ flag.Options) error { +func (h *ReportHook) PreReport(_ context.Context, _ *types.Report, _ flag.Options) error { return nil } -func (h *CloudPlatformResultsHook) PostReport(ctx context.Context, report *types.Report, _ flag.Options) error { +func (h *ReportHook) PostReport(ctx context.Context, report *types.Report, _ flag.Options) error { h.logger.Debug("PostReport called with report") jsonReport, err := json.MarshalIndent(report, "", " ") if err != nil { @@ -56,7 +57,7 @@ func (h *CloudPlatformResultsHook) PostReport(ctx context.Context, report *types return h.uploadResults(ctx, jsonReport) } -func (h *CloudPlatformResultsHook) uploadResults(ctx context.Context, jsonReport []byte) error { +func (h *ReportHook) uploadResults(ctx context.Context, jsonReport []byte) error { uploadUrl, err := h.getPresignedUploadUrl(ctx) if err != nil { return fmt.Errorf("failed to get presigned upload URL: %w", err) @@ -83,8 +84,8 @@ func (h *CloudPlatformResultsHook) uploadResults(ctx context.Context, jsonReport return nil } -func (h *CloudPlatformResultsHook) getPresignedUploadUrl(ctx context.Context) (string, error) { - uploadUrl, err := url.JoinPath(h.cloudConfig.Api.URL, presignedUploadUrl) +func (h *ReportHook) getPresignedUploadUrl(ctx context.Context) (string, error) { + uploadUrl, err := url.JoinPath(h.apiUrl, presignedUploadUrl) if err != nil { return "", fmt.Errorf("failed to join API URL and presigned upload URL: %w", err) } @@ -95,7 +96,7 @@ func (h *CloudPlatformResultsHook) getPresignedUploadUrl(ctx context.Context) (s return "", fmt.Errorf("failed to create request: %w", err) } - req.Header.Set("Authorization", "Bearer "+h.cloudConfig.Token) + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", h.accessToken)) resp, err := h.client.Do(req) if err != nil { return "", fmt.Errorf("failed to get upload URL: %w", err) diff --git a/pkg/cloud/token.go b/pkg/cloud/token.go new file mode 100644 index 000000000000..3216a9f1b422 --- /dev/null +++ b/pkg/cloud/token.go @@ -0,0 +1,62 @@ +package cloud + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/log" + xhttp "github.com/aquasecurity/trivy/pkg/x/http" +) + +const ( + accessTokenPath = "/api-keys/access-tokens" +) + +func GetAccessToken(ctx context.Context, opts flag.Options) (string, error) { + if opts.CloudOptions.CloudToken == "" { + return "", xerrors.New("no cloud token provided for getting access token from Trivy Cloud") + } + + if opts.CloudOptions.ApiURL == "" { + return "", xerrors.New("no API URL provided for getting access token from Trivy Cloud") + } + + logger := log.WithPrefix(log.PrefixCloud) + + client := xhttp.Client() + url, err := url.JoinPath(opts.CloudOptions.ApiURL, accessTokenPath) + if err != nil { + return "", xerrors.Errorf("failed to join server URL and token path: %w", err) + } + logger.Debug("Requesting access token from Trivy Cloud", log.String("url", url)) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, http.NoBody) + if err != nil { + return "", xerrors.Errorf("failed to create token request: %w", err) + } + + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", opts.CloudOptions.CloudToken)) + resp, err := client.Do(req) + if err != nil { + return "", xerrors.Errorf("failed to get access token: %w", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusCreated { + return "", xerrors.Errorf("failed to get access token: received status code %d", resp.StatusCode) + } + + var accessTokenResponse struct { + Token string `json:"token"` + } + if err := json.NewDecoder(resp.Body).Decode(&accessTokenResponse); err != nil { + return "", xerrors.Errorf("failed to decode access token response: %w", err) + } + + logger.Debug("Created a new access token") + return accessTokenResponse.Token, nil +} diff --git a/pkg/cloud/token_test.go b/pkg/cloud/token_test.go new file mode 100644 index 000000000000..7c4104935d29 --- /dev/null +++ b/pkg/cloud/token_test.go @@ -0,0 +1,100 @@ +package cloud + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/flag" +) + +type mockApiServer struct { + server *httptest.Server +} + +func (m *mockApiServer) Start() { + m.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("Authorization") != "Bearer valid-token" { + w.WriteHeader(http.StatusUnauthorized) + return + } + // handle access token request + if r.URL.Path == accessTokenPath { + w.WriteHeader(http.StatusCreated) + w.Write([]byte(`{"token": "test-token"}`)) + } + if r.URL.Path == "/configs/secrets/secret-config.yaml" { + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"content": {"key": "value"}}`)) + } + })) +} + +func (m *mockApiServer) Close() { + m.server.Close() +} + +func TestGetAccessToken(t *testing.T) { + + mockServer := &mockApiServer{} + mockServer.Start() + defer mockServer.Close() + + tests := []struct { + name string + opts flag.Options + want string + expectedStatusCode int + errorContains string + }{ + { + name: "happy path", + opts: flag.Options{ + CloudOptions: flag.CloudOptions{ + CloudToken: "valid-token", + ApiURL: mockServer.server.URL, + }, + }, + want: "test-token", + expectedStatusCode: http.StatusCreated, + }, + { + name: "no API URL", + opts: flag.Options{ + CloudOptions: flag.CloudOptions{ + CloudToken: "valid-token", + ApiURL: "", + }, + }, + errorContains: "no API URL provided for getting access token from Trivy Cloud", + expectedStatusCode: http.StatusInternalServerError, + }, + { + name: "invalid token", + opts: flag.Options{ + CloudOptions: flag.CloudOptions{ + CloudToken: "invalid-token", + ApiURL: mockServer.server.URL, + }, + }, + errorContains: "failed to get access token: received status code 401", + expectedStatusCode: http.StatusUnauthorized, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetAccessToken(t.Context(), tt.opts) + + if tt.errorContains != "" { + require.ErrorContains(t, err, tt.errorContains) + } else { + require.NoError(t, err) + } + assert.Equal(t, tt.want, got) + + }) + } +} diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 0b139ef553ab..8270dbd1debb 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -17,7 +17,6 @@ import ( "github.com/aquasecurity/trivy/pkg/commands/artifact" "github.com/aquasecurity/trivy/pkg/commands/auth" "github.com/aquasecurity/trivy/pkg/commands/clean" - "github.com/aquasecurity/trivy/pkg/commands/cloud" "github.com/aquasecurity/trivy/pkg/commands/convert" "github.com/aquasecurity/trivy/pkg/commands/server" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" @@ -82,10 +81,6 @@ func NewApp() *cobra.Command { ID: groupUtility, Title: "Utility Commands", }, - &cobra.Group{ - ID: cloud.GroupCloud, - Title: "Trivy Cloud Commands", - }, ) rootCmd.SetCompletionCommandGroupID(groupUtility) rootCmd.SetHelpCommandGroupID(groupUtility) @@ -107,9 +102,6 @@ func NewApp() *cobra.Command { NewCleanCommand(globalFlags), NewRegistryCommand(globalFlags), NewVEXCommand(globalFlags), - NewLoginCommand(globalFlags), - NewLogoutCommand(), - NewCloudCommand(), ) if plugins := loadPluginCommands(); len(plugins) > 0 { @@ -217,7 +209,7 @@ func NewRootCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { // Initialize logger log.InitLogger(opts.Debug, opts.Quiet) - return cloud.CheckTrivyCloudStatus(cmd) + return nil }, RunE: func(cmd *cobra.Command, args []string) error { flags := flag.Flags{globalFlags} @@ -276,6 +268,7 @@ func NewImageCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { flag.NewScanFlagGroup(), flag.NewSecretFlagGroup(), flag.NewVulnerabilityFlagGroup(), + flag.NewCloudFlagGroup(), } cmd := &cobra.Command{ @@ -1422,149 +1415,6 @@ func NewVEXCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { return cmd } -func NewLoginCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { - loginFlags := &flag.Flags{ - globalFlags, - flag.NewCloudFlagGroup(), - } - - loginCmd := &cobra.Command{ - Use: "login [flags]", - Short: "Log in to the Trivy Cloud platform", - Long: "Log in to the Trivy Cloud platform to enable scanning of images and repositories in the cloud using the token retrieved from the Trivy Cloud platform", - GroupID: cloud.GroupCloud, - Args: cobra.NoArgs, - Example: ` # Log in to the Trivy Cloud platform - $ trivy login --token `, - PreRunE: func(cmd *cobra.Command, _ []string) error { - if err := loginFlags.Bind(cmd); err != nil { - return xerrors.Errorf("flag bind error: %w", err) - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - if err := loginFlags.Bind(cmd); err != nil { - return xerrors.Errorf("flag bind error: %w", err) - } - cloudOptions, err := loginFlags.ToOptions(args) - if err != nil { - return xerrors.Errorf("flag error: %w", err) - } - return cloud.Login(cmd.Context(), cloudOptions) - }, - } - - loginFlags.AddFlags(loginCmd) - loginCmd.SetFlagErrorFunc(flagErrorFunc) - - return loginCmd -} - -func NewLogoutCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "logout", - Short: "Log out of Trivy Cloud platform", - GroupID: cloud.GroupCloud, - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, _ []string) error { - return cloud.Logout() - }, - } - - return cmd -} - -func NewCloudCommand() *cobra.Command { - cloudCmd := &cobra.Command{ - Use: "cloud subcommand", - Short: "Control Trivy Cloud platform integration settings", - GroupID: cloud.GroupCloud, - } - - // add the group the sub commands so they don't check the login status - cloudCmd.AddGroup(&cobra.Group{ - ID: cloud.GroupCloud, - Title: "Trivy Cloud Commands", - }) - - configCmd := &cobra.Command{ - Use: "config subcommand", - Short: "Control Trivy Cloud configuration", - GroupID: cloud.GroupCloud, - } - - configCmd.AddGroup(&cobra.Group{ - ID: cloud.GroupCloud, - Title: "Trivy Cloud Configuration Commands", - }) - - configCmd.AddCommand( - &cobra.Command{ - Use: "edit", - Short: "Edit Trivy Cloud configuration", - Long: "Edit Trivy Cloud platform configuration in the default editor specified in the EDITOR environment variable", - GroupID: cloud.GroupCloud, - RunE: func(_ *cobra.Command, _ []string) error { - return cloud.EditConfig() - }, - }, - &cobra.Command{ - Use: "list", - Short: "List Trivy Cloud configuration", - Long: "List Trivy Cloud platform configuration in human readable format", - GroupID: cloud.GroupCloud, - RunE: func(_ *cobra.Command, _ []string) error { - return cloud.ListConfig() - }, - }, - &cobra.Command{ - Use: "set [setting] [value]", - Short: "Set Trivy Cloud configuration", - Long: `Set a Trivy Cloud platform setting - -Available config settings can be viewed by using the ` + "`trivy cloud config list`" + ` command`, - Example: ` $ trivy cloud config set server.scanning.enabled true - $ trivy cloud config set server.scanning.upload-results false`, - Args: cobra.ExactArgs(2), - GroupID: cloud.GroupCloud, - RunE: func(_ *cobra.Command, args []string) error { - return cloud.SetConfig(args[0], args[1]) - }, - }, - &cobra.Command{ - Use: "unset [setting]", - Short: "Unset Trivy Cloud configuration", - Long: `Unset a Trivy Cloud platform configuration and return it to the default setting - -Available config settings can be viewed by using the ` + "`trivy cloud config list`" + ` command`, - Example: ` $ trivy cloud config unset server.scanning.enabled - $ trivy cloud config unset server.scanning.upload-results`, - Args: cobra.ExactArgs(1), - GroupID: cloud.GroupCloud, - RunE: func(_ *cobra.Command, args []string) error { - return cloud.UnsetConfig(args[0]) - }, - }, - &cobra.Command{ - Use: "get [setting]", - Short: "Get Trivy Cloud configuration", - Long: `Get a Trivy Cloud platform configuration - -Available config settings can be viewed by using the ` + "`trivy cloud config list`" + ` command`, - Example: ` $ trivy cloud config get server.scanning.enabled - $ trivy cloud config get server.scanning.upload-results`, - Args: cobra.ExactArgs(1), - GroupID: cloud.GroupCloud, - RunE: func(_ *cobra.Command, args []string) error { - return cloud.GetConfig(args[0]) - }, - }, - ) - cloudCmd.AddCommand(configCmd) - - return cloudCmd -} - func NewVersionCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { var versionFormat string cmd := &cobra.Command{ diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 5c0eb2c5e9f2..f0c56d333d92 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -14,6 +14,7 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/cache" + "github.com/aquasecurity/trivy/pkg/commands/cloud" "github.com/aquasecurity/trivy/pkg/commands/operation" "github.com/aquasecurity/trivy/pkg/db" "github.com/aquasecurity/trivy/pkg/extension" @@ -376,6 +377,11 @@ func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err err ctx, cancel := context.WithTimeout(ctx, opts.Timeout) defer cancel() + if err := cloud.UpdateOptsForCloudIntegration(ctx, &opts); err != nil { + // log failure but continue with the scan + log.Error("failed to check Trivy Cloud integration", "error", err) + } + if opts.GenerateDefaultConfig { log.Info("Writing the default config to trivy-default.yaml...") diff --git a/pkg/commands/cloud/run.go b/pkg/commands/cloud/run.go index 184574a142ed..3c621b0db7ee 100644 --- a/pkg/commands/cloud/run.go +++ b/pkg/commands/cloud/run.go @@ -3,9 +3,7 @@ package cloud import ( "context" "fmt" - "os" - "github.com/spf13/cobra" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/cloud" @@ -15,106 +13,42 @@ import ( "github.com/aquasecurity/trivy/pkg/log" ) -const GroupCloud = "cloud" - -// Login performs a login to the Trivy Cloud Server service using the provided credentials. -func Login(ctx context.Context, opts flag.Options) error { - creds := opts.CloudOptions.LoginCredentials - if creds.Token == "" { - return xerrors.New("token is required for Trivy Cloud login") - } - if opts.CloudOptions.TrivyServerUrl == "" { - return xerrors.New("trivy server url is required for Trivy Cloud login") - } - if opts.CloudOptions.ApiUrl == "" { - return xerrors.New("api url is required for Trivy Cloud login") - } - - // load the existing config or get the default - cloudConfig, err := cloud.Load() - if err != nil { - return xerrors.Errorf("failed to load Trivy Cloud config: %w", err) - } - cloudConfig.Token = creds.Token - cloudConfig.Server.URL = opts.CloudOptions.TrivyServerUrl - cloudConfig.Api.URL = opts.CloudOptions.ApiUrl - - if err := cloudConfig.Verify(ctx); err != nil { - return xerrors.Errorf("failed to verify Trivy Cloud config: %w", err) - } - - if err := cloudConfig.Save(); err != nil { - return xerrors.Errorf("failed to save Trivy Cloud config: %w", err) - } - - log.WithPrefix(log.PrefixCloud).Info("Trivy Cloud login successful") - return nil -} - -// Logout removes the Trivy cloud configuration from both keychain and config file. -func Logout() error { - if err := cloud.Clear(); err != nil { - return xerrors.Errorf("failed to clear Trivy Cloud configuration: %w", err) - } - - log.WithPrefix(log.PrefixCloud).Info("Logged out of Trivy cloud and removed configuration") - return nil -} - -// CheckTrivyCloudStatus checks if the Trivy Cloud configuration file exists and verifies the token. -// If the token is valid, it sets the environment variables TRIVY_SERVER and TRIVY_TOKEN. -func CheckTrivyCloudStatus(cmd *cobra.Command) error { - if cmd.GroupID == GroupCloud { +// UpdateOptsForCloudIntegration checks if the Trivy Cloud integration is enabled and configures the options accordingly +// if there are variables that are already set that would cause a conflict, we return an error. +// if the token is not provided, we don't need to check the integration and can return early. +func UpdateOptsForCloudIntegration(ctx context.Context, opts *flag.Options) error { + if opts.CloudOptions.CloudToken == "" { return nil } logger := log.WithPrefix(log.PrefixCloud) - cloudConfig, err := cloud.Load() + accessToken, err := cloud.GetAccessToken(ctx, *opts) if err != nil { - logger.Error("Failed to load Trivy Cloud config file", log.Err(err)) - return nil + return xerrors.Errorf("failed to get access token for Trivy Cloud: %w", err) } - if cloudConfig != nil && cloudConfig.Verify(cmd.Context()) == nil { - logger.Info("Trivy cloud is logged in") - if cloudConfig.Server.Scanning.Enabled { - logger.Info("Trivy Cloud server scanning is enabled") - os.Setenv("TRIVY_SERVER", cloudConfig.Server.URL) - os.Setenv("TRIVY_TOKEN_HEADER", "Authorization") - os.Setenv("TRIVY_TOKEN", fmt.Sprintf("Bearer %s", cloudConfig.Token)) + if opts.CloudOptions.UseServerSideScanning { + // ensure that the server address hasn't been already set, this would be an unacceptable config conflict. + if opts.ServerAddr != "" && opts.ServerAddr != opts.CloudOptions.TrivyServerURL { + return xerrors.Errorf("server-side scanning is enabled, but server address is already set to %s", opts.ServerAddr) } - if cloudConfig.Server.Scanning.UploadResults { - logger.Info("Trivy Cloud results upload is enabled") - // add hook to upload the results to Trivy Cloud - resultHook := hooks.NewResultsHook(cloudConfig) - extension.RegisterHook(resultHook) - } + logger.Debug("Using server-side scanning for Trivy Cloud, updating opts") + opts.ServerAddr = opts.CloudOptions.TrivyServerURL + opts.CustomHeaders.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken)) } - return nil -} - -func ListConfig() error { - return cloud.ListConfig() -} - -func EditConfig() error { - return cloud.OpenConfigForEditing() -} - -func SetConfig(attribute string, value any) error { - return cloud.Set(attribute, value) -} + if opts.CloudOptions.SecretConfig || opts.CloudOptions.MisconfigConfig { + if err := cloud.GetConfigs(ctx, opts, accessToken); err != nil { + return xerrors.Errorf("failed to download configs: %w", err) + } + } -func UnsetConfig(attribute string) error { - return cloud.Unset(attribute) -} -func GetConfig(attribute string) error { - value, err := cloud.Get(attribute) - if err != nil { - return xerrors.Errorf("failed to get Trivy Cloud config: %w", err) + // if uploading results we need to register a report hook with the required details + if opts.CloudOptions.UploadResults { + reportHook := hooks.NewReportHook(opts.CloudOptions.ApiURL, accessToken) + extension.RegisterHook(reportHook) } - fmt.Println(value) + return nil } diff --git a/pkg/commands/cloud/run_test.go b/pkg/commands/cloud/run_test.go deleted file mode 100644 index 5c487aa76539..000000000000 --- a/pkg/commands/cloud/run_test.go +++ /dev/null @@ -1,145 +0,0 @@ -package cloud - -import ( - "context" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/zalando/go-keyring" - - "github.com/aquasecurity/trivy/pkg/cloud" - "github.com/aquasecurity/trivy/pkg/flag" -) - -func TestLogout(t *testing.T) { - tests := []struct { - name string - createConfigFile bool - }{ - { - name: "successful logout when the config file exists", - createConfigFile: true, - }, - { - name: "successful logout when the config file does not exist", - createConfigFile: false, - }, - } - - tempDir := t.TempDir() - t.Setenv("XDG_DATA_HOME", tempDir) - - keyring.MockInit() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - defer keyring.DeleteAll(cloud.ServiceName) - defer cloud.Clear() - cloud.Clear() - - if tt.createConfigFile { - config := &cloud.Config{ - Server: cloud.Server{ - URL: "https://example.com", - }, - Api: cloud.Api{ - URL: "https://api.example.com", - }, - } - err := config.Save() - require.NoError(t, err) - } - - err := Logout() - require.NoError(t, err) - }) - } -} - -func TestLogin(t *testing.T) { - tests := []struct { - name string - token string - serverResponse int - wantErr string - }{ - { - name: "successful login with valid token", - token: "valid-token-123", - serverResponse: http.StatusOK, - }, - { - name: "login fails with empty token", - token: "", - serverResponse: http.StatusOK, - wantErr: "token is required for Trivy Cloud login", - }, - { - name: "login fails with server error", - token: "valid-token-123", - serverResponse: http.StatusUnauthorized, - wantErr: "failed to verify token: received status code 401", - }, - { - name: "login fails with server internal error", - token: "valid-token-123", - serverResponse: http.StatusInternalServerError, - wantErr: "failed to verify token: received status code 500", - }, - } - - tempDir := t.TempDir() - t.Setenv("XDG_DATA_HOME", tempDir) - - keyring.MockInit() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - defer keyring.DeleteAll(cloud.ServiceName) - - defer cloud.Clear() - cloud.Clear() - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodPost, r.Method) - assert.Equal(t, "/verify", r.URL.Path) - - if tt.token != "" { - expectedAuth := "Bearer " + tt.token - assert.Equal(t, expectedAuth, r.Header.Get("Authorization")) - } - - w.WriteHeader(tt.serverResponse) - })) - defer server.Close() - - opts := flag.Options{ - CloudOptions: flag.CloudOptions{ - LoginCredentials: flag.CloudLoginCredentials{ - Token: tt.token, - }, - ApiUrl: server.URL + "/api", - TrivyServerUrl: server.URL, - }, - } - - ctx := context.Background() - err := Login(ctx, opts) - - if tt.wantErr != "" { - require.ErrorContains(t, err, tt.wantErr) - return - } - - require.NoError(t, err) - - config, err := cloud.Load() - require.NoError(t, err) - require.Equal(t, tt.token, config.Token) - require.Equal(t, server.URL, config.Server.URL) - require.Equal(t, server.URL+"/api", config.Api.URL) - }) - } -} diff --git a/pkg/flag/cloud_flags.go b/pkg/flag/cloud_flags.go index 0e0b1a5b729f..12ed84c14f24 100644 --- a/pkg/flag/cloud_flags.go +++ b/pkg/flag/cloud_flags.go @@ -1,40 +1,85 @@ package flag -import "github.com/aquasecurity/trivy/pkg/cloud" +const ( + DefaultApiURL = "https://api.trivy.dev" + DefaultTrivyServerURL = "https://scan.trivy.dev" +) var ( CloudTokenFlag = Flag[string]{ - Name: "token", - ConfigName: "cloud.token", + Name: "cloud-token", + ConfigName: "cloud.cloud-token", Usage: "Token used to athenticate with Trivy Cloud platform", } - CloudApiUrlFlag = Flag[string]{ - Name: "api-url", - ConfigName: "cloud.api-url", - Default: cloud.DefaultApiUrl, - Usage: "API URL for Trivy Cloud platform", + CloudApiURLFlag = Flag[string]{ + Name: "cloud-api-url", + ConfigName: "cloud.api-url", + Default: DefaultApiURL, + Usage: "API URL for Trivy Cloud platform", + TelemetrySafe: true, + } + + CloudTrivyServerURLFlag = Flag[string]{ + Name: "cloud-trivy-server-url", + ConfigName: "cloud.trivy-server-url", + Default: DefaultTrivyServerURL, + Usage: "Trivy Server URL for Trivy Cloud platform", + TelemetrySafe: true, + } + + CloudUploadResultsFlag = Flag[bool]{ + Name: "cloud-upload-results", + ConfigName: "cloud.upload-results", + Default: true, + Usage: "Upload results to Trivy Cloud platform", + TelemetrySafe: true, + } + + CloudSecretConfigFlag = Flag[bool]{ + Name: "cloud-download-secret-config", + ConfigName: "cloud.download-secret-config", + Default: true, + Usage: "Download secret configurations from Trivy Cloud platform", + TelemetrySafe: true, + } + + CloudMisconfigConfigFlag = Flag[bool]{ + Name: "cloud-download-misconfig-config", + ConfigName: "cloud.download-misconfig-config", + Default: true, + Usage: "Download misconfig configurations from Trivy Cloud platform", + TelemetrySafe: true, } - CloudTrivyServerUrlFlag = Flag[string]{ - Name: "trivy-server-url", - ConfigName: "cloud.trivy_server_url", - Default: cloud.DefaultTrivyServerUrl, - Usage: "Trivy Server URL for Trivy Cloud platform", + CloudUseServerSideScanningFlag = Flag[bool]{ + Name: "cloud-server-scanning", + ConfigName: "cloud.server-scanning", + Default: true, + Usage: "Use server-side image scanning in Trivy Cloud platform", + TelemetrySafe: true, } ) type CloudFlagGroup struct { - CloudToken *Flag[string] - CloudApiUrl *Flag[string] - CloudTrivyServerUrl *Flag[string] + CloudToken *Flag[string] + CloudApiURL *Flag[string] + CloudTrivyServerURL *Flag[string] + CloudUploadResults *Flag[bool] + CloudSecretConfig *Flag[bool] + CloudMisconfigConfig *Flag[bool] + CloudUseServerSideScanning *Flag[bool] } func NewCloudFlagGroup() *CloudFlagGroup { return &CloudFlagGroup{ - CloudToken: CloudTokenFlag.Clone(), - CloudApiUrl: CloudApiUrlFlag.Clone(), - CloudTrivyServerUrl: CloudTrivyServerUrlFlag.Clone(), + CloudToken: CloudTokenFlag.Clone(), + CloudApiURL: CloudApiURLFlag.Clone(), + CloudTrivyServerURL: CloudTrivyServerURLFlag.Clone(), + CloudUploadResults: CloudUploadResultsFlag.Clone(), + CloudSecretConfig: CloudSecretConfigFlag.Clone(), + CloudMisconfigConfig: CloudMisconfigConfigFlag.Clone(), + CloudUseServerSideScanning: CloudUseServerSideScanningFlag.Clone(), } } @@ -45,8 +90,12 @@ func (f *CloudFlagGroup) Name() string { func (f *CloudFlagGroup) Flags() []Flagger { return []Flagger{ f.CloudToken, - f.CloudApiUrl, - f.CloudTrivyServerUrl, + f.CloudApiURL, + f.CloudTrivyServerURL, + f.CloudUploadResults, + f.CloudSecretConfig, + f.CloudMisconfigConfig, + f.CloudUseServerSideScanning, } } @@ -57,19 +106,25 @@ type CloudLoginCredentials struct { } type CloudOptions struct { - LoginCredentials CloudLoginCredentials - ApiUrl string - TrivyServerUrl string + CloudToken string + ApiURL string + TrivyServerURL string + UploadResults bool + SecretConfig bool + MisconfigConfig bool + UseServerSideScanning bool } // ToOptions converts the flags to options func (f *CloudFlagGroup) ToOptions(opts *Options) error { opts.CloudOptions = CloudOptions{ - LoginCredentials: CloudLoginCredentials{ - Token: f.CloudToken.Value(), - }, - ApiUrl: f.CloudApiUrl.Value(), - TrivyServerUrl: f.CloudTrivyServerUrl.Value(), + CloudToken: f.CloudToken.Value(), + ApiURL: f.CloudApiURL.Value(), + TrivyServerURL: f.CloudTrivyServerURL.Value(), + UploadResults: f.CloudUploadResults.Value(), + SecretConfig: f.CloudSecretConfig.Value(), + MisconfigConfig: f.CloudMisconfigConfig.Value(), + UseServerSideScanning: f.CloudUseServerSideScanning.Value(), } return nil } From 24a21d938837a4f584496d00dfd36a69300607ca Mon Sep 17 00:00:00 2001 From: Owen Rumney Date: Mon, 27 Oct 2025 16:25:40 +0000 Subject: [PATCH 02/11] docs: update the docs with cloud settings --- .../configuration/cli/trivy_config.md | 6 + .../configuration/cli/trivy_filesystem.md | 6 + .../configuration/cli/trivy_image.md | 1 - .../configuration/cli/trivy_repository.md | 6 + .../configuration/cli/trivy_rootfs.md | 6 + .../references/configuration/cli/trivy_vm.md | 6 + pkg/commands/app.go | 5 + pkg/commands/cloud/run.go | 3 +- pkg/commands/cloud/run_test.go | 149 ++++++++++++++++++ pkg/flag/cloud_flags.go | 13 -- 10 files changed, 186 insertions(+), 15 deletions(-) create mode 100644 pkg/commands/cloud/run_test.go diff --git a/docs/docs/references/configuration/cli/trivy_config.md b/docs/docs/references/configuration/cli/trivy_config.md index 899e8e55c9e5..850c98e124a4 100644 --- a/docs/docs/references/configuration/cli/trivy_config.md +++ b/docs/docs/references/configuration/cli/trivy_config.md @@ -14,6 +14,12 @@ trivy config [flags] DIR --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") + --cloud-api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") + --cloud-download-secret-config Download secret configurations from Trivy Cloud platform (default true) + --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform (default true) + --cloud-token string Token used to athenticate with Trivy Cloud platform + --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform (default "https://scan.trivy.dev") + --cloud-upload-results Upload results to Trivy Cloud platform (default true) --compliance string compliance report to generate --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index dde53f11d91e..582801d9edc5 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -24,6 +24,12 @@ trivy filesystem [flags] PATH --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") + --cloud-api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") + --cloud-download-secret-config Download secret configurations from Trivy Cloud platform (default true) + --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform (default true) + --cloud-token string Token used to athenticate with Trivy Cloud platform + --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform (default "https://scan.trivy.dev") + --cloud-upload-results Upload results to Trivy Cloud platform (default true) --compliance string compliance report to generate --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index 1b673b331152..1aba368353fa 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -39,7 +39,6 @@ trivy image [flags] IMAGE_NAME --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") --cloud-api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") - --cloud-download-misconfig-config Download misconfig configurations from Trivy Cloud platform (default true) --cloud-download-secret-config Download secret configurations from Trivy Cloud platform (default true) --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform (default true) --cloud-token string Token used to athenticate with Trivy Cloud platform diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index 1a6ac9bd93bd..ca85a32622df 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -24,6 +24,12 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") + --cloud-api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") + --cloud-download-secret-config Download secret configurations from Trivy Cloud platform (default true) + --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform (default true) + --cloud-token string Token used to athenticate with Trivy Cloud platform + --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform (default "https://scan.trivy.dev") + --cloud-upload-results Upload results to Trivy Cloud platform (default true) --commit string pass the commit hash to be scanned --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 8a0d51085daa..3b9f76507cd9 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -27,6 +27,12 @@ trivy rootfs [flags] ROOTDIR --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") + --cloud-api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") + --cloud-download-secret-config Download secret configurations from Trivy Cloud platform (default true) + --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform (default true) + --cloud-token string Token used to athenticate with Trivy Cloud platform + --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform (default "https://scan.trivy.dev") + --cloud-upload-results Upload results to Trivy Cloud platform (default true) --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded --config-file-schemas strings specify paths to JSON configuration file schemas to determine that a file matches some configuration and pass the schema to Rego checks for type checking diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index 4fbb5d4d4a1e..4e777e254e3a 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -24,6 +24,12 @@ trivy vm [flags] VM_IMAGE --cache-backend string [EXPERIMENTAL] cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") + --cloud-api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") + --cloud-download-secret-config Download secret configurations from Trivy Cloud platform (default true) + --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform (default true) + --cloud-token string Token used to athenticate with Trivy Cloud platform + --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform (default "https://scan.trivy.dev") + --cloud-upload-results Upload results to Trivy Cloud platform (default true) --compliance string compliance report to generate --config-file-schemas strings specify paths to JSON configuration file schemas to determine that a file matches some configuration and pass the schema to Rego checks for type checking --custom-headers strings custom headers in client mode diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 8270dbd1debb..bd6cc3700a92 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -351,6 +351,7 @@ func NewFilesystemCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { flag.NewScanFlagGroup(), flag.NewSecretFlagGroup(), flag.NewVulnerabilityFlagGroup(), + flag.NewCloudFlagGroup(), } cmd := &cobra.Command{ @@ -417,6 +418,7 @@ func NewRootfsCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { flag.NewScanFlagGroup(), flag.NewSecretFlagGroup(), flag.NewVulnerabilityFlagGroup(), + flag.NewCloudFlagGroup(), } cmd := &cobra.Command{ @@ -482,6 +484,7 @@ func NewRepositoryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { flag.NewSecretFlagGroup(), flag.NewVulnerabilityFlagGroup(), flag.NewRepoFlagGroup(), + flag.NewCloudFlagGroup(), } cmd := &cobra.Command{ @@ -704,6 +707,7 @@ func NewConfigCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { flag.NewModuleFlagGroup(), flag.NewRegistryFlagGroup(), flag.NewRegoFlagGroup(), + flag.NewCloudFlagGroup(), &flag.K8sFlagGroup{ // Keep only --k8s-version flag and disable others K8sVersion: flag.K8sVersionFlag.Clone(), @@ -1097,6 +1101,7 @@ func NewVMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { flag.NewScanFlagGroup(), flag.NewSecretFlagGroup(), flag.NewVulnerabilityFlagGroup(), + flag.NewCloudFlagGroup(), &flag.AWSFlagGroup{ Region: &flag.Flag[string]{ Name: "aws-region", diff --git a/pkg/commands/cloud/run.go b/pkg/commands/cloud/run.go index 3c621b0db7ee..2411d093eecb 100644 --- a/pkg/commands/cloud/run.go +++ b/pkg/commands/cloud/run.go @@ -11,6 +11,7 @@ import ( "github.com/aquasecurity/trivy/pkg/extension" "github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" ) // UpdateOptsForCloudIntegration checks if the Trivy Cloud integration is enabled and configures the options accordingly @@ -38,7 +39,7 @@ func UpdateOptsForCloudIntegration(ctx context.Context, opts *flag.Options) erro opts.CustomHeaders.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken)) } - if opts.CloudOptions.SecretConfig || opts.CloudOptions.MisconfigConfig { + if opts.CloudOptions.SecretConfig && opts.Scanners.Enabled(types.SecretScanner) { if err := cloud.GetConfigs(ctx, opts, accessToken); err != nil { return xerrors.Errorf("failed to download configs: %w", err) } diff --git a/pkg/commands/cloud/run_test.go b/pkg/commands/cloud/run_test.go new file mode 100644 index 000000000000..af4a1aa86c14 --- /dev/null +++ b/pkg/commands/cloud/run_test.go @@ -0,0 +1,149 @@ +package cloud + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/types" +) + +type mockCloudServer struct { + server *httptest.Server + configAvailable bool +} + +func (m *mockCloudServer) Start() { + m.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("Authorization") != "Bearer valid-cloud-token" && r.Header.Get("Authorization") != "Bearer test-access-token" { + w.WriteHeader(http.StatusUnauthorized) + return + } + if r.URL.Path == "/api-keys/access-tokens" { + w.WriteHeader(http.StatusCreated) + w.Write([]byte(`{"token": "test-access-token"}`)) + return + } + + if r.URL.Path == "/configs/secrets/secret-config.yaml" { + if !m.configAvailable { + w.WriteHeader(http.StatusNotFound) + return + } + if r.Header.Get("Authorization") != "Bearer test-access-token" { + w.WriteHeader(http.StatusUnauthorized) + return + } + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"content": {"key": "value"}}`)) + return + } + + w.WriteHeader(http.StatusNotFound) + })) +} + +func (m *mockCloudServer) Close() { + m.server.Close() +} + +func TestUpdateOptsForCloudIntegration(t *testing.T) { + mockServer := &mockCloudServer{} + mockServer.Start() + defer mockServer.Close() + + tests := []struct { + name string + opts *flag.Options + configAvailable bool + errorContains string + }{ + { + name: "valid token and config to download", + opts: &flag.Options{ + CloudOptions: flag.CloudOptions{ + CloudToken: "valid-cloud-token", + ApiURL: mockServer.server.URL, + TrivyServerURL: mockServer.server.URL, + SecretConfig: true, + }, + ScanOptions: flag.ScanOptions{ + Scanners: types.Scanners{types.SecretScanner}, + }, + }, + configAvailable: true, + }, + { + name: "valid token but config not requested", + opts: &flag.Options{ + CloudOptions: flag.CloudOptions{ + CloudToken: "valid-cloud-token", + ApiURL: mockServer.server.URL, + TrivyServerURL: mockServer.server.URL, + SecretConfig: false, + }, + ScanOptions: flag.ScanOptions{ + Scanners: types.Scanners{types.SecretScanner}, + }, + }, + configAvailable: true, + }, + { + name: "valid token but config not available", + opts: &flag.Options{ + CloudOptions: flag.CloudOptions{ + CloudToken: "valid-cloud-token", + ApiURL: mockServer.server.URL, + TrivyServerURL: mockServer.server.URL, + SecretConfig: false, + }, + }, + configAvailable: false, + }, + { + name: "invalid token 401 status code", + opts: &flag.Options{ + CloudOptions: flag.CloudOptions{ + CloudToken: "invalid-token", + ApiURL: mockServer.server.URL, + TrivyServerURL: mockServer.server.URL, + SecretConfig: false, + }, + ScanOptions: flag.ScanOptions{ + Scanners: types.Scanners{types.SecretScanner}, + }, + }, + configAvailable: true, + errorContains: "failed to get access token for Trivy Cloud", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tempDir := t.TempDir() + t.Setenv("XDG_DATA_HOME", tempDir) + mockServer.configAvailable = tt.configAvailable + + err := UpdateOptsForCloudIntegration(context.Background(), tt.opts) + + if tt.errorContains != "" { + require.ErrorContains(t, err, tt.errorContains) + return + } + + require.NoError(t, err) + + if tt.opts.CloudOptions.SecretConfig && tt.opts.ScanOptions.Scanners.Enabled(types.SecretScanner) { + assert.NotEmpty(t, tt.opts.SecretOptions.SecretConfigPath) + assert.FileExists(t, tt.opts.SecretOptions.SecretConfigPath) + } else { + assert.Empty(t, tt.opts.SecretOptions.SecretConfigPath) + } + }) + } +} diff --git a/pkg/flag/cloud_flags.go b/pkg/flag/cloud_flags.go index 12ed84c14f24..f80812974d11 100644 --- a/pkg/flag/cloud_flags.go +++ b/pkg/flag/cloud_flags.go @@ -44,14 +44,6 @@ var ( TelemetrySafe: true, } - CloudMisconfigConfigFlag = Flag[bool]{ - Name: "cloud-download-misconfig-config", - ConfigName: "cloud.download-misconfig-config", - Default: true, - Usage: "Download misconfig configurations from Trivy Cloud platform", - TelemetrySafe: true, - } - CloudUseServerSideScanningFlag = Flag[bool]{ Name: "cloud-server-scanning", ConfigName: "cloud.server-scanning", @@ -67,7 +59,6 @@ type CloudFlagGroup struct { CloudTrivyServerURL *Flag[string] CloudUploadResults *Flag[bool] CloudSecretConfig *Flag[bool] - CloudMisconfigConfig *Flag[bool] CloudUseServerSideScanning *Flag[bool] } @@ -78,7 +69,6 @@ func NewCloudFlagGroup() *CloudFlagGroup { CloudTrivyServerURL: CloudTrivyServerURLFlag.Clone(), CloudUploadResults: CloudUploadResultsFlag.Clone(), CloudSecretConfig: CloudSecretConfigFlag.Clone(), - CloudMisconfigConfig: CloudMisconfigConfigFlag.Clone(), CloudUseServerSideScanning: CloudUseServerSideScanningFlag.Clone(), } } @@ -94,7 +84,6 @@ func (f *CloudFlagGroup) Flags() []Flagger { f.CloudTrivyServerURL, f.CloudUploadResults, f.CloudSecretConfig, - f.CloudMisconfigConfig, f.CloudUseServerSideScanning, } } @@ -111,7 +100,6 @@ type CloudOptions struct { TrivyServerURL string UploadResults bool SecretConfig bool - MisconfigConfig bool UseServerSideScanning bool } @@ -123,7 +111,6 @@ func (f *CloudFlagGroup) ToOptions(opts *Options) error { TrivyServerURL: f.CloudTrivyServerURL.Value(), UploadResults: f.CloudUploadResults.Value(), SecretConfig: f.CloudSecretConfig.Value(), - MisconfigConfig: f.CloudMisconfigConfig.Value(), UseServerSideScanning: f.CloudUseServerSideScanning.Value(), } return nil From cb4e440064633cf3c9ff0c552a6ba4435013d12f Mon Sep 17 00:00:00 2001 From: Owen Rumney Date: Tue, 28 Oct 2025 09:40:10 +0000 Subject: [PATCH 03/11] test: add a test for the report hook --- pkg/cloud/config.go | 6 +- pkg/cloud/hooks/report_hook_test.go | 318 ++++++++++++++++++++++++++++ 2 files changed, 321 insertions(+), 3 deletions(-) create mode 100644 pkg/cloud/hooks/report_hook_test.go diff --git a/pkg/cloud/config.go b/pkg/cloud/config.go index a8a8e6bdc9c6..4b287c24cb84 100644 --- a/pkg/cloud/config.go +++ b/pkg/cloud/config.go @@ -23,13 +23,13 @@ import ( type ConfigType string const ( - // Additional config types can be added here - in future custom checks etc + // Additional config types can be added here - in future pipeline rego etc ConfigTypeSecret ConfigType = "secret" ) const ( SecretConfigPath = "/configs/secrets/secret-config.yaml" - configCacheTTL = -time.Hour + configCacheTTL = time.Hour ) var configPaths = map[ConfigType]string{ @@ -73,7 +73,7 @@ func getConfigFromTrivyCloud(ctx context.Context, client *http.Client, opts *fla configFilename := filepath.Join(configDir, "config.yaml") // Return cached config if it was updated within the last hour - if stat, err := os.Stat(configFilename); err == nil && stat.ModTime().After(time.Now().Add(configCacheTTL)) { + if stat, err := os.Stat(configFilename); err == nil && stat.ModTime().After(time.Now().Add(-configCacheTTL)) { logger.Debug("Config found in cache", log.String("configType", string(configType)), log.String("configPath", configFilename)) return configFilename, nil } diff --git a/pkg/cloud/hooks/report_hook_test.go b/pkg/cloud/hooks/report_hook_test.go new file mode 100644 index 000000000000..526f73cef7ef --- /dev/null +++ b/pkg/cloud/hooks/report_hook_test.go @@ -0,0 +1,318 @@ +package hooks + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/types" +) + +type mockReportServer struct { + server *httptest.Server + uploadURLRequested bool + reportUploaded bool + uploadedReport *types.Report + returnUnauthorized bool + returnInvalidJSON bool + failUpload bool + presignedUploadPath string +} + +func (m *mockReportServer) Start() { + m.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == presignedUploadUrl { + m.handlePresignedURLRequest(w, r) + return + } + + if r.URL.Path == m.presignedUploadPath { + m.handleReportUpload(w, r) + return + } + + w.WriteHeader(http.StatusNotFound) + })) + m.presignedUploadPath = "/upload-report" +} + +func (m *mockReportServer) handlePresignedURLRequest(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + if r.Header.Get("Authorization") != "Bearer test-token" { + w.WriteHeader(http.StatusUnauthorized) + return + } + + if m.returnUnauthorized { + w.WriteHeader(http.StatusUnauthorized) + return + } + + m.uploadURLRequested = true + + if m.returnInvalidJSON { + w.WriteHeader(http.StatusOK) + w.Write([]byte(`invalid json`)) + return + } + + uploadURL := m.server.URL + m.presignedUploadPath + response := map[string]string{ + "uploadUrl": uploadURL, + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(response) +} + +func (m *mockReportServer) handleReportUpload(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPut { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + if r.Header.Get("Content-Type") != "application/json" { + w.WriteHeader(http.StatusBadRequest) + return + } + + if m.failUpload { + w.WriteHeader(http.StatusInternalServerError) + return + } + + body, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + var report types.Report + if err := json.Unmarshal(body, &report); err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + m.reportUploaded = true + m.uploadedReport = &report + + w.WriteHeader(http.StatusOK) +} + +func (m *mockReportServer) Close() { + if m.server != nil { + m.server.Close() + } +} + +func TestReportHook_Name(t *testing.T) { + hook := NewReportHook("http://api.example.com", "test-token") + assert.Equal(t, "Trivy Cloud Results Hook", hook.Name()) +} + +func TestReportHook_PreReport(t *testing.T) { + hook := NewReportHook("http://api.example.com", "test-token") + err := hook.PreReport(context.Background(), &types.Report{}, flag.Options{}) + assert.NoError(t, err) +} + +func TestReportHook_PostReport(t *testing.T) { + tests := []struct { + name string + report *types.Report + returnUnauthorized bool + returnInvalidJSON bool + failUpload bool + errorContains string + }{ + { + name: "successful upload", + report: &types.Report{ + ArtifactName: "test-artifact", + ArtifactType: ftypes.TypeContainerImage, + Results: types.Results{ + { + Target: "test-target", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2021-1234", + PkgName: "test-package", + PkgID: "test-package@1.0.0", + }, + }, + }, + }, + }, + }, + { + name: "empty report", + report: &types.Report{ + ArtifactName: "empty-artifact", + ArtifactType: ftypes.TypeContainerImage, + }, + }, + { + name: "invalid token 401 status code", + report: &types.Report{ + ArtifactName: "test-artifact", + }, + returnUnauthorized: true, + errorContains: "failed to get presigned upload URL", + }, + { + name: "unauthorized access", + report: &types.Report{ + ArtifactName: "test-artifact", + }, + returnUnauthorized: true, + errorContains: "failed to get presigned upload URL", + }, + { + name: "invalid json response", + report: &types.Report{ + ArtifactName: "test-artifact", + }, + returnInvalidJSON: true, + errorContains: "failed to decode upload URL response", + }, + { + name: "upload failure", + report: &types.Report{ + ArtifactName: "test-artifact", + }, + failUpload: true, + errorContains: "failed to upload results", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockServer := &mockReportServer{ + returnUnauthorized: tt.returnUnauthorized, + returnInvalidJSON: tt.returnInvalidJSON, + failUpload: tt.failUpload, + } + mockServer.Start() + defer mockServer.Close() + + hook := NewReportHook(mockServer.server.URL, "test-token") + err := hook.PostReport(context.Background(), tt.report, flag.Options{}) + + if tt.errorContains != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.errorContains) + return + } + + require.NoError(t, err) + assert.True(t, mockServer.uploadURLRequested) + assert.True(t, mockServer.reportUploaded) + assert.Equal(t, tt.report.ArtifactName, mockServer.uploadedReport.ArtifactName) + }) + } +} + +func TestReportHook_uploadResults(t *testing.T) { + tests := []struct { + name string + jsonReport []byte + failUpload bool + errorContains string + }{ + { + name: "successful upload", + jsonReport: []byte(`{"artifactName": "test"}`), + }, + { + name: "upload failure", + jsonReport: []byte(`{"artifactName": "test"}`), + failUpload: true, + errorContains: "failed to upload results", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockServer := &mockReportServer{ + failUpload: tt.failUpload, + } + mockServer.Start() + defer mockServer.Close() + + hook := NewReportHook(mockServer.server.URL, "test-token") + err := hook.uploadResults(context.Background(), tt.jsonReport) + + if tt.errorContains != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.errorContains) + return + } + + require.NoError(t, err) + assert.True(t, mockServer.reportUploaded) + }) + } +} + +func TestReportHook_getPresignedUploadUrl(t *testing.T) { + tests := []struct { + name string + returnUnauthorized bool + returnInvalidJSON bool + errorContains string + expectedURL string + }{ + { + name: "successful request", + }, + { + name: "unauthorized", + returnUnauthorized: true, + errorContains: "failed to get upload URL", + }, + { + name: "invalid json response", + returnInvalidJSON: true, + errorContains: "failed to decode upload URL response", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockServer := &mockReportServer{ + returnUnauthorized: tt.returnUnauthorized, + returnInvalidJSON: tt.returnInvalidJSON, + } + mockServer.Start() + defer mockServer.Close() + + hook := NewReportHook(mockServer.server.URL, "test-token") + url, err := hook.getPresignedUploadUrl(context.Background()) + + if tt.errorContains != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.errorContains) + assert.Empty(t, url) + return + } + + require.NoError(t, err) + assert.NotEmpty(t, url) + assert.Contains(t, url, mockServer.server.URL) + assert.Contains(t, url, "/upload-report") + }) + } +} From fd183fa6e1204ea47461edfc198ceddf3193266d Mon Sep 17 00:00:00 2001 From: Owen Rumney Date: Wed, 29 Oct 2025 12:16:53 +0000 Subject: [PATCH 04/11] chore: PR review comments Updates based on the PR comments and regeneration of the docs --- .../configuration/cli/trivy_config.md | 2 +- .../configuration/cli/trivy_filesystem.md | 2 +- .../configuration/cli/trivy_image.md | 2 +- .../configuration/cli/trivy_repository.md | 2 +- .../configuration/cli/trivy_rootfs.md | 2 +- .../references/configuration/cli/trivy_vm.md | 2 +- pkg/cloud/hooks/report_hook_test.go | 7 +++---- pkg/cloud/token.go | 18 ++++++++++-------- pkg/flag/cloud_flags.go | 6 +++--- 9 files changed, 22 insertions(+), 21 deletions(-) diff --git a/docs/docs/references/configuration/cli/trivy_config.md b/docs/docs/references/configuration/cli/trivy_config.md index 850c98e124a4..0d1eb389b980 100644 --- a/docs/docs/references/configuration/cli/trivy_config.md +++ b/docs/docs/references/configuration/cli/trivy_config.md @@ -15,11 +15,11 @@ trivy config [flags] DIR --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") --cloud-api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") - --cloud-download-secret-config Download secret configurations from Trivy Cloud platform (default true) --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform (default true) --cloud-token string Token used to athenticate with Trivy Cloud platform --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform (default "https://scan.trivy.dev") --cloud-upload-results Upload results to Trivy Cloud platform (default true) + --cloud-use-secret-config Use secret configurations from Trivy Cloud platform (default true) --compliance string compliance report to generate --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index 582801d9edc5..4b852c42f434 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -25,11 +25,11 @@ trivy filesystem [flags] PATH --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") --cloud-api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") - --cloud-download-secret-config Download secret configurations from Trivy Cloud platform (default true) --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform (default true) --cloud-token string Token used to athenticate with Trivy Cloud platform --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform (default "https://scan.trivy.dev") --cloud-upload-results Upload results to Trivy Cloud platform (default true) + --cloud-use-secret-config Use secret configurations from Trivy Cloud platform (default true) --compliance string compliance report to generate --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index 1aba368353fa..28a5c7b2b776 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -39,11 +39,11 @@ trivy image [flags] IMAGE_NAME --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") --cloud-api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") - --cloud-download-secret-config Download secret configurations from Trivy Cloud platform (default true) --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform (default true) --cloud-token string Token used to athenticate with Trivy Cloud platform --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform (default "https://scan.trivy.dev") --cloud-upload-results Upload results to Trivy Cloud platform (default true) + --cloud-use-secret-config Use secret configurations from Trivy Cloud platform (default true) --compliance string compliance report to generate (built-in compliance's: docker-cis-1.6.0) --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index ca85a32622df..5aca45c81e78 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -25,11 +25,11 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") --cloud-api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") - --cloud-download-secret-config Download secret configurations from Trivy Cloud platform (default true) --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform (default true) --cloud-token string Token used to athenticate with Trivy Cloud platform --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform (default "https://scan.trivy.dev") --cloud-upload-results Upload results to Trivy Cloud platform (default true) + --cloud-use-secret-config Use secret configurations from Trivy Cloud platform (default true) --commit string pass the commit hash to be scanned --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 3b9f76507cd9..4a15bb352db3 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -28,11 +28,11 @@ trivy rootfs [flags] ROOTDIR --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") --cloud-api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") - --cloud-download-secret-config Download secret configurations from Trivy Cloud platform (default true) --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform (default true) --cloud-token string Token used to athenticate with Trivy Cloud platform --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform (default "https://scan.trivy.dev") --cloud-upload-results Upload results to Trivy Cloud platform (default true) + --cloud-use-secret-config Use secret configurations from Trivy Cloud platform (default true) --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded --config-file-schemas strings specify paths to JSON configuration file schemas to determine that a file matches some configuration and pass the schema to Rego checks for type checking diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index 4e777e254e3a..de79611e9cd7 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -25,11 +25,11 @@ trivy vm [flags] VM_IMAGE --cache-ttl duration cache TTL when using redis as cache backend --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") --cloud-api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") - --cloud-download-secret-config Download secret configurations from Trivy Cloud platform (default true) --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform (default true) --cloud-token string Token used to athenticate with Trivy Cloud platform --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform (default "https://scan.trivy.dev") --cloud-upload-results Upload results to Trivy Cloud platform (default true) + --cloud-use-secret-config Use secret configurations from Trivy Cloud platform (default true) --compliance string compliance report to generate --config-file-schemas strings specify paths to JSON configuration file schemas to determine that a file matches some configuration and pass the schema to Rego checks for type checking --custom-headers strings custom headers in client mode diff --git a/pkg/cloud/hooks/report_hook_test.go b/pkg/cloud/hooks/report_hook_test.go index 526f73cef7ef..4d400c911b52 100644 --- a/pkg/cloud/hooks/report_hook_test.go +++ b/pkg/cloud/hooks/report_hook_test.go @@ -213,7 +213,7 @@ func TestReportHook_PostReport(t *testing.T) { if tt.errorContains != "" { require.Error(t, err) - assert.Contains(t, err.Error(), tt.errorContains) + require.ErrorContains(t, err, tt.errorContains) return } @@ -257,7 +257,7 @@ func TestReportHook_uploadResults(t *testing.T) { if tt.errorContains != "" { require.Error(t, err) - assert.Contains(t, err.Error(), tt.errorContains) + require.ErrorContains(t, err, tt.errorContains) return } @@ -304,13 +304,12 @@ func TestReportHook_getPresignedUploadUrl(t *testing.T) { if tt.errorContains != "" { require.Error(t, err) - assert.Contains(t, err.Error(), tt.errorContains) + require.ErrorContains(t, err, tt.errorContains) assert.Empty(t, url) return } require.NoError(t, err) - assert.NotEmpty(t, url) assert.Contains(t, url, mockServer.server.URL) assert.Contains(t, url, "/upload-report") }) diff --git a/pkg/cloud/token.go b/pkg/cloud/token.go index 3216a9f1b422..b30e2cde1f48 100644 --- a/pkg/cloud/token.go +++ b/pkg/cloud/token.go @@ -14,6 +14,10 @@ import ( xhttp "github.com/aquasecurity/trivy/pkg/x/http" ) +type accessTokenResponse struct { + Token string `json:"token"` +} + const ( accessTokenPath = "/api-keys/access-tokens" ) @@ -30,12 +34,12 @@ func GetAccessToken(ctx context.Context, opts flag.Options) (string, error) { logger := log.WithPrefix(log.PrefixCloud) client := xhttp.Client() - url, err := url.JoinPath(opts.CloudOptions.ApiURL, accessTokenPath) + u, err := url.JoinPath(opts.CloudOptions.ApiURL, accessTokenPath) if err != nil { return "", xerrors.Errorf("failed to join server URL and token path: %w", err) } - logger.Debug("Requesting access token from Trivy Cloud", log.String("url", url)) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, http.NoBody) + logger.Debug("Requesting access token from Trivy Cloud", log.String("url", u)) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, http.NoBody) if err != nil { return "", xerrors.Errorf("failed to create token request: %w", err) } @@ -50,13 +54,11 @@ func GetAccessToken(ctx context.Context, opts flag.Options) (string, error) { return "", xerrors.Errorf("failed to get access token: received status code %d", resp.StatusCode) } - var accessTokenResponse struct { - Token string `json:"token"` - } - if err := json.NewDecoder(resp.Body).Decode(&accessTokenResponse); err != nil { + var tokenResponse accessTokenResponse + if err := json.NewDecoder(resp.Body).Decode(&tokenResponse); err != nil { return "", xerrors.Errorf("failed to decode access token response: %w", err) } logger.Debug("Created a new access token") - return accessTokenResponse.Token, nil + return tokenResponse.Token, nil } diff --git a/pkg/flag/cloud_flags.go b/pkg/flag/cloud_flags.go index f80812974d11..fcfc6fa98806 100644 --- a/pkg/flag/cloud_flags.go +++ b/pkg/flag/cloud_flags.go @@ -37,10 +37,10 @@ var ( } CloudSecretConfigFlag = Flag[bool]{ - Name: "cloud-download-secret-config", - ConfigName: "cloud.download-secret-config", + Name: "cloud-use-secret-config", + ConfigName: "cloud.use-secret-config", Default: true, - Usage: "Download secret configurations from Trivy Cloud platform", + Usage: "Use secret configurations from Trivy Cloud platform", TelemetrySafe: true, } From a945c521636bd7d6a497e7ae7d43d3e3c0f61a89 Mon Sep 17 00:00:00 2001 From: Owen Rumney Date: Wed, 29 Oct 2025 12:49:23 +0000 Subject: [PATCH 05/11] Update pkg/flag/cloud_flags.go Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> --- pkg/flag/cloud_flags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/flag/cloud_flags.go b/pkg/flag/cloud_flags.go index fcfc6fa98806..cc38f0c052ab 100644 --- a/pkg/flag/cloud_flags.go +++ b/pkg/flag/cloud_flags.go @@ -8,7 +8,7 @@ const ( var ( CloudTokenFlag = Flag[string]{ Name: "cloud-token", - ConfigName: "cloud.cloud-token", + ConfigName: "cloud.token", Usage: "Token used to athenticate with Trivy Cloud platform", } From 954506beaf5a97f346b26c6f22f1575a30bce670 Mon Sep 17 00:00:00 2001 From: Owen Rumney Date: Thu, 30 Oct 2025 09:11:33 +0000 Subject: [PATCH 06/11] chore(cli): pr comment updates Updates to logging and tests based on the PR comments --- pkg/cloud/config.go | 8 ++++---- pkg/commands/cloud/run_test.go | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/cloud/config.go b/pkg/cloud/config.go index 4b287c24cb84..b4b1639f51a3 100644 --- a/pkg/cloud/config.go +++ b/pkg/cloud/config.go @@ -46,7 +46,7 @@ func GetConfigs(ctx context.Context, opts *flag.Options, accessToken string) err if opts.CloudOptions.SecretConfig && opts.Scanners.Enabled(types.SecretScanner) { if opts.SecretOptions.SecretConfigPath != "" { - logger.Warn("Secret config path already set", log.String("configPath", opts.SecretOptions.SecretConfigPath)) + logger.Warn("Secret config path already set", log.FilePath(opts.SecretOptions.SecretConfigPath)) return nil } @@ -64,7 +64,7 @@ func GetConfigs(ctx context.Context, opts *flag.Options, accessToken string) err // getConfigFromTrivyCloud downloads a config from Trivy Cloud and saves it to a file // it returns the path to the config file if it was downloaded successfully, otherwise it returns an error func getConfigFromTrivyCloud(ctx context.Context, client *http.Client, opts *flag.Options, accessToken string, configType ConfigType) (string, error) { - logger := log.WithPrefix(log.PrefixCloud) + logger := log.WithPrefix(log.PrefixCloud).With("configType", configType) configTypeStr := string(configType) configDir := filepath.Join(fsutils.TrivyHomeDir(), "cloud", configTypeStr) if err := os.MkdirAll(configDir, os.ModePerm); err != nil { @@ -74,11 +74,11 @@ func getConfigFromTrivyCloud(ctx context.Context, client *http.Client, opts *fla configFilename := filepath.Join(configDir, "config.yaml") // Return cached config if it was updated within the last hour if stat, err := os.Stat(configFilename); err == nil && stat.ModTime().After(time.Now().Add(-configCacheTTL)) { - logger.Debug("Config found in cache", log.String("configType", string(configType)), log.String("configPath", configFilename)) + logger.Debug("Config found in cache", log.FilePath(configFilename)) return configFilename, nil } - logger.Debug("Config not found in cache", log.String("configType", string(configType)), log.String("configPath", configFilename)) + logger.Debug("Config not found in cache", log.FilePath(configFilename)) configPath, ok := configPaths[configType] if !ok { return "", xerrors.Errorf("unknown config type: %s", configType) diff --git a/pkg/commands/cloud/run_test.go b/pkg/commands/cloud/run_test.go index af4a1aa86c14..d695b2693425 100644 --- a/pkg/commands/cloud/run_test.go +++ b/pkg/commands/cloud/run_test.go @@ -1,7 +1,6 @@ package cloud import ( - "context" "net/http" "net/http/httptest" "testing" @@ -129,7 +128,7 @@ func TestUpdateOptsForCloudIntegration(t *testing.T) { t.Setenv("XDG_DATA_HOME", tempDir) mockServer.configAvailable = tt.configAvailable - err := UpdateOptsForCloudIntegration(context.Background(), tt.opts) + err := UpdateOptsForCloudIntegration(t.Context(), tt.opts) if tt.errorContains != "" { require.ErrorContains(t, err, tt.errorContains) From 638a070d01ed355fe8e482849c9389fd8c309886 Mon Sep 17 00:00:00 2001 From: Owen Rumney Date: Thu, 30 Oct 2025 14:47:26 +0000 Subject: [PATCH 07/11] Update pkg/cloud/token_test.go Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> --- pkg/cloud/token_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/cloud/token_test.go b/pkg/cloud/token_test.go index 7c4104935d29..1799a2ca30b1 100644 --- a/pkg/cloud/token_test.go +++ b/pkg/cloud/token_test.go @@ -89,10 +89,10 @@ func TestGetAccessToken(t *testing.T) { got, err := GetAccessToken(t.Context(), tt.opts) if tt.errorContains != "" { - require.ErrorContains(t, err, tt.errorContains) - } else { - require.NoError(t, err) + return } + + require.NoError(t, err) assert.Equal(t, tt.want, got) }) From 0100911edc8f1aa9d412552b1d85d233971d2d63 Mon Sep 17 00:00:00 2001 From: Owen Rumney Date: Thu, 30 Oct 2025 17:05:43 +0000 Subject: [PATCH 08/11] docs: add clear information about token requirement --- .../configuration/cli/trivy_config.md | 10 ++++----- .../configuration/cli/trivy_filesystem.md | 10 ++++----- .../configuration/cli/trivy_image.md | 10 ++++----- .../configuration/cli/trivy_repository.md | 10 ++++----- .../configuration/cli/trivy_rootfs.md | 10 ++++----- .../references/configuration/cli/trivy_vm.md | 10 ++++----- pkg/flag/cloud_flags.go | 21 ++++++++++--------- 7 files changed, 41 insertions(+), 40 deletions(-) diff --git a/docs/docs/references/configuration/cli/trivy_config.md b/docs/docs/references/configuration/cli/trivy_config.md index 0d1eb389b980..166d96c4b16c 100644 --- a/docs/docs/references/configuration/cli/trivy_config.md +++ b/docs/docs/references/configuration/cli/trivy_config.md @@ -14,12 +14,12 @@ trivy config [flags] DIR --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") - --cloud-api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") - --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform (default true) + --cloud-api-url string API URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://api.trivy.dev") + --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform, requires the token to be provided to have an effect (default true) --cloud-token string Token used to athenticate with Trivy Cloud platform - --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform (default "https://scan.trivy.dev") - --cloud-upload-results Upload results to Trivy Cloud platform (default true) - --cloud-use-secret-config Use secret configurations from Trivy Cloud platform (default true) + --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://scan.trivy.dev") + --cloud-upload-results Upload results to Trivy Cloud platform, requires the token to be provided to have an effect (default true) + --cloud-use-secret-config Use secret configurations from Trivy Cloud platform, requires the token to be provided to have an effect (default true) --compliance string compliance report to generate --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index 4b852c42f434..7ceabfb1775d 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -24,12 +24,12 @@ trivy filesystem [flags] PATH --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") - --cloud-api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") - --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform (default true) + --cloud-api-url string API URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://api.trivy.dev") + --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform, requires the token to be provided to have an effect (default true) --cloud-token string Token used to athenticate with Trivy Cloud platform - --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform (default "https://scan.trivy.dev") - --cloud-upload-results Upload results to Trivy Cloud platform (default true) - --cloud-use-secret-config Use secret configurations from Trivy Cloud platform (default true) + --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://scan.trivy.dev") + --cloud-upload-results Upload results to Trivy Cloud platform, requires the token to be provided to have an effect (default true) + --cloud-use-secret-config Use secret configurations from Trivy Cloud platform, requires the token to be provided to have an effect (default true) --compliance string compliance report to generate --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index 28a5c7b2b776..5daf22b4e7b8 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -38,12 +38,12 @@ trivy image [flags] IMAGE_NAME --cache-ttl duration cache TTL when using redis as cache backend --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") - --cloud-api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") - --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform (default true) + --cloud-api-url string API URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://api.trivy.dev") + --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform, requires the token to be provided to have an effect (default true) --cloud-token string Token used to athenticate with Trivy Cloud platform - --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform (default "https://scan.trivy.dev") - --cloud-upload-results Upload results to Trivy Cloud platform (default true) - --cloud-use-secret-config Use secret configurations from Trivy Cloud platform (default true) + --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://scan.trivy.dev") + --cloud-upload-results Upload results to Trivy Cloud platform, requires the token to be provided to have an effect (default true) + --cloud-use-secret-config Use secret configurations from Trivy Cloud platform, requires the token to be provided to have an effect (default true) --compliance string compliance report to generate (built-in compliance's: docker-cis-1.6.0) --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index 5aca45c81e78..557f51f5f1cb 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -24,12 +24,12 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") - --cloud-api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") - --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform (default true) + --cloud-api-url string API URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://api.trivy.dev") + --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform, requires the token to be provided to have an effect (default true) --cloud-token string Token used to athenticate with Trivy Cloud platform - --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform (default "https://scan.trivy.dev") - --cloud-upload-results Upload results to Trivy Cloud platform (default true) - --cloud-use-secret-config Use secret configurations from Trivy Cloud platform (default true) + --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://scan.trivy.dev") + --cloud-upload-results Upload results to Trivy Cloud platform, requires the token to be provided to have an effect (default true) + --cloud-use-secret-config Use secret configurations from Trivy Cloud platform, requires the token to be provided to have an effect (default true) --commit string pass the commit hash to be scanned --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 4a15bb352db3..3f1acd2a4516 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -27,12 +27,12 @@ trivy rootfs [flags] ROOTDIR --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") - --cloud-api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") - --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform (default true) + --cloud-api-url string API URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://api.trivy.dev") + --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform, requires the token to be provided to have an effect (default true) --cloud-token string Token used to athenticate with Trivy Cloud platform - --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform (default "https://scan.trivy.dev") - --cloud-upload-results Upload results to Trivy Cloud platform (default true) - --cloud-use-secret-config Use secret configurations from Trivy Cloud platform (default true) + --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://scan.trivy.dev") + --cloud-upload-results Upload results to Trivy Cloud platform, requires the token to be provided to have an effect (default true) + --cloud-use-secret-config Use secret configurations from Trivy Cloud platform, requires the token to be provided to have an effect (default true) --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded --config-file-schemas strings specify paths to JSON configuration file schemas to determine that a file matches some configuration and pass the schema to Rego checks for type checking diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index de79611e9cd7..363b6b382ffc 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -24,12 +24,12 @@ trivy vm [flags] VM_IMAGE --cache-backend string [EXPERIMENTAL] cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") - --cloud-api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") - --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform (default true) + --cloud-api-url string API URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://api.trivy.dev") + --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform, requires the token to be provided to have an effect (default true) --cloud-token string Token used to athenticate with Trivy Cloud platform - --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform (default "https://scan.trivy.dev") - --cloud-upload-results Upload results to Trivy Cloud platform (default true) - --cloud-use-secret-config Use secret configurations from Trivy Cloud platform (default true) + --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://scan.trivy.dev") + --cloud-upload-results Upload results to Trivy Cloud platform, requires the token to be provided to have an effect (default true) + --cloud-use-secret-config Use secret configurations from Trivy Cloud platform, requires the token to be provided to have an effect (default true) --compliance string compliance report to generate --config-file-schemas strings specify paths to JSON configuration file schemas to determine that a file matches some configuration and pass the schema to Rego checks for type checking --custom-headers strings custom headers in client mode diff --git a/pkg/flag/cloud_flags.go b/pkg/flag/cloud_flags.go index cc38f0c052ab..7552de7446a0 100644 --- a/pkg/flag/cloud_flags.go +++ b/pkg/flag/cloud_flags.go @@ -16,7 +16,7 @@ var ( Name: "cloud-api-url", ConfigName: "cloud.api-url", Default: DefaultApiURL, - Usage: "API URL for Trivy Cloud platform", + Usage: "API URL for Trivy Cloud platform, requires the token to be provided to have an effect", TelemetrySafe: true, } @@ -24,7 +24,7 @@ var ( Name: "cloud-trivy-server-url", ConfigName: "cloud.trivy-server-url", Default: DefaultTrivyServerURL, - Usage: "Trivy Server URL for Trivy Cloud platform", + Usage: "Trivy Server URL for Trivy Cloud platform, requires the token to be provided to have an effect", TelemetrySafe: true, } @@ -32,7 +32,7 @@ var ( Name: "cloud-upload-results", ConfigName: "cloud.upload-results", Default: true, - Usage: "Upload results to Trivy Cloud platform", + Usage: "Upload results to Trivy Cloud platform, requires the token to be provided to have an effect", TelemetrySafe: true, } @@ -40,7 +40,7 @@ var ( Name: "cloud-use-secret-config", ConfigName: "cloud.use-secret-config", Default: true, - Usage: "Use secret configurations from Trivy Cloud platform", + Usage: "Use secret configurations from Trivy Cloud platform, requires the token to be provided to have an effect", TelemetrySafe: true, } @@ -48,17 +48,18 @@ var ( Name: "cloud-server-scanning", ConfigName: "cloud.server-scanning", Default: true, - Usage: "Use server-side image scanning in Trivy Cloud platform", + Usage: "Use server-side image scanning in Trivy Cloud platform, requires the token to be provided to have an effect", TelemetrySafe: true, } ) type CloudFlagGroup struct { - CloudToken *Flag[string] - CloudApiURL *Flag[string] - CloudTrivyServerURL *Flag[string] - CloudUploadResults *Flag[bool] - CloudSecretConfig *Flag[bool] + CloudToken *Flag[string] + CloudApiURL *Flag[string] + CloudTrivyServerURL *Flag[string] + CloudUploadResults *Flag[bool] + CloudSecretConfig *Flag[bool] + CloudUseServerSideScanning *Flag[bool] } From 0860b0cacfbf9157462258ecf4c75cbe4701c8c0 Mon Sep 17 00:00:00 2001 From: Owen Rumney Date: Mon, 3 Nov 2025 11:06:33 +0000 Subject: [PATCH 09/11] chore: rename the --cloud- flags to --pro- --- .../configuration/cli/trivy_config.md | 12 +- .../configuration/cli/trivy_filesystem.md | 12 +- .../configuration/cli/trivy_image.md | 12 +- .../configuration/cli/trivy_repository.md | 12 +- .../configuration/cli/trivy_rootfs.md | 12 +- .../references/configuration/cli/trivy_vm.md | 12 +- pkg/cloud/config.go | 12 +- pkg/cloud/config_test.go | 2 +- pkg/cloud/hooks/report_hook.go | 4 +- pkg/cloud/hooks/report_hook_test.go | 2 +- pkg/cloud/token.go | 16 +-- pkg/cloud/token_test.go | 20 +-- pkg/commands/app.go | 12 +- pkg/commands/artifact/run.go | 4 +- pkg/commands/cloud/run.go | 22 ++-- pkg/commands/cloud/run_test.go | 22 ++-- pkg/flag/cloud_flags.go | 120 +++++++++--------- pkg/flag/options.go | 2 +- 18 files changed, 155 insertions(+), 155 deletions(-) diff --git a/docs/docs/references/configuration/cli/trivy_config.md b/docs/docs/references/configuration/cli/trivy_config.md index 166d96c4b16c..c85554746692 100644 --- a/docs/docs/references/configuration/cli/trivy_config.md +++ b/docs/docs/references/configuration/cli/trivy_config.md @@ -14,12 +14,6 @@ trivy config [flags] DIR --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") - --cloud-api-url string API URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://api.trivy.dev") - --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform, requires the token to be provided to have an effect (default true) - --cloud-token string Token used to athenticate with Trivy Cloud platform - --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://scan.trivy.dev") - --cloud-upload-results Upload results to Trivy Cloud platform, requires the token to be provided to have an effect (default true) - --cloud-use-secret-config Use secret configurations from Trivy Cloud platform, requires the token to be provided to have an effect (default true) --compliance string compliance report to generate --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded @@ -58,6 +52,12 @@ trivy config [flags] DIR --output-plugin-arg string [EXPERIMENTAL] output plugin arguments --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. --password-stdin password from stdin. Comma-separated passwords are not supported. + --pro-api-url string API URL for Trivy Pro platform, requires the token to be provided to have an effect (default "https://api.trivy.dev") + --pro-server-scanning Use server-side image scanning in Trivy Pro platform, requires the token to be provided to have an effect (default true) + --pro-token string Token used to athenticate with Trivy Pro platform + --pro-trivy-server-url string Trivy Server URL for Trivy Pro platform, requires the token to be provided to have an effect (default "https://scan.trivy.dev") + --pro-upload-results Upload results to Trivy Pro platform, requires the token to be provided to have an effect (default true) + --pro-use-secret-config Use secret configurations from Trivy Pro platform, requires the token to be provided to have an effect (default true) --raw-config-scanners strings specify the types of scanners that will also scan raw configurations. For example, scanners will scan a non-adapted configuration into a shared state (allowed values: terraform) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index 7ceabfb1775d..9b1eab682ecc 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -24,12 +24,6 @@ trivy filesystem [flags] PATH --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") - --cloud-api-url string API URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://api.trivy.dev") - --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform, requires the token to be provided to have an effect (default true) - --cloud-token string Token used to athenticate with Trivy Cloud platform - --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://scan.trivy.dev") - --cloud-upload-results Upload results to Trivy Cloud platform, requires the token to be provided to have an effect (default true) - --cloud-use-secret-config Use secret configurations from Trivy Cloud platform, requires the token to be provided to have an effect (default true) --compliance string compliance report to generate --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded @@ -106,6 +100,12 @@ trivy filesystem [flags] PATH - indirect (default [unknown,root,workspace,direct,indirect]) --pkg-types strings list of package types (allowed values: os,library) (default [os,library]) + --pro-api-url string API URL for Trivy Pro platform, requires the token to be provided to have an effect (default "https://api.trivy.dev") + --pro-server-scanning Use server-side image scanning in Trivy Pro platform, requires the token to be provided to have an effect (default true) + --pro-token string Token used to athenticate with Trivy Pro platform + --pro-trivy-server-url string Trivy Server URL for Trivy Pro platform, requires the token to be provided to have an effect (default "https://scan.trivy.dev") + --pro-upload-results Upload results to Trivy Pro platform, requires the token to be provided to have an effect (default true) + --pro-use-secret-config Use secret configurations from Trivy Pro platform, requires the token to be provided to have an effect (default true) --raw-config-scanners strings specify the types of scanners that will also scan raw configurations. For example, scanners will scan a non-adapted configuration into a shared state (allowed values: terraform) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index 5daf22b4e7b8..d4154455e2b9 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -38,12 +38,6 @@ trivy image [flags] IMAGE_NAME --cache-ttl duration cache TTL when using redis as cache backend --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") - --cloud-api-url string API URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://api.trivy.dev") - --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform, requires the token to be provided to have an effect (default true) - --cloud-token string Token used to athenticate with Trivy Cloud platform - --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://scan.trivy.dev") - --cloud-upload-results Upload results to Trivy Cloud platform, requires the token to be provided to have an effect (default true) - --cloud-use-secret-config Use secret configurations from Trivy Cloud platform, requires the token to be provided to have an effect (default true) --compliance string compliance report to generate (built-in compliance's: docker-cis-1.6.0) --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded @@ -127,6 +121,12 @@ trivy image [flags] IMAGE_NAME --pkg-types strings list of package types (allowed values: os,library) (default [os,library]) --platform string set platform in the form os/arch if image is multi-platform capable --podman-host string unix podman socket path to use for podman scanning + --pro-api-url string API URL for Trivy Pro platform, requires the token to be provided to have an effect (default "https://api.trivy.dev") + --pro-server-scanning Use server-side image scanning in Trivy Pro platform, requires the token to be provided to have an effect (default true) + --pro-token string Token used to athenticate with Trivy Pro platform + --pro-trivy-server-url string Trivy Server URL for Trivy Pro platform, requires the token to be provided to have an effect (default "https://scan.trivy.dev") + --pro-upload-results Upload results to Trivy Pro platform, requires the token to be provided to have an effect (default true) + --pro-use-secret-config Use secret configurations from Trivy Pro platform, requires the token to be provided to have an effect (default true) --raw-config-scanners strings specify the types of scanners that will also scan raw configurations. For example, scanners will scan a non-adapted configuration into a shared state (allowed values: terraform) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index 557f51f5f1cb..902deeacc103 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -24,12 +24,6 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") - --cloud-api-url string API URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://api.trivy.dev") - --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform, requires the token to be provided to have an effect (default true) - --cloud-token string Token used to athenticate with Trivy Cloud platform - --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://scan.trivy.dev") - --cloud-upload-results Upload results to Trivy Cloud platform, requires the token to be provided to have an effect (default true) - --cloud-use-secret-config Use secret configurations from Trivy Cloud platform, requires the token to be provided to have an effect (default true) --commit string pass the commit hash to be scanned --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded @@ -105,6 +99,12 @@ trivy repository [flags] (REPO_PATH | REPO_URL) - indirect (default [unknown,root,workspace,direct,indirect]) --pkg-types strings list of package types (allowed values: os,library) (default [os,library]) + --pro-api-url string API URL for Trivy Pro platform, requires the token to be provided to have an effect (default "https://api.trivy.dev") + --pro-server-scanning Use server-side image scanning in Trivy Pro platform, requires the token to be provided to have an effect (default true) + --pro-token string Token used to athenticate with Trivy Pro platform + --pro-trivy-server-url string Trivy Server URL for Trivy Pro platform, requires the token to be provided to have an effect (default "https://scan.trivy.dev") + --pro-upload-results Upload results to Trivy Pro platform, requires the token to be provided to have an effect (default true) + --pro-use-secret-config Use secret configurations from Trivy Pro platform, requires the token to be provided to have an effect (default true) --raw-config-scanners strings specify the types of scanners that will also scan raw configurations. For example, scanners will scan a non-adapted configuration into a shared state (allowed values: terraform) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 3f1acd2a4516..c391d7d65a39 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -27,12 +27,6 @@ trivy rootfs [flags] ROOTDIR --cf-params strings specify paths to override the CloudFormation parameters files --check-namespaces strings Rego namespaces --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") - --cloud-api-url string API URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://api.trivy.dev") - --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform, requires the token to be provided to have an effect (default true) - --cloud-token string Token used to athenticate with Trivy Cloud platform - --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://scan.trivy.dev") - --cloud-upload-results Upload results to Trivy Cloud platform, requires the token to be provided to have an effect (default true) - --cloud-use-secret-config Use secret configurations from Trivy Cloud platform, requires the token to be provided to have an effect (default true) --config-check strings specify the paths to the Rego check files or to the directories containing them, applying config files --config-data strings specify paths from which data for the Rego checks will be recursively loaded --config-file-schemas strings specify paths to JSON configuration file schemas to determine that a file matches some configuration and pass the schema to Rego checks for type checking @@ -108,6 +102,12 @@ trivy rootfs [flags] ROOTDIR - indirect (default [unknown,root,workspace,direct,indirect]) --pkg-types strings list of package types (allowed values: os,library) (default [os,library]) + --pro-api-url string API URL for Trivy Pro platform, requires the token to be provided to have an effect (default "https://api.trivy.dev") + --pro-server-scanning Use server-side image scanning in Trivy Pro platform, requires the token to be provided to have an effect (default true) + --pro-token string Token used to athenticate with Trivy Pro platform + --pro-trivy-server-url string Trivy Server URL for Trivy Pro platform, requires the token to be provided to have an effect (default "https://scan.trivy.dev") + --pro-upload-results Upload results to Trivy Pro platform, requires the token to be provided to have an effect (default true) + --pro-use-secret-config Use secret configurations from Trivy Pro platform, requires the token to be provided to have an effect (default true) --raw-config-scanners strings specify the types of scanners that will also scan raw configurations. For example, scanners will scan a non-adapted configuration into a shared state (allowed values: terraform) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index 363b6b382ffc..e93d1501cf76 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -24,12 +24,6 @@ trivy vm [flags] VM_IMAGE --cache-backend string [EXPERIMENTAL] cache backend (e.g. redis://localhost:6379) (default "fs") --cache-ttl duration cache TTL when using redis as cache backend --checks-bundle-repository string OCI registry URL to retrieve checks bundle from (default "mirror.gcr.io/aquasec/trivy-checks:1") - --cloud-api-url string API URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://api.trivy.dev") - --cloud-server-scanning Use server-side image scanning in Trivy Cloud platform, requires the token to be provided to have an effect (default true) - --cloud-token string Token used to athenticate with Trivy Cloud platform - --cloud-trivy-server-url string Trivy Server URL for Trivy Cloud platform, requires the token to be provided to have an effect (default "https://scan.trivy.dev") - --cloud-upload-results Upload results to Trivy Cloud platform, requires the token to be provided to have an effect (default true) - --cloud-use-secret-config Use secret configurations from Trivy Cloud platform, requires the token to be provided to have an effect (default true) --compliance string compliance report to generate --config-file-schemas strings specify paths to JSON configuration file schemas to determine that a file matches some configuration and pass the schema to Rego checks for type checking --custom-headers strings custom headers in client mode @@ -98,6 +92,12 @@ trivy vm [flags] VM_IMAGE - indirect (default [unknown,root,workspace,direct,indirect]) --pkg-types strings list of package types (allowed values: os,library) (default [os,library]) + --pro-api-url string API URL for Trivy Pro platform, requires the token to be provided to have an effect (default "https://api.trivy.dev") + --pro-server-scanning Use server-side image scanning in Trivy Pro platform, requires the token to be provided to have an effect (default true) + --pro-token string Token used to athenticate with Trivy Pro platform + --pro-trivy-server-url string Trivy Server URL for Trivy Pro platform, requires the token to be provided to have an effect (default "https://scan.trivy.dev") + --pro-upload-results Upload results to Trivy Pro platform, requires the token to be provided to have an effect (default true) + --pro-use-secret-config Use secret configurations from Trivy Pro platform, requires the token to be provided to have an effect (default true) --raw-config-scanners strings specify the types of scanners that will also scan raw configurations. For example, scanners will scan a non-adapted configuration into a shared state (allowed values: terraform) --redis-ca string redis ca file location, if using redis as cache backend --redis-cert string redis certificate file location, if using redis as cache backend diff --git a/pkg/cloud/config.go b/pkg/cloud/config.go index b4b1639f51a3..54806c957926 100644 --- a/pkg/cloud/config.go +++ b/pkg/cloud/config.go @@ -44,13 +44,13 @@ func GetConfigs(ctx context.Context, opts *flag.Options, accessToken string) err logger := log.WithPrefix(log.PrefixCloud) client := xhttp.ClientWithContext(ctx) - if opts.CloudOptions.SecretConfig && opts.Scanners.Enabled(types.SecretScanner) { + if opts.ProOptions.SecretConfig && opts.Scanners.Enabled(types.SecretScanner) { if opts.SecretOptions.SecretConfigPath != "" { logger.Warn("Secret config path already set", log.FilePath(opts.SecretOptions.SecretConfigPath)) return nil } - configPath, err := getConfigFromTrivyCloud(ctx, client, opts, accessToken, ConfigTypeSecret) + configPath, err := getConfigFromTrivyPro(ctx, client, opts, accessToken, ConfigTypeSecret) if err != nil { return xerrors.Errorf("failed to get secret config: %w", err) } @@ -61,9 +61,9 @@ func GetConfigs(ctx context.Context, opts *flag.Options, accessToken string) err return nil } -// getConfigFromTrivyCloud downloads a config from Trivy Cloud and saves it to a file +// getConfigFromTrivyPro downloads a config from Trivy Pro and saves it to a file // it returns the path to the config file if it was downloaded successfully, otherwise it returns an error -func getConfigFromTrivyCloud(ctx context.Context, client *http.Client, opts *flag.Options, accessToken string, configType ConfigType) (string, error) { +func getConfigFromTrivyPro(ctx context.Context, client *http.Client, opts *flag.Options, accessToken string, configType ConfigType) (string, error) { logger := log.WithPrefix(log.PrefixCloud).With("configType", configType) configTypeStr := string(configType) configDir := filepath.Join(fsutils.TrivyHomeDir(), "cloud", configTypeStr) @@ -83,7 +83,7 @@ func getConfigFromTrivyCloud(ctx context.Context, client *http.Client, opts *fla if !ok { return "", xerrors.Errorf("unknown config type: %s", configType) } - configUrl, err := url.JoinPath(opts.CloudOptions.TrivyServerURL, configPath) + configUrl, err := url.JoinPath(opts.ProOptions.TrivyServerURL, configPath) if err != nil { return "", xerrors.Errorf("failed to join API URL and config path: %w", err) } @@ -99,7 +99,7 @@ func getConfigFromTrivyCloud(ctx context.Context, client *http.Client, opts *fla defer resp.Body.Close() if resp.StatusCode != http.StatusOK { if resp.StatusCode == http.StatusNotFound { - logger.Debug("Config not found in Trivy Cloud", log.String("configType", string(configType))) + logger.Debug("Config not found in Trivy Pro", log.String("configType", string(configType))) return "", nil } return "", xerrors.Errorf("failed to get config: received status code %d", resp.StatusCode) diff --git a/pkg/cloud/config_test.go b/pkg/cloud/config_test.go index 7b3e5be2aa87..f0cb3ea88eec 100644 --- a/pkg/cloud/config_test.go +++ b/pkg/cloud/config_test.go @@ -48,7 +48,7 @@ func TestGetConfigs_Secrets(t *testing.T) { t.Setenv("XDG_DATA_HOME", tempDir) opts := &flag.Options{ - CloudOptions: flag.CloudOptions{ + ProOptions: flag.ProOptions{ SecretConfig: true, TrivyServerURL: tt.serverURL, }, diff --git a/pkg/cloud/hooks/report_hook.go b/pkg/cloud/hooks/report_hook.go index 4b9dfaffa897..fcd2e650f632 100644 --- a/pkg/cloud/hooks/report_hook.go +++ b/pkg/cloud/hooks/report_hook.go @@ -30,7 +30,7 @@ type ReportHook struct { func NewReportHook(apiUrl, accessToken string) *ReportHook { return &ReportHook{ - name: "Trivy Cloud Results Hook", + name: "Trivy Pro Results Hook", apiUrl: apiUrl, accessToken: accessToken, client: xhttp.Client(), @@ -80,7 +80,7 @@ func (h *ReportHook) uploadResults(ctx context.Context, jsonReport []byte) error return fmt.Errorf("failed to upload results: received status code %d", resp.StatusCode) } - h.logger.Info("Report uploaded successfully to Trivy Cloud") + h.logger.Info("Report uploaded successfully to Trivy Pro") return nil } diff --git a/pkg/cloud/hooks/report_hook_test.go b/pkg/cloud/hooks/report_hook_test.go index 4d400c911b52..c3d13d912497 100644 --- a/pkg/cloud/hooks/report_hook_test.go +++ b/pkg/cloud/hooks/report_hook_test.go @@ -120,7 +120,7 @@ func (m *mockReportServer) Close() { func TestReportHook_Name(t *testing.T) { hook := NewReportHook("http://api.example.com", "test-token") - assert.Equal(t, "Trivy Cloud Results Hook", hook.Name()) + assert.Equal(t, "Trivy Pro Results Hook", hook.Name()) } func TestReportHook_PreReport(t *testing.T) { diff --git a/pkg/cloud/token.go b/pkg/cloud/token.go index b30e2cde1f48..efae514f0973 100644 --- a/pkg/cloud/token.go +++ b/pkg/cloud/token.go @@ -23,35 +23,35 @@ const ( ) func GetAccessToken(ctx context.Context, opts flag.Options) (string, error) { - if opts.CloudOptions.CloudToken == "" { - return "", xerrors.New("no cloud token provided for getting access token from Trivy Cloud") + if opts.ProOptions.ProToken == "" { + return "", xerrors.New("no pro token provided for getting access token from Trivy Pro") } - if opts.CloudOptions.ApiURL == "" { - return "", xerrors.New("no API URL provided for getting access token from Trivy Cloud") + if opts.ProOptions.ApiURL == "" { + return "", xerrors.New("no API URL provided for getting access token from Trivy Pro") } logger := log.WithPrefix(log.PrefixCloud) client := xhttp.Client() - u, err := url.JoinPath(opts.CloudOptions.ApiURL, accessTokenPath) + u, err := url.JoinPath(opts.ProOptions.ApiURL, accessTokenPath) if err != nil { return "", xerrors.Errorf("failed to join server URL and token path: %w", err) } - logger.Debug("Requesting access token from Trivy Cloud", log.String("url", u)) + logger.Debug("Requesting access token from Trivy Pro", log.String("url", u)) req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, http.NoBody) if err != nil { return "", xerrors.Errorf("failed to create token request: %w", err) } - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", opts.CloudOptions.CloudToken)) + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", opts.ProOptions.ProToken)) resp, err := client.Do(req) if err != nil { return "", xerrors.Errorf("failed to get access token: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusCreated { - return "", xerrors.Errorf("failed to get access token: received status code %d", resp.StatusCode) + return "", xerrors.Errorf("failed to get access token from Trivy Pro: received status code %d", resp.StatusCode) } var tokenResponse accessTokenResponse diff --git a/pkg/cloud/token_test.go b/pkg/cloud/token_test.go index 1799a2ca30b1..7def570c732e 100644 --- a/pkg/cloud/token_test.go +++ b/pkg/cloud/token_test.go @@ -53,9 +53,9 @@ func TestGetAccessToken(t *testing.T) { { name: "happy path", opts: flag.Options{ - CloudOptions: flag.CloudOptions{ - CloudToken: "valid-token", - ApiURL: mockServer.server.URL, + ProOptions: flag.ProOptions{ + ProToken: "valid-token", + ApiURL: mockServer.server.URL, }, }, want: "test-token", @@ -64,20 +64,20 @@ func TestGetAccessToken(t *testing.T) { { name: "no API URL", opts: flag.Options{ - CloudOptions: flag.CloudOptions{ - CloudToken: "valid-token", - ApiURL: "", + ProOptions: flag.ProOptions{ + ProToken: "valid-token", + ApiURL: "", }, }, - errorContains: "no API URL provided for getting access token from Trivy Cloud", + errorContains: "no API URL provided for getting access token from Trivy Pro", expectedStatusCode: http.StatusInternalServerError, }, { name: "invalid token", opts: flag.Options{ - CloudOptions: flag.CloudOptions{ - CloudToken: "invalid-token", - ApiURL: mockServer.server.URL, + ProOptions: flag.ProOptions{ + ProToken: "invalid-token", + ApiURL: mockServer.server.URL, }, }, errorContains: "failed to get access token: received status code 401", diff --git a/pkg/commands/app.go b/pkg/commands/app.go index bd6cc3700a92..ec0262c3d17e 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -268,7 +268,7 @@ func NewImageCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { flag.NewScanFlagGroup(), flag.NewSecretFlagGroup(), flag.NewVulnerabilityFlagGroup(), - flag.NewCloudFlagGroup(), + flag.NewProFlagGroup(), } cmd := &cobra.Command{ @@ -351,7 +351,7 @@ func NewFilesystemCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { flag.NewScanFlagGroup(), flag.NewSecretFlagGroup(), flag.NewVulnerabilityFlagGroup(), - flag.NewCloudFlagGroup(), + flag.NewProFlagGroup(), } cmd := &cobra.Command{ @@ -418,7 +418,7 @@ func NewRootfsCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { flag.NewScanFlagGroup(), flag.NewSecretFlagGroup(), flag.NewVulnerabilityFlagGroup(), - flag.NewCloudFlagGroup(), + flag.NewProFlagGroup(), } cmd := &cobra.Command{ @@ -484,7 +484,7 @@ func NewRepositoryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { flag.NewSecretFlagGroup(), flag.NewVulnerabilityFlagGroup(), flag.NewRepoFlagGroup(), - flag.NewCloudFlagGroup(), + flag.NewProFlagGroup(), } cmd := &cobra.Command{ @@ -707,7 +707,7 @@ func NewConfigCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { flag.NewModuleFlagGroup(), flag.NewRegistryFlagGroup(), flag.NewRegoFlagGroup(), - flag.NewCloudFlagGroup(), + flag.NewProFlagGroup(), &flag.K8sFlagGroup{ // Keep only --k8s-version flag and disable others K8sVersion: flag.K8sVersionFlag.Clone(), @@ -1101,7 +1101,7 @@ func NewVMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { flag.NewScanFlagGroup(), flag.NewSecretFlagGroup(), flag.NewVulnerabilityFlagGroup(), - flag.NewCloudFlagGroup(), + flag.NewProFlagGroup(), &flag.AWSFlagGroup{ Region: &flag.Flag[string]{ Name: "aws-region", diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index f0c56d333d92..057268d7030c 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -377,9 +377,9 @@ func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err err ctx, cancel := context.WithTimeout(ctx, opts.Timeout) defer cancel() - if err := cloud.UpdateOptsForCloudIntegration(ctx, &opts); err != nil { + if err := cloud.UpdateOptsForProIntegration(ctx, &opts); err != nil { // log failure but continue with the scan - log.Error("failed to check Trivy Cloud integration", "error", err) + log.Error("failed to check Trivy Pro integration", "error", err) } if opts.GenerateDefaultConfig { diff --git a/pkg/commands/cloud/run.go b/pkg/commands/cloud/run.go index 2411d093eecb..6a21b1850d79 100644 --- a/pkg/commands/cloud/run.go +++ b/pkg/commands/cloud/run.go @@ -14,40 +14,40 @@ import ( "github.com/aquasecurity/trivy/pkg/types" ) -// UpdateOptsForCloudIntegration checks if the Trivy Cloud integration is enabled and configures the options accordingly +// UpdateOptsForProIntegration checks if the Trivy Pro integration is enabled and configures the options accordingly // if there are variables that are already set that would cause a conflict, we return an error. // if the token is not provided, we don't need to check the integration and can return early. -func UpdateOptsForCloudIntegration(ctx context.Context, opts *flag.Options) error { - if opts.CloudOptions.CloudToken == "" { +func UpdateOptsForProIntegration(ctx context.Context, opts *flag.Options) error { + if opts.ProOptions.ProToken == "" { return nil } logger := log.WithPrefix(log.PrefixCloud) accessToken, err := cloud.GetAccessToken(ctx, *opts) if err != nil { - return xerrors.Errorf("failed to get access token for Trivy Cloud: %w", err) + return xerrors.Errorf("failed to get access token for Trivy Pro: %w", err) } - if opts.CloudOptions.UseServerSideScanning { + if opts.ProOptions.UseServerSideScanning { // ensure that the server address hasn't been already set, this would be an unacceptable config conflict. - if opts.ServerAddr != "" && opts.ServerAddr != opts.CloudOptions.TrivyServerURL { + if opts.ServerAddr != "" && opts.ServerAddr != opts.ProOptions.TrivyServerURL { return xerrors.Errorf("server-side scanning is enabled, but server address is already set to %s", opts.ServerAddr) } - logger.Debug("Using server-side scanning for Trivy Cloud, updating opts") - opts.ServerAddr = opts.CloudOptions.TrivyServerURL + logger.Debug("Using server-side scanning for Trivy Pro, updating opts") + opts.ServerAddr = opts.ProOptions.TrivyServerURL opts.CustomHeaders.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken)) } - if opts.CloudOptions.SecretConfig && opts.Scanners.Enabled(types.SecretScanner) { + if opts.ProOptions.SecretConfig && opts.Scanners.Enabled(types.SecretScanner) { if err := cloud.GetConfigs(ctx, opts, accessToken); err != nil { return xerrors.Errorf("failed to download configs: %w", err) } } // if uploading results we need to register a report hook with the required details - if opts.CloudOptions.UploadResults { - reportHook := hooks.NewReportHook(opts.CloudOptions.ApiURL, accessToken) + if opts.ProOptions.UploadResults { + reportHook := hooks.NewReportHook(opts.ProOptions.ApiURL, accessToken) extension.RegisterHook(reportHook) } diff --git a/pkg/commands/cloud/run_test.go b/pkg/commands/cloud/run_test.go index d695b2693425..7aa62ccdb250 100644 --- a/pkg/commands/cloud/run_test.go +++ b/pkg/commands/cloud/run_test.go @@ -65,8 +65,8 @@ func TestUpdateOptsForCloudIntegration(t *testing.T) { { name: "valid token and config to download", opts: &flag.Options{ - CloudOptions: flag.CloudOptions{ - CloudToken: "valid-cloud-token", + ProOptions: flag.ProOptions{ + ProToken: "valid-cloud-token", ApiURL: mockServer.server.URL, TrivyServerURL: mockServer.server.URL, SecretConfig: true, @@ -80,8 +80,8 @@ func TestUpdateOptsForCloudIntegration(t *testing.T) { { name: "valid token but config not requested", opts: &flag.Options{ - CloudOptions: flag.CloudOptions{ - CloudToken: "valid-cloud-token", + ProOptions: flag.ProOptions{ + ProToken: "valid-cloud-token", ApiURL: mockServer.server.URL, TrivyServerURL: mockServer.server.URL, SecretConfig: false, @@ -95,8 +95,8 @@ func TestUpdateOptsForCloudIntegration(t *testing.T) { { name: "valid token but config not available", opts: &flag.Options{ - CloudOptions: flag.CloudOptions{ - CloudToken: "valid-cloud-token", + ProOptions: flag.ProOptions{ + ProToken: "valid-cloud-token", ApiURL: mockServer.server.URL, TrivyServerURL: mockServer.server.URL, SecretConfig: false, @@ -107,8 +107,8 @@ func TestUpdateOptsForCloudIntegration(t *testing.T) { { name: "invalid token 401 status code", opts: &flag.Options{ - CloudOptions: flag.CloudOptions{ - CloudToken: "invalid-token", + ProOptions: flag.ProOptions{ + ProToken: "invalid-token", ApiURL: mockServer.server.URL, TrivyServerURL: mockServer.server.URL, SecretConfig: false, @@ -118,7 +118,7 @@ func TestUpdateOptsForCloudIntegration(t *testing.T) { }, }, configAvailable: true, - errorContains: "failed to get access token for Trivy Cloud", + errorContains: "failed to get access token for Trivy Pro", }, } @@ -128,7 +128,7 @@ func TestUpdateOptsForCloudIntegration(t *testing.T) { t.Setenv("XDG_DATA_HOME", tempDir) mockServer.configAvailable = tt.configAvailable - err := UpdateOptsForCloudIntegration(t.Context(), tt.opts) + err := UpdateOptsForProIntegration(t.Context(), tt.opts) if tt.errorContains != "" { require.ErrorContains(t, err, tt.errorContains) @@ -137,7 +137,7 @@ func TestUpdateOptsForCloudIntegration(t *testing.T) { require.NoError(t, err) - if tt.opts.CloudOptions.SecretConfig && tt.opts.ScanOptions.Scanners.Enabled(types.SecretScanner) { + if tt.opts.ProOptions.SecretConfig && tt.opts.ScanOptions.Scanners.Enabled(types.SecretScanner) { assert.NotEmpty(t, tt.opts.SecretOptions.SecretConfigPath) assert.FileExists(t, tt.opts.SecretOptions.SecretConfigPath) } else { diff --git a/pkg/flag/cloud_flags.go b/pkg/flag/cloud_flags.go index 7552de7446a0..e4bde7b0cdf4 100644 --- a/pkg/flag/cloud_flags.go +++ b/pkg/flag/cloud_flags.go @@ -6,97 +6,97 @@ const ( ) var ( - CloudTokenFlag = Flag[string]{ - Name: "cloud-token", - ConfigName: "cloud.token", - Usage: "Token used to athenticate with Trivy Cloud platform", + ProTokenFlag = Flag[string]{ + Name: "pro-token", + ConfigName: "pro.token", + Usage: "Token used to athenticate with Trivy Pro platform", } - CloudApiURLFlag = Flag[string]{ - Name: "cloud-api-url", - ConfigName: "cloud.api-url", + ProAPIURLFlag = Flag[string]{ + Name: "pro-api-url", + ConfigName: "pro.api-url", Default: DefaultApiURL, - Usage: "API URL for Trivy Cloud platform, requires the token to be provided to have an effect", + Usage: "API URL for Trivy Pro platform, requires the token to be provided to have an effect", TelemetrySafe: true, } - CloudTrivyServerURLFlag = Flag[string]{ - Name: "cloud-trivy-server-url", - ConfigName: "cloud.trivy-server-url", + ProTrivyServerURLFlag = Flag[string]{ + Name: "pro-trivy-server-url", + ConfigName: "pro.trivy-server-url", Default: DefaultTrivyServerURL, - Usage: "Trivy Server URL for Trivy Cloud platform, requires the token to be provided to have an effect", + Usage: "Trivy Server URL for Trivy Pro platform, requires the token to be provided to have an effect", TelemetrySafe: true, } - CloudUploadResultsFlag = Flag[bool]{ - Name: "cloud-upload-results", - ConfigName: "cloud.upload-results", + ProUploadResultsFlag = Flag[bool]{ + Name: "pro-upload-results", + ConfigName: "pro.upload-results", Default: true, - Usage: "Upload results to Trivy Cloud platform, requires the token to be provided to have an effect", + Usage: "Upload results to Trivy Pro platform, requires the token to be provided to have an effect", TelemetrySafe: true, } - CloudSecretConfigFlag = Flag[bool]{ - Name: "cloud-use-secret-config", - ConfigName: "cloud.use-secret-config", + ProSecretConfigFlag = Flag[bool]{ + Name: "pro-use-secret-config", + ConfigName: "pro.use-secret-config", Default: true, - Usage: "Use secret configurations from Trivy Cloud platform, requires the token to be provided to have an effect", + Usage: "Use secret configurations from Trivy Pro platform, requires the token to be provided to have an effect", TelemetrySafe: true, } - CloudUseServerSideScanningFlag = Flag[bool]{ - Name: "cloud-server-scanning", - ConfigName: "cloud.server-scanning", + ProUseServerSideScanningFlag = Flag[bool]{ + Name: "pro-server-scanning", + ConfigName: "pro.server-scanning", Default: true, - Usage: "Use server-side image scanning in Trivy Cloud platform, requires the token to be provided to have an effect", + Usage: "Use server-side image scanning in Trivy Pro platform, requires the token to be provided to have an effect", TelemetrySafe: true, } ) -type CloudFlagGroup struct { - CloudToken *Flag[string] - CloudApiURL *Flag[string] - CloudTrivyServerURL *Flag[string] - CloudUploadResults *Flag[bool] - CloudSecretConfig *Flag[bool] +type ProFlagGroup struct { + ProToken *Flag[string] + ProApiURL *Flag[string] + ProTrivyServerURL *Flag[string] + ProUploadResults *Flag[bool] + ProSecretConfig *Flag[bool] - CloudUseServerSideScanning *Flag[bool] + ProUseServerSideScanning *Flag[bool] } -func NewCloudFlagGroup() *CloudFlagGroup { - return &CloudFlagGroup{ - CloudToken: CloudTokenFlag.Clone(), - CloudApiURL: CloudApiURLFlag.Clone(), - CloudTrivyServerURL: CloudTrivyServerURLFlag.Clone(), - CloudUploadResults: CloudUploadResultsFlag.Clone(), - CloudSecretConfig: CloudSecretConfigFlag.Clone(), - CloudUseServerSideScanning: CloudUseServerSideScanningFlag.Clone(), +func NewProFlagGroup() *ProFlagGroup { + return &ProFlagGroup{ + ProToken: ProTokenFlag.Clone(), + ProApiURL: ProAPIURLFlag.Clone(), + ProTrivyServerURL: ProTrivyServerURLFlag.Clone(), + ProUploadResults: ProUploadResultsFlag.Clone(), + ProSecretConfig: ProSecretConfigFlag.Clone(), + ProUseServerSideScanning: ProUseServerSideScanningFlag.Clone(), } } -func (f *CloudFlagGroup) Name() string { - return "Trivy Cloud" +func (f *ProFlagGroup) Name() string { + return "Trivy Pro" } -func (f *CloudFlagGroup) Flags() []Flagger { +func (f *ProFlagGroup) Flags() []Flagger { return []Flagger{ - f.CloudToken, - f.CloudApiURL, - f.CloudTrivyServerURL, - f.CloudUploadResults, - f.CloudSecretConfig, - f.CloudUseServerSideScanning, + f.ProToken, + f.ProApiURL, + f.ProTrivyServerURL, + f.ProUploadResults, + f.ProSecretConfig, + f.ProUseServerSideScanning, } } -// CloudLoginCredentials is the credentials used to authenticate with Trivy Cloud platform +// ProLoginCredentials is the credentials used to authenticate with Trivy Pro platform // In the future this would likely have more information stored for refresh tokens, etc -type CloudLoginCredentials struct { +type ProLoginCredentials struct { Token string } -type CloudOptions struct { - CloudToken string +type ProOptions struct { + ProToken string ApiURL string TrivyServerURL string UploadResults bool @@ -105,14 +105,14 @@ type CloudOptions struct { } // ToOptions converts the flags to options -func (f *CloudFlagGroup) ToOptions(opts *Options) error { - opts.CloudOptions = CloudOptions{ - CloudToken: f.CloudToken.Value(), - ApiURL: f.CloudApiURL.Value(), - TrivyServerURL: f.CloudTrivyServerURL.Value(), - UploadResults: f.CloudUploadResults.Value(), - SecretConfig: f.CloudSecretConfig.Value(), - UseServerSideScanning: f.CloudUseServerSideScanning.Value(), +func (f *ProFlagGroup) ToOptions(opts *Options) error { + opts.ProOptions = ProOptions{ + ProToken: f.ProToken.Value(), + ApiURL: f.ProApiURL.Value(), + TrivyServerURL: f.ProTrivyServerURL.Value(), + UploadResults: f.ProUploadResults.Value(), + SecretConfig: f.ProSecretConfig.Value(), + UseServerSideScanning: f.ProUseServerSideScanning.Value(), } return nil } diff --git a/pkg/flag/options.go b/pkg/flag/options.go index b3c325256d1b..62eef386c8f0 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -406,7 +406,7 @@ type Options struct { RemoteOptions RepoOptions ReportOptions - CloudOptions + ProOptions ScanOptions SecretOptions VulnerabilityOptions From e740f1706397dcb54855433d278dc2ed4dafa595 Mon Sep 17 00:00:00 2001 From: Owen Rumney Date: Mon, 3 Nov 2025 20:52:38 +0000 Subject: [PATCH 10/11] refactor: rename cloud package and flags to pro --- pkg/commands/artifact/run.go | 4 ++-- pkg/commands/{cloud => pro}/run.go | 2 +- pkg/commands/{cloud => pro}/run_test.go | 2 +- pkg/flag/{cloud_flags.go => pro_flags.go} | 0 4 files changed, 4 insertions(+), 4 deletions(-) rename pkg/commands/{cloud => pro}/run.go (99%) rename pkg/commands/{cloud => pro}/run_test.go (99%) rename pkg/flag/{cloud_flags.go => pro_flags.go} (100%) diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 057268d7030c..be76717f621f 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -14,8 +14,8 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/cache" - "github.com/aquasecurity/trivy/pkg/commands/cloud" "github.com/aquasecurity/trivy/pkg/commands/operation" + "github.com/aquasecurity/trivy/pkg/commands/pro" "github.com/aquasecurity/trivy/pkg/db" "github.com/aquasecurity/trivy/pkg/extension" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" @@ -377,7 +377,7 @@ func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err err ctx, cancel := context.WithTimeout(ctx, opts.Timeout) defer cancel() - if err := cloud.UpdateOptsForProIntegration(ctx, &opts); err != nil { + if err := pro.UpdateOptsForProIntegration(ctx, &opts); err != nil { // log failure but continue with the scan log.Error("failed to check Trivy Pro integration", "error", err) } diff --git a/pkg/commands/cloud/run.go b/pkg/commands/pro/run.go similarity index 99% rename from pkg/commands/cloud/run.go rename to pkg/commands/pro/run.go index 6a21b1850d79..a393d458461b 100644 --- a/pkg/commands/cloud/run.go +++ b/pkg/commands/pro/run.go @@ -1,4 +1,4 @@ -package cloud +package pro import ( "context" diff --git a/pkg/commands/cloud/run_test.go b/pkg/commands/pro/run_test.go similarity index 99% rename from pkg/commands/cloud/run_test.go rename to pkg/commands/pro/run_test.go index 7aa62ccdb250..5183223d7ae5 100644 --- a/pkg/commands/cloud/run_test.go +++ b/pkg/commands/pro/run_test.go @@ -1,4 +1,4 @@ -package cloud +package pro import ( "net/http" diff --git a/pkg/flag/cloud_flags.go b/pkg/flag/pro_flags.go similarity index 100% rename from pkg/flag/cloud_flags.go rename to pkg/flag/pro_flags.go From d04c73fcf1f030eb441eb20b620aa70c7f2eed38 Mon Sep 17 00:00:00 2001 From: Owen Rumney Date: Tue, 4 Nov 2025 10:39:33 +0000 Subject: [PATCH 11/11] refactor: move cloud flag to pro --- pkg/commands/pro/run.go | 8 ++++---- pkg/{cloud => pro}/config.go | 2 +- pkg/{cloud => pro}/config_test.go | 2 +- pkg/{cloud => pro}/hooks/report_hook.go | 0 pkg/{cloud => pro}/hooks/report_hook_test.go | 0 pkg/{cloud => pro}/token.go | 2 +- pkg/{cloud => pro}/token_test.go | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) rename pkg/{cloud => pro}/config.go (99%) rename pkg/{cloud => pro}/config_test.go (99%) rename pkg/{cloud => pro}/hooks/report_hook.go (100%) rename pkg/{cloud => pro}/hooks/report_hook_test.go (100%) rename pkg/{cloud => pro}/token.go (99%) rename pkg/{cloud => pro}/token_test.go (99%) diff --git a/pkg/commands/pro/run.go b/pkg/commands/pro/run.go index a393d458461b..228e89c42bb4 100644 --- a/pkg/commands/pro/run.go +++ b/pkg/commands/pro/run.go @@ -6,11 +6,11 @@ import ( "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/cloud" - "github.com/aquasecurity/trivy/pkg/cloud/hooks" "github.com/aquasecurity/trivy/pkg/extension" "github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/pro" + "github.com/aquasecurity/trivy/pkg/pro/hooks" "github.com/aquasecurity/trivy/pkg/types" ) @@ -23,7 +23,7 @@ func UpdateOptsForProIntegration(ctx context.Context, opts *flag.Options) error } logger := log.WithPrefix(log.PrefixCloud) - accessToken, err := cloud.GetAccessToken(ctx, *opts) + accessToken, err := pro.GetAccessToken(ctx, *opts) if err != nil { return xerrors.Errorf("failed to get access token for Trivy Pro: %w", err) } @@ -40,7 +40,7 @@ func UpdateOptsForProIntegration(ctx context.Context, opts *flag.Options) error } if opts.ProOptions.SecretConfig && opts.Scanners.Enabled(types.SecretScanner) { - if err := cloud.GetConfigs(ctx, opts, accessToken); err != nil { + if err := pro.GetConfigs(ctx, opts, accessToken); err != nil { return xerrors.Errorf("failed to download configs: %w", err) } } diff --git a/pkg/cloud/config.go b/pkg/pro/config.go similarity index 99% rename from pkg/cloud/config.go rename to pkg/pro/config.go index 54806c957926..646b35705eea 100644 --- a/pkg/cloud/config.go +++ b/pkg/pro/config.go @@ -1,4 +1,4 @@ -package cloud +package pro import ( "context" diff --git a/pkg/cloud/config_test.go b/pkg/pro/config_test.go similarity index 99% rename from pkg/cloud/config_test.go rename to pkg/pro/config_test.go index f0cb3ea88eec..7dbec9f01ff1 100644 --- a/pkg/cloud/config_test.go +++ b/pkg/pro/config_test.go @@ -1,4 +1,4 @@ -package cloud +package pro import ( "context" diff --git a/pkg/cloud/hooks/report_hook.go b/pkg/pro/hooks/report_hook.go similarity index 100% rename from pkg/cloud/hooks/report_hook.go rename to pkg/pro/hooks/report_hook.go diff --git a/pkg/cloud/hooks/report_hook_test.go b/pkg/pro/hooks/report_hook_test.go similarity index 100% rename from pkg/cloud/hooks/report_hook_test.go rename to pkg/pro/hooks/report_hook_test.go diff --git a/pkg/cloud/token.go b/pkg/pro/token.go similarity index 99% rename from pkg/cloud/token.go rename to pkg/pro/token.go index efae514f0973..1028207af6ac 100644 --- a/pkg/cloud/token.go +++ b/pkg/pro/token.go @@ -1,4 +1,4 @@ -package cloud +package pro import ( "context" diff --git a/pkg/cloud/token_test.go b/pkg/pro/token_test.go similarity index 99% rename from pkg/cloud/token_test.go rename to pkg/pro/token_test.go index 7def570c732e..f375d09654f5 100644 --- a/pkg/cloud/token_test.go +++ b/pkg/pro/token_test.go @@ -1,4 +1,4 @@ -package cloud +package pro import ( "net/http"