diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e5c1e551ea..a9291f958d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -10,9 +10,9 @@ pkg/scanner/ @knqyf263 @DmitriyLewen # Misconfiguration scanning docs/guide/scanner/misconfiguration/ @simar7 @nikpivkin docs/guide/target/aws.md @simar7 @nikpivkin -pkg/fanal/analyzer/config/ @simar7 @nikpivkin -pkg/cloud/ @simar7 @nikpivkin -pkg/iac/ @simar7 @nikpivkin +pkg/fanal/analyzer/config/ @simar7 @nikpivkin +pkg/config/aws/ @simar7 @nikpivkin +pkg/iac/ @simar7 @nikpivkin # Helm chart helm/trivy/ @afdesk @simar7 diff --git a/docs/guide/references/configuration/cli/trivy.md b/docs/guide/references/configuration/cli/trivy.md index a22b0a9376..9776926b83 100644 --- a/docs/guide/references/configuration/cli/trivy.md +++ b/docs/guide/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/guide/references/configuration/cli/trivy_cloud.md b/docs/guide/references/configuration/cli/trivy_cloud.md deleted file mode 100644 index f3d95cb04c..0000000000 --- a/docs/guide/references/configuration/cli/trivy_cloud.md +++ /dev/null @@ -1,29 +0,0 @@ -## trivy cloud - -Control Trivy Cloud platform integration settings - -### Options - -``` - -h, --help help for cloud -``` - -### Options inherited from parent commands - -``` - --cacert string Path to PEM-encoded CA certificate file - --cache-dir string cache directory (default "/path/to/cache") - -c, --config string config path (default "trivy.yaml") - -d, --debug debug mode - --generate-default-config write the default config to trivy-default.yaml - --insecure allow insecure server connections - -q, --quiet suppress progress bar and log output - --timeout duration timeout (default 5m0s) - -v, --version show version -``` - -### SEE ALSO - -* [trivy](trivy.md) - Unified security scanner -* [trivy cloud config](trivy_cloud_config.md) - Control Trivy Cloud configuration - diff --git a/docs/guide/references/configuration/cli/trivy_cloud_config.md b/docs/guide/references/configuration/cli/trivy_cloud_config.md deleted file mode 100644 index cbb85ff7bc..0000000000 --- a/docs/guide/references/configuration/cli/trivy_cloud_config.md +++ /dev/null @@ -1,33 +0,0 @@ -## trivy cloud config - -Control Trivy Cloud configuration - -### Options - -``` - -h, --help help for config -``` - -### Options inherited from parent commands - -``` - --cacert string Path to PEM-encoded CA certificate file - --cache-dir string cache directory (default "/path/to/cache") - -c, --config string config path (default "trivy.yaml") - -d, --debug debug mode - --generate-default-config write the default config to trivy-default.yaml - --insecure allow insecure server connections - -q, --quiet suppress progress bar and log output - --timeout duration timeout (default 5m0s) - -v, --version show version -``` - -### SEE ALSO - -* [trivy cloud](trivy_cloud.md) - Control Trivy Cloud platform integration settings -* [trivy cloud config edit](trivy_cloud_config_edit.md) - Edit Trivy Cloud configuration -* [trivy cloud config get](trivy_cloud_config_get.md) - Get Trivy Cloud configuration -* [trivy cloud config list](trivy_cloud_config_list.md) - List Trivy Cloud configuration -* [trivy cloud config set](trivy_cloud_config_set.md) - Set Trivy Cloud configuration -* [trivy cloud config unset](trivy_cloud_config_unset.md) - Unset Trivy Cloud configuration - diff --git a/docs/guide/references/configuration/cli/trivy_cloud_config_edit.md b/docs/guide/references/configuration/cli/trivy_cloud_config_edit.md deleted file mode 100644 index 26a9e58332..0000000000 --- a/docs/guide/references/configuration/cli/trivy_cloud_config_edit.md +++ /dev/null @@ -1,36 +0,0 @@ -## trivy cloud config edit - -Edit Trivy Cloud configuration - -### Synopsis - -Edit Trivy Cloud platform configuration in the default editor specified in the EDITOR environment variable - -``` -trivy cloud config edit [flags] -``` - -### Options - -``` - -h, --help help for edit -``` - -### Options inherited from parent commands - -``` - --cacert string Path to PEM-encoded CA certificate file - --cache-dir string cache directory (default "/path/to/cache") - -c, --config string config path (default "trivy.yaml") - -d, --debug debug mode - --generate-default-config write the default config to trivy-default.yaml - --insecure allow insecure server connections - -q, --quiet suppress progress bar and log output - --timeout duration timeout (default 5m0s) - -v, --version show version -``` - -### SEE ALSO - -* [trivy cloud config](trivy_cloud_config.md) - Control Trivy Cloud configuration - diff --git a/docs/guide/references/configuration/cli/trivy_cloud_config_get.md b/docs/guide/references/configuration/cli/trivy_cloud_config_get.md deleted file mode 100644 index 6c6f6ce8f6..0000000000 --- a/docs/guide/references/configuration/cli/trivy_cloud_config_get.md +++ /dev/null @@ -1,45 +0,0 @@ -## trivy cloud config get - -Get Trivy Cloud configuration - -### Synopsis - -Get a Trivy Cloud platform configuration - -Available config settings can be viewed by using the `trivy cloud config list` command - -``` -trivy cloud config get [setting] [flags] -``` - -### Examples - -``` - $ trivy cloud config get server.scanning.enabled - $ trivy cloud config get server.scanning.upload-results -``` - -### Options - -``` - -h, --help help for get -``` - -### Options inherited from parent commands - -``` - --cacert string Path to PEM-encoded CA certificate file - --cache-dir string cache directory (default "/path/to/cache") - -c, --config string config path (default "trivy.yaml") - -d, --debug debug mode - --generate-default-config write the default config to trivy-default.yaml - --insecure allow insecure server connections - -q, --quiet suppress progress bar and log output - --timeout duration timeout (default 5m0s) - -v, --version show version -``` - -### SEE ALSO - -* [trivy cloud config](trivy_cloud_config.md) - Control Trivy Cloud configuration - diff --git a/docs/guide/references/configuration/cli/trivy_cloud_config_list.md b/docs/guide/references/configuration/cli/trivy_cloud_config_list.md deleted file mode 100644 index 07772a5df7..0000000000 --- a/docs/guide/references/configuration/cli/trivy_cloud_config_list.md +++ /dev/null @@ -1,36 +0,0 @@ -## trivy cloud config list - -List Trivy Cloud configuration - -### Synopsis - -List Trivy Cloud platform configuration in human readable format - -``` -trivy cloud config list [flags] -``` - -### Options - -``` - -h, --help help for list -``` - -### Options inherited from parent commands - -``` - --cacert string Path to PEM-encoded CA certificate file - --cache-dir string cache directory (default "/path/to/cache") - -c, --config string config path (default "trivy.yaml") - -d, --debug debug mode - --generate-default-config write the default config to trivy-default.yaml - --insecure allow insecure server connections - -q, --quiet suppress progress bar and log output - --timeout duration timeout (default 5m0s) - -v, --version show version -``` - -### SEE ALSO - -* [trivy cloud config](trivy_cloud_config.md) - Control Trivy Cloud configuration - diff --git a/docs/guide/references/configuration/cli/trivy_cloud_config_set.md b/docs/guide/references/configuration/cli/trivy_cloud_config_set.md deleted file mode 100644 index 95fdfc94f8..0000000000 --- a/docs/guide/references/configuration/cli/trivy_cloud_config_set.md +++ /dev/null @@ -1,45 +0,0 @@ -## trivy cloud config set - -Set Trivy Cloud configuration - -### Synopsis - -Set a Trivy Cloud platform setting - -Available config settings can be viewed by using the `trivy cloud config list` command - -``` -trivy cloud config set [setting] [value] [flags] -``` - -### Examples - -``` - $ trivy cloud config set server.scanning.enabled true - $ trivy cloud config set server.scanning.upload-results false -``` - -### Options - -``` - -h, --help help for set -``` - -### Options inherited from parent commands - -``` - --cacert string Path to PEM-encoded CA certificate file - --cache-dir string cache directory (default "/path/to/cache") - -c, --config string config path (default "trivy.yaml") - -d, --debug debug mode - --generate-default-config write the default config to trivy-default.yaml - --insecure allow insecure server connections - -q, --quiet suppress progress bar and log output - --timeout duration timeout (default 5m0s) - -v, --version show version -``` - -### SEE ALSO - -* [trivy cloud config](trivy_cloud_config.md) - Control Trivy Cloud configuration - diff --git a/docs/guide/references/configuration/cli/trivy_cloud_config_unset.md b/docs/guide/references/configuration/cli/trivy_cloud_config_unset.md deleted file mode 100644 index e01871037a..0000000000 --- a/docs/guide/references/configuration/cli/trivy_cloud_config_unset.md +++ /dev/null @@ -1,45 +0,0 @@ -## trivy cloud config unset - -Unset Trivy Cloud configuration - -### Synopsis - -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 - -``` -trivy cloud config unset [setting] [flags] -``` - -### Examples - -``` - $ trivy cloud config unset server.scanning.enabled - $ trivy cloud config unset server.scanning.upload-results -``` - -### Options - -``` - -h, --help help for unset -``` - -### Options inherited from parent commands - -``` - --cacert string Path to PEM-encoded CA certificate file - --cache-dir string cache directory (default "/path/to/cache") - -c, --config string config path (default "trivy.yaml") - -d, --debug debug mode - --generate-default-config write the default config to trivy-default.yaml - --insecure allow insecure server connections - -q, --quiet suppress progress bar and log output - --timeout duration timeout (default 5m0s) - -v, --version show version -``` - -### SEE ALSO - -* [trivy cloud config](trivy_cloud_config.md) - Control Trivy Cloud configuration - diff --git a/docs/guide/references/configuration/cli/trivy_login.md b/docs/guide/references/configuration/cli/trivy_login.md deleted file mode 100644 index 07f0a3b618..0000000000 --- a/docs/guide/references/configuration/cli/trivy_login.md +++ /dev/null @@ -1,46 +0,0 @@ -## trivy login - -Log in to the Trivy Cloud platform - -### Synopsis - -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 - -``` -trivy login [flags] -``` - -### Examples - -``` - # Log in to the Trivy Cloud platform - $ trivy login --token -``` - -### Options - -``` - --api-url string API URL for Trivy Cloud platform (default "https://api.trivy.dev") - -h, --help help for login - --token string Token used to athenticate with Trivy Cloud platform - --trivy-server-url string Trivy Server URL for Trivy Cloud platform (default "https://scan.trivy.dev") -``` - -### Options inherited from parent commands - -``` - --cacert string Path to PEM-encoded CA certificate file - --cache-dir string cache directory (default "/path/to/cache") - -c, --config string config path (default "trivy.yaml") - -d, --debug debug mode - --generate-default-config write the default config to trivy-default.yaml - --insecure allow insecure server connections - -q, --quiet suppress progress bar and log output - --timeout duration timeout (default 5m0s) - -v, --version show version -``` - -### SEE ALSO - -* [trivy](trivy.md) - Unified security scanner - diff --git a/docs/guide/references/configuration/cli/trivy_logout.md b/docs/guide/references/configuration/cli/trivy_logout.md deleted file mode 100644 index 61e52e9fee..0000000000 --- a/docs/guide/references/configuration/cli/trivy_logout.md +++ /dev/null @@ -1,32 +0,0 @@ -## trivy logout - -Log out of Trivy Cloud platform - -``` -trivy logout [flags] -``` - -### Options - -``` - -h, --help help for logout -``` - -### Options inherited from parent commands - -``` - --cacert string Path to PEM-encoded CA certificate file - --cache-dir string cache directory (default "/path/to/cache") - -c, --config string config path (default "trivy.yaml") - -d, --debug debug mode - --generate-default-config write the default config to trivy-default.yaml - --insecure allow insecure server connections - -q, --quiet suppress progress bar and log output - --timeout duration timeout (default 5m0s) - -v, --version show version -``` - -### SEE ALSO - -* [trivy](trivy.md) - Unified security scanner - diff --git a/go.mod b/go.mod index e635caa4b4..d958a3ebb1 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 @@ -227,7 +226,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 @@ -284,7 +282,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.13.0 // 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 53dbfe29ba..277766ff7c 100644 --- a/go.sum +++ b/go.sum @@ -687,8 +687,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 5dc0fae181..cdac8392c5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -169,21 +169,11 @@ nav: - CLI: - Overview: guide/references/configuration/cli/trivy.md - Clean: guide/references/configuration/cli/trivy_clean.md - - Cloud: - - Cloud: guide/references/configuration/cli/trivy_cloud.md - - Cloud Config: guide/references/configuration/cli/trivy_cloud_config.md - - Cloud Config Edit: guide/references/configuration/cli/trivy_cloud_config_edit.md - - Cloud Config List: guide/references/configuration/cli/trivy_cloud_config_list.md - - Cloud Config Set: guide/references/configuration/cli/trivy_cloud_config_set.md - - Cloud Config Unset: guide/references/configuration/cli/trivy_cloud_config_unset.md - - Cloud Config Get: guide/references/configuration/cli/trivy_cloud_config_get.md - Config: guide/references/configuration/cli/trivy_config.md - Convert: guide/references/configuration/cli/trivy_convert.md - Filesystem: guide/references/configuration/cli/trivy_filesystem.md - Image: guide/references/configuration/cli/trivy_image.md - Kubernetes: guide/references/configuration/cli/trivy_kubernetes.md - - Login: guide/references/configuration/cli/trivy_login.md - - Logout: guide/references/configuration/cli/trivy_logout.md - Module: - Module: guide/references/configuration/cli/trivy_module.md - Module Install: guide/references/configuration/cli/trivy_module_install.md diff --git a/pkg/cloud/config.go b/pkg/cloud/config.go deleted file mode 100644 index 64828bb08a..0000000000 --- a/pkg/cloud/config.go +++ /dev/null @@ -1,298 +0,0 @@ -package cloud - -import ( - "context" - "errors" - "fmt" - "net/http" - "net/url" - "os" - "path/filepath" - "reflect" - "strconv" - "strings" - - "github.com/samber/lo" - "github.com/zalando/go-keyring" - "golang.org/x/xerrors" - "gopkg.in/yaml.v3" - - "github.com/aquasecurity/trivy/pkg/log" - "github.com/aquasecurity/trivy/pkg/utils/fsutils" - xhttp "github.com/aquasecurity/trivy/pkg/x/http" -) - -const ( - ServiceName = "trivy-cloud" - TokenKey = "token" - DefaultApiUrl = "https://api.trivy.dev" - DefaultTrivyServerUrl = "https://scan.trivy.dev" -) - -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:"-"` -} - -var defaultConfig = &Config{ - Api: Api{ - URL: DefaultApiUrl, - }, - Server: Server{ - URL: DefaultTrivyServerUrl, - Scanning: Scanning{}, - }, -} - -func getConfigPath() string { - configFileName := fmt.Sprintf("%s.yaml", ServiceName) - return filepath.Join(fsutils.TrivyHomeDir(), configFileName) -} - -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 Clear() error { - if err := keyring.Delete(ServiceName, TokenKey); err != nil { - if !errors.Is(err, keyring.ErrNotFound) { - return err - } - } - - configPath := getConfigPath() - if err := os.Remove(configPath); err != nil { - if !errors.Is(err, os.ErrNotExist) { - return 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 - } - 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) { - 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 - } - - 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 - } - - 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") - } - - 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") - if err != nil { - return xerrors.Errorf("failed to join server URL and verify 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) - if err != nil { - return xerrors.Errorf("failed to create verification request: %w", err) - } - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.Token)) - resp, err := client.Do(req) - if err != nil { - return xerrors.Errorf("failed to verify token: %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) - } - - var loggedIn bool - if cloudConfig.Verify(context.Background()) == nil { - loggedIn = true - } else { - loggedIn = false - } - - 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)) - } - - for _, field := range fields { - fmt.Printf("%-*s %s\n", maxKeyLen, field.path, formatValue(field.value)) - } - - 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, - }) - } - } - - 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()) - } -} diff --git a/pkg/cloud/config_edit.go b/pkg/cloud/config_edit.go deleted file mode 100644 index ac05dc0102..0000000000 --- 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 9dd8bf11a9..0000000000 --- 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 22c79ae612..0000000000 --- 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 deleted file mode 100644 index e3901a53bd..0000000000 --- a/pkg/cloud/config_test.go +++ /dev/null @@ -1,345 +0,0 @@ -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) - - configPath := getConfigPath() - assert.FileExists(t, configPath) - } - - err := Clear() - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - configPath := getConfigPath() - assert.NoFileExists(t, configPath) - }) - } -} - -func TestLoad(t *testing.T) { - tests := []struct { - name string - createConfig bool - expectDefault bool - }{ - { - name: "success when there is config to load", - createConfig: true, - expectDefault: false, - }, - { - 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: "error with invalid config", - config: &Config{}, - status: http.StatusUnauthorized, - wantErr: 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() - - 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) - }) - } -} - -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, - }, - }, - 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() - }() - - output, _ := io.ReadAll(r) - os.Stdout = originalStdout - - err = <-errChan - if tt.wantErr != "" { - require.ErrorContains(t, err, tt.wantErr) - return - } - require.NoError(t, err) - - outputStr := string(output) - for _, want := range tt.wantContains { - assert.Contains(t, outputStr, want) - } - }) - } -} diff --git a/pkg/cloud/hooks/report_hook.go b/pkg/cloud/hooks/report_hook.go deleted file mode 100644 index 495a36c5ad..0000000000 --- a/pkg/cloud/hooks/report_hook.go +++ /dev/null @@ -1,119 +0,0 @@ -package hooks - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - - "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" - xhttp "github.com/aquasecurity/trivy/pkg/x/http" -) - -const ( - presignedUploadUrl = "/trivy-reports/upload-url" -) - -type CloudPlatformResultsHook struct { - name string - cloudConfig *cloud.Config - client *http.Client - logger *log.Logger -} - -func NewResultsHook(cloudCfg *cloud.Config) *CloudPlatformResultsHook { - return &CloudPlatformResultsHook{ - name: "Trivy Cloud Results Hook", - cloudConfig: cloudCfg, - client: xhttp.Client(), - logger: log.WithPrefix(log.PrefixCloud), - } -} - -func (h *CloudPlatformResultsHook) 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 { - return nil -} - -func (h *CloudPlatformResultsHook) 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 { - return xerrors.Errorf("failed to marshal report to JSON: %w", err) - } - - return h.uploadResults(ctx, jsonReport) -} - -func (h *CloudPlatformResultsHook) 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) - } - - // create a new request to upload the results - req, err := http.NewRequestWithContext(ctx, http.MethodPut, uploadUrl, bytes.NewBuffer(jsonReport)) - if err != nil { - return fmt.Errorf("failed to create upload request: %w", err) - } - - req.Header.Set("Content-Type", "application/json") - resp, err := h.client.Do(req) - if err != nil { - return fmt.Errorf("failed to upload results: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("failed to upload results: received status code %d", resp.StatusCode) - } - - h.logger.Info("Report uploaded successfully to Trivy Cloud") - return nil -} - -func (h *CloudPlatformResultsHook) getPresignedUploadUrl(ctx context.Context) (string, error) { - uploadUrl, err := url.JoinPath(h.cloudConfig.Api.URL, presignedUploadUrl) - if err != nil { - return "", fmt.Errorf("failed to join API URL and presigned upload URL: %w", err) - } - h.logger.Debug("Requesting result upload URL", log.String("uploadUrl", uploadUrl)) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, uploadUrl, http.NoBody) - if err != nil { - return "", fmt.Errorf("failed to create request: %w", err) - } - - req.Header.Set("Authorization", "Bearer "+h.cloudConfig.Token) - resp, err := h.client.Do(req) - if err != nil { - return "", fmt.Errorf("failed to get upload URL: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("failed to get upload URL: %w", err) - } - - // read the upload URL from the response - var uploadResponse struct { - UploadURL string `json:"uploadUrl"` - } - - if err := json.NewDecoder(resp.Body).Decode(&uploadResponse); err != nil { - return "", xerrors.Errorf("failed to decode upload URL response: %w", err) - } - - return uploadResponse.UploadURL, nil -} diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 0b139ef553..c528c06afc 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 { @@ -216,8 +208,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} @@ -1422,149 +1413,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/cloud/run.go b/pkg/commands/cloud/run.go deleted file mode 100644 index 184574a142..0000000000 --- a/pkg/commands/cloud/run.go +++ /dev/null @@ -1,120 +0,0 @@ -package cloud - -import ( - "context" - "fmt" - "os" - - "github.com/spf13/cobra" - "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" -) - -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 { - return nil - } - - logger := log.WithPrefix(log.PrefixCloud) - cloudConfig, err := cloud.Load() - if err != nil { - logger.Error("Failed to load Trivy Cloud config file", log.Err(err)) - return nil - } - - 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 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) - } - } - - 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) -} - -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) - } - 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 5c487aa765..0000000000 --- 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 deleted file mode 100644 index 0e0b1a5b72..0000000000 --- a/pkg/flag/cloud_flags.go +++ /dev/null @@ -1,75 +0,0 @@ -package flag - -import "github.com/aquasecurity/trivy/pkg/cloud" - -var ( - CloudTokenFlag = Flag[string]{ - Name: "token", - ConfigName: "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", - } - - CloudTrivyServerUrlFlag = Flag[string]{ - Name: "trivy-server-url", - ConfigName: "cloud.trivy_server_url", - Default: cloud.DefaultTrivyServerUrl, - Usage: "Trivy Server URL for Trivy Cloud platform", - } -) - -type CloudFlagGroup struct { - CloudToken *Flag[string] - CloudApiUrl *Flag[string] - CloudTrivyServerUrl *Flag[string] -} - -func NewCloudFlagGroup() *CloudFlagGroup { - return &CloudFlagGroup{ - CloudToken: CloudTokenFlag.Clone(), - CloudApiUrl: CloudApiUrlFlag.Clone(), - CloudTrivyServerUrl: CloudTrivyServerUrlFlag.Clone(), - } -} - -func (f *CloudFlagGroup) Name() string { - return "Trivy Cloud" -} - -func (f *CloudFlagGroup) Flags() []Flagger { - return []Flagger{ - f.CloudToken, - f.CloudApiUrl, - f.CloudTrivyServerUrl, - } -} - -// CloudLoginCredentials is the credentials used to authenticate with Trivy Cloud platform -// In the future this would likely have more information stored for refresh tokens, etc -type CloudLoginCredentials struct { - Token string -} - -type CloudOptions struct { - LoginCredentials CloudLoginCredentials - ApiUrl string - TrivyServerUrl string -} - -// 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(), - } - return nil -} diff --git a/pkg/flag/options.go b/pkg/flag/options.go index b3c325256d..54852e31f5 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -406,7 +406,6 @@ type Options struct { RemoteOptions RepoOptions ReportOptions - CloudOptions ScanOptions SecretOptions VulnerabilityOptions diff --git a/pkg/log/logger.go b/pkg/log/logger.go index e4c1babc19..a616554536 100644 --- a/pkg/log/logger.go +++ b/pkg/log/logger.go @@ -27,7 +27,6 @@ const ( PrefixJavaDB = "javadb" PrefixSPDX = "spdx" PrefixCycloneDX = "cyclonedx" - PrefixCloud = "cloud" ) // Logger is an alias of slog.Logger