Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ func main() {
var err error
bindProxy()
bindKeysToEnvAndDefault()
configuration.LoadConfiguration()
err = configuration.LoadConfiguration()
exitIfError(err)
scans := viper.GetString(params.ScansPathKey)
groups := viper.GetString(params.GroupsPathKey)
logs := viper.GetString(params.LogsPathKey)
Expand Down
31 changes: 25 additions & 6 deletions internal/commands/util/configuration_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package util

import (
"github.com/checkmarx/ast-cli/internal/params"
"github.com/spf13/viper"
"os"
"strings"
"testing"
Expand Down Expand Up @@ -47,9 +49,11 @@ func TestGetConfigFilePath_CheckmarxConfigFileExists_Success(t *testing.T) {
}

func TestWriteSingleConfigKeyToExistingFile_ChangeAscaPortToZero_Success(t *testing.T) {
configuration.LoadConfiguration()
err := configuration.LoadConfiguration()
assert.NilError(t, err)

configFilePath, _ := configuration.GetConfigFilePath()
err := configuration.SafeWriteSingleConfigKey(configFilePath, cxAscaPort, 0)
err = configuration.SafeWriteSingleConfigKey(configFilePath, cxAscaPort, 0)
assert.NilError(t, err)

config, err := configuration.LoadConfig(configFilePath)
Expand Down Expand Up @@ -78,7 +82,9 @@ func TestWriteSingleConfigKeyNonExistingFile_CreatingTheFileAndWritesTheKey_Succ
}

func TestChangedOnlyAscaPortInConfigFile_ConfigFileExistsWithDefaultValues_OnlyAscaPortChangedSuccess(t *testing.T) {
configuration.LoadConfiguration()
err := configuration.LoadConfiguration()
assert.NilError(t, err)

configFilePath, _ := configuration.GetConfigFilePath()

oldConfig, err := configuration.LoadConfig(configFilePath)
Expand All @@ -100,9 +106,11 @@ func TestChangedOnlyAscaPortInConfigFile_ConfigFileExistsWithDefaultValues_OnlyA
}

func TestWriteSingleConfigKeyStringToExistingFile_UpdateScsScanOverviewPath_Success(t *testing.T) {
configuration.LoadConfiguration()
err := configuration.LoadConfiguration()
assert.NilError(t, err)

configFilePath, _ := configuration.GetConfigFilePath()
err := configuration.SafeWriteSingleConfigKeyString(configFilePath, cxScsScanOverviewPath, defaultScsScanOverviewPath)
err = configuration.SafeWriteSingleConfigKeyString(configFilePath, cxScsScanOverviewPath, defaultScsScanOverviewPath)
assert.NilError(t, err)

config, err := configuration.LoadConfig(configFilePath)
Expand Down Expand Up @@ -131,7 +139,9 @@ func TestWriteSingleConfigKeyStringNonExistingFile_CreatingTheFileAndWritesTheKe
}

func TestChangedOnlyScsScanOverviewPathInConfigFile_ConfigFileExistsWithDefaultValues_OnlyScsScanOverviewPathChangedSuccess(t *testing.T) {
configuration.LoadConfiguration()
err := configuration.LoadConfiguration()
assert.NilError(t, err)

configFilePath, _ := configuration.GetConfigFilePath()

oldConfig, err := configuration.LoadConfig(configFilePath)
Expand All @@ -151,3 +161,12 @@ func TestChangedOnlyScsScanOverviewPathInConfigFile_ConfigFileExistsWithDefaultV
}
}
}

