diff --git a/cmd/main.go b/cmd/main.go index 71eeed7d8..0c51bde2e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -3,8 +3,13 @@ package main import ( "fmt" "os" + "os/exec" + "os/signal" + "strings" + "syscall" "github.com/checkmarx/ast-cli/internal/commands" + "github.com/checkmarx/ast-cli/internal/logger" "github.com/checkmarx/ast-cli/internal/params" "github.com/checkmarx/ast-cli/internal/wrappers" "github.com/checkmarx/ast-cli/internal/wrappers/configuration" @@ -14,6 +19,7 @@ import ( const ( successfulExitCode = 0 failureExitCode = 1 + killCommand = "kill" ) func main() { @@ -60,7 +66,7 @@ func main() { gitLabWrapper, bflWrapper, ) - + exitListener() err = astCli.Execute() exitIfError(err) os.Exit(successfulExitCode) @@ -88,3 +94,39 @@ func bindKeysToEnvAndDefault() { viper.SetDefault(b.Key, b.Default) } } + +func exitListener() { + signalChanel := make(chan os.Signal, 1) + signal.Notify(signalChanel, + syscall.SIGTERM) + go signalHandler(signalChanel) +} + +func signalHandler(signalChanel chan os.Signal) { + kicsRunArgs := []string{ + killCommand, + viper.GetString(params.KicsContainerNameKey), + } + for { + s := <-signalChanel + switch s { + case syscall.SIGTERM: + out, err := exec.Command("docker", "ps").CombinedOutput() + if err != nil { + os.Exit(failureExitCode) + } + logger.PrintIfVerbose(string(out)) + if strings.Contains(string(out), viper.GetString(params.KicsContainerNameKey)) { + out, err = exec.Command("docker", kicsRunArgs...).CombinedOutput() + logger.PrintIfVerbose(string(out)) + if err != nil { + os.Exit(failureExitCode) + } + } + os.Exit(successfulExitCode) + // Should not get here since we only listen to SIGTERM, kept for safety + default: + os.Exit(failureExitCode) + } + } +} diff --git a/internal/commands/data/Dockerfile b/internal/commands/data/Dockerfile new file mode 100644 index 000000000..46598e803 --- /dev/null +++ b/internal/commands/data/Dockerfile @@ -0,0 +1,16 @@ +FROM openjdk:11.0.1-jre-slim-stretch + +ARG webwolf_version=v8.0.0-SNAPSHOT + +RUN \ + apt-get update && apt-get install && \ + useradd --home-dir /home/webwolf --create-home -U webwolf + +USER webwolf +COPY target/webwolf-${webwolf_version}.jar /home/webwolf/webwolf.jar +COPY start-webwolf.sh /home/webwolf + +EXPOSE 9090 + +ENTRYPOINT ["/home/webwolf/start-webwolf.sh"] +CMD ["--server.port=9090", "--server.address=0.0.0.0"] \ No newline at end of file diff --git a/internal/commands/data/empty.Dockerfile b/internal/commands/data/empty.Dockerfile new file mode 100644 index 000000000..e69de29bb diff --git a/internal/commands/scan.go b/internal/commands/scan.go index d2de8e008..554143389 100644 --- a/internal/commands/scan.go +++ b/internal/commands/scan.go @@ -32,20 +32,47 @@ import ( ) const ( - failedCreating = "Failed creating a scan" - failedGetting = "Failed showing a scan" - failedGettingTags = "Failed getting tags" - failedDeleting = "Failed deleting a scan" - failedCanceling = "Failed canceling a scan" - failedGettingAll = "Failed listing" - thresholdLog = "%s: Limit = %d, Current = %v" - thresholdMsgLog = "Threshold check finished with status %s : %s" - mbBytes = 1024.0 * 1024.0 - scaType = "sca" - notExploitable = "NOT_EXPLOITABLE" - git = "git" - invalidSSHSource = "provided source does not need a key. Make sure you are defining the right source or remove the flag --ssh-key" - errorUnzippingFile = "an error occurred while unzipping file. Reason: " + failedCreating = "Failed creating a scan" + failedGetting = "Failed showing a scan" + failedGettingTags = "Failed getting tags" + failedDeleting = "Failed deleting a scan" + failedCanceling = "Failed canceling a scan" + failedGettingAll = "Failed listing" + thresholdLog = "%s: Limit = %d, Current = %v" + thresholdMsgLog = "Threshold check finished with status %s : %s" + mbBytes = 1024.0 * 1024.0 + scaType = "sca" + notExploitable = "NOT_EXPLOITABLE" + git = "git" + invalidSSHSource = "provided source does not need a key. Make sure you are defining the right source or remove the flag --ssh-key" + errorUnzippingFile = "an error occurred while unzipping file. Reason: " + containerRun = "run" + containerVolumeFlag = "-v" + containerNameFlag = "--name" + containerRemove = "--rm" + containerImage = "checkmarx/kics:latest" + containerScan = "scan" + containerScanPathFlag = "-p" + containerScanPath = "/path" + containerScanOutputFlag = "-o" + containerScanOutput = "/path" + containerScanFormatFlag = "--report-formats" + containerScanFormatOutput = "json" + noResultsError = "No results available" + kicsExitCode = "exit status 40" + kicsExitCodeNoResults = "exit status 50" + containerStarting = "Starting kics container" + containerFormatInfo = "The report format and output path cannot be overridden." + containerFolderRemoving = "Removing folder in temp" + containerCreateFolderError = "Error creating temporary directory" + containerWriteFolderError = " Error writing file to temporary directory" + containerFileSourceMissing = "--file is required for kics-realtime command" + containerFileSourceIncompatible = ". Provided file is not supported by kics" + containerFileSourceError = " Error reading file" + containerResultsFileFormat = "%s/results.json" + containerVolumeFormat = "%s:/path" + containerTempDirPattern = "kics" + kicsContainerPrefixName = "cli-kics-realtime-" ) var ( @@ -67,6 +94,7 @@ var ( }, ",", ), ) + aditionalParameters []string ) func NewScanCommand( @@ -106,6 +134,8 @@ func NewScanCommand( logsCmd := scanLogsSubCommand(logsWrapper) + kicsRealtimeCmd := scanRealtimeSubCommand() + addFormatFlagToMultipleCommands( []*cobra.Command{listScansCmd, showScanCmd, workflowScanCmd}, printer.FormatTable, printer.FormatList, printer.FormatJSON, @@ -122,10 +152,41 @@ func NewScanCommand( cancelScanCmd, tagsCmd, logsCmd, + kicsRealtimeCmd, ) return scanCmd } +func scanRealtimeSubCommand() *cobra.Command { + kicsContainerID := uuid.New() + viper.Set(commonParams.KicsContainerNameKey, kicsContainerPrefixName+kicsContainerID.String()) + realtimeScanCmd := &cobra.Command{ + Use: "kics-realtime", + Short: "Create and run kics scan", + Long: "The kics-realtime command enables the ability to create, run and retrieve results from a kics scan using a docker image.", + Example: heredoc.Doc( + ` + $ cx scan kics-realtime --file --additional-params --engine + `, + ), + Annotations: map[string]string{ + "command:doc": heredoc.Doc( + ` + `, + ), + }, + RunE: runKicksRealtime(), + } + realtimeScanCmd.PersistentFlags(). + StringSliceVar(&aditionalParameters, commonParams.KicsRealtimeAdditionalParams, []string{}, + "Additional scan options supported by kics. "+ + "Should follow comma separated format. For example : --additional-params -v, --exclude-results,fec62a97d569662093dbb9739360942f") + realtimeScanCmd.PersistentFlags().String(commonParams.KicsRealtimeFile, "", "Path to input file for kics realtime scanner") + realtimeScanCmd.PersistentFlags().String(commonParams.KicsRealtimeEngine, "docker", "Name in the $PATH for the container engine to run kics. Example:podman.") + markFlagAsRequired(realtimeScanCmd, commonParams.KicsRealtimeFile) + return realtimeScanCmd +} + func scanLogsSubCommand(logsWrapper wrappers.LogsWrapper) *cobra.Command { logsCmd := &cobra.Command{ Use: "logs", @@ -916,7 +977,7 @@ func getUploadURLFromSource( } func UnzipFile(f string) (string, error) { - tempDir := os.TempDir() + string(os.PathSeparator) + "cx-unzipped-temp-dir-" + uuid.New().String() + string(os.PathSeparator) + tempDir := filepath.Join(os.TempDir(), "cx-unzipped-temp-dir-") + uuid.New().String() + string(os.PathSeparator) err := os.Mkdir(tempDir, directoryPermission) if err != nil { @@ -1534,6 +1595,29 @@ func runDownloadLogs(logsWrapper wrappers.LogsWrapper) func(*cobra.Command, []st } } +func runKicksRealtime() func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, _ []string) error { + // Create temp location and add it to container volumes + volumeMap, tempDir, err := createKicsScanEnv(cmd) + if err != nil { + return errors.Errorf("%s", err) + } + + // Run kics container + err = runKicsScan(cmd, volumeMap, tempDir, aditionalParameters) + if err != nil { + // Removing temporary dir + logger.PrintIfVerbose(containerFolderRemoving) + os.RemoveAll(tempDir) + return errors.Errorf("%s", err) + } + // Removing temporary dir + logger.PrintIfVerbose(containerFolderRemoving) + os.RemoveAll(tempDir) + return nil + } +} + type scanView struct { ID string `format:"name:Scan ID"` ProjectID string `format:"name:Project ID"` @@ -1596,3 +1680,116 @@ func toScanView(scan *wrappers.ScanResponseModel) *scanView { Origin: origin, } } + +func createKicsScanEnv(cmd *cobra.Command) (volumeMap, kicsDir string, err error) { + kicsDir, err = ioutil.TempDir("", containerTempDirPattern) + if err != nil { + return "", "", errors.New(containerCreateFolderError) + } + kicsFilePath, _ := cmd.Flags().GetString(commonParams.KicsRealtimeFile) + if len(kicsFilePath) < 1 { + return "", "", errors.New(containerFileSourceMissing) + } + if !contains(commonParams.KicsBaseFilters, kicsFilePath) { + return "", "", errors.New(kicsFilePath + containerFileSourceIncompatible) + } + kicsFile, err := ioutil.ReadFile(kicsFilePath) + if err != nil { + return "", "", errors.New(containerFileSourceError) + } + _, file := filepath.Split(kicsFilePath) + destinationFile := fmt.Sprintf("%s/%s", kicsDir, file) + err = ioutil.WriteFile(destinationFile, kicsFile, 0666) + if err != nil { + return "", "", errors.New(containerWriteFolderError) + } + volumeMap = fmt.Sprintf(containerVolumeFormat, kicsDir) + return volumeMap, kicsDir, nil +} + +func contains(s []string, str string) bool { + for _, v := range s { + if strings.Contains(str, v) { + return true + } + } + return false +} + +func readKicsResultsFile(tempDir string) (wrappers.KicsResultsCollection, error) { + // Open and read the file from the temp folder + var resultsModel wrappers.KicsResultsCollection + resultsFile := fmt.Sprintf(containerResultsFileFormat, tempDir) + jsonFile, err := os.Open(resultsFile) + if err != nil { + return resultsModel, err + } + byteValue, err := ioutil.ReadAll(jsonFile) + if err != nil { + return resultsModel, err + } + // Unmarshal into the object KicsResultsCollection + err = json.Unmarshal(byteValue, &resultsModel) + if err != nil { + return wrappers.KicsResultsCollection{}, err + } + return resultsModel, nil +} + +func runKicsScan(cmd *cobra.Command, volumeMap, tempDir string, additionalParameters []string) error { + var errs error + kicsRunArgs := []string{ + containerRun, + containerRemove, + containerVolumeFlag, + volumeMap, + containerNameFlag, + viper.GetString(commonParams.KicsContainerNameKey), + containerImage, + containerScan, + containerScanPathFlag, + containerScanPath, + containerScanOutputFlag, + containerScanOutput, + containerScanFormatFlag, + containerScanFormatOutput, + } + // join the additional parameters + if len(additionalParameters) > 0 { + kicsRunArgs = append(kicsRunArgs, additionalParameters...) + } + logger.PrintIfVerbose(containerStarting) + logger.PrintIfVerbose(containerFormatInfo) + kicsCmd, _ := cmd.Flags().GetString(commonParams.KicsRealtimeEngine) + out, err := exec.Command(kicsCmd, kicsRunArgs...).CombinedOutput() + logger.PrintIfVerbose(string(out)) + /* NOTE: the kics container returns 40 instead of 0 when successful!! This + definitely an incorrect behavior but the following check gets past it. + */ + + // This case is when kics successfully executes returning the expected error code + if err != nil && kicsExitCode == err.Error() { + var resultsModel wrappers.KicsResultsCollection + resultsModel, errs = readKicsResultsFile(tempDir) + if errs != nil { + return errors.Errorf("%s", errs) + } + var resultsJSON []byte + resultsJSON, errs = json.Marshal(resultsModel) + if errs != nil { + return errors.Errorf("%s", errs) + } + fmt.Println(string(resultsJSON)) + } else { + // Case kics returns the noResults error code + if err != nil && kicsExitCodeNoResults == err.Error() { + return errors.Errorf("%s", noResultsError) + } // Need this to get correct error message when the container execution actually fails + if err != nil && kicsExitCodeNoResults != err.Error() { + return errors.Errorf("Check container engine state. Failed: %s", err.Error()) + } + return errors.Errorf("Check input file. Scan failed.") + } + + return nil +} diff --git a/internal/commands/scan_test.go b/internal/commands/scan_test.go index 485c7e7c7..f65a391f9 100644 --- a/internal/commands/scan_test.go +++ b/internal/commands/scan_test.go @@ -3,7 +3,6 @@ package commands import ( - "os" "strings" "testing" @@ -14,13 +13,28 @@ import ( ) const ( - unknownFlag = "unknown flag: --chibutero" - blankSpace = " " - errorMissingBranch = "Failed creating a scan: Please provide a branch" - dummyRepo = "https://github.com/dummyuser/dummy_project.git" - dummySSHRepo = "git@github.com:dummyRepo/dummyProject.git" - errorSourceBadFormat = "Failed creating a scan: Input in bad format: Sources input has bad format: " - scaPathError = "ScaResolver error: exec: \"resolver\": executable file not found in " + unknownFlag = "unknown flag: --chibutero" + blankSpace = " " + errorMissingBranch = "Failed creating a scan: Please provide a branch" + dummyRepo = "https://github.com/dummyuser/dummy_project.git" + dummySSHRepo = "git@github.com:dummyRepo/dummyProject.git" + errorSourceBadFormat = "Failed creating a scan: Input in bad format: Sources input has bad format: " + scaPathError = "ScaResolver error: exec: \"resolver\": executable file not found in " + fileSourceFlag = "--file" + fileSourceValueEmpty = "data/empty.Dockerfile" + fileSourceValue = "data/Dockerfile" + fileSourceIncorrectValue = "data/source.zip" + fileSourceIncorrectValueError = "data/source.zip. Provided file is not supported by kics" + fileSourceError = "flag needs an argument: --file" + engineFlag = "--engine" + engineValue = "docker" + engineError = "flag needs an argument: --engine" + additionalParamsFlag = "--additional-params" + additionalParamsValue = "-v" + additionalParamsError = "flag needs an argument: --additional-params" + scanCommand = "scan" + kicsRealtimeCommand = "kics-realtime" + scanFailed = "Check input file. Scan failed." ) func TestScanHelp(t *testing.T) { @@ -307,15 +321,53 @@ func TestCreateScanFilterZipFile(t *testing.T) { execCmdNilAssertion(t, append(baseArgs, "-s", "data/sources.zip", "--file-filter", "!.java")...) } -func TestAsyncScanWithFile(t *testing.T) { - baseArgs := []string{"scan", "create", "--project-name", "MOCK", "-b", "dummy_branch", "--report-format", "summaryHTML", "-s", dummyRepo, "--async"} +func TestCreateRealtimeKics(t *testing.T) { + baseArgs := []string{scanCommand, kicsRealtimeCommand, fileSourceFlag, fileSourceValue} cmd := createASTTestCommand() err := executeTestCommand(cmd, baseArgs...) assert.NilError(t, err) - _, readError := os.ReadFile("cx_result.html") - defer func() { - err := os.Remove("cx_result.html") - assert.NilError(t, err) - }() - assert.NilError(t, readError) +} + +func TestCreateRealtimeKicsMissingFile(t *testing.T) { + baseArgs := []string{scanCommand, kicsRealtimeCommand, fileSourceFlag} + err := execCmdNotNilAssertion(t, baseArgs...) + assert.Error(t, err, fileSourceError, err.Error()) +} + +func TestCreateRealtimeKicsInvalidFile(t *testing.T) { + baseArgs := []string{scanCommand, kicsRealtimeCommand, fileSourceFlag, fileSourceIncorrectValue} + err := execCmdNotNilAssertion(t, baseArgs...) + assert.Error(t, err, fileSourceIncorrectValueError, err.Error()) +} + +func TestCreateRealtimeKicsWithEngine(t *testing.T) { + baseArgs := []string{scanCommand, kicsRealtimeCommand, fileSourceFlag, fileSourceValue, engineFlag, engineValue} + cmd := createASTTestCommand() + err := executeTestCommand(cmd, baseArgs...) + assert.NilError(t, err) +} + +func TestCreateRealtimeKicsMissingEngine(t *testing.T) { + baseArgs := []string{scanCommand, kicsRealtimeCommand, fileSourceFlag, fileSourceValue, engineFlag} + err := execCmdNotNilAssertion(t, baseArgs...) + assert.Error(t, err, engineError, err.Error()) +} + +func TestCreateRealtimeKicsWithAdditionalParams(t *testing.T) { + baseArgs := []string{scanCommand, kicsRealtimeCommand, fileSourceFlag, fileSourceValue, engineFlag, engineValue, additionalParamsFlag, additionalParamsValue} + cmd := createASTTestCommand() + err := executeTestCommand(cmd, baseArgs...) + assert.NilError(t, err) +} + +func TestCreateRealtimeKicsMissingAdditionalParams(t *testing.T) { + baseArgs := []string{scanCommand, kicsRealtimeCommand, fileSourceFlag, fileSourceValue, additionalParamsFlag} + err := execCmdNotNilAssertion(t, baseArgs...) + assert.Error(t, err, additionalParamsError, err.Error()) +} + +func TestCreateRealtimeKicsFailedScan(t *testing.T) { + baseArgs := []string{scanCommand, kicsRealtimeCommand, fileSourceFlag, fileSourceValueEmpty} + err := execCmdNotNilAssertion(t, baseArgs...) + assert.Error(t, err, scanFailed, err.Error()) } diff --git a/internal/params/filters.go b/internal/params/filters.go index a24425c43..4dc56be44 100644 --- a/internal/params/filters.go +++ b/internal/params/filters.go @@ -125,6 +125,18 @@ var BaseFilters = []string{ "Dockerfile", } +var KicsBaseFilters = []string{ + ".tf", + ".yaml", + ".yml", + ".json", + ".auto.tfvars", + ".terraform.tfvars", + "Dockerfile", + ".proto", + ".dockerfile", +} + var DisabledExclusions = map[string]bool{ ".git": true, } diff --git a/internal/params/flags.go b/internal/params/flags.go index 4cb7ec34a..8f7b33074 100644 --- a/internal/params/flags.go +++ b/internal/params/flags.go @@ -2,112 +2,118 @@ package params // Flags const ( - AgentFlag = "agent" - AgentFlagUsage = "Scan origin name" - DefaultAgent = "ASTCLI" - DebugFlag = "debug" - DebugUsage = "Debug mode with detailed logs" - RetryFlag = "retry" - RetryDefault = 3 - RetryUsage = "Retry requests to AST on connection failure" - RetryDelayFlag = "retry-delay" - RetryDelayDefault = 3 - RetryDelayUsage = "Time between retries in seconds, use with --" + RetryFlag - SourcesFlag = "file-source" - SourcesFlagSh = "s" - TenantFlag = "tenant" - TenantFlagUsage = "Checkmarx tenant" - AsyncFlag = "async" - WaitDelayFlag = "wait-delay" - ScanTimeoutFlag = "scan-timeout" - SourceDirFilterFlag = "file-filter" - SourceDirFilterFlagSh = "f" - IncludeFilterFlag = "file-include" - IncludeFilterFlagSh = "i" - ProjectIDFlag = "project-id" - BranchFlag = "branch" - BranchFlagSh = "b" - ScanIDFlag = "scan-id" - BranchFlagUsage = "Branch to scan" - MainBranchFlag = "branch" - ScaResolverFlag = "sca-resolver" - ScaResolverParamsFlag = "sca-resolver-params" - AccessKeyIDFlag = "client-id" - AccessKeySecretFlag = "client-secret" - AccessKeyIDFlagUsage = "The OAuth2 client ID" - AccessKeySecretFlagUsage = "The OAuth2 client secret" - InsecureFlag = "insecure" - InsecureFlagUsage = "Ignore TLS certificate validations" - ScanInfoFormatFlag = "scan-info-format" - FormatFlag = "format" - FormatFlagUsageFormat = "Format for the output. One of %s" - FilterFlag = "filter" - BaseURIFlag = "base-uri" - ProxyFlag = "proxy" - ProxyFlagUsage = "Proxy server to send communication through" - ProxyTypeFlag = "proxy-auth-type" - ProxyTypeFlagUsage = "Proxy authentication type, (basic or ntlm)" - TimeoutFlag = "timeout" - TimeoutFlagUsage = "Timeout for network activity, (default 5 seconds)" - NtlmProxyDomainFlag = "proxy-ntlm-domain" - NtlmProxyDomainFlagUsage = "Window domain when using NTLM proxy" - BaseURIFlagUsage = "The base system URI" - BaseAuthURIFlag = "base-auth-uri" - BaseAuthURIFlagUsage = "The base system IAM URI" - AstAPIKeyFlag = "apikey" - AstAPIKeyUsage = "The API Key to login to AST" - ClientRolesFlag = "roles" - ClientRolesSh = "r" - ClientDescriptionFlag = "description" - ClientDescriptionSh = "d" - UsernameFlag = "username" - UsernameSh = "u" - PasswordFlag = "password" - PasswordSh = "p" - ProfileFlag = "profile" - ProfileFlagUsage = "The default configuration profile" - Help = "help" - TargetFlag = "output-name" - TargetPathFlag = "output-path" - TargetFormatFlag = "report-format" - ProjectName = "project-name" - ScanTypes = "scan-types" - ScanTypeFlag = "scan-type" - TagList = "tags" - GroupList = "groups" - ProjectGroupList = "project-groups" - ProjectTagList = "project-tags" - IncrementalSast = "sast-incremental" - PresetName = "sast-preset-name" - Threshold = "threshold" - KeyValuePairSize = 2 - WaitDelayDefault = 5 - SimilarityIDFlag = "similarity-id" - SeverityFlag = "severity" - StateFlag = "state" - CommentFlag = "comment" - LanguageFlag = "language" - VulnerabilityTypeFlag = "vulnerability-type" - CweIDFlag = "cwe-id" - SCMTokenFlag = "token" - AzureTokenUsage = "Azure DevOps personal access token. Requires “Connected server” and “Code“ scope." - GithubTokenUsage = "GitHub OAuth token. Requires “Repo” scope and organization SSO authorization, if enforced by the organization" - GitLabTokenUsage = "GitLab OAuth token" - BotCount = "Note: dependabot is not counted but other bots might be considered as contributors." - URLFlag = "url" - GitLabURLFlag = "url-gitlab" - URLFlagUsage = "API base URL" - QueryIDFlag = "query-id" - SSHKeyFlag = "ssh-key" - RepoURLFlag = "repo-url" - AstToken = "ast-token" - SSHValue = "ssh-value" - SastFilterFlag = "sast-filter" - SastFilterUsage = "SAST filter" - KicsFilterFlag = "kics-filter" - KicsFilterUsage = "KICS filter" - ScaFilterFlag = "sca-filter" - ScaFilterUsage = "SCA filter" + AgentFlag = "agent" + AgentFlagUsage = "Scan origin name" + DefaultAgent = "ASTCLI" + DebugFlag = "debug" + DebugUsage = "Debug mode with detailed logs" + RetryFlag = "retry" + RetryDefault = 3 + RetryUsage = "Retry requests to AST on connection failure" + RetryDelayFlag = "retry-delay" + RetryDelayDefault = 3 + RetryDelayUsage = "Time between retries in seconds, use with --" + RetryFlag + SourcesFlag = "file-source" + SourcesFlagSh = "s" + TenantFlag = "tenant" + TenantFlagUsage = "Checkmarx tenant" + AsyncFlag = "async" + WaitDelayFlag = "wait-delay" + ScanTimeoutFlag = "scan-timeout" + SourceDirFilterFlag = "file-filter" + SourceDirFilterFlagSh = "f" + IncludeFilterFlag = "file-include" + IncludeFilterFlagSh = "i" + ProjectIDFlag = "project-id" + BranchFlag = "branch" + BranchFlagSh = "b" + ScanIDFlag = "scan-id" + BranchFlagUsage = "Branch to scan" + MainBranchFlag = "branch" + ScaResolverFlag = "sca-resolver" + ScaResolverParamsFlag = "sca-resolver-params" + AccessKeyIDFlag = "client-id" + AccessKeySecretFlag = "client-secret" + AccessKeyIDFlagUsage = "The OAuth2 client ID" + AccessKeySecretFlagUsage = "The OAuth2 client secret" + InsecureFlag = "insecure" + InsecureFlagUsage = "Ignore TLS certificate validations" + ScanInfoFormatFlag = "scan-info-format" + FormatFlag = "format" + FormatFlagUsageFormat = "Format for the output. One of %s" + FilterFlag = "filter" + BaseURIFlag = "base-uri" + ProxyFlag = "proxy" + ProxyFlagUsage = "Proxy server to send communication through" + ProxyTypeFlag = "proxy-auth-type" + ProxyTypeFlagUsage = "Proxy authentication type, (basic or ntlm)" + TimeoutFlag = "timeout" + TimeoutFlagUsage = "Timeout for network activity, (default 5 seconds)" + NtlmProxyDomainFlag = "proxy-ntlm-domain" + NtlmProxyDomainFlagUsage = "Window domain when using NTLM proxy" + BaseURIFlagUsage = "The base system URI" + BaseAuthURIFlag = "base-auth-uri" + BaseAuthURIFlagUsage = "The base system IAM URI" + AstAPIKeyFlag = "apikey" + AstAPIKeyUsage = "The API Key to login to AST" + ClientRolesFlag = "roles" + ClientRolesSh = "r" + ClientDescriptionFlag = "description" + ClientDescriptionSh = "d" + UsernameFlag = "username" + UsernameSh = "u" + PasswordFlag = "password" + PasswordSh = "p" + ProfileFlag = "profile" + ProfileFlagUsage = "The default configuration profile" + Help = "help" + TargetFlag = "output-name" + TargetPathFlag = "output-path" + TargetFormatFlag = "report-format" + ProjectName = "project-name" + ScanTypes = "scan-types" + ScanTypeFlag = "scan-type" + KicsRealtimeFile = "file" + KicsRealtimeEngine = "engine" + KicsRealtimeAdditionalParams = "additional-params" + TagList = "tags" + GroupList = "groups" + ProjectGroupList = "project-groups" + ProjectTagList = "project-tags" + IncrementalSast = "sast-incremental" + PresetName = "sast-preset-name" + Threshold = "threshold" + KeyValuePairSize = 2 + WaitDelayDefault = 5 + SimilarityIDFlag = "similarity-id" + SeverityFlag = "severity" + StateFlag = "state" + CommentFlag = "comment" + LanguageFlag = "language" + VulnerabilityTypeFlag = "vulnerability-type" + CweIDFlag = "cwe-id" + SCMTokenFlag = "token" + AzureTokenUsage = "Azure DevOps personal access token. Requires “Connected server” and “Code“ scope." + GithubTokenUsage = "GitHub OAuth token. Requires “Repo” scope and organization SSO authorization, if enforced by the organization" + GitLabTokenUsage = "GitLab OAuth token" + BotCount = "Note: dependabot is not counted but other bots might be considered as contributors." + URLFlag = "url" + GitLabURLFlag = "url-gitlab" + URLFlagUsage = "API base URL" + QueryIDFlag = "query-id" + SSHKeyFlag = "ssh-key" + RepoURLFlag = "repo-url" + AstToken = "ast-token" + SSHValue = "ssh-value" + KicsContainerNameKey = "kics-container-name" + + // INDIVIDUAL FILTER FLAGS + SastFilterFlag = "sast-filter" + SastFilterUsage = "SAST filter" + KicsFilterFlag = "kics-filter" + KicsFilterUsage = "KICS filter" + ScaFilterFlag = "sca-filter" + ScaFilterUsage = "SCA filter" ) // Parameter values diff --git a/internal/wrappers/predicates-http.go b/internal/wrappers/predicates-http.go index fe175d04b..34cf8ae47 100644 --- a/internal/wrappers/predicates-http.go +++ b/internal/wrappers/predicates-http.go @@ -42,7 +42,7 @@ func (r *ResultsPredicatesHTTPWrapper) GetAllPredicatesForSimilarityID(similarit return nil, nil, errors.Errorf(invalidScanType, scannerType) } - logger.PrintIfVerbose(fmt.Sprintf("Fetching the predicate history for SimilarityId : %s", similarityID)) + logger.PrintIfVerbose(fmt.Sprintf("Fetching the predicate history for SimilarityID : %s", similarityID)) r.SetPath(triageAPIPath) var request = "/" + similarityID + "?project-ids=" + projectID diff --git a/internal/wrappers/scan-kics-realtime.go b/internal/wrappers/scan-kics-realtime.go new file mode 100644 index 000000000..12ed10996 --- /dev/null +++ b/internal/wrappers/scan-kics-realtime.go @@ -0,0 +1,38 @@ +package wrappers + +type KicsResultsCollection struct { + Version string `json:"kics_version"` + Count uint `json:"total_counter"` + Results []KicsQueries `json:"queries"` + KicsSummary KicsSummary `json:"severity_counters"` +} + +type KicsQueries struct { + QueryName string `json:"query_name"` + QueryID string `json:"query_id"` + Severity string `json:"severity"` + Platform string `json:"platform"` + Category string `json:"category"` + Description string `json:"description"` + QueryURL string `json:"query_url"` + Locations []KicsFiles `json:"files"` +} + +type KicsFiles struct { + Filename string `json:"file_name"` + SimilarityID string `json:"similarity_id"` + Line uint `json:"line"` + IssueType string `json:"issue_type"` + SearchKey string `json:"search_key"` + SearchLine uint `json:"search_line"` + SearchValue string `json:"search_value"` + ExpectedValue string `json:"expected_value"` + ActualValue string `json:"actual_value"` +} + +type KicsSummary struct { + High uint `json:"HIGH"` + Info uint `json:"INFO"` + Low uint `json:"LOW"` + Medium uint `json:"MEDIUM"` +} diff --git a/test/integration/data/mock.dockerfile b/test/integration/data/mock.dockerfile new file mode 100644 index 000000000..46598e803 --- /dev/null +++ b/test/integration/data/mock.dockerfile @@ -0,0 +1,16 @@ +FROM openjdk:11.0.1-jre-slim-stretch + +ARG webwolf_version=v8.0.0-SNAPSHOT + +RUN \ + apt-get update && apt-get install && \ + useradd --home-dir /home/webwolf --create-home -U webwolf + +USER webwolf +COPY target/webwolf-${webwolf_version}.jar /home/webwolf/webwolf.jar +COPY start-webwolf.sh /home/webwolf + +EXPOSE 9090 + +ENTRYPOINT ["/home/webwolf/start-webwolf.sh"] +CMD ["--server.port=9090", "--server.address=0.0.0.0"] \ No newline at end of file diff --git a/test/integration/scan_test.go b/test/integration/scan_test.go index fdb604aa6..4bf6f2bc1 100644 --- a/test/integration/scan_test.go +++ b/test/integration/scan_test.go @@ -23,6 +23,23 @@ import ( "gotest.tools/assert" ) +const ( + fileSourceFlag = "--file" + fileSourceValue = "data/Dockerfile" + fileSourceValueVul = "data/mock.dockerfile" + fileSourceIncorrectValue = "data/source.zip" + fileSourceIncorrectValueError = "data/source.zip. Provided file is not supported by kics" + fileSourceError = "flag needs an argument: --file" + engineFlag = "--engine" + engineValue = "docker" + engineError = "flag needs an argument: --engine" + additionalParamsFlag = "--additional-params" + additionalParamsValue = "-v" + additionalParamsError = "flag needs an argument: --additional-params" + scanCommand = "scan" + kicsRealtimeCommand = "kics-realtime" +) + // Type for scan workflow response, used to assert the validity of the command's response type ScanWorkflowResponse struct { Source string `json:"source"` @@ -579,23 +596,50 @@ func TestCreateScanFilterZipFile(t *testing.T) { executeCmdWithTimeOutNilAssertion(t, "Scan must complete successfully", 4*time.Minute, args...) } -func TestAsynCreateScan(t *testing.T) { +func TestRunKicsScan(t *testing.T) { + outputBuffer := executeCmdNilAssertion( + t, "Runing KICS real-time command should pass", + scanCommand, kicsRealtimeCommand, + flag(params.KicsRealtimeFile), fileSourceValueVul) + + assert.Assert(t, outputBuffer != nil, "Scan must complete successfully") +} + +func TestRunKicsScanWithouResults(t *testing.T) { args := []string{ - flag(params.AsyncFlag), - flag(params.TargetFormatFlag), "summaryHTML", + scanCommand, kicsRealtimeCommand, + flag(params.KicsRealtimeFile), fileSourceValue, } - scanID, projectID := executeCreateScan(t, append(getCreateArgs(Dir, map[string]string{}, "sast,kics"), args...)) - assert.Assert(t, scanID != "", "Scan ID should not be empty") - assert.Assert(t, projectID != "", "Project ID should not be empty") - file, err := ioutil.ReadFile("cx_result.html") - defer func() { - os.Remove("cx_result.html") - }() - if err != nil { - t.Errorf("Error reading file: %v", err) + + err, _ := executeCommand(t, args...) + assertError(t, err, "No results available") +} + +func TestRunKicsScanWithoutFileSources(t *testing.T) { + args := []string{ + scanCommand, kicsRealtimeCommand, } - contents := string(file) - assert.Assert(t, contents != "", "Contents should not be empty") - executeCmdNilAssertion(t, "Cancel should pass", "scan", "cancel", flag(params.ScanIDFlag), scanID) + err, _ := executeCommand(t, args...) + assertError(t, err, "required flag(s) \"file\" not set") +} + +func TestRunKicsScanWithEngine(t *testing.T) { + outputBuffer := executeCmdNilAssertion( + t, "Runing KICS real-time with engine command should pass", + scanCommand, kicsRealtimeCommand, + flag(params.KicsRealtimeFile), fileSourceValueVul, + flag(params.KicsRealtimeEngine),engineValue,) + + assert.Assert(t, outputBuffer != nil, "Scan must complete successfully") +} + +func TestRunKicsScanWithAdditionalParams(t *testing.T) { + outputBuffer := executeCmdNilAssertion( + t, "Runing KICS real-time with additional params command should pass", + scanCommand, kicsRealtimeCommand, + flag(params.KicsRealtimeFile), fileSourceValueVul, + flag(params.KicsRealtimeEngine),engineValue, + flag(params.KicsRealtimeAdditionalParams),additionalParamsValue) + assert.Assert(t, outputBuffer != nil, "Scan must complete successfully") }