diff --git a/cmd/main.go b/cmd/main.go index c1a66f0f9..53d2ae664 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -59,6 +59,7 @@ func main() { accessManagementPath := viper.GetString(params.AccessManagementPathKey) byorPath := viper.GetString(params.ByorPathKey) realtimeScannerPath := viper.GetString(params.RealtimeScannerPathKey) + dastEnvironmentsPath := viper.GetString(params.DastEnvironmentsPathKey) customStatesWrapper := wrappers.NewCustomStatesHTTPWrapper() scansWrapper := wrappers.NewHTTPScansWrapper(scans) @@ -97,6 +98,7 @@ func main() { containerResolverWrapper := wrappers.NewContainerResolverWrapper() realTimeWrapper := wrappers.NewRealtimeScannerHTTPWrapper(realtimeScannerPath, jwtWrapper, featureFlagsWrapper) telemetryWrapper := wrappers.NewHTTPTelemetryAIWrapper(realtimeScannerPath) + dastEnvironmentsWrapper := wrappers.NewHTTPDastEnvironmentsWrapper(dastEnvironmentsPath) astCli := commands.NewAstCLI( applicationsWrapper, @@ -136,6 +138,7 @@ func main() { containerResolverWrapper, realTimeWrapper, telemetryWrapper, + dastEnvironmentsWrapper, ) exitListener() err = astCli.Execute() diff --git a/internal/commands/commandutils/commandutils.go b/internal/commands/commandutils/commandutils.go new file mode 100644 index 000000000..64fb4b143 --- /dev/null +++ b/internal/commands/commandutils/commandutils.go @@ -0,0 +1,45 @@ +package commandutils + +import ( + "errors" + "fmt" + "strings" + + "github.com/checkmarx/ast-cli/internal/commands/util/printer" + "github.com/checkmarx/ast-cli/internal/params" + "github.com/spf13/cobra" +) + +func AddFormatFlagToMultipleCommands(commands []*cobra.Command, defaultFormat string, otherAvailableFormats ...string) { + for _, c := range commands { + AddFormatFlag(c, defaultFormat, otherAvailableFormats...) + } +} + +func AddFormatFlag(cmd *cobra.Command, defaultFormat string, otherAvailableFormats ...string) { + cmd.PersistentFlags().String( + params.FormatFlag, defaultFormat, + fmt.Sprintf(params.FormatFlagUsageFormat, append(otherAvailableFormats, defaultFormat)), + ) +} + +func PrintByFormat(cmd *cobra.Command, view interface{}) error { + f, _ := cmd.Flags().GetString(params.FormatFlag) + return printer.Print(cmd.OutOrStdout(), view, f) +} + +func GetFilters(cmd *cobra.Command) (map[string]string, error) { + filters, _ := cmd.Flags().GetStringSlice(params.FilterFlag) + allFilters := make(map[string]string) + for _, filter := range filters { + filterKeyVal := strings.Split(filter, "=") + if len(filterKeyVal) != params.KeyValuePairSize { + return nil, errors.New("invalid filters, filters should be in a KEY=VALUE format") + } + allFilters[filterKeyVal[0]] = strings.Replace( + filterKeyVal[1], ";", ",", + strings.Count(filterKeyVal[1], ";"), + ) + } + return allFilters, nil +} diff --git a/internal/commands/dast/dast-environments.go b/internal/commands/dast/dast-environments.go new file mode 100644 index 000000000..ce282fe4d --- /dev/null +++ b/internal/commands/dast/dast-environments.go @@ -0,0 +1,142 @@ +package dast + +import ( + "fmt" + "strings" + + "github.com/MakeNowJust/heredoc" + "github.com/checkmarx/ast-cli/internal/commands/commandutils" + "github.com/checkmarx/ast-cli/internal/commands/util/printer" + commonParams "github.com/checkmarx/ast-cli/internal/params" + "github.com/checkmarx/ast-cli/internal/services" + "github.com/checkmarx/ast-cli/internal/wrappers" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +const ( + failedGettingDastEnvironments = "Failed getting DAST environments" +) + +var ( + filterDastEnvironmentsListFlagUsage = fmt.Sprintf( + "Filter the list of DAST environments. Use ';' as the delimiter for arrays. Available filters are: %s", + strings.Join( + []string{ + commonParams.FromQueryParam, + commonParams.ToQueryParam, + commonParams.SearchQueryParam, + commonParams.SortQueryParam, + }, ",", + ), + ) +) + +// NewDastEnvironmentsCommand creates the DAST environments command +func NewDastEnvironmentsCommand(dastEnvironmentsWrapper wrappers.DastEnvironmentsWrapper) *cobra.Command { + environmentsCmd := &cobra.Command{ + Use: "dast-environments", + Short: "Manage DAST environments", + Long: "The environments command enables the ability to manage DAST environments in Checkmarx One", + Annotations: map[string]string{ + "command:doc": heredoc.Doc( + ` + https://checkmarx.com/resource/documents/en/todo + `, + ), + }, + } + + listDastEnvironmentsCmd := &cobra.Command{ + Use: "list", + Short: "List all DAST environments in the system", + Example: heredoc.Doc( + ` + $ cx dast-environments list --format list + $ cx dast-environments list --filter "from=1,to=10" + $ cx dast-environments list --filter "search=production,sort=created" + `, + ), + Annotations: map[string]string{ + "command:doc": heredoc.Doc( + ` + https://checkmarx.com/resource/documents/en/todo + `, + ), + }, + RunE: runListDastEnvironmentsCommand(dastEnvironmentsWrapper), + } + listDastEnvironmentsCmd.PersistentFlags().StringSlice(commonParams.FilterFlag, []string{}, filterDastEnvironmentsListFlagUsage) + + commandutils.AddFormatFlagToMultipleCommands( + []*cobra.Command{listDastEnvironmentsCmd}, + printer.FormatTable, + printer.FormatJSON, + printer.FormatList, + ) + + environmentsCmd.AddCommand(listDastEnvironmentsCmd) + return environmentsCmd +} + +func runListDastEnvironmentsCommand(dastEnvironmentsWrapper wrappers.DastEnvironmentsWrapper) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + var allEnvironmentsModel *wrappers.DastEnvironmentsCollectionResponseModel + var errorModel *wrappers.ErrorModel + + params, err := commandutils.GetFilters(cmd) + if err != nil { + return errors.Wrapf(err, "%s", failedGettingDastEnvironments) + } + + // The API expects: from, to, search, sort + // from and to are pagination parameters (e.g., from=1, to=10 for first page) + allEnvironmentsModel, errorModel, err = dastEnvironmentsWrapper.Get(params) + if err != nil { + return errors.Wrapf(err, "%s\n", failedGettingDastEnvironments) + } + + // Checking the response + if errorModel != nil { + return errors.Errorf(services.ErrorCodeFormat, failedGettingDastEnvironments, errorModel.Code, errorModel.Message) + } else if allEnvironmentsModel != nil && allEnvironmentsModel.Environments != nil { + err = commandutils.PrintByFormat(cmd, toEnvironmentViews(allEnvironmentsModel.Environments)) + if err != nil { + return err + } + } + return nil + } +} + +func toEnvironmentViews(models []wrappers.DastEnvironmentResponseModel) []environmentView { + result := make([]environmentView, len(models)) + for i := 0; i < len(models); i++ { + result[i] = toEnvironmentView(&models[i]) + } + return result +} + +func toEnvironmentView(model *wrappers.DastEnvironmentResponseModel) environmentView { + return environmentView{ + EnvironmentID: model.EnvironmentID, + Domain: model.Domain, + URL: model.URL, + ScanType: model.ScanType, + Created: model.Created, + RiskRating: model.RiskRating, + LastScanTime: model.LastScanTime, + LastStatus: model.LastStatus, + } +} + +type environmentView struct { + EnvironmentID string `format:"name:Environment ID"` + Domain string + URL string + ScanType string `format:"name:Scan Type"` + Created string + RiskRating string `format:"name:Risk Rating"` + LastScanTime string `format:"name:Last Scan Time"` + LastStatus string `format:"name:Last Status"` +} diff --git a/internal/commands/dast/dast-environments_test.go b/internal/commands/dast/dast-environments_test.go new file mode 100644 index 000000000..715ad6997 --- /dev/null +++ b/internal/commands/dast/dast-environments_test.go @@ -0,0 +1,51 @@ +//go:build !integration + +package dast + +import ( + "testing" + + "github.com/checkmarx/ast-cli/internal/wrappers/mock" + "gotest.tools/assert" +) + +func createTestCommand(args ...string) error { + cmd := NewDastEnvironmentsCommand(&mock.DastEnvironmentsMockWrapper{}) + cmd.SetArgs(args) + return cmd.Execute() +} + +func TestDastEnvironmentsHelp(t *testing.T) { + err := createTestCommand("--help") + assert.NilError(t, err) +} + +func TestDastEnvironmentsNoSub(t *testing.T) { + err := createTestCommand() + assert.NilError(t, err) +} + +func TestDastEnvironmentsList(t *testing.T) { + err := createTestCommand("list") + assert.NilError(t, err) +} + +func TestDastEnvironmentsListWithFormat(t *testing.T) { + err := createTestCommand("list", "--format", "json") + assert.NilError(t, err) +} + +func TestDastEnvironmentsListWithFilters(t *testing.T) { + err := createTestCommand("list", "--filter", "from=1,to=10") + assert.NilError(t, err) +} + +func TestDastEnvironmentsListWithSearch(t *testing.T) { + err := createTestCommand("list", "--filter", "search=test") + assert.NilError(t, err) +} + +func TestDastEnvironmentsListWithSort(t *testing.T) { + err := createTestCommand("list", "--filter", "sort=domain:asc") + assert.NilError(t, err) +} diff --git a/internal/commands/predicates.go b/internal/commands/predicates.go index c292f8f3e..306cc605e 100644 --- a/internal/commands/predicates.go +++ b/internal/commands/predicates.go @@ -9,6 +9,7 @@ import ( "encoding/json" "github.com/MakeNowJust/heredoc" + "github.com/checkmarx/ast-cli/internal/commands/commandutils" "github.com/checkmarx/ast-cli/internal/commands/util/printer" "github.com/checkmarx/ast-cli/internal/logger" "github.com/checkmarx/ast-cli/internal/params" @@ -35,7 +36,7 @@ func NewResultsPredicatesCommand(resultsPredicatesWrapper wrappers.ResultsPredic triageUpdateCmd := triageUpdateSubCommand(resultsPredicatesWrapper, featureFlagsWrapper, customStatesWrapper) triageGetStatesCmd := triageGetStatesSubCommand(customStatesWrapper, featureFlagsWrapper) - addFormatFlagToMultipleCommands( + commandutils.AddFormatFlagToMultipleCommands( []*cobra.Command{triageShowCmd}, printer.FormatList, printer.FormatTable, printer.FormatJSON, ) @@ -184,7 +185,7 @@ func runTriageShow(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper) f if err != nil { return errors.Wrapf(err, "%s", "Failed showing the predicate") } - err = printByFormat(cmd, toScaPredicateResultView(scaPredicates)) + err = commandutils.PrintByFormat(cmd, toScaPredicateResultView(scaPredicates)) if err != nil { return err } @@ -203,7 +204,7 @@ func runTriageShow(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper) f errorModel.Message, ) } else if predicatesCollection != nil { - err = printByFormat(cmd, toPredicatesView(*predicatesCollection)) + err = commandutils.PrintByFormat(cmd, toPredicatesView(*predicatesCollection)) if err != nil { return err } diff --git a/internal/commands/project.go b/internal/commands/project.go index 4f6cbb957..1ae334f95 100644 --- a/internal/commands/project.go +++ b/internal/commands/project.go @@ -7,6 +7,7 @@ import ( "time" "github.com/MakeNowJust/heredoc" + "github.com/checkmarx/ast-cli/internal/commands/commandutils" "github.com/checkmarx/ast-cli/internal/commands/util" "github.com/checkmarx/ast-cli/internal/commands/util/printer" errorConstants "github.com/checkmarx/ast-cli/internal/constants/errors" @@ -196,7 +197,7 @@ func NewProjectCommand(applicationsWrapper wrappers.ApplicationsWrapper, project RunE: runGetProjectsTagsCommand(projectsWrapper), } - addFormatFlagToMultipleCommands( + commandutils.AddFormatFlagToMultipleCommands( []*cobra.Command{showProjectCmd, listProjectsCmd, createProjCmd}, printer.FormatTable, printer.FormatJSON, @@ -281,7 +282,7 @@ func runCreateProjectCommand( if errorModel != nil { return errors.Errorf(services.ErrorCodeFormat, services.FailedCreatingProj, errorModel.Code, errorModel.Message) } else if projResponseModel != nil { - err = printByFormat(cmd, toProjectView(*projResponseModel)) + err = commandutils.PrintByFormat(cmd, toProjectView(*projResponseModel)) if err != nil { return errors.Wrapf(err, "%s", services.FailedCreatingProj) } @@ -414,7 +415,7 @@ func runListProjectsCommand(projectsWrapper wrappers.ProjectsWrapper) func(cmd * if errorModel != nil { return errors.Errorf(services.ErrorCodeFormat, failedGettingAll, errorModel.Code, errorModel.Message) } else if allProjectsModel != nil && allProjectsModel.Projects != nil { - err = printByFormat(cmd, toProjectViews(allProjectsModel.Projects)) + err = commandutils.PrintByFormat(cmd, toProjectViews(allProjectsModel.Projects)) if err != nil { return err } @@ -484,7 +485,7 @@ func runGetProjectByIDCommand(projectsWrapper wrappers.ProjectsWrapper) func(cmd resp := GetProjectByName(projectResponseModel.Name, projectsWrapper) projectResponseModel.Groups = resp.Groups - err = printByFormat(cmd, toProjectView(*projectResponseModel)) + err = commandutils.PrintByFormat(cmd, toProjectView(*projectResponseModel)) if err != nil { return err } diff --git a/internal/commands/result.go b/internal/commands/result.go index f37c5e46b..efce5b148 100644 --- a/internal/commands/result.go +++ b/internal/commands/result.go @@ -16,6 +16,7 @@ import ( "time" "github.com/MakeNowJust/heredoc" + "github.com/checkmarx/ast-cli/internal/commands/commandutils" "github.com/checkmarx/ast-cli/internal/commands/util" "github.com/checkmarx/ast-cli/internal/commands/util/printer" errorConstants "github.com/checkmarx/ast-cli/internal/constants/errors" @@ -250,7 +251,7 @@ func riskManagementSubCommand(riskManagement wrappers.RiskManagementWrapper, fea riskManagementCmd.PersistentFlags().String(commonParams.ScanIDFlag, "", "Scan ID") riskManagementCmd.PersistentFlags().Int(commonParams.LimitFlag, -1, "Limit") - addFormatFlag(riskManagementCmd, printer.FormatJSON, printer.FormatTable, printer.FormatList) + commandutils.AddFormatFlag(riskManagementCmd, printer.FormatJSON, printer.FormatTable, printer.FormatList) return riskManagementCmd } @@ -334,7 +335,7 @@ func resultBflSubCommand(bflWrapper wrappers.BflWrapper) *cobra.Command { } addScanIDFlag(resultBflCmd, "ID to report on") addQueryIDFlag(resultBflCmd, "Query Id from the result") - addFormatFlag(resultBflCmd, printer.FormatList, printer.FormatJSON) + commandutils.AddFormatFlag(resultBflCmd, printer.FormatList, printer.FormatJSON) markFlagAsRequired(resultBflCmd, commonParams.ScanIDFlag) markFlagAsRequired(resultBflCmd, commonParams.QueryIDFlag) @@ -380,7 +381,7 @@ func runRiskManagementCommand(riskManagement wrappers.RiskManagementWrapper, fea return err } results.Results = utils.LimitSlice(results.Results, limit) - err = printByFormat(cmd, results) + err = commandutils.PrintByFormat(cmd, results) return err } } @@ -513,7 +514,7 @@ func runGetBestFixLocationCommand(bflWrapper wrappers.BflWrapper) func(cmd *cobr if errorModel != nil { return errors.Errorf("%s: CODE: %d, %s", failedGettingBfl, errorModel.Code, errorModel.Message) } else if bflResponseModel != nil { - err = printByFormat(cmd, toBflView(*bflResponseModel)) + err = commandutils.PrintByFormat(cmd, toBflView(*bflResponseModel)) if err != nil { return err } @@ -574,7 +575,7 @@ func resultCodeBashing(codeBashingWrapper wrappers.CodeBashingWrapper) *cobra.Co if err != nil { log.Fatal(err) } - addFormatFlag(resultCmd, printer.FormatJSON, printer.FormatTable, printer.FormatList) + commandutils.AddFormatFlag(resultCmd, printer.FormatJSON, printer.FormatTable, printer.FormatList) return resultCmd } @@ -1132,7 +1133,7 @@ func runGetCodeBashingCommand( if webError != nil { return errors.New(webError.Message) } - err = printByFormat(cmd, *CodeBashingModel) + err = commandutils.PrintByFormat(cmd, *CodeBashingModel) if err != nil { return errors.Wrapf(err, "%s", failedListingCodeBashing) } diff --git a/internal/commands/root.go b/internal/commands/root.go index 453070073..2b1560b9d 100644 --- a/internal/commands/root.go +++ b/internal/commands/root.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/MakeNowJust/heredoc" + "github.com/checkmarx/ast-cli/internal/commands/dast" "github.com/checkmarx/ast-cli/internal/commands/util" "github.com/checkmarx/ast-cli/internal/commands/util/printer" "github.com/checkmarx/ast-cli/internal/logger" @@ -61,6 +62,7 @@ func NewAstCLI( containerResolverWrapper wrappers.ContainerResolverWrapper, realTimeWrapper wrappers.RealtimeScannerWrapper, telemetryWrapper wrappers.TelemetryWrapper, + dastEnvironmentsWrapper wrappers.DastEnvironmentsWrapper, ) *cobra.Command { // Create the root rootCmd := &cobra.Command{ @@ -187,6 +189,7 @@ func NewAstCLI( realTimeWrapper, ) projectCmd := NewProjectCommand(applicationsWrapper, projectsWrapper, groupsWrapper, accessManagementWrapper, featureFlagsWrapper) + dastEnvironmentsCmd := dast.NewDastEnvironmentsCommand(dastEnvironmentsWrapper) resultsCmd := NewResultsCommand( resultsWrapper, @@ -237,6 +240,7 @@ func NewAstCLI( rootCmd.AddCommand( scanCmd, projectCmd, + dastEnvironmentsCmd, resultsCmd, triageCmd, versionCmd, @@ -304,19 +308,6 @@ func validateExtraFilters(filterKeyVal []string) []string { return filterKeyVal } -func addFormatFlagToMultipleCommands(commands []*cobra.Command, defaultFormat string, otherAvailableFormats ...string) { - for _, c := range commands { - addFormatFlag(c, defaultFormat, otherAvailableFormats...) - } -} - -func addFormatFlag(cmd *cobra.Command, defaultFormat string, otherAvailableFormats ...string) { - cmd.PersistentFlags().String( - params.FormatFlag, defaultFormat, - fmt.Sprintf(params.FormatFlagUsageFormat, append(otherAvailableFormats, defaultFormat)), - ) -} - func addScanInfoFormatFlag(cmd *cobra.Command, defaultFormat string, otherAvailableFormats ...string) { cmd.PersistentFlags().String( params.ScanInfoFormatFlag, defaultFormat, @@ -350,10 +341,6 @@ func addQueryIDFlag(cmd *cobra.Command, helpMsg string) { cmd.PersistentFlags().String(params.QueryIDFlag, "", helpMsg) } -func printByFormat(cmd *cobra.Command, view interface{}) error { - f, _ := cmd.Flags().GetString(params.FormatFlag) - return printer.Print(cmd.OutOrStdout(), view, f) -} func printByScanInfoFormat(cmd *cobra.Command, view interface{}) error { f, _ := cmd.Flags().GetString(params.ScanInfoFormatFlag) return printer.Print(cmd.OutOrStdout(), view, f) diff --git a/internal/commands/root_test.go b/internal/commands/root_test.go index 654594c5b..924807aa7 100644 --- a/internal/commands/root_test.go +++ b/internal/commands/root_test.go @@ -73,6 +73,7 @@ func createASTTestCommand() *cobra.Command { customStatesMockWrapper := &mock.CustomStatesMockWrapper{} realTimeWrapper := &mock.RealtimeScannerMockWrapper{} telemetryWrapper := &mock.TelemetryMockWrapper{} + dastEnvironmentsWrapper := &mock.DastEnvironmentsMockWrapper{} return NewAstCLI( applicationWrapper, scansMockWrapper, @@ -111,6 +112,7 @@ func createASTTestCommand() *cobra.Command { containerResolverMockWrapper, realTimeWrapper, telemetryWrapper, + dastEnvironmentsWrapper, ) } diff --git a/internal/commands/scan.go b/internal/commands/scan.go index 2dd7c3ca4..2d88000aa 100644 --- a/internal/commands/scan.go +++ b/internal/commands/scan.go @@ -23,6 +23,7 @@ import ( "unicode" "github.com/checkmarx/ast-cli/internal/commands/asca" + "github.com/checkmarx/ast-cli/internal/commands/commandutils" "github.com/checkmarx/ast-cli/internal/commands/scarealtime" "github.com/checkmarx/ast-cli/internal/commands/util" "github.com/checkmarx/ast-cli/internal/commands/util/printer" @@ -245,7 +246,7 @@ func NewScanCommand( iacRealtimeCmd := scanIacRealtimeSubCommand(jwtWrapper, featureFlagsWrapper) - addFormatFlagToMultipleCommands( + commandutils.AddFormatFlagToMultipleCommands( []*cobra.Command{listScansCmd, showScanCmd, workflowScanCmd}, printer.FormatTable, printer.FormatList, printer.FormatJSON, ) @@ -3047,7 +3048,7 @@ func runListScansCommand(scansWrapper wrappers.ScansWrapper, sastMetadataWrapper if err != nil { return err } - err = printByFormat(cmd, views) + err = commandutils.PrintByFormat(cmd, views) if err != nil { return err } @@ -3073,7 +3074,7 @@ func runGetScanByIDCommand(scansWrapper wrappers.ScansWrapper) func(cmd *cobra.C if errorModel != nil { return errors.Errorf("%s: CODE: %d, %s", failedGetting, errorModel.Code, errorModel.Message) } else if scanResponseModel != nil { - err = printByFormat(cmd, toScanView(scanResponseModel)) + err = commandutils.PrintByFormat(cmd, toScanView(scanResponseModel)) if err != nil { return err } @@ -3099,7 +3100,7 @@ func runScanWorkflowByIDCommand(scansWrapper wrappers.ScansWrapper) func(cmd *co if errorModel != nil { return errors.Errorf("%s: CODE: %d, %s", failedGetting, errorModel.Code, errorModel.Message) } else if taskResponseModel != nil { - err = printByFormat(cmd, taskResponseModel) + err = commandutils.PrintByFormat(cmd, taskResponseModel) if err != nil { return err } diff --git a/internal/params/binds.go b/internal/params/binds.go index d65f595e7..fe8068701 100644 --- a/internal/params/binds.go +++ b/internal/params/binds.go @@ -82,6 +82,7 @@ var EnvVarsBinds = []struct { {RiskManagementPathKey, RiskManagementPathEnv, "api/risk-management/projects/%s/results?scanID=%s"}, {ConfigFilePathKey, ConfigFilePathEnv, ""}, {RealtimeScannerPathKey, RealtimeScannerPathEnv, "api/realtime-scanner"}, + {DastEnvironmentsPathKey, DastEnvironmentsPathEnv, "api/dast/scans/environments"}, {StartMultiPartUploadPathKey, StartMultiPartUploadPathEnv, "api/uploads/start-multipart-upload"}, {MultipartPresignedPathKey, MultipartPresignedPathEnv, "api/uploads/multipart-presigned"}, {CompleteMultiPartUploadPathKey, CompleteMultipartUploadPathEnv, "api/uploads/complete-multipart-upload"}, diff --git a/internal/params/envs.go b/internal/params/envs.go index 614792cbf..093f93d9f 100644 --- a/internal/params/envs.go +++ b/internal/params/envs.go @@ -84,6 +84,7 @@ const ( RiskManagementPathEnv = "CX_RISK_MANAGEMENT_PATH" ConfigFilePathEnv = "CX_CONFIG_FILE_PATH" RealtimeScannerPathEnv = "CX_REALTIME_SCANNER_PATH" + DastEnvironmentsPathEnv = "CX_DAST_ENVIRONMENTS_PATH" UniqueIDEnv = "CX_UNIQUE_ID" StartMultiPartUploadPathEnv = "CX_START_MULTIPART_UPLOAD_PATH" MultipartPresignedPathEnv = "CX_MULTIPART_PRESIGNED_URL_PATH" diff --git a/internal/params/flags.go b/internal/params/flags.go index 13f43f4bd..82b5f8449 100644 --- a/internal/params/flags.go +++ b/internal/params/flags.go @@ -275,6 +275,8 @@ const ( ProjectIDQueryParam = "project-id" FromDateQueryParam = "from-date" ToDateQueryParam = "to-date" + FromQueryParam = "from" + ToQueryParam = "to" SeverityQueryParam = "severity" StateQueryParam = "state" GroupQueryParam = "group" @@ -282,6 +284,7 @@ const ( NodeIDsQueryParam = "node-ids" IncludeNodesQueryParam = "include-nodes" SortQueryParam = "sort" + SearchQueryParam = "search" Profile = "default" BaseURI = "" BaseIAMURI = "" diff --git a/internal/params/keys.go b/internal/params/keys.go index 09aeea2c8..1c4e7ccec 100644 --- a/internal/params/keys.go +++ b/internal/params/keys.go @@ -83,6 +83,7 @@ var ( RiskManagementPathKey = strings.ToLower(RiskManagementPathEnv) ConfigFilePathKey = strings.ToLower(ConfigFilePathEnv) RealtimeScannerPathKey = strings.ToLower(RealtimeScannerPathEnv) + DastEnvironmentsPathKey = strings.ToLower(DastEnvironmentsPathEnv) UniqueIDConfigKey = strings.ToLower(UniqueIDEnv) StartMultiPartUploadPathKey = strings.ToLower(StartMultiPartUploadPathEnv) MultipartPresignedPathKey = strings.ToLower(MultipartPresignedPathEnv) diff --git a/internal/wrappers/dast-environments-http.go b/internal/wrappers/dast-environments-http.go new file mode 100644 index 000000000..20b717ddb --- /dev/null +++ b/internal/wrappers/dast-environments-http.go @@ -0,0 +1,61 @@ +package wrappers + +import ( + "encoding/json" + "net/http" + + commonParams "github.com/checkmarx/ast-cli/internal/params" + "github.com/pkg/errors" + "github.com/spf13/viper" +) + +const ( + failedToParseDastEnvironments = "Failed to parse DAST environments" +) + +// DastEnvironmentsHTTPWrapper implements the DastEnvironmentsWrapper interface +type DastEnvironmentsHTTPWrapper struct { + path string +} + +// NewHTTPDastEnvironmentsWrapper creates a new HTTP DAST environments wrapper +func NewHTTPDastEnvironmentsWrapper(path string) DastEnvironmentsWrapper { + return &DastEnvironmentsHTTPWrapper{ + path: path, + } +} + +// Get retrieves DAST environments with optional query parameters +func (e *DastEnvironmentsHTTPWrapper) Get(params map[string]string) (*DastEnvironmentsCollectionResponseModel, *ErrorModel, error) { + clientTimeout := viper.GetUint(commonParams.ClientTimeoutKey) + + resp, err := SendHTTPRequestWithQueryParams(http.MethodGet, e.path, params, nil, clientTimeout) + if err != nil { + return nil, nil, err + } + + defer func() { + _ = resp.Body.Close() + }() + + decoder := json.NewDecoder(resp.Body) + + switch resp.StatusCode { + case http.StatusBadRequest, http.StatusInternalServerError: + errorModel := ErrorModel{} + err = decoder.Decode(&errorModel) + if err != nil { + return nil, nil, errors.Wrapf(err, failedToParseDastEnvironments) + } + return nil, &errorModel, nil + case http.StatusOK: + model := DastEnvironmentsCollectionResponseModel{} + err = decoder.Decode(&model) + if err != nil { + return nil, nil, errors.Wrapf(err, failedToParseDastEnvironments) + } + return &model, nil, nil + default: + return nil, nil, errors.Errorf("response status code %d", resp.StatusCode) + } +} diff --git a/internal/wrappers/dast-environments.go b/internal/wrappers/dast-environments.go new file mode 100644 index 000000000..3cdba6b73 --- /dev/null +++ b/internal/wrappers/dast-environments.go @@ -0,0 +1,62 @@ +package wrappers + +import "encoding/json" + +// DastEnvironmentsWrapper defines the interface for DAST environments operations +type DastEnvironmentsWrapper interface { + // Get retrieves environments with optional query parameters (from, to, search, sort) + Get(params map[string]string) (*DastEnvironmentsCollectionResponseModel, *ErrorModel, error) +} + +// DastEnvironmentsCollectionResponseModel represents the response from the DAST environments API +type DastEnvironmentsCollectionResponseModel struct { + Environments []DastEnvironmentResponseModel `json:"environments"` + MisconfiguredCount int `json:"misconfiguredCount"` + TotalItems int `json:"totalItems"` + ZrokHost string `json:"zrokHost"` +} + +// DastEnvironmentResponseModel represents a single DAST environment +type DastEnvironmentResponseModel struct { + EnvironmentID string `json:"environmentId"` + TunnelID string `json:"tunnelId"` + Created string `json:"created"` + Domain string `json:"domain"` + URL string `json:"url"` + ScanType string `json:"scanType"` + ProjectIds []string `json:"projectIds"` + Tags []string `json:"tags"` + Groups []string `json:"groups"` + Applications []DastEnvironmentApp `json:"applications"` + RiskLevel RiskLevel `json:"riskLevel"` + RiskRating string `json:"riskRating"` + AlertRiskLevel RiskLevel `json:"alertRiskLevel"` + LastScanID string `json:"lastScanID"` + LastScanTime string `json:"lastScanTime"` + LastStatus string `json:"lastStatus"` + AuthSuccess bool `json:"authSuccess"` + IsPublic bool `json:"isPublic"` + AuthMethod string `json:"authMethod"` + LastAuthUUID string `json:"lastAuthUUID"` + LastAuthSuccess bool `json:"lastAuthSuccess"` + Settings json.RawMessage `json:"settings"` // Keep as raw JSON + HasReport bool `json:"hasReport"` + HasAuth bool `json:"hasAuth"` + TunnelState string `json:"tunnelState"` + ScanConfig json.RawMessage `json:"scanConfig"` // Keep as raw JSON +} + +// DastEnvironmentApp represents an application associated with a DAST environment +type DastEnvironmentApp struct { + ApplicationID string `json:"applicationId"` + IsPrimary bool `json:"isPrimary"` +} + +// RiskLevel represents risk counts by severity +type RiskLevel struct { + CriticalCount int `json:"criticalCount"` + HighCount int `json:"highCount"` + MediumCount int `json:"mediumCount"` + LowCount int `json:"lowCount"` + InfoCount int `json:"infoCount"` +} diff --git a/internal/wrappers/mock/dast-environments-mock.go b/internal/wrappers/mock/dast-environments-mock.go new file mode 100644 index 000000000..9af4cae1d --- /dev/null +++ b/internal/wrappers/mock/dast-environments-mock.go @@ -0,0 +1,25 @@ +package mock + +import "github.com/checkmarx/ast-cli/internal/wrappers" + +// DastEnvironmentsMockWrapper is a mock implementation of DastEnvironmentsWrapper +type DastEnvironmentsMockWrapper struct{} + +// Get mocks the Get method +func (e *DastEnvironmentsMockWrapper) Get(params map[string]string) (*wrappers.DastEnvironmentsCollectionResponseModel, *wrappers.ErrorModel, error) { + return &wrappers.DastEnvironmentsCollectionResponseModel{ + Environments: []wrappers.DastEnvironmentResponseModel{ + { + EnvironmentID: "test-env-id", + Domain: "test-domain", + URL: "https://test.example.com", + ScanType: "DAST", + Created: "2024-01-01T00:00:00Z", + RiskRating: "Low risk", + LastScanTime: "2024-01-02T00:00:00Z", + LastStatus: "Finished@Scan finished successfully", + }, + }, + TotalItems: 1, + }, nil, nil +} diff --git a/test/integration/util_command.go b/test/integration/util_command.go index 92f2aa904..57de7958b 100644 --- a/test/integration/util_command.go +++ b/test/integration/util_command.go @@ -89,6 +89,7 @@ func createASTIntegrationTestCommand(t *testing.T) *cobra.Command { accessManagementPath := viper.GetString(params.AccessManagementPathKey) byorPath := viper.GetString(params.ByorPathKey) realtimeScannerPath := viper.GetString(params.RealtimeScannerPathKey) + dastEnvironmentsPath := viper.GetString(params.DastEnvironmentsPathKey) scansWrapper := wrappers.NewHTTPScansWrapper(scans) applicationsWrapper := wrappers.NewApplicationsHTTPWrapper(applications) @@ -127,6 +128,7 @@ func createASTIntegrationTestCommand(t *testing.T) *cobra.Command { containerResolverWrapper := wrappers.NewContainerResolverWrapper() realtimeScannerWrapper := wrappers.NewRealtimeScannerHTTPWrapper(realtimeScannerPath, jwtWrapper, featureFlagsWrapper) telemetryWrapper := wrappers.NewHTTPTelemetryAIWrapper(realtimeScannerPath) + dastEnvironmentsWrapper := wrappers.NewHTTPDastEnvironmentsWrapper(dastEnvironmentsPath) astCli := commands.NewAstCLI( applicationsWrapper, @@ -166,6 +168,7 @@ func createASTIntegrationTestCommand(t *testing.T) *cobra.Command { containerResolverWrapper, realtimeScannerWrapper, telemetryWrapper, + dastEnvironmentsWrapper, ) return astCli }