-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathpredicates.go
More file actions
473 lines (423 loc) · 17.8 KB
/
predicates.go
File metadata and controls
473 lines (423 loc) · 17.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
package commands
import (
"fmt"
"sort"
"strings"
"time"
"encoding/json"
"github.com/MakeNowJust/heredoc"
"github.com/checkmarx/ast-cli/internal/commands/util/printer"
"github.com/checkmarx/ast-cli/internal/logger"
"github.com/checkmarx/ast-cli/internal/params"
"github.com/checkmarx/ast-cli/internal/wrappers"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var systemStates = []wrappers.CustomState{
{ID: -1, Name: params.ToVerify, Type: ""},
{ID: -1, Name: params.NotExploitable, Type: ""},
{ID: -1, Name: params.ProposedNotExploitable, Type: ""},
{ID: -1, Name: params.CONFIRMED, Type: ""},
{ID: -1, Name: params.URGENT, Type: ""},
}
func NewResultsPredicatesCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper, customStatesWrapper wrappers.CustomStatesWrapper) *cobra.Command {
triageCmd := &cobra.Command{
Use: "triage",
Short: "Manage results",
Long: "The 'triage' command enables the ability to manage results in Checkmarx One",
}
triageShowCmd := triageShowSubCommand(resultsPredicatesWrapper)
triageUpdateCmd := triageUpdateSubCommand(resultsPredicatesWrapper, featureFlagsWrapper, customStatesWrapper)
triageGetStatesCmd := triageGetStatesSubCommand(customStatesWrapper, featureFlagsWrapper)
addFormatFlagToMultipleCommands(
[]*cobra.Command{triageShowCmd},
printer.FormatList, printer.FormatTable, printer.FormatJSON,
)
triageCmd.AddCommand(triageShowCmd, triageUpdateCmd, triageGetStatesCmd)
return triageCmd
}
func triageGetStatesSubCommand(customStatesWrapper wrappers.CustomStatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper) *cobra.Command {
triageGetStatesCmd := &cobra.Command{
Use: "get-states",
Short: "Show the custom states that have been configured in your tenant",
Long: "The get-states command shows information about each of the custom states that have been configured in your tenant account",
Example: heredoc.Doc(
`
$ cx triage get-states
$ cx triage get-states --all
`,
),
RunE: runTriageGetStates(customStatesWrapper, featureFlagsWrapper),
}
triageGetStatesCmd.PersistentFlags().Bool(params.AllStatesFlag, false, "Show all custom states, including the ones that have been deleted")
triageGetStatesCmd.PersistentFlags().String(params.ScanTypeFlag, "sast", "Scan Type")
return triageGetStatesCmd
}
func runTriageGetStates(customStatesWrapper wrappers.CustomStatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, _ []string) error {
scanType, _ := cmd.Flags().GetString(params.ScanTypeFlag)
scanType = strings.TrimSpace(strings.ToLower(scanType))
flagResponse, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, wrappers.CustomStatesFeatureFlag)
if !flagResponse.Status {
return printer.Print(cmd.OutOrStdout(), systemStates, printer.FormatJSON)
}
switch scanType {
case params.KicsType:
kicsCustomStates, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, "KICS_CUSTOM_STATES_ENABLED")
if !kicsCustomStates.Status {
return printer.Print(cmd.OutOrStdout(), systemStates, printer.FormatJSON)
}
case params.ScaType:
scaCustomStates, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, "SCA_CUSTOM_STATES")
if !scaCustomStates.Status {
return printer.Print(cmd.OutOrStdout(), systemStates, printer.FormatJSON)
}
default:
sastCustomStates, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, "SAST_CUSTOM_STATES_ENABLED")
if !sastCustomStates.Status {
return printer.Print(cmd.OutOrStdout(), systemStates, printer.FormatJSON)
}
}
includeDeleted, _ := cmd.Flags().GetBool(params.AllStatesFlag)
states, err := customStatesWrapper.GetAllCustomStates(includeDeleted)
if err != nil {
return errors.Wrap(err, "Failed to fetch custom states")
}
states = append(states, systemStates...)
err = printer.Print(cmd.OutOrStdout(), states, printer.FormatJSON)
return err
}
}
func triageShowSubCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper) *cobra.Command {
triageShowCmd := &cobra.Command{
Use: "show",
Short: "Get the predicates history for the given issue",
Long: "The show command provides a list of all the predicates in the issue",
Example: heredoc.Doc(
`
$ cx triage show --similarity-id <SimilarityID> --project-id <ProjectID> --scan-type <SAST||IAC-SECURITY>
`,
),
RunE: runTriageShow(resultsPredicatesWrapper),
}
triageShowCmd.PersistentFlags().String(params.SimilarityIDFlag, "", "Similarity ID")
triageShowCmd.PersistentFlags().String(params.ProjectIDFlag, "", "Project ID")
triageShowCmd.PersistentFlags().String(params.ScanTypeFlag, "", "Scan Type")
triageShowCmd.PersistentFlags().StringSlice(params.VulnerabilitiesFlag, []string{}, "SCA Vulnerabilities details")
markFlagAsRequired(triageShowCmd, params.ProjectIDFlag)
markFlagAsRequired(triageShowCmd, params.ScanTypeFlag)
return triageShowCmd
}
func triageUpdateSubCommand(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper, customStatesWrapper wrappers.CustomStatesWrapper) *cobra.Command {
triageUpdateCmd := &cobra.Command{
Use: "update",
Short: "Update the state, severity or comment for the given issue",
Long: "The update command enables the ability to triage the results in Checkmarx One",
Example: heredoc.Doc(
`
$ cx triage update
--similarity-id <SimilarityID>
--project-id <ProjectID>
--state <TO_VERIFY|NOT_EXPLOITABLE|PROPOSED_NOT_EXPLOITABLE|CONFIRMED|URGENT|custom state>
--state-id <custom state ID>
--severity <CRITICAL|HIGH|MEDIUM|LOW|INFO>
--comment <Comment(Optional)>
--scan-type <SAST|IAC-SECURITY>
`,
),
RunE: runTriageUpdate(resultsPredicatesWrapper, featureFlagsWrapper, customStatesWrapper),
}
triageUpdateCmd.PersistentFlags().String(params.SimilarityIDFlag, "", "Similarity ID")
triageUpdateCmd.PersistentFlags().String(params.SeverityFlag, "", "Severity")
triageUpdateCmd.PersistentFlags().String(params.ProjectIDFlag, "", "Project ID")
triageUpdateCmd.PersistentFlags().String(params.StateFlag, "", "Specify the state that you would like to apply. Can be a pre-configured state (e.g., not_exploitable) or a custom state created in your account")
triageUpdateCmd.PersistentFlags().Int(params.CustomStateIDFlag, -1, "Specify the ID of the states that you would like to apply to this result")
triageUpdateCmd.PersistentFlags().String(params.CommentFlag, "", "Optional comment")
triageUpdateCmd.PersistentFlags().String(params.ScanTypeFlag, "", "Scan Type")
triageUpdateCmd.PersistentFlags().StringSlice(params.VulnerabilitiesFlag, []string{}, "SCA Vulnerabilities details")
markFlagAsRequired(triageUpdateCmd, params.ProjectIDFlag)
markFlagAsRequired(triageUpdateCmd, params.ScanTypeFlag)
return triageUpdateCmd
}
func runTriageShow(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, _ []string) error {
var predicatesCollection *wrappers.PredicatesCollectionResponseModel
var errorModel *wrappers.WebError
var err error
similarityID, _ := cmd.Flags().GetString(params.SimilarityIDFlag)
scanType, _ := cmd.Flags().GetString(params.ScanTypeFlag)
projectID, _ := cmd.Flags().GetString(params.ProjectIDFlag)
vulnerabilityDetails, _ := cmd.Flags().GetStringSlice(params.VulnerabilitiesFlag)
projectIDs := strings.Split(projectID, ",")
if len(projectIDs) > 1 {
return errors.Errorf("%s", "Multiple project-ids are not allowed.")
}
if strings.EqualFold(strings.ToLower(strings.TrimSpace(scanType)), params.ScaType) {
if len(vulnerabilityDetails) == 0 {
return errors.Errorf("%s", "Failed showing the predicate. Vulnerabilities are required for SCA triage")
}
scaPredicates, err := resultsPredicatesWrapper.GetScaPredicates(vulnerabilityDetails, projectID)
if err != nil {
return errors.Wrapf(err, "%s", "Failed showing the predicate")
}
err = printByFormat(cmd, toScaPredicateResultView(scaPredicates))
if err != nil {
return err
}
return nil
} else {
predicatesCollection, errorModel, err = resultsPredicatesWrapper.GetAllPredicatesForSimilarityID(similarityID, projectID, scanType)
if err != nil {
return errors.Wrapf(err, "%s", "Failed showing the predicate")
}
// Checking the response
if errorModel != nil {
return errors.Errorf(
"%s: CODE: %d, %s",
"Failed showing the predicate.",
errorModel.Code,
errorModel.Message,
)
} else if predicatesCollection != nil {
err = printByFormat(cmd, toPredicatesView(*predicatesCollection))
if err != nil {
return err
}
}
return nil
}
}
}
func runTriageUpdate(resultsPredicatesWrapper wrappers.ResultsPredicatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper, customStatesWrapper wrappers.CustomStatesWrapper) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, _ []string) error {
similarityID, _ := cmd.Flags().GetString(params.SimilarityIDFlag)
projectID, _ := cmd.Flags().GetString(params.ProjectIDFlag)
severity, _ := cmd.Flags().GetString(params.SeverityFlag)
state, _ := cmd.Flags().GetString(params.StateFlag)
customStateID, _ := cmd.Flags().GetInt(params.CustomStateIDFlag)
comment, _ := cmd.Flags().GetString(params.CommentFlag)
scanType, _ := cmd.Flags().GetString(params.ScanTypeFlag)
// check if the current tenant has critical severity available
flagResponse, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, wrappers.CVSSV3Enabled)
vulnerabilityDetails, _ := cmd.Flags().GetStringSlice(params.VulnerabilitiesFlag)
criticalEnabled := flagResponse.Status
if !criticalEnabled && strings.EqualFold(severity, "critical") {
return errors.Errorf("%s", "Critical severity is not available for your tenant.This severity status will be enabled shortly")
}
var err error
state, customStateID, err = determineSystemOrCustomState(customStatesWrapper, featureFlagsWrapper, state, customStateID)
if err != nil {
return errors.Wrapf(err, "%s", "Failed updating the predicate")
}
predicate, err := preparePredicateRequest(vulnerabilityDetails, similarityID, projectID, severity, state, customStateID, comment, scanType)
if err != nil {
return errors.Wrapf(err, "%s", "Failed updating the predicate")
}
_, err = resultsPredicatesWrapper.PredicateSeverityAndState(predicate, scanType)
if err != nil {
return errors.Wrapf(err, "%s", "Failed updating the predicate")
}
return nil
}
}
func preparePredicateRequest(vulnerabilityDetails []string, similarityID, projectID, severity, state string, customStateID int, comment, scanType string) (interface{}, error) {
scanType = strings.ToLower(scanType)
scanType = strings.TrimSpace(scanType)
if strings.EqualFold(scanType, Sca) {
state = transformState(state)
payload, err := prepareScaTriagePayload(vulnerabilityDetails, comment, state, projectID)
if err != nil {
return nil, err
}
return payload, nil
} else {
payload := &wrappers.PredicateRequest{
SimilarityID: similarityID,
ProjectID: projectID,
Severity: severity,
Comment: comment,
}
if state != "" {
payload.State = &state
} else {
payload.CustomStateID = &customStateID
}
return payload, nil
}
}
func transformState(state string) string {
state = strings.ToLower(strings.TrimSpace(state))
switch state {
case strings.ToLower(params.ToVerify):
return wrappers.ToVerify
case strings.ToLower(params.URGENT):
return wrappers.Urgent
case strings.ToLower(params.NotExploitable):
return wrappers.NotExploitable
case strings.ToLower(params.ProposedNotExploitable):
return wrappers.ProposedNotExploitable
case strings.ToLower(params.CONFIRMED):
return wrappers.Confirmed
}
return ""
}
func prepareScaTriagePayload(vulnerabilityDetails []string, comment, state, projectID string) (interface{}, error) {
if len(vulnerabilityDetails) == 0 {
return nil, errors.Errorf("Vulnerabilities details are required.")
}
scaTriageInfo := make(map[string]interface{})
for _, vulnerability := range vulnerabilityDetails {
vulnerabilityKeyVal := strings.Split(vulnerability, "=")
err := validateVulnerabilityDetails(vulnerabilityKeyVal)
if err != nil {
return nil, err
}
scaTriageInfo[strings.TrimSpace(vulnerabilityKeyVal[0])] = strings.TrimSpace(vulnerabilityKeyVal[1])
}
if scaTriageInfo["packageName"] == nil && scaTriageInfo["packagename"] == nil {
return nil, errors.Errorf("Package name is required")
}
if scaTriageInfo["packageVersion"] == nil && scaTriageInfo["packageversion"] == nil {
return nil, errors.Errorf("Package version is required")
}
if scaTriageInfo["packageManager"] == nil && scaTriageInfo["packagemanager"] == nil {
return nil, errors.Errorf("Package manager is required")
}
scaTriageInfo["projectIds"] = []string{projectID}
actionInfo := make(map[string]interface{})
actionInfo["actionType"] = params.ChangeState
actionInfo["value"] = state
actionInfo["comment"] = comment
scaTriageInfo["actions"] = []map[string]interface{}{actionInfo}
b, err := json.Marshal(scaTriageInfo)
if err != nil {
logger.PrintIfVerbose(fmt.Sprintf("Failed to serialize vulnerabilities %s", scaTriageInfo))
return nil, errors.Errorf("Failed to prepare SCA triage request")
}
payload := wrappers.ScaPredicateRequest{}
err = json.Unmarshal(b, &payload)
if err != nil {
logger.PrintIfVerbose(fmt.Sprintf("Failed to deserialize vulnerabilities %s", string(b)))
return nil, errors.Errorf("Failed to prepare SCA triage request")
}
return payload, nil
}
func validateVulnerabilityDetails(vulnerability []string) error {
if len(vulnerability) != params.KeyValuePairSize {
return errors.Errorf("Invalid vulnerabilities. It should be in a KEY=VALUE format")
}
return nil
}
func determineSystemOrCustomState(customStatesWrapper wrappers.CustomStatesWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper, state string, customStateID int) (string, int, error) {
if !isCustomState(state) {
return state, -1, nil
}
flagResponse, _ := wrappers.GetSpecificFeatureFlag(featureFlagsWrapper, wrappers.SastCustomStateEnabled)
sastCustomStateEnabled := flagResponse.Status
if !sastCustomStateEnabled {
return "", -1, errors.Errorf("%s", "Custom state is not available for your tenant.")
}
if customStateID == -1 {
if state == "" {
return "", -1, errors.Errorf("state-id is required when state is not provided")
}
var err error
customStateID, err = getCustomStateID(customStatesWrapper, state)
if err != nil {
return "", -1, errors.Wrapf(err, "Failed to get custom state ID for state: %s", state)
}
}
return "", customStateID, nil
}
func isCustomState(state string) bool {
if state == "" {
return true
}
for _, systemState := range systemStates {
if strings.EqualFold(state, systemState.Name) {
return false
}
}
return true
}
func getCustomStateID(customStatesWrapper wrappers.CustomStatesWrapper, state string) (int, error) {
customStates, err := customStatesWrapper.GetAllCustomStates(false)
if err != nil {
return -1, errors.Wrap(err, "Failed to fetch custom states")
}
for _, customState := range customStates {
if customState.Name == state {
return customState.ID, nil
}
}
return -1, errors.Errorf("No matching state found for %s", state)
}
type predicateView struct {
ID string `format:"name:ID"`
ProjectID string `format:"name:Project ID"`
SimilarityID string `format:"name:Similarity ID"`
Severity string
State string
StateID int
Comment string
CreatedBy string
CreatedAt time.Time `format:"name:Created at;time:01-02-06 15:04:05"`
}
type scaPredicateResultView struct {
VulnerabilityID string `format:"name:Vulnerability ID"`
PackageName string `format:"name:Package Name"`
PackageVersion string `format:"name:Package Version"`
PackageManager string `format:"name:Package Manager"`
Comment string `format:"name:Comment"`
State string `format:"name:State"`
CreatedBy string `format:"name:Created By"`
CreatedAt time.Time `format:"name:Created at;time:01-02-06 15:04:05"`
}
func toScaPredicateResultView(scaPredicateResult *wrappers.ScaPredicateResult) []scaPredicateResultView {
view := []scaPredicateResultView{}
if len(scaPredicateResult.Actions) > 0 {
for _, action := range scaPredicateResult.Actions {
view = append(view, scaPredicateResultView{
VulnerabilityID: scaPredicateResult.Context.VulnerabilityID,
PackageName: scaPredicateResult.Context.PackageName,
PackageVersion: scaPredicateResult.Context.PackageVersion,
PackageManager: scaPredicateResult.Context.PackageManager,
Comment: action.Message,
State: action.ActionValue,
CreatedBy: action.UserName,
CreatedAt: action.CreatedAt,
})
}
}
sort.Slice(view, func(i, j int) bool {
return view[i].CreatedAt.After(view[j].CreatedAt)
})
return view
}
func toPredicatesView(predicatesCollection wrappers.PredicatesCollectionResponseModel) []predicateView {
projectPredicatesCollection := predicatesCollection.PredicateHistoryPerProject
if len(projectPredicatesCollection) > 0 {
predicatesPerProject := predicatesCollection.PredicateHistoryPerProject[0]
predicatesOfSingleProject := predicatesPerProject.Predicates
views := make([]predicateView, len(predicatesOfSingleProject))
for i := 0; i < len(predicatesOfSingleProject); i++ {
views[i] = toSinglePredicateView(&predicatesOfSingleProject[i])
}
return views
}
views := make([]predicateView, 0)
return views
}
func toSinglePredicateView(predicate *wrappers.Predicate) predicateView {
return predicateView{
ID: predicate.ID,
ProjectID: predicate.ProjectID,
SimilarityID: predicate.SimilarityID,
Severity: predicate.Severity,
State: predicate.State,
StateID: predicate.StateID,
Comment: predicate.Comment,
CreatedBy: predicate.CreatedBy,
CreatedAt: predicate.CreatedAt,
}
}