chore(cli): Remove Trivy Cloud (#9847)

This commit is contained in:
Owen Rumney
2025-11-26 09:14:41 +00:00
committed by GitHub
parent 7aca80151c
commit 96e7083337
26 changed files with 5 additions and 2061 deletions

2
.github/CODEOWNERS vendored
View File

@@ -11,7 +11,7 @@ pkg/scanner/ @knqyf263 @DmitriyLewen
docs/guide/scanner/misconfiguration/ @simar7 @nikpivkin
docs/guide/target/aws.md @simar7 @nikpivkin
pkg/fanal/analyzer/config/ @simar7 @nikpivkin
pkg/cloud/ @simar7 @nikpivkin
pkg/config/aws/ @simar7 @nikpivkin
pkg/iac/ @simar7 @nikpivkin
# Helm chart

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

5
go.mod
View File

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

2
go.sum
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -406,7 +406,6 @@ type Options struct {
RemoteOptions
RepoOptions
ReportOptions
CloudOptions
ScanOptions
SecretOptions
VulnerabilityOptions

View File

@@ -27,7 +27,6 @@ const (
PrefixJavaDB = "javadb"
PrefixSPDX = "spdx"
PrefixCycloneDX = "cyclonedx"
PrefixCloud = "cloud"
)
// Logger is an alias of slog.Logger