func TestGetConfigFilePath_CustomFile(t *testing.T) {
expectedPath := "/custom/path/checkmarxcli.yaml"
viper.Set(params.ConfigFilePathKey, expectedPath)

actualPath, err := configuration.GetConfigFilePath()
assert.NilError(t, err)
assert.Equal(t, actualPath, expectedPath, "Expected path to match the set value in viper")
}
1 change: 1 addition & 0 deletions internal/params/binds.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,5 @@ var EnvVarsBinds = []struct {
{ASCAPortKey, ASCAPortEnv, ""},
{ScsRepoTokenKey, ScsRepoTokenEnv, ""},
{RiskManagementPathKey, RiskManagementPathEnv, "api/risk-management/projects/%s/results"},
{ConfigFilePathKey, ConfigFilePathEnv, ""},
}
1 change: 1 addition & 0 deletions internal/params/envs.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,5 @@ const (
ASCAPortEnv = "CX_ASCA_PORT"
ScsRepoTokenEnv = "SCS_REPO_TOKEN"
RiskManagementPathEnv = "CX_RISK_MANAGEMENT_PATH"
ConfigFilePathEnv = "CX_CONFIG_FILE_PATH"
)
1 change: 1 addition & 0 deletions internal/params/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,5 @@ var (
ASCAPortKey = strings.ToLower(ASCAPortEnv)
ScsRepoTokenKey = strings.ToLower(ScsRepoTokenEnv)
RiskManagementPathKey = strings.ToLower(RiskManagementPathEnv)
ConfigFilePathKey = strings.ToLower(ConfigFilePathEnv)
)
74 changes: 59 additions & 15 deletions internal/wrappers/configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ func setConfigPropertyQuiet(propName, propValue string) {
// SafeWriteConfig() will not update files but it will create them, combined
// this code will successfully update files.
if viperErr := viper.SafeWriteConfig(); viperErr != nil {
_ = viper.WriteConfig()
err := viper.WriteConfig()
if err != nil {
fmt.Println("Error writing config file", err)
}
}
}

Expand All @@ -117,18 +120,54 @@ func SetConfigProperty(propName, propValue string) {
setConfigPropertyQuiet(propName, propValue)
}

