From 469fb6850882203fb0edcc55f67d13e572b7d604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Reigota?= Date: Thu, 1 Apr 2021 14:36:18 +0100 Subject: [PATCH 1/8] Add status code to results --- .github/workflows/go-ci-integration.yml | 2 +- cmd/console/main.go | 3 ++- docs/getting-started.md | 1 + internal/console/helpers/helpers.go | 20 ++++++++++++++++++++ internal/console/scan.go | 22 +++++++++++++++++++++- internal/constants/constants.go | 9 +++++++++ 6 files changed, 54 insertions(+), 3 deletions(-) diff --git a/.github/workflows/go-ci-integration.yml b/.github/workflows/go-ci-integration.yml index 442bd924f02..f8b618fe1c4 100644 --- a/.github/workflows/go-ci-integration.yml +++ b/.github/workflows/go-ci-integration.yml @@ -46,7 +46,7 @@ jobs: run: | docker run -v ${PWD}/assets/queries:/path \ -e SENTRY_DSN=${{secrets.SENTRY_DSN}} \ - kics:${{ github.sha }} --silent --log-level DEBUG --log-file --log-path "/path/info.log" -p "/path" -o "/path/results.json" + kics:${{ github.sha }} --silent --force --log-level DEBUG --log-file --log-path "/path/info.log" -p "/path" -o "/path/results.json" - name: Archive test logs uses: actions/upload-artifact@v2 if: always() diff --git a/cmd/console/main.go b/cmd/console/main.go index 1a58aabe37b..d676b678c6f 100644 --- a/cmd/console/main.go +++ b/cmd/console/main.go @@ -4,10 +4,11 @@ import ( "os" "github.com/Checkmarx/kics/internal/console" + "github.com/Checkmarx/kics/internal/constants" ) func main() { // nolint:funlen,gocyclo if err := console.Execute(); err != nil { - os.Exit(1) + os.Exit(constants.EngineErrorCode) } } diff --git a/docs/getting-started.md b/docs/getting-started.md index 926b19e8b13..b39aa922eea 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -113,6 +113,7 @@ Flags: -x, --exclude-results strings exclude results by providing the similarity ID of a result can be provided multiple times or as a comma separated string example: 'fec62a97d569662093dbb9739360942f...,31263s5696620s93dbb973d9360942fc2a...' + --force return status code 0 even if there are results -h, --help help for scan --minimal-ui simplified version of CLI output --no-progress hides the progress bar diff --git a/internal/console/helpers/helpers.go b/internal/console/helpers/helpers.go index 00ad6364698..9a6d714e6ed 100644 --- a/internal/console/helpers/helpers.go +++ b/internal/console/helpers/helpers.go @@ -298,3 +298,23 @@ func (p *Printer) PrintBySev(content, sev string) string { } return content } + +// ExitCodeCal calculate exit code base on severity of results +func ExitCodeCal(summary *model.Summary) int { + var exitValue = 0 + // codeArr is needed to make sure 'for' cycle is made in an ordered fashion + codeArr := []model.Severity{"HIGH", "MEDIUM", "LOW", "INFO"} + codeMap := map[model.Severity]int{"HIGH": 5, "MEDIUM": 4, "LOW": 3, "INFO": 2} + exitMap := summary.SeveritySummary.SeverityCounters + for _, sev := range codeArr { + if exitMap[sev] > 0 { + if exitValue == 0 { + exitValue = codeMap[sev] * 10 + continue + } + exitValue += codeMap[sev] + } + } + + return exitValue +} diff --git a/internal/console/scan.go b/internal/console/scan.go index cda10e4bb00..e242fedebd6 100644 --- a/internal/console/scan.go +++ b/internal/console/scan.go @@ -4,8 +4,10 @@ import ( _ "embed" // Embed kics CLI img "fmt" "os" + "os/signal" "path/filepath" "strings" + "syscall" "time" consoleHelpers "github.com/Checkmarx/kics/internal/console/helpers" @@ -49,6 +51,7 @@ var ( previewLines int //go:embed img/kics-console banner string + force bool ) var scanCmd = &cobra.Command{ @@ -58,6 +61,7 @@ var scanCmd = &cobra.Command{ return initializeConfig(cmd) }, RunE: func(cmd *cobra.Command, args []string) error { + gracefulShutdown() return scan() }, } @@ -178,6 +182,7 @@ func initScanCmd() { "\nexample: './shouldNotScan/*,somefile.txt'", ) scanCmd.Flags().BoolVarP(&min, "minimal-ui", "", false, "simplified version of CLI output") + scanCmd.Flags().BoolVarP(&force, "force", "", false, "return status code 0 even if there are results") scanCmd.Flags().StringSliceVarP(&types, "type", "t", []string{""}, "case insensitive list of platform types to scan\n"+ fmt.Sprintf("(%s)", strings.Join(source.ListSupportedPlatforms(), ", "))) scanCmd.Flags().BoolVarP(&noProgress, "no-progress", "", false, "hides the progress bar") @@ -363,9 +368,14 @@ func scan() error { log.Info().Msgf(elapsedStrFormat, elapsed) if summary.FailedToExecuteQueries > 0 { - os.Exit(1) + os.Exit(constants.DetectLineErrorCode) } + status := 0 + if !force { + status = consoleHelpers.ExitCodeCal(&summary) + } + os.Exit(status) return nil } @@ -424,3 +434,13 @@ func printOutput(outputPath, filename string, body interface{}, formats []string } return err } + +// gracefulShutdown catches signal interrupt and returns the appropriate exit code +func gracefulShutdown() { + c := make(chan os.Signal) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + <-c + os.Exit(constants.SignalInterruptCode) + }() +} diff --git a/internal/constants/constants.go b/internal/constants/constants.go index bd5df2a972a..ce2f19d2057 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -20,3 +20,12 @@ const MinimumPreviewLines = 1 // MaximumPreviewLines - default maximum preview lines number const MaximumPreviewLines = 30 + +// DetectLineError - Exit Status code for failed to detect line +const DetectLineErrorCode = 110 + +// EngineError - Exit Status code for error in engine +const EngineErrorCode = 126 + +// SignalInterruptCode - Exit Status code for a signal interrupt +const SignalInterruptCode = 130 From 4dc4b6bcd20dcf2bc932058187df261d415d8da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Reigota?= Date: Thu, 1 Apr 2021 17:46:29 +0100 Subject: [PATCH 2/8] Update documentation --- docs/results.md | 41 ++++++++++++++++++++++++++++++++++++----- mkdocs.yml | 9 ++++++++- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/docs/results.md b/docs/results.md index 45b565818e6..0eaaf9c0890 100644 --- a/docs/results.md +++ b/docs/results.md @@ -1,4 +1,4 @@ -## Results +# Results KICS can export results in multiple formats which can be seen on the following list: - JSON - SARIF @@ -21,9 +21,9 @@ where the output-path will be the directory containing all report files and repo The last command will execute the scan and save JSON and SARIF reports on output folder. -### Report examples +# Report examples -#### JSON +## JSON ```json { @@ -64,7 +64,7 @@ The last command will execute the scan and save JSON and SARIF reports on output "total_counter": 1 } ``` -#### SARIF +## SARIF ```json { "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", @@ -289,5 +289,36 @@ The last command will execute the scan and save JSON and SARIF reports on output } ``` -#### HTML +## HTML + +# Exit Status Code + +## Results Status Code + +| Code | Description | +| --------------| ------------------------------------------| +| `0` | No Results were Found | +| `50` | Found only `HIGH` Results | +| `40` | Found only `MEDIUM` Results | +| `30` | Found only `LOW` Results | +| `20` | Found only `INFO` Results | +| `59` | Found Results with all severities | +| `57` | Found `HIGH`, `MEDIUM` and `LOW` Results | +| `56` | Found `HIGH`, `MEDIUM` and `INFO` ResultS | +| `55` | Found `HIGH`, `INFO` and `LOW` Results | +| `54` | Found `HIGH`, and `MEDIUM` Results | +| `53` | Found `HIGH`, and `LOW` Results | +| `52` | Found `HIGH`, and `INFO` Results | +| `45` | Found `MEDIUM`, `INFO` and `LOW` Results | +| `43` | Found `MEDIUM`, and `LOW` Results | +| `42` | Found `MEDIUM`, and `INFO` Results | +| `32` | Found `LOW`, and `INFO` Results | + +## Error Status Code + +| Code | Description | +| --------------| ------------------------------------------| +| `126` | Engine Error | +| `130` | Signal-Interrupt | +| `110` | Failed to detect line | diff --git a/mkdocs.yml b/mkdocs.yml index 03b10615223..56d17f30d39 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -18,7 +18,7 @@ nav: - Configuration: configuration-file.md - Results: results.md - Architecture: architecture.md - - Queries: + - Queries: - General Info: queries.md - Creating Queries: creating-queries.md - Queries List: @@ -65,3 +65,10 @@ plugins: google_analytics: - UA-186818347-2 - auto + + +markdown_extensions: + - toc: + permalink: true + toc_depth: 2 + separator: "_" From 69c2ad536a8bcbfe4707019fe46ddb76462ef05b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Reigota?= Date: Thu, 1 Apr 2021 17:55:46 +0100 Subject: [PATCH 3/8] fix codacy issues --- internal/constants/constants.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/constants/constants.go b/internal/constants/constants.go index ce2f19d2057..a0b705a4c34 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -21,10 +21,10 @@ const MinimumPreviewLines = 1 // MaximumPreviewLines - default maximum preview lines number const MaximumPreviewLines = 30 -// DetectLineError - Exit Status code for failed to detect line +// DetectLineErrorCode - Exit Status code for failed to detect line const DetectLineErrorCode = 110 -// EngineError - Exit Status code for error in engine +// EngineErrorCode - Exit Status code for error in engine const EngineErrorCode = 126 // SignalInterruptCode - Exit Status code for a signal interrupt From 5b9172107696e2fa9df4841eb090390b22711718 Mon Sep 17 00:00:00 2001 From: Felipe Avelar Date: Fri, 23 Apr 2021 12:07:51 +0100 Subject: [PATCH 4/8] Added ignore-on-exit and fail-on flags Signed-off-by: Felipe Avelar --- cmd/console/main.go | 5 +- internal/console/helpers/exit_handler.go | 68 +++++ internal/console/helpers/exit_handler_test.go | 250 ++++++++++++++++++ internal/console/helpers/helpers.go | 20 -- internal/console/kics.go | 8 +- internal/console/scan.go | 111 +++++--- internal/constants/constants.go | 3 - pkg/parser/parser.go | 2 +- 8 files changed, 398 insertions(+), 69 deletions(-) create mode 100644 internal/console/helpers/exit_handler.go create mode 100644 internal/console/helpers/exit_handler_test.go diff --git a/cmd/console/main.go b/cmd/console/main.go index d676b678c6f..9a2fc4c3b2a 100644 --- a/cmd/console/main.go +++ b/cmd/console/main.go @@ -4,11 +4,14 @@ import ( "os" "github.com/Checkmarx/kics/internal/console" + "github.com/Checkmarx/kics/internal/console/helpers" "github.com/Checkmarx/kics/internal/constants" ) func main() { // nolint:funlen,gocyclo if err := console.Execute(); err != nil { - os.Exit(constants.EngineErrorCode) + if helpers.ShowError("errors") { + os.Exit(constants.EngineErrorCode) + } } } diff --git a/internal/console/helpers/exit_handler.go b/internal/console/helpers/exit_handler.go new file mode 100644 index 00000000000..4a2cb760950 --- /dev/null +++ b/internal/console/helpers/exit_handler.go @@ -0,0 +1,68 @@ +package helpers + +import ( + "fmt" + "strings" + + "github.com/Checkmarx/kics/pkg/model" +) + +var shouldIgnore string +var shouldFail map[string]struct{} + +// ResultsExitCode calculate exit code base on severity of results, returns 0 if no results was reported +func ResultsExitCode(summary *model.Summary) int { + // severityArr is needed to make sure 'for' cycle is made in an ordered fashion + severityArr := []model.Severity{"HIGH", "MEDIUM", "LOW", "INFO"} + codeMap := map[model.Severity]int{"HIGH": 50, "MEDIUM": 40, "LOW": 30, "INFO": 20} + exitMap := summary.SeveritySummary.SeverityCounters + for _, severity := range severityArr { + if _, reportSeverity := shouldFail[strings.ToLower(string(severity))]; !reportSeverity { + continue + } + if exitMap[severity] > 0 { + return codeMap[severity] + } + } + return 0 +} + +func InitShouldIgnoreArg(arg string) error { + validArgs := []string{"none", "all", "results", "errors"} + for _, validArg := range validArgs { + if strings.EqualFold(validArg, arg) { + shouldIgnore = strings.ToLower(arg) + return nil + } + } + return fmt.Errorf("unknown argument for --ignore-on-exit: %s\nvalid arguments:\n %s", arg, strings.Join(validArgs, "\n ")) +} + +func InitShouldFailArg(args []string) error { + possibleArgs := map[string]struct{}{ + "high": {}, + "medium": {}, + "low": {}, + "info": {}, + } + if len(args) == 0 { + shouldFail = possibleArgs + return nil + } + + argsConverted := make(map[string]struct{}) + for _, arg := range args { + if _, ok := possibleArgs[strings.ToLower(arg)]; !ok { + validArgs := []string{"high", "medium", "low", "info"} + return fmt.Errorf("unknown argument for --fail-on: %s\nvalid arguments:\n %s", arg, strings.Join(validArgs, "\n ")) + } + argsConverted[strings.ToLower(arg)] = struct{}{} + } + + shouldFail = argsConverted + return nil +} + +func ShowError(kind string) bool { + return strings.EqualFold(shouldIgnore, "none") || (!strings.EqualFold(shouldIgnore, "all") && !strings.EqualFold(shouldIgnore, kind)) +} diff --git a/internal/console/helpers/exit_handler_test.go b/internal/console/helpers/exit_handler_test.go new file mode 100644 index 00000000000..78e89e886ac --- /dev/null +++ b/internal/console/helpers/exit_handler_test.go @@ -0,0 +1,250 @@ +package helpers + +import ( + "fmt" + "testing" + + "github.com/Checkmarx/kics/pkg/model" + "github.com/Checkmarx/kics/test" + "github.com/stretchr/testify/require" +) + +type resultExitCode struct { + summary model.Summary + failOn map[string]struct{} +} + +var resultsExitCodeTests = []struct { + caseTest resultExitCode + expectedResult int +}{ + { + caseTest: resultExitCode{ + summary: test.SummaryMock, + failOn: map[string]struct{}{ + "high": {}, + "medium": {}, + "low": {}, + "info": {}, + }, + }, + expectedResult: 50, + }, + { + caseTest: resultExitCode{ + summary: test.SummaryMock, + failOn: map[string]struct{}{ + "medium": {}, + }, + }, + expectedResult: 0, + }, + { + caseTest: resultExitCode{ + summary: test.ComplexSummaryMock, + failOn: map[string]struct{}{ + "medium": {}, + }, + }, + expectedResult: 40, + }, + { + caseTest: resultExitCode{ + summary: test.ComplexSummaryMock, + failOn: map[string]struct{}{ + "high": {}, + "medium": {}, + "low": {}, + "info": {}, + }, + }, + expectedResult: 50, + }, +} + +// TestResultsExitCode tests the functions [ResultsExitCode()] and all the methods called by them +func TestResultsExitCode(t *testing.T) { + for idx, testCase := range resultsExitCodeTests { + t.Run(fmt.Sprintf("Print test case %d", idx), func(t *testing.T) { + shouldFail = testCase.caseTest.failOn + result := ResultsExitCode(&testCase.caseTest.summary) + require.Equal(t, testCase.expectedResult, result) + }) + } +} + +type initIgnoreResult struct { + wantErr bool + want string +} + +var initShouldIgnoreExitCodeTests = []struct { + caseTest string + expectedResult initIgnoreResult +}{ + { + caseTest: "NONE", + expectedResult: initIgnoreResult{ + wantErr: false, + want: "none", + }, + }, + { + caseTest: "None", + expectedResult: initIgnoreResult{ + wantErr: false, + want: "none", + }, + }, + { + caseTest: "none", + expectedResult: initIgnoreResult{ + wantErr: false, + want: "none", + }, + }, + { + caseTest: "all", + expectedResult: initIgnoreResult{ + wantErr: false, + want: "all", + }, + }, + { + caseTest: "results", + expectedResult: initIgnoreResult{ + wantErr: false, + want: "results", + }, + }, + { + caseTest: "errors", + expectedResult: initIgnoreResult{ + wantErr: false, + want: "errors", + }, + }, + { + caseTest: "invalid", + expectedResult: initIgnoreResult{ + wantErr: true, + want: "none", + }, + }, +} + +// TestInitShouldIgnoreArg tests the functions [InitShouldIgnoreArg()] and all the methods called by them +func TestInitShouldIgnoreArg(t *testing.T) { + for idx, testCase := range initShouldIgnoreExitCodeTests { + t.Run(fmt.Sprintf("Print test case %d", idx), func(t *testing.T) { + shouldIgnore = "none" + err := InitShouldIgnoreArg(testCase.caseTest) + if testCase.expectedResult.wantErr { + require.NotNil(t, err) + } else { + require.Nil(t, err) + } + require.Equal(t, testCase.expectedResult.want, shouldIgnore) + }) + } +} + +type initFail struct { + wantErr bool + want map[string]struct{} +} + +var initShouldFailTests = []struct { + caseTest []string + expectedResult initFail +}{ + { + caseTest: []string{}, + expectedResult: initFail{ + wantErr: false, + want: map[string]struct{}{ + "high": {}, + "medium": {}, + "low": {}, + "info": {}, + }, + }, + }, + { + caseTest: []string{"HIGH"}, + expectedResult: initFail{ + wantErr: false, + want: map[string]struct{}{ + "high": {}, + }, + }, + }, + { + caseTest: []string{"HIGH", "Medium", "loW", "info"}, + expectedResult: initFail{ + wantErr: false, + want: map[string]struct{}{ + "high": {}, + "medium": {}, + "low": {}, + "info": {}, + }, + }, + }, + { + caseTest: []string{"invalid"}, + expectedResult: initFail{ + wantErr: true, + want: map[string]struct{}{}, + }, + }, +} + +// TestInitShouldFailArg tests the functions [InitShouldFailArg()] and all the methods called by them +func TestInitShouldFailArg(t *testing.T) { + for idx, testCase := range initShouldFailTests { + t.Run(fmt.Sprintf("Print test case %d", idx), func(t *testing.T) { + shouldFail = make(map[string]struct{}) + err := InitShouldFailArg(testCase.caseTest) + if testCase.expectedResult.wantErr { + require.NotNil(t, err) + } else { + require.Nil(t, err) + } + require.Equal(t, testCase.expectedResult.want, shouldFail) + }) + } +} + +var showResultsTests = []struct { + caseTest string + expectedResult bool +}{ + { + caseTest: "none", + expectedResult: true, + }, + { + caseTest: "all", + expectedResult: false, + }, + { + caseTest: "results", + expectedResult: true, + }, + { + caseTest: "errors", + expectedResult: false, + }, +} + +// TestShowError tests the functions [ShowError()] and all the methods called by them +func TestShowError(t *testing.T) { + for idx, testCase := range showResultsTests { + t.Run(fmt.Sprintf("Print test case %d", idx), func(t *testing.T) { + shouldIgnore = testCase.caseTest + result := ShowError("errors") + require.Equal(t, testCase.expectedResult, result) + }) + } +} diff --git a/internal/console/helpers/helpers.go b/internal/console/helpers/helpers.go index cbc61f429b8..7e76e62bcfa 100644 --- a/internal/console/helpers/helpers.go +++ b/internal/console/helpers/helpers.go @@ -329,23 +329,3 @@ func (p *Printer) PrintBySev(content, sev string) string { } return content } - -// ExitCodeCal calculate exit code base on severity of results -func ExitCodeCal(summary *model.Summary) int { - var exitValue = 0 - // codeArr is needed to make sure 'for' cycle is made in an ordered fashion - codeArr := []model.Severity{"HIGH", "MEDIUM", "LOW", "INFO"} - codeMap := map[model.Severity]int{"HIGH": 5, "MEDIUM": 4, "LOW": 3, "INFO": 2} - exitMap := summary.SeveritySummary.SeverityCounters - for _, sev := range codeArr { - if exitMap[sev] > 0 { - if exitValue == 0 { - exitValue = codeMap[sev] * 10 - continue - } - exitValue += codeMap[sev] - } - } - - return exitValue -} diff --git a/internal/console/kics.go b/internal/console/kics.go index f06974407e6..79458f4a104 100644 --- a/internal/console/kics.go +++ b/internal/console/kics.go @@ -23,14 +23,14 @@ const ( var ( ctx = context.Background() - verbose bool + ci bool logFile bool - logPath string - logLevel string logFormat string + logLevel string + logPath string noColor bool silent bool - ci bool + verbose bool warning []string ) diff --git a/internal/console/scan.go b/internal/console/scan.go index 9bf40cb4cad..f4fd2efc084 100644 --- a/internal/console/scan.go +++ b/internal/console/scan.go @@ -36,50 +36,52 @@ import ( ) var ( - path string - queryPath string - outputPath string - payloadPath string + //go:embed img/kics-console + banner string + + cfgFile string excludeCategories []string - excludePath []string excludeIDs []string + excludePath []string excludeResults []string + failOn []string + ignoreOnExit string + min bool + noProgress bool + outputPath string + path string + payloadPath string + previewLines int + queryPath string reportFormats []string - cfgFile string - - noProgress bool - types []string - min bool - previewLines int - //go:embed img/kics-console - banner string - force bool + types []string ) const ( - scanCommandStr = "scan" - pathFlag = "path" - pathFlagShorthand = "p" configFlag = "config" - queriesPathShorthand = "q" - outputPathFlag = "output-path" - outputPathShorthand = "o" - reportFormatsFlag = "report-formats" - previewLinesFlag = "preview-lines" + excludeCategoriesFlag = "exclude-categories" excludePathsFlag = "exclude-paths" excludePathsShorthand = "e" + excludeQueriesFlag = "exclude-queries" + excludeResultsFlag = "exclude-results" + excludeResutlsShorthand = "x" + failOnFlag = "fail-on" + ignoreOnExitFlag = "ignore-on-exit" minimalUIFlag = "minimal-ui" + noProgressFlag = "no-progress" + outputPathFlag = "output-path" + outputPathShorthand = "o" + pathFlag = "path" + pathFlagShorthand = "p" payloadPathFlag = "payload-path" payloadPathShorthand = "d" + previewLinesFlag = "preview-lines" + queriesPathCmdName = "queries-path" + queriesPathShorthand = "q" + reportFormatsFlag = "report-formats" + scanCommandStr = "scan" typeFlag = "type" typeShorthand = "t" - noProgressFlag = "no-progress" - excludeQueriesFlag = "exclude-queries" - excludeResultsFlag = "exclude-results" - excludeResutlsShorthand = "x" - excludeCategoriesFlag = "exclude-categories" - queriesPathCmdName = "queries-path" - forceFlag = "force" ) // NewScanCmd creates a new instance of the scan Command @@ -100,6 +102,12 @@ func NewScanCmd() *cobra.Command { }, RunE: func(cmd *cobra.Command, args []string) error { changedDefaultQueryPath := cmd.Flags().Lookup(queriesPathCmdName).Changed + if err := consoleHelpers.InitShouldIgnoreArg(ignoreOnExit); err != nil { + return err + } + if err := consoleHelpers.InitShouldFailArg(failOn); err != nil { + return err + } gracefulShutdown() return scan(changedDefaultQueryPath) }, @@ -200,7 +208,7 @@ func setBoundFlags(flagName string, val interface{}, cmd *cobra.Command) { } } -func initScanCmd(scanCmd *cobra.Command) { +func initScanFlags(scanCmd *cobra.Command) { scanCmd.Flags().StringVarP(&path, pathFlag, pathFlagShorthand, @@ -251,7 +259,6 @@ func initScanCmd(scanCmd *cobra.Command) { "", false, "simplified version of CLI output") - scanCmd.Flags().BoolVarP(&force, forceFlag, "", false, "return status code 0 even if there are results") scanCmd.Flags().StringSliceVarP(&types, typeFlag, typeShorthand, @@ -287,6 +294,33 @@ func initScanCmd(scanCmd *cobra.Command) { "can be provided multiple times or as a comma separated string\n"+ "example: 'Access control,Best practices'", ) + scanCmd.Flags().StringSliceVarP(&failOn, + failOnFlag, + "", + []string{"high", "medium", "low", "info"}, + "which kind of results should return an exit code different from 0\n"+ + "accetps: high, medium, low and info\n"+ + "example: 'high,low'", + ) + scanCmd.Flags().StringVarP(&ignoreOnExit, + ignoreOnExitFlag, + "", + "none", + "defines which kind of non-zero exits code should be ignored\n"+"accepts: all, results, errors, none\n"+ + "example: if 'results' is set, only engine errors will make KICS exit code different from 0", + ) +} + +func initScanCmd(scanCmd *cobra.Command) { + initScanFlags(scanCmd) + scanCmd.PersistentFlags().StringVarP(&ignoreOnExit, + ignoreOnExitFlag, + "", + "none", + "defines which kind of non-zero exits code should be ignored\n"+ + "accepts: all, results, errors, none\n"+ + "example: if 'results' is set, only engine errors will make KICS exit code different from 0", + ) if err := scanCmd.MarkFlagRequired("path"); err != nil { sentry.CaptureException(err) @@ -453,15 +487,10 @@ func scan(changedDefaultQueryPath bool) error { fmt.Printf(elapsedStrFormat, elapsed) log.Info().Msgf(elapsedStrFormat, elapsed) - if summary.FailedToExecuteQueries > 0 { - os.Exit(constants.DetectLineErrorCode) + exitCode := consoleHelpers.ResultsExitCode(&summary) + if consoleHelpers.ShowError("results") && exitCode != 0 { + os.Exit(exitCode) } - status := 0 - - if !force { - status = consoleHelpers.ExitCodeCal(&summary) - } - os.Exit(status) return nil } @@ -527,6 +556,8 @@ func gracefulShutdown() { signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { <-c - os.Exit(constants.SignalInterruptCode) + if consoleHelpers.ShowError("errors") { + os.Exit(constants.SignalInterruptCode) + } }() } diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 6e641531f3c..55c21f74fcb 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -23,9 +23,6 @@ const MinimumPreviewLines = 1 // MaximumPreviewLines - default maximum preview lines number const MaximumPreviewLines = 30 -// DetectLineErrorCode - Exit Status code for failed to detect line -const DetectLineErrorCode = 110 - // EngineErrorCode - Exit Status code for error in engine const EngineErrorCode = 126 diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go index 7c9a0ec5278..acc3a99dc07 100644 --- a/pkg/parser/parser.go +++ b/pkg/parser/parser.go @@ -100,7 +100,7 @@ func (c *Parser) SupportedExtensions() model.Extensions { func validateArguments(types, validArgs []string) error { validArgs = removeDuplicateValues(validArgs) if invalidType, ok, _ := contains(types, validArgs); !ok { - return fmt.Errorf("unknown argument: %s\nvalid arguments:\n %s", invalidType, strings.Join(validArgs, "\n ")) + return fmt.Errorf("unknown argument for --type: %s\nvalid arguments:\n %s", invalidType, strings.Join(validArgs, "\n ")) } return nil } From 0c2b850bcccbcdd37f8602daf51258725bdcd9a0 Mon Sep 17 00:00:00 2001 From: Felipe Avelar Date: Fri, 23 Apr 2021 12:17:28 +0100 Subject: [PATCH 5/8] Corrected CI flow and docs Signed-off-by: Felipe Avelar --- .github/workflows/go-ci-integration.yml | 2 +- docs/results.md | 22 +++++----------------- docs/usage/commands.md | 7 ++++++- internal/console/scan.go | 10 +--------- 4 files changed, 13 insertions(+), 28 deletions(-) diff --git a/.github/workflows/go-ci-integration.yml b/.github/workflows/go-ci-integration.yml index 3cdda09ce61..47c277d4383 100644 --- a/.github/workflows/go-ci-integration.yml +++ b/.github/workflows/go-ci-integration.yml @@ -50,7 +50,7 @@ jobs: run: | docker run -v ${PWD}/assets/queries:/path \ -e SENTRY_DSN=${{secrets.SENTRY_DSN}} \ - kics:${{ github.sha }} --silent --force --log-level DEBUG --log-file --log-path "/path/info.log" -p "/path" -o "/path/results.json" + kics:${{ github.sha }} --silent --ignore-on-exit "results" --log-level DEBUG --log-file --log-path "/path/info.log" -p "/path" -o "/path/results.json" - name: Archive test logs uses: actions/upload-artifact@v2 if: always() diff --git a/docs/results.md b/docs/results.md index dddcdc853d5..d2b469563b0 100644 --- a/docs/results.md +++ b/docs/results.md @@ -304,24 +304,13 @@ HTML reports are sorted by severity (from high to info), the results will have q ## Results Status Code -| Code | Description | +| Code | Description | | --------------| ------------------------------------------| | `0` | No Results were Found | -| `50` | Found only `HIGH` Results | -| `40` | Found only `MEDIUM` Results | -| `30` | Found only `LOW` Results | -| `20` | Found only `INFO` Results | -| `59` | Found Results with all severities | -| `57` | Found `HIGH`, `MEDIUM` and `LOW` Results | -| `56` | Found `HIGH`, `MEDIUM` and `INFO` ResultS | -| `55` | Found `HIGH`, `INFO` and `LOW` Results | -| `54` | Found `HIGH`, and `MEDIUM` Results | -| `53` | Found `HIGH`, and `LOW` Results | -| `52` | Found `HIGH`, and `INFO` Results | -| `45` | Found `MEDIUM`, `INFO` and `LOW` Results | -| `43` | Found `MEDIUM`, and `LOW` Results | -| `42` | Found `MEDIUM`, and `INFO` Results | -| `32` | Found `LOW`, and `INFO` Results | +| `50` | Found any `HIGH` Results | +| `40` | Found any `MEDIUM` Results | +| `30` | Found any `LOW` Results | +| `20` | Found any `INFO` Results | ## Error Status Code @@ -329,4 +318,3 @@ HTML reports are sorted by severity (from high to info), the results will have q | --------------| ------------------------------------------| | `126` | Engine Error | | `130` | Signal-Interrupt | -| `110` | Failed to detect line | diff --git a/docs/usage/commands.md b/docs/usage/commands.md index 30efdcf9a0a..18a9c675cf8 100644 --- a/docs/usage/commands.md +++ b/docs/usage/commands.md @@ -50,7 +50,12 @@ Flags: -x, --exclude-results strings exclude results by providing the similarity ID of a result can be provided multiple times or as a comma separated string example: 'fec62a97d569662093dbb9739360942f...,31263s5696620s93dbb973d9360942fc2a...' - --force return status code 0 even if there are results + --fail-on which kind of results should return an exit code different from 0 + accetps: high, medium, low and info + example: "high,low" + --ignoreOnExitFlag defines which kind of non-zero exits code should be ignored + accepts: all, results, errors, none + example: if 'results' is set, only engine errors will make KICS exit code different from 0 -h, --help help for scan --minimal-ui simplified version of CLI output --no-progress hides the progress bar diff --git a/internal/console/scan.go b/internal/console/scan.go index f4fd2efc084..020a7887854 100644 --- a/internal/console/scan.go +++ b/internal/console/scan.go @@ -300,7 +300,7 @@ func initScanFlags(scanCmd *cobra.Command) { []string{"high", "medium", "low", "info"}, "which kind of results should return an exit code different from 0\n"+ "accetps: high, medium, low and info\n"+ - "example: 'high,low'", + "example: \"high,low\"", ) scanCmd.Flags().StringVarP(&ignoreOnExit, ignoreOnExitFlag, @@ -313,14 +313,6 @@ func initScanFlags(scanCmd *cobra.Command) { func initScanCmd(scanCmd *cobra.Command) { initScanFlags(scanCmd) - scanCmd.PersistentFlags().StringVarP(&ignoreOnExit, - ignoreOnExitFlag, - "", - "none", - "defines which kind of non-zero exits code should be ignored\n"+ - "accepts: all, results, errors, none\n"+ - "example: if 'results' is set, only engine errors will make KICS exit code different from 0", - ) if err := scanCmd.MarkFlagRequired("path"); err != nil { sentry.CaptureException(err) From 5b1a6abc1054f3e8d63cd43f11a1a9cb434da379 Mon Sep 17 00:00:00 2001 From: Felipe Avelar Date: Fri, 23 Apr 2021 12:23:15 +0100 Subject: [PATCH 6/8] Codacy issues Signed-off-by: Felipe Avelar --- internal/console/helpers/exit_handler.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/console/helpers/exit_handler.go b/internal/console/helpers/exit_handler.go index 4a2cb760950..ac7c936bcbd 100644 --- a/internal/console/helpers/exit_handler.go +++ b/internal/console/helpers/exit_handler.go @@ -27,6 +27,7 @@ func ResultsExitCode(summary *model.Summary) int { return 0 } +// InitShouldIgnoreArg initializes what kind of errors should be used on exit codes func InitShouldIgnoreArg(arg string) error { validArgs := []string{"none", "all", "results", "errors"} for _, validArg := range validArgs { @@ -38,6 +39,7 @@ func InitShouldIgnoreArg(arg string) error { return fmt.Errorf("unknown argument for --ignore-on-exit: %s\nvalid arguments:\n %s", arg, strings.Join(validArgs, "\n ")) } +// InitShouldFailArg initializes which kind of vulnerability severity should changes exit code func InitShouldFailArg(args []string) error { possibleArgs := map[string]struct{}{ "high": {}, @@ -63,6 +65,7 @@ func InitShouldFailArg(args []string) error { return nil } +// ShowError returns true if should show error, otherwise returns false func ShowError(kind string) bool { return strings.EqualFold(shouldIgnore, "none") || (!strings.EqualFold(shouldIgnore, "all") && !strings.EqualFold(shouldIgnore, kind)) } From db24c101eafb49a1707d01c5adbf0bb2f52a237d Mon Sep 17 00:00:00 2001 From: Felipe Avelar Date: Fri, 23 Apr 2021 13:07:14 +0100 Subject: [PATCH 7/8] Updated e2e tests Signed-off-by: Felipe Avelar --- e2e/cli_test.go | 6 +++--- e2e/fixtures/E2E_CLI_002 | 6 ++++++ e2e/fixtures/E2E_CLI_003 | 6 ++++++ e2e/fixtures/E2E_CLI_004 | 6 ++++++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/e2e/cli_test.go b/e2e/cli_test.go index c685a838e6c..c5b977bcb5b 100644 --- a/e2e/cli_test.go +++ b/e2e/cli_test.go @@ -70,7 +70,7 @@ var tests = []struct { }, expectedOut: []string{"E2E_CLI_003"}, }, - wantStatus: 1, + wantStatus: 126, }, // E2E-CLI-004 - KICS scan command had a mandatory flag -p the CLI should exhibit // an error message and return exit code 1 @@ -88,7 +88,7 @@ var tests = []struct { "E2E_CLI_004", }, }, - wantStatus: 1, + wantStatus: 126, }, // E2E-CLI-005 - KICS scan with -- payload-path flag should create a file with the // passed name containing the payload of the files scanned @@ -106,7 +106,7 @@ var tests = []struct { "E2E_CLI_005_PAYLOAD", }, }, - wantStatus: 0, + wantStatus: 50, removePayload: []string{"payload.json"}, }, } diff --git a/e2e/fixtures/E2E_CLI_002 b/e2e/fixtures/E2E_CLI_002 index 447cedb221d..fdb4a0524bb 100644 --- a/e2e/fixtures/E2E_CLI_002 +++ b/e2e/fixtures/E2E_CLI_002 @@ -17,7 +17,13 @@ Flags: -x, --exclude-results strings exclude results by providing the similarity ID of a result can be provided multiple times or as a comma separated string example: 'fec62a97d569662093dbb9739360942f...,31263s5696620s93dbb973d9360942fc2a...' + --fail-on strings which kind of results should return an exit code different from 0 + accetps: high, medium, low and info + example: "high,low" (default [high,medium,low,info]) -h, --help help for scan + --ignore-on-exit string defines which kind of non-zero exits code should be ignored + accepts: all, results, errors, none + example: if 'results' is set, only engine errors will make KICS exit code different from 0 (default "none") --minimal-ui simplified version of CLI output --no-progress hides the progress bar -o, --output-path string directory path to store reports diff --git a/e2e/fixtures/E2E_CLI_003 b/e2e/fixtures/E2E_CLI_003 index 49a577393a9..bd1fa4bcabf 100644 --- a/e2e/fixtures/E2E_CLI_003 +++ b/e2e/fixtures/E2E_CLI_003 @@ -16,7 +16,13 @@ Flags: -x, --exclude-results strings exclude results by providing the similarity ID of a result can be provided multiple times or as a comma separated string example: 'fec62a97d569662093dbb9739360942f...,31263s5696620s93dbb973d9360942fc2a...' + --fail-on strings which kind of results should return an exit code different from 0 + accetps: high, medium, low and info + example: "high,low" (default [high,medium,low,info]) -h, --help help for scan + --ignore-on-exit string defines which kind of non-zero exits code should be ignored + accepts: all, results, errors, none + example: if 'results' is set, only engine errors will make KICS exit code different from 0 (default "none") --minimal-ui simplified version of CLI output --no-progress hides the progress bar -o, --output-path string directory path to store reports diff --git a/e2e/fixtures/E2E_CLI_004 b/e2e/fixtures/E2E_CLI_004 index 53cfe7f5607..0c02de5e63d 100644 --- a/e2e/fixtures/E2E_CLI_004 +++ b/e2e/fixtures/E2E_CLI_004 @@ -16,7 +16,13 @@ Flags: -x, --exclude-results strings exclude results by providing the similarity ID of a result can be provided multiple times or as a comma separated string example: 'fec62a97d569662093dbb9739360942f...,31263s5696620s93dbb973d9360942fc2a...' + --fail-on strings which kind of results should return an exit code different from 0 + accetps: high, medium, low and info + example: "high,low" (default [high,medium,low,info]) -h, --help help for scan + --ignore-on-exit string defines which kind of non-zero exits code should be ignored + accepts: all, results, errors, none + example: if 'results' is set, only engine errors will make KICS exit code different from 0 (default "none") --minimal-ui simplified version of CLI output --no-progress hides the progress bar -o, --output-path string directory path to store reports From c23c2e6c0d4e9cf78a9a87c17154975980042c83 Mon Sep 17 00:00:00 2001 From: Felipe Avelar Date: Fri, 23 Apr 2021 15:30:03 +0100 Subject: [PATCH 8/8] Corrected test functions names Signed-off-by: Felipe Avelar --- internal/console/helpers/exit_handler_test.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/internal/console/helpers/exit_handler_test.go b/internal/console/helpers/exit_handler_test.go index 78e89e886ac..1b2f58c9a05 100644 --- a/internal/console/helpers/exit_handler_test.go +++ b/internal/console/helpers/exit_handler_test.go @@ -62,8 +62,7 @@ var resultsExitCodeTests = []struct { }, } -// TestResultsExitCode tests the functions [ResultsExitCode()] and all the methods called by them -func TestResultsExitCode(t *testing.T) { +func TestExitHandler_ResultsExitCode(t *testing.T) { for idx, testCase := range resultsExitCodeTests { t.Run(fmt.Sprintf("Print test case %d", idx), func(t *testing.T) { shouldFail = testCase.caseTest.failOn @@ -133,8 +132,7 @@ var initShouldIgnoreExitCodeTests = []struct { }, } -// TestInitShouldIgnoreArg tests the functions [InitShouldIgnoreArg()] and all the methods called by them -func TestInitShouldIgnoreArg(t *testing.T) { +func TestExitHandler_InitShouldIgnoreArg(t *testing.T) { for idx, testCase := range initShouldIgnoreExitCodeTests { t.Run(fmt.Sprintf("Print test case %d", idx), func(t *testing.T) { shouldIgnore = "none" @@ -200,8 +198,7 @@ var initShouldFailTests = []struct { }, } -// TestInitShouldFailArg tests the functions [InitShouldFailArg()] and all the methods called by them -func TestInitShouldFailArg(t *testing.T) { +func TestExitHandler_InitShouldFailArg(t *testing.T) { for idx, testCase := range initShouldFailTests { t.Run(fmt.Sprintf("Print test case %d", idx), func(t *testing.T) { shouldFail = make(map[string]struct{}) @@ -238,8 +235,7 @@ var showResultsTests = []struct { }, } -// TestShowError tests the functions [ShowError()] and all the methods called by them -func TestShowError(t *testing.T) { +func TestExitHandler_ShowError(t *testing.T) { for idx, testCase := range showResultsTests { t.Run(fmt.Sprintf("Print test case %d", idx), func(t *testing.T) { shouldIgnore = testCase.caseTest