func LoadConfiguration() {
usr, err := user.Current()
func LoadConfiguration() error {
configFilePath := viper.GetString(params.ConfigFilePathKey)

if configFilePath != "" {
err := validateConfigFile(configFilePath)
if err != nil {
return err
}
viper.SetConfigFile(configFilePath)
if err = viper.ReadInConfig(); err != nil {
return errors.New("An error occurred while accessing the file or environment variable. Please verify the CLI configuration file")
}
} else {
usr, err := user.Current()
if err != nil {
log.Fatal("Cannot file home directory.", err)
}
fullPath := usr.HomeDir + configDirName
verifyConfigDir(fullPath)
viper.AddConfigPath(fullPath)
configFile := "checkmarxcli"
viper.SetConfigName(configFile)
viper.SetConfigType("yaml")
_ = viper.ReadInConfig()
}
return nil
}

func validateConfigFile(configFilePath string) error {
info, err := os.Stat(configFilePath)
if err != nil {
log.Fatal("Cannot file home directory.", err)
if os.IsNotExist(err) {
return fmt.Errorf("The specified file does not exist. Please check the path and ensure the CLI configuration file is available.")
}
return fmt.Errorf("An error occurred while accessing the file or environment variable. Please verify the CLI configuration file")
}

if info.IsDir() {
return fmt.Errorf("The specified path points to a directory, not a file. Please provide a valid CLI configuration file path.")
}

file, err := os.OpenFile(configFilePath, os.O_RDONLY, 0644)
if err != nil {
return fmt.Errorf("Access to the specified file is restricted. Please ensure you have the necessary permissions to access the CLI configuration file")
}
fullPath := usr.HomeDir + configDirName
verifyConfigDir(fullPath)
viper.AddConfigPath(fullPath)
configFile := "checkmarxcli"
viper.SetConfigName(configFile)
viper.SetConfigType("yaml")
_ = viper.ReadInConfig()
defer file.Close()

return nil
}

func SafeWriteSingleConfigKey(configFilePath, key string, value int) error {
Expand Down Expand Up @@ -231,11 +270,16 @@ func SaveConfig(path string, config map[string]interface{}) error {
}

func GetConfigFilePath() (string, error) {
usr, err := user.Current()
if err != nil {
return "", fmt.Errorf("error getting current user: %w", err)
configFilePath := viper.GetString(params.ConfigFilePathKey)

if configFilePath == "" {
usr, err := user.Current()
if err != nil {
return "", fmt.Errorf("error getting current user: %w", err)
}
configFilePath = usr.HomeDir + configDirName + "/checkmarxcli.yaml"
}
return usr.HomeDir + configDirName + "/checkmarxcli.yaml", nil
return configFilePath, nil
}

func verifyConfigDir(fullPath string) {
Expand Down
75 changes: 75 additions & 0 deletions test/integration/configuration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//go:build integration

package integration

import (
"github.com/checkmarx/ast-cli/internal/wrappers/configuration"
"github.com/spf13/viper"
"gotest.tools/assert"
"os"
"strings"
"testing"
)

const filePath = "data/config.yaml"

func TestLoadConfiguration_EnvVarConfigFilePath(t *testing.T) {
os.Setenv("CX_CONFIG_FILE_PATH", filePath)
defer os.Unsetenv("CX_CONFIG_FILE_PATH")

_ = viper.BindEnv("CX_CONFIG_FILE_PATH")
err := configuration.LoadConfiguration()
assert.NilError(t, err)
}

func TestLoadConfiguration_FileNotFound(t *testing.T) {
os.Setenv("CX_CONFIG_FILE_PATH", "data/nonexistent_config.yaml")
defer os.Unsetenv("CX_CONFIG_FILE_PATH")

_ = viper.BindEnv("CX_CONFIG_FILE_PATH")
var err error
err = configuration.LoadConfiguration()
assert.ErrorContains(t, err, "The specified file does not exist")
}
func TestLoadConfiguration_ValidDirectory(t *testing.T) {
validDirPath := "data"
os.Setenv("CX_CONFIG_FILE_PATH", validDirPath)
defer os.Unsetenv("CX_CONFIG_FILE_PATH")

_ = viper.BindEnv("CX_CONFIG_FILE_PATH")
err := configuration.LoadConfiguration()
assert.ErrorContains(t, err, "The specified path points to a directory")
}
func TestLoadConfiguration_FileWithoutPermission_UsingConfigFile(t *testing.T) {
if err := os.Chmod(filePath, 0000); err != nil {
t.Fatalf("failed to set file permissions: %v", err)
}
defer os.Chmod(filePath, 0644)

os.Setenv("CX_CONFIG_FILE_PATH", filePath)
defer os.Unsetenv("CX_CONFIG_FILE_PATH")

_ = viper.BindEnv("CX_CONFIG_FILE_PATH")
err := configuration.LoadConfiguration()

assert.ErrorContains(t, err, "Access to the specified file is restricted")
}

func TestSetConfigProperty_EnvVarConfigFilePath(t *testing.T) {
os.Setenv("CX_CONFIG_FILE_PATH", filePath)
defer os.Unsetenv("CX_CONFIG_FILE_PATH")

_ = viper.BindEnv("CX_CONFIG_FILE_PATH")
err := configuration.LoadConfiguration()
assert.NilError(t, err)

err, _ = executeCommand(t, "configure", "set", "--prop-name", "cx_client_id", "--prop-value", "dummy-client_id")
assert.NilError(t, err)

content, err := os.ReadFile(filePath)
assert.NilError(t, err)
assert.Assert(t, strings.Contains(string(content), "dummy-client_id"))

err, _ = executeCommand(t, "configure", "set", "--prop-name", "cx_client_id", "--prop-value", "example_client_id")
assert.NilError(t, err)
}
82 changes: 82 additions & 0 deletions test/integration/data/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
apikey-override: false
cx_access_management_path: api/access-management
cx_agent_name: ASTCLI
cx_aiproxy_azureai_route: api/ai-proxy/redirect/externalAzure
cx_aiproxy_checkmarxai_route: api/ai-proxy/redirect/azure
cx_api_key: example_api_key
cx_apikey: ""
cx_applications_path: api/applications
cx_asca_port: ""
cx_ast_keycloak_web_app_health_check_path: auth
cx_ast_role: SCA_AGENT
cx_ast_web_app_health_check_path: '#/projects'
cx_base_auth_uri: https://auth.example.com
cx_base_uri: https://example.com
cx_bfl_path: api/bfl
cx_branch: ""
cx_byor_path: api/byor
cx_client_id: example_client_id
cx_client_secret: example_client_secret
cx_codebashing_path: api/codebashing/lessons
cx_config_file_path: data/config.yaml
cx_create_oath2_client_path: auth/realms/organization/pip/clients
cx_custom_states_path: api/custom-states
cx_descriptions_path: api/queries/descriptions
cx_export_path: api/sca/export
cx_feature_flags_path: api/flags
cx_groups_path: auth/realms/organization/pip/groups
cx_healthcheck_db_path: logging
cx_healthcheck_in_memory_db_path: in-memory-db
cx_healthcheck_message_queue_path: message-queue
cx_healthcheck_object_store_path: object-store
cx_healthcheck_path: api/healthcheck
cx_healthcheck_sast_engines_path: sast-engines
cx_healthcheck_scan_flow_path: scan-flow
cx_ignore_proxy: ""
cx_kics_results_path: api/kics-results
cx_kics_results_predicates_path: api/kics-results-predicates
cx_logs_engine_log_path: /%s/%s
cx_logs_path: api/logs
cx_origin: CLI
cx_policy_evaluation_path: api/policy_management_service_uri/evaluation
cx_pr_decoration_azure_path: api/flow-publisher/pr/azure
cx_pr_decoration_bitbucket_cloud_path: api/flow-publisher/pr/bitbucket
cx_pr_decoration_bitbucket_server_path: api/flow-publisher/pr/bitbucket-server
cx_pr_decoration_github_path: api/flow-publisher/pr/github
cx_pr_decoration_gitlab_path: api/flow-publisher/pr/gitlab
cx_projects_path: api/projects
cx_proxy: http://proxy.example.com
cx_proxy_auth_type: basic
cx_proxy_ntlm_domain: ""
cx_queries_clone_path: clone
cx_queries_path: api/queries
cx_results_path: api/results
cx_results_pdf_report_path: api/reports
cx_risk_management_path: api/risk-management/projects/%s/results
cx_risks_overview_path: api/apisec/static/api/scan/%s/risks-overview
cx_sast_results_path: api/sast-results
cx_sast_results_predicates_path: api/sast-results-predicates
cx_sast_rm_path: api/sast-rm
cx_sast_scan_inc_metrics_path: '%s/metrics'
cx_sast_scan_inc_path: api/sast-metadata
cx_scan_summary_path: api/scan-summary
cx_scans_path: api/scans
cx_scs_results_predicates_read_path: api/micro-engines/read/predicates
cx_scs_results_predicates_write_path: api/micro-engines/write/predicates
cx_scs_scan_overview_path: api/micro-engines/read/scans/%s/scan-overview
cx_tenant: example_tenant
cx_tenant_configuration_path: api/configuration/tenant
cx_timeout: "30"
cx_token_expiry_seconds: 2
cx_uploads_path: api/uploads
debug: false
http_proxy: ""
insecure: false
kics-container-name: cli-kics-realtime-b0ddfae9-fb90-4571-8b81-85cbf08fd4b3
retry: "3"
retry-delay: "5"
sca_resolver: ./ScaResolver
scs_repo_token: ""
token: ""
url: https://api.github.com
url-gitlab: https://gitlab.com
2 changes: 2 additions & 0 deletions test/integration/util_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/checkmarx/ast-cli/internal/commands"
"github.com/checkmarx/ast-cli/internal/params"
"github.com/checkmarx/ast-cli/internal/wrappers"
"github.com/checkmarx/ast-cli/internal/wrappers/configuration"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gotest.tools/assert"
Expand Down Expand Up @@ -56,6 +57,7 @@ func bindProxy(t *testing.T) {
func createASTIntegrationTestCommand(t *testing.T) *cobra.Command {
bindProxy(t)
bindKeysToEnvAndDefault(t)
configuration.LoadConfiguration()
_ = viper.BindEnv(pat)
viper.AutomaticEnv()
viper.Set("CX_TOKEN_EXPIRY_SECONDS", 2)
Expand Down
Loading