From 0d97f431147acafb7b8bf8e39844e1375776c957 Mon Sep 17 00:00:00 2001 From: AlvoBen <144705560+AlvoBen@users.noreply.github.com> Date: Mon, 24 Jun 2024 16:19:26 +0300 Subject: [PATCH] CLI | Add Vorpal Engine Logic (AST-38523) (#767) * add vorpal engine logic * add vorpal engine logic * fix linter errors and integration test compilation * fix linter errors and integration test compilation * fix integration test compilation * fix linter * fix unit tests and added mock vorpal wrapper * fix integration tests compilation * fix integration tests compilation * fix integration tests compilation * fix unit test * fix unit test * fix unit test * change vorpalWrapper initialization logic to handle edge cases * change vorpalWrapper initialization logic to handle edge cases * change vorpalWrapper initialization logic to handle edge cases * added different process configuration to mac-linux and windows * added unit tests * add testify to depguard * fix unit tests * added integration tests * extract method of vorpal installation logic * added integration tests * added integration tests * fix unit tests * fix unit tests * fix tests and move ConfigureIndependentProcess functionality to osinstaller * fix tests and move and resolve conversations * added packageEnforcementEnabled FF check in IsAllowedEngine func * added packageEnforcementEnabled FF check in IsAllowedEngine func * chenge jwtMock * resolve conversations * resolve conversations * resolve linter * resolve linter * resolve linter * resolve linter * resolve conversation * add integration test license check * add test * add test * fix test * increase time initializing vorpal service * fix vorpal installation condition * add integration test * increase timeout for waiting for server to 5 seconds * Added serving field to avoid unnecessary health check calls * resolve conversations * add imports * resolve conversations * resolve conversations * revert omitempty in scanDetails * revert omitempty in scanDetails * update osinstaller --------- Co-authored-by: AlvoBen --- .golangci.yml | 2 + go.mod | 1 + go.sum | 2 + internal/commands/scan.go | 7 +- internal/commands/vorpal-engine.go | 122 ---------- internal/commands/vorpal/vorpal-engine.go | 42 ++++ .../{ => vorpal}/vorpal-engine_test.go | 66 +++--- internal/commands/vorpal/vorpal_test.go | 2 +- internal/constants/errors/errors.go | 1 + internal/params/binds.go | 1 + internal/params/envs.go | 1 + internal/params/flags.go | 1 + internal/params/keys.go | 1 + .../services/osinstaller/linux-mac-utils.go | 8 + internal/services/osinstaller/os-installer.go | 3 +- .../services/osinstaller/windows-utils.go | 8 + internal/services/vorpal.go | 213 +++++++++++++++++- internal/services/vorpal_test.go | 68 ++++++ internal/wrappers/grpcs/vorpal-grpc.go | 38 +++- internal/wrappers/grpcs/vorpal.go | 14 +- internal/wrappers/jwt-helper.go | 39 +++- internal/wrappers/mock/jwt-helper-mock.go | 14 +- internal/wrappers/mock/vorpal-mock.go | 92 ++++++++ test/integration/vorpal-engine_test.go | 209 +++++++++++++++-- 24 files changed, 740 insertions(+), 215 deletions(-) delete mode 100644 internal/commands/vorpal-engine.go create mode 100644 internal/commands/vorpal/vorpal-engine.go rename internal/commands/{ => vorpal}/vorpal-engine_test.go (66%) create mode 100644 internal/services/vorpal_test.go create mode 100644 internal/wrappers/mock/vorpal-mock.go diff --git a/.golangci.yml b/.golangci.yml index db742e170..77c542c00 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -13,6 +13,8 @@ linters-settings: - github.com/pkg/errors - github.com/google - github.com/MakeNowJust/heredoc + - github.com/jsumners/go-getport + - github.com/stretchr/testify/assert dupl: threshold: 500 funlen: diff --git a/go.mod b/go.mod index 2f6505b0d..03de2110b 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/uuid v1.6.0 github.com/gookit/color v1.5.4 + github.com/jsumners/go-getport v1.0.0 github.com/mssola/user_agent v0.6.0 github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.8.0 diff --git a/go.sum b/go.sum index 38e76693a..95c1eff03 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jsumners/go-getport v1.0.0 h1:d11eDaP25dKKoJRAFeBrchCayceft735pDSTFCEdkb4= +github.com/jsumners/go-getport v1.0.0/go.mod h1:KpeJgwNSkpuXuoGhJ2Hgl5QJqWbLG1m0jY2rQsYUTIE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= diff --git a/internal/commands/scan.go b/internal/commands/scan.go index 836768659..6126a6c85 100644 --- a/internal/commands/scan.go +++ b/internal/commands/scan.go @@ -21,6 +21,7 @@ import ( "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" + "github.com/checkmarx/ast-cli/internal/commands/vorpal" "github.com/checkmarx/ast-cli/internal/constants" errorConstants "github.com/checkmarx/ast-cli/internal/constants/errors" exitCodes "github.com/checkmarx/ast-cli/internal/constants/exit-codes" @@ -176,7 +177,7 @@ func NewScanCommand( showScanCmd := scanShowSubCommand(scansWrapper) - scanVorpalCmd := scanVorpalSubCommand() + scanVorpalCmd := scanVorpalSubCommand(jwtWrapper, featureFlagsWrapper) workflowScanCmd := scanWorkflowSubCommand(scansWrapper) @@ -389,7 +390,7 @@ func scanShowSubCommand(scansWrapper wrappers.ScansWrapper) *cobra.Command { return showScanCmd } -func scanVorpalSubCommand() *cobra.Command { +func scanVorpalSubCommand(jwtWrapper wrappers.JWTWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper) *cobra.Command { scanVorpalCmd := &cobra.Command{ Hidden: true, Use: "vorpal", @@ -407,7 +408,7 @@ func scanVorpalSubCommand() *cobra.Command { `, ), }, - RunE: runScanVorpalCommand(), + RunE: vorpal.RunScanVorpalCommand(jwtWrapper, featureFlagsWrapper), } scanVorpalCmd.PersistentFlags().Bool(commonParams.VorpalLatestVersion, false, diff --git a/internal/commands/vorpal-engine.go b/internal/commands/vorpal-engine.go deleted file mode 100644 index 9dcac1ae7..000000000 --- a/internal/commands/vorpal-engine.go +++ /dev/null @@ -1,122 +0,0 @@ -package commands - -import ( - "path/filepath" - - "github.com/checkmarx/ast-cli/internal/commands/util/printer" - errorConstants "github.com/checkmarx/ast-cli/internal/constants/errors" - commonParams "github.com/checkmarx/ast-cli/internal/params" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -func runScanVorpalCommand() func(cmd *cobra.Command, args []string) error { - return func(cmd *cobra.Command, args []string) error { - vorpalLatestVersion, _ := cmd.Flags().GetBool(commonParams.VorpalLatestVersion) - fileSourceFlag, _ := cmd.Flags().GetString(commonParams.SourcesFlag) - - scanResult, err := ExecuteVorpalScan(fileSourceFlag, vorpalLatestVersion) - if err != nil { - return err - } - - err = printer.Print(cmd.OutOrStdout(), scanResult, printer.FormatJSON) - if err != nil { - return err - } - - return nil - } -} - -func ExecuteVorpalScan(fileSourceFlag string, vorpalUpdateVersion bool) (*ScanResult, error) { - if fileSourceFlag == "" { - return nil, nil - } - - if filepath.Ext(fileSourceFlag) == "" { - return nil, errors.New(errorConstants.FileExtensionIsRequired) - } - - //TODO: add vorpal scan logic here - if vorpalUpdateVersion { - return ReturnSuccessfulResponseMock(), nil - } - //TODO: returning mock failure just to test the vorpalUpdateVersion flag for now - return ReturnFailureResponseMock(), nil -} - -func ReturnSuccessfulResponseMock() *ScanResult { - return &ScanResult{ - RequestID: "1234567890", - Status: true, - Message: "Scan completed successfully.", - ScanDetails: []ScanDetail{ - { - Language: "Python", - RuleName: "Stored XSS", - Severity: "High", - FileName: "python-vul-file.py", - Line: 37, - RemediationAdvise: "Fully encode all dynamic data, regardless of source, before embedding it in output.", - Description: "The method undefined embeds untrusted data in generated output with write, at line 80 of /python-vul-file.py." + - "This untrusted data is embedded into the output without proper sanitization or encoding, enabling an attacker to inject malicious code into the generated web-page." + - "The attacker would be able to alter the returned web page by saving malicious data in a data-store ahead of time." + - "The attacker's modified data is then read from the database by the undefined method with read, at line 37 of /python-vul-file.py." + - "This untrusted data then flows through the code straight to the output web page, without sanitization. This can enable a Stored Cross-Site Scripting (XSS) attack.", - }, - { - Language: "Python", - RuleName: "Missing HSTS Header", - Severity: "Medium", - FileName: "python-vul-file.py", - Line: 76, - RemediationAdvise: "Before setting the HSTS header - consider the implications it may have: Forcing HTTPS will prevent any future use of HTTP", - Description: "The web-application does not define an HSTS header, leaving it vulnerable to attack.", - }, - }, - } -} - -func ReturnFailureResponseMock() *ScanResult { - return &ScanResult{ - RequestID: "some-request-id", - Status: false, - Message: "Scan failed.", - Error: &Error{InternalError, "An internal error occurred."}, - } -} - -type ScanResult struct { - RequestID string `json:"request_id"` - Status bool `json:"status"` - Message string `json:"message"` - ScanDetails []ScanDetail `json:"scan_details"` - Error *Error `json:"error"` -} - -type ScanDetail struct { - RuleID uint32 `json:"rule_id"` - Language string `json:"language"` - RuleName string `json:"rule_name"` - Severity string `json:"severity"` - FileName string `json:"file_name"` - Line uint32 `json:"line"` - Length uint32 `json:"length"` - ProblematicLine uint32 `json:"problematic_line"` - RemediationAdvise string `json:"remediation_advise"` - Description string `json:"description"` -} - -type Error struct { - Code ErrorCode `json:"code"` - Description string `json:"description"` -} - -type ErrorCode int32 - -const ( - UnknownError = 0 - InvalidRequest = 1 - InternalError = 2 -) diff --git a/internal/commands/vorpal/vorpal-engine.go b/internal/commands/vorpal/vorpal-engine.go new file mode 100644 index 000000000..01f317658 --- /dev/null +++ b/internal/commands/vorpal/vorpal-engine.go @@ -0,0 +1,42 @@ +package vorpal + +import ( + "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/checkmarx/ast-cli/internal/wrappers/grpcs" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func RunScanVorpalCommand(jwtWrapper wrappers.JWTWrapper, featureFlagsWrapper wrappers.FeatureFlagsWrapper) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + vorpalLatestVersion, _ := cmd.Flags().GetBool(commonParams.VorpalLatestVersion) + fileSourceFlag, _ := cmd.Flags().GetString(commonParams.SourcesFlag) + agent, _ := cmd.Flags().GetString(commonParams.AgentFlag) + var port = viper.GetInt(commonParams.VorpalPortKey) + vorpalWrapper := grpcs.NewVorpalGrpcWrapper(port) + vorpalParams := services.VorpalScanParams{ + FilePath: fileSourceFlag, + VorpalUpdateVersion: vorpalLatestVersion, + IsDefaultAgent: agent == commonParams.DefaultAgent, + } + wrapperParams := services.VorpalWrappersParam{ + JwtWrapper: jwtWrapper, + FeatureFlagsWrapper: featureFlagsWrapper, + VorpalWrapper: vorpalWrapper, + } + scanResult, err := services.CreateVorpalScanRequest(vorpalParams, wrapperParams) + if err != nil { + return err + } + + err = printer.Print(cmd.OutOrStdout(), scanResult, printer.FormatJSON) + if err != nil { + return err + } + + return nil + } +} diff --git a/internal/commands/vorpal-engine_test.go b/internal/commands/vorpal/vorpal-engine_test.go similarity index 66% rename from internal/commands/vorpal-engine_test.go rename to internal/commands/vorpal/vorpal-engine_test.go index a4fe6eb5b..9349a9d8d 100644 --- a/internal/commands/vorpal-engine_test.go +++ b/internal/commands/vorpal/vorpal-engine_test.go @@ -1,12 +1,14 @@ -package commands +package vorpal import ( "reflect" "testing" "github.com/checkmarx/ast-cli/internal/commands/util/printer" - errorConstants "github.com/checkmarx/ast-cli/internal/constants/errors" commonParams "github.com/checkmarx/ast-cli/internal/params" + "github.com/checkmarx/ast-cli/internal/services" + "github.com/checkmarx/ast-cli/internal/wrappers/grpcs" + "github.com/checkmarx/ast-cli/internal/wrappers/mock" "github.com/spf13/cobra" ) @@ -18,7 +20,7 @@ func Test_ExecuteVorpalScan(t *testing.T) { tests := []struct { name string args args - want *ScanResult + want *grpcs.ScanResult wantErr bool wantErrMsg string }{ @@ -28,54 +30,53 @@ func Test_ExecuteVorpalScan(t *testing.T) { fileSourceFlag: "", vorpalUpdateVersion: true, }, - want: nil, - wantErr: false, - }, - { - name: "Test path to file without extension", - args: args{ - fileSourceFlag: "data/python-vul-file", - vorpalUpdateVersion: false, + want: &grpcs.ScanResult{ + Message: services.FilePathNotProvided, }, - want: nil, - wantErr: true, - wantErrMsg: errorConstants.FileExtensionIsRequired, + wantErr: false, }, { name: "Test with valid flags. vorpalUpdateVersion set to true", args: args{ - fileSourceFlag: "data/python-vul-file.py", + fileSourceFlag: "../data/python-vul-file.py", vorpalUpdateVersion: true, }, - //TODO: update mocks when there's a real engine - want: ReturnSuccessfulResponseMock(), + want: mock.ReturnSuccessfulResponseMock(), wantErr: false, }, { name: "Test with valid flags. vorpalUpdateVersion set to false", args: args{ - fileSourceFlag: "data/python-vul-file.py", + fileSourceFlag: "../data/python-vul-file.py", vorpalUpdateVersion: false, }, - //TODO: update mocks when there's a real engine - want: ReturnFailureResponseMock(), + want: mock.ReturnSuccessfulResponseMock(), wantErr: false, }, { - name: "Test with valid flags and no vulnerabilities in file", + name: "Test with valid flags. vorpal scan failed", args: args{ - fileSourceFlag: "data/csharp-no-vul.cs", + fileSourceFlag: "../data/csharp-no-vul.cs", vorpalUpdateVersion: false, }, - //TODO: update mocks when there's a real engine - want: ReturnFailureResponseMock(), + want: mock.ReturnFailureResponseMock(), wantErr: false, }, } for _, tt := range tests { ttt := tt t.Run(ttt.name, func(t *testing.T) { - got, err := ExecuteVorpalScan(ttt.args.fileSourceFlag, ttt.args.vorpalUpdateVersion) + vorpalParams := services.VorpalScanParams{ + FilePath: ttt.args.fileSourceFlag, + VorpalUpdateVersion: ttt.args.vorpalUpdateVersion, + IsDefaultAgent: true, + } + wrapperParams := services.VorpalWrappersParam{ + JwtWrapper: &mock.JWTMockWrapper{}, + FeatureFlagsWrapper: &mock.FeatureFlagsMockWrapper{}, + VorpalWrapper: &mock.VorpalMockWrapper{}, + } + got, err := services.CreateVorpalScanRequest(vorpalParams, wrapperParams) if (err != nil) != ttt.wantErr { t.Errorf("executeVorpalScan() error = %v, wantErr %v", err, ttt.wantErr) return @@ -96,7 +97,7 @@ func Test_runScanVorpalCommand(t *testing.T) { sourceFlag string engineFlag bool wantErr bool - want *ScanResult + want *grpcs.ScanResult wantErrMsg string }{ { @@ -106,13 +107,6 @@ func Test_runScanVorpalCommand(t *testing.T) { wantErr: false, want: nil, }, - { - name: "Test with file without extension", - sourceFlag: "data/python-vul-file", - engineFlag: true, - wantErr: true, - wantErrMsg: errorConstants.FileExtensionIsRequired, - }, { name: "Test with valid fileSource Flag and vorpalUpdateVersion flag set false ", sourceFlag: "data/python-vul-file.py", @@ -135,14 +129,14 @@ func Test_runScanVorpalCommand(t *testing.T) { cmd.Flags().String(commonParams.SourcesFlag, ttt.sourceFlag, "") cmd.Flags().Bool(commonParams.VorpalLatestVersion, ttt.engineFlag, "") cmd.Flags().String(commonParams.FormatFlag, printer.FormatJSON, "") - runFunc := runScanVorpalCommand() + runFunc := RunScanVorpalCommand(&mock.JWTMockWrapper{}, &mock.FeatureFlagsMockWrapper{}) err := runFunc(cmd, []string{}) if (err != nil) != ttt.wantErr { - t.Errorf("runScanVorpalCommand() error = %v, wantErr %v", err, ttt.wantErr) + t.Errorf("RunScanVorpalCommand() error = %v, wantErr %v", err, ttt.wantErr) return } if ttt.wantErr && err.Error() != ttt.wantErrMsg { - t.Errorf("runScanVorpalCommand() error message = %v, wantErrMsg %v", err.Error(), ttt.wantErrMsg) + t.Errorf("RunScanVorpalCommand() error message = %v, wantErrMsg %v", err.Error(), ttt.wantErrMsg) } }) } diff --git a/internal/commands/vorpal/vorpal_test.go b/internal/commands/vorpal/vorpal_test.go index 47b698e15..42a838b3e 100644 --- a/internal/commands/vorpal/vorpal_test.go +++ b/internal/commands/vorpal/vorpal_test.go @@ -1,4 +1,4 @@ -package scarealtime +package vorpal import ( "os" diff --git a/internal/constants/errors/errors.go b/internal/constants/errors/errors.go index f030b941a..7a2cb1f1f 100644 --- a/internal/constants/errors/errors.go +++ b/internal/constants/errors/errors.go @@ -19,6 +19,7 @@ const ( SarifInvalidFileExtension = "Invalid file extension. Supported extensions are .sarif and .zip containing sarif files." ImportSarifFileError = "There was a problem importing the SARIF file. Please contact support for further details." ImportSarifFileErrorMessageWithMessage = "There was a problem importing the SARIF file. Please contact support for further details with the following error code: %d %s" + NoVorpalLicense = "User doesn't have \"AI Protection\" license" // Vorpal Engine FileExtensionIsRequired = "file must have an extension" diff --git a/internal/params/binds.go b/internal/params/binds.go index a15982f8e..ade04407a 100644 --- a/internal/params/binds.go +++ b/internal/params/binds.go @@ -63,4 +63,5 @@ var EnvVarsBinds = []struct { {PolicyEvaluationPathKey, PolicyEvaluationPathEnv, "api/policy_management_service_uri/evaluation"}, {AccessManagementPathKey, AccessManagementPathEnv, "api/access-management"}, {ByorPathKey, ByorPathEnv, "api/byor"}, + {VorpalPortKey, VorpalPortEnv, ""}, } diff --git a/internal/params/envs.go b/internal/params/envs.go index c71d72653..fbaa1fe89 100644 --- a/internal/params/envs.go +++ b/internal/params/envs.go @@ -62,4 +62,5 @@ const ( AccessManagementPathEnv = "CX_ACCESS_MANAGEMENT_PATH" ByorPathEnv = "CX_BYOR_PATH" IgnoreProxyEnv = "CX_IGNORE_PROXY" + VorpalPortEnv = "CX_VORPAL_PORT" ) diff --git a/internal/params/flags.go b/internal/params/flags.go index 1e53edb1e..14f1343ee 100644 --- a/internal/params/flags.go +++ b/internal/params/flags.go @@ -228,6 +228,7 @@ const ( SastType = "sast" KicsType = "kics" APISecurityType = "api-security" + AIProtectionType = "AI Protection" ContainersType = "containers" APIDocumentationFlag = "apisec-swagger-filter" IacType = "iac-security" diff --git a/internal/params/keys.go b/internal/params/keys.go index 369d5aba4..44cedfda5 100644 --- a/internal/params/keys.go +++ b/internal/params/keys.go @@ -62,4 +62,5 @@ var ( PolicyEvaluationPathKey = strings.ToLower(PolicyEvaluationPathEnv) AccessManagementPathKey = strings.ToLower(AccessManagementPathEnv) ByorPathKey = strings.ToLower(ByorPathEnv) + VorpalPortKey = strings.ToLower(VorpalPortEnv) ) diff --git a/internal/services/osinstaller/linux-mac-utils.go b/internal/services/osinstaller/linux-mac-utils.go index a0c0075ea..e68b930e9 100644 --- a/internal/services/osinstaller/linux-mac-utils.go +++ b/internal/services/osinstaller/linux-mac-utils.go @@ -10,7 +10,9 @@ import ( "io/fs" "log" "os" + "os/exec" "path/filepath" + "syscall" "github.com/checkmarx/ast-cli/internal/logger" ) @@ -84,3 +86,9 @@ func extractFiles(installationConfiguration *InstallationConfiguration, tarReade } return nil } + +func ConfigureIndependentProcess(cmd *exec.Cmd) { + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setsid: true, + } +} diff --git a/internal/services/osinstaller/os-installer.go b/internal/services/osinstaller/os-installer.go index 6d00bd3e2..b508125c4 100644 --- a/internal/services/osinstaller/os-installer.go +++ b/internal/services/osinstaller/os-installer.go @@ -6,7 +6,6 @@ import ( "fmt" "io" "io/fs" - "log" "net/http" "os" "path/filepath" @@ -143,7 +142,7 @@ func FileExists(path string) (bool, error) { func getHashValue(hashFilePath string) ([]byte, error) { f, err := os.Open(hashFilePath) if err != nil { - log.Fatal(err) + return nil, err } defer func() { _ = f.Close() diff --git a/internal/services/osinstaller/windows-utils.go b/internal/services/osinstaller/windows-utils.go index aabeabab4..060bbc34f 100644 --- a/internal/services/osinstaller/windows-utils.go +++ b/internal/services/osinstaller/windows-utils.go @@ -7,8 +7,10 @@ import ( "fmt" "io" "os" + "os/exec" "path/filepath" "strings" + "syscall" "github.com/checkmarx/ast-cli/internal/logger" ) @@ -82,3 +84,9 @@ func UnzipOrExtractFiles(installationConfiguration *InstallationConfiguration) e return nil } + +func ConfigureIndependentProcess(cmd *exec.Cmd) { + cmd.SysProcAttr = &syscall.SysProcAttr{ + CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP, + } +} diff --git a/internal/services/vorpal.go b/internal/services/vorpal.go index 60a7e1a5c..08a65eeab 100644 --- a/internal/services/vorpal.go +++ b/internal/services/vorpal.go @@ -1,20 +1,225 @@ package services import ( + "fmt" + "net" "os" + "os/exec" "path/filepath" + "time" + "github.com/checkmarx/ast-cli/internal/commands/vorpal/vorpalconfig" + errorconstants "github.com/checkmarx/ast-cli/internal/constants/errors" "github.com/checkmarx/ast-cli/internal/logger" + "github.com/checkmarx/ast-cli/internal/params" + "github.com/checkmarx/ast-cli/internal/services/osinstaller" + "github.com/checkmarx/ast-cli/internal/wrappers" "github.com/checkmarx/ast-cli/internal/wrappers/grpcs" + getport "github.com/jsumners/go-getport" + "github.com/spf13/viper" ) -func CreateVorpalScanRequest(vorpalWrapper grpcs.VorpalWrapper, filePath string) (*grpcs.ScanResult, error) { - data, err := os.ReadFile(filePath) +const ( + FilePathNotProvided = "File path not provided, Vorpal engine is running successfully." + FileNotFound = "File %s not found" +) + +type VorpalScanParams struct { + FilePath string + VorpalUpdateVersion bool + IsDefaultAgent bool +} + +type VorpalWrappersParam struct { + JwtWrapper wrappers.JWTWrapper + FeatureFlagsWrapper wrappers.FeatureFlagsWrapper + VorpalWrapper grpcs.VorpalWrapper +} + +func CreateVorpalScanRequest(vorpalParams VorpalScanParams, wrapperParams VorpalWrappersParam) (*grpcs.ScanResult, error) { + var err error + wrapperParams.VorpalWrapper, err = configureVorpalWrapper(wrapperParams.VorpalWrapper) + vorpalWrapper := wrapperParams.VorpalWrapper + if err != nil { + return nil, err + } + + err = manageVorpalInstallation(vorpalParams, wrapperParams.VorpalWrapper) + if err != nil { + return nil, err + } + + err = ensureVorpalServiceRunning(wrapperParams, vorpalWrapper.GetPort(), vorpalParams) + if err != nil { + return nil, err + } + + emptyResults := validateFilePath(vorpalParams.FilePath) + if emptyResults != nil { + return emptyResults, nil + } + + return executeScan(wrapperParams.VorpalWrapper, vorpalParams.FilePath) +} + +func validateFilePath(filePath string) *grpcs.ScanResult { + if filePath == "" { + logger.PrintIfVerbose(FilePathNotProvided) + return &grpcs.ScanResult{ + Message: FilePathNotProvided, + } + } + + if exists, _ := osinstaller.FileExists(filePath); !exists { + fileNotFoundMsg := fmt.Sprintf(FileNotFound, filePath) + logger.PrintIfVerbose(fileNotFoundMsg) + return &grpcs.ScanResult{ + Error: &grpcs.Error{ + Description: fileNotFoundMsg, + }, + } + } + + return nil +} + +func executeScan(vorpalWrapper grpcs.VorpalWrapper, filePath string) (*grpcs.ScanResult, error) { + sourceCode, err := readSourceCode(filePath) if err != nil { - logger.Printf("Error reading file %v: %v", filePath, err) return nil, err } + _, fileName := filepath.Split(filePath) - sourceCode := string(data) return vorpalWrapper.Scan(fileName, sourceCode) } + +func manageVorpalInstallation(vorpalParams VorpalScanParams, vorpalWrapper grpcs.VorpalWrapper) error { + vorpalInstalled, _ := osinstaller.FileExists(vorpalconfig.Params.ExecutableFilePath()) + + if vorpalParams.VorpalUpdateVersion || !vorpalInstalled { + if err := vorpalWrapper.HealthCheck(); err == nil { + _ = vorpalWrapper.ShutDown() + } + if err := osinstaller.InstallOrUpgrade(&vorpalconfig.Params); err != nil { + return err + } + } + return nil +} + +func findVorpalPort() (int, error) { + port, err := getAvailablePort() + if err != nil { + return 0, err + } + setConfigPropertyQuiet(params.VorpalPortKey, port) + return port, nil +} + +func getAvailablePort() (int, error) { + port, err := getport.GetTcpPort() + if err != nil { + return 0, err + } + return port.Port, nil +} + +func configureVorpalWrapper(existingVorpalWrapper grpcs.VorpalWrapper) (grpcs.VorpalWrapper, error) { + if err := existingVorpalWrapper.HealthCheck(); err != nil { + port, portErr := findVorpalPort() + if portErr != nil { + return nil, portErr + } + existingVorpalWrapper.ConfigurePort(port) + } + return existingVorpalWrapper, nil +} + +func setConfigPropertyQuiet(propName string, propValue int) { + viper.Set(propName, propValue) + if viperErr := viper.SafeWriteConfig(); viperErr != nil { + _ = viper.WriteConfig() + } +} + +func ensureVorpalServiceRunning(wrappersParam VorpalWrappersParam, port int, vorpalParams VorpalScanParams) error { + if err := wrappersParam.VorpalWrapper.HealthCheck(); err != nil { + err = checkLicense(vorpalParams.IsDefaultAgent, wrappersParam) + if err != nil { + return err + } + + if err := RunVorpalEngine(port); err != nil { + return err + } + + if err := wrappersParam.VorpalWrapper.HealthCheck(); err != nil { + return err + } + } + return nil +} + +func checkLicense(isDefaultAgent bool, wrapperParams VorpalWrappersParam) error { + if !isDefaultAgent { + allowed, err := wrapperParams.JwtWrapper.IsAllowedEngine(params.AIProtectionType, wrapperParams.FeatureFlagsWrapper) + if err != nil { + return err + } + if !allowed { + return fmt.Errorf("%v", errorconstants.NoVorpalLicense) + } + } + return nil +} + +func readSourceCode(filePath string) (string, error) { + data, err := os.ReadFile(filePath) + if err != nil { + logger.Printf("Error reading file %v: %v", filePath, err) + return "", err + } + return string(data), nil +} + +func RunVorpalEngine(port int) error { + dialTimeout := 5 * time.Second + args := []string{ + "-listen", + fmt.Sprintf("%d", port), + } + + logger.PrintIfVerbose(fmt.Sprintf("Running vorpal engine with args: %v \n", args)) + + cmd := exec.Command(vorpalconfig.Params.ExecutableFilePath(), args...) + + osinstaller.ConfigureIndependentProcess(cmd) + + err := cmd.Start() + if err != nil { + return err + } + + ready := waitForServer(fmt.Sprintf("localhost:%d", port), dialTimeout) + if !ready { + return fmt.Errorf("server did not become ready in time") + } + + logger.PrintIfVerbose("Vorpal engine started successfully!") + + return nil +} + +func waitForServer(address string, timeout time.Duration) bool { + waitingDuration := 50 * time.Millisecond + deadline := time.Now().Add(timeout) + for time.Now().Before(deadline) { + conn, err := net.Dial("tcp", address) + if err == nil { + _ = conn.Close() + return true + } + time.Sleep(waitingDuration) + } + return false +} diff --git a/internal/services/vorpal_test.go b/internal/services/vorpal_test.go new file mode 100644 index 000000000..4d20f7d03 --- /dev/null +++ b/internal/services/vorpal_test.go @@ -0,0 +1,68 @@ +package services + +import ( + "fmt" + "testing" + + errorconstants "github.com/checkmarx/ast-cli/internal/constants/errors" + "github.com/checkmarx/ast-cli/internal/wrappers/mock" + "github.com/stretchr/testify/assert" +) + +func TestCreateVorpalScanRequest_DefaultAgent_Success(t *testing.T) { + vorpalParams := VorpalScanParams{ + FilePath: "data/python-vul-file.py", + VorpalUpdateVersion: false, + IsDefaultAgent: true, + } + wrapperParams := VorpalWrappersParam{ + JwtWrapper: &mock.JWTMockWrapper{}, + FeatureFlagsWrapper: &mock.FeatureFlagsMockWrapper{}, + VorpalWrapper: mock.NewVorpalMockWrapper(1234), + } + sr, err := CreateVorpalScanRequest(vorpalParams, wrapperParams) + if err != nil { + t.Fatalf("Failed to create vorpal scan request: %v", err) + } + if sr == nil { + t.Fatalf("Failed to create vorpal scan request: %v", err) + } + fmt.Println(sr) +} + +func TestCreateVorpalScanRequest_DefaultAgentAndLatestVersionFlag_Success(t *testing.T) { + vorpalParams := VorpalScanParams{ + FilePath: "data/python-vul-file.py", + VorpalUpdateVersion: true, + IsDefaultAgent: true, + } + wrapperParams := VorpalWrappersParam{ + JwtWrapper: &mock.JWTMockWrapper{}, + FeatureFlagsWrapper: &mock.FeatureFlagsMockWrapper{}, + VorpalWrapper: mock.NewVorpalMockWrapper(1234), + } + sr, err := CreateVorpalScanRequest(vorpalParams, wrapperParams) + if err != nil { + t.Fatalf("Failed to create vorpal scan request: %v", err) + } + if sr == nil { + t.Fatalf("Failed to create vorpal scan request: %v", err) + } + fmt.Println(sr) +} + +func TestCreateVorpalScanRequest_SpecialAgentAndNoLicense_Fail(t *testing.T) { + specialErrorPort := 1 + vorpalParams := VorpalScanParams{ + FilePath: "data/python-vul-file.py", + VorpalUpdateVersion: true, + IsDefaultAgent: false, + } + wrapperParams := VorpalWrappersParam{ + JwtWrapper: &mock.JWTMockWrapper{AIEnabled: mock.AIProtectionDisabled}, + FeatureFlagsWrapper: &mock.FeatureFlagsMockWrapper{}, + VorpalWrapper: &mock.VorpalMockWrapper{Port: specialErrorPort}, + } + _, err := CreateVorpalScanRequest(vorpalParams, wrapperParams) + assert.ErrorContains(t, err, errorconstants.NoVorpalLicense) +} diff --git a/internal/wrappers/grpcs/vorpal-grpc.go b/internal/wrappers/grpcs/vorpal-grpc.go index a2f30b375..46251b83a 100644 --- a/internal/wrappers/grpcs/vorpal-grpc.go +++ b/internal/wrappers/grpcs/vorpal-grpc.go @@ -15,6 +15,8 @@ import ( type VorpalGrpcWrapper struct { grpcClient *ClientWithTimeout hostAddress string + port int + serving bool } const ( @@ -28,6 +30,7 @@ func NewVorpalGrpcWrapper(port int) VorpalWrapper { return &VorpalGrpcWrapper{ grpcClient: NewGRPCClientWithTimeout(serverHostAddress, 1*time.Second).(*ClientWithTimeout), hostAddress: serverHostAddress, + port: port, } } @@ -58,15 +61,19 @@ func (v *VorpalGrpcWrapper) Scan(fileName, sourceCode string) (*ScanResult, erro return nil, errors.Wrapf(err, vorpalScanErrMsg, fileName, scanID) } + var scanError *Error + if resp.Error != nil { + scanError = &Error{ + Code: ErrorCode(resp.Error.Code), + Description: resp.Error.Description, + } + } return &ScanResult{ RequestID: resp.RequestId, Status: resp.Status, Message: resp.Message, ScanDetails: convertScanDetails(resp.ScanDetails), - Error: &Error{ - Code: ErrorCode(resp.Error.Code), - Description: resp.Error.Description, - }, + Error: scanError, }, nil } @@ -90,11 +97,14 @@ func convertScanDetails(details []*vorpalScan.ScanResult_ScanDetail) []ScanDetai } func (v *VorpalGrpcWrapper) HealthCheck() error { - err := v.grpcClient.HealthCheck(v.grpcClient, serviceName) - if err != nil { - return err + if !v.serving { + err := v.grpcClient.HealthCheck(v.grpcClient, serviceName) + if err != nil { + return err + } + logger.PrintIfVerbose(fmt.Sprintf("End of Health Check. Status: Serving, Host Address: %v", v.hostAddress)) + v.serving = true } - logger.PrintIfVerbose(fmt.Sprintf("End of Health Check. Status: Serving, Host Address: %v", v.hostAddress)) return nil } @@ -114,5 +124,17 @@ func (v *VorpalGrpcWrapper) ShutDown() error { return errors.Wrap(shutdownErr, "failed to shutdown") } logger.PrintfIfVerbose("Vorpal service is shutting down") + v.serving = false return nil } + +func (v *VorpalGrpcWrapper) GetPort() int { + return v.port +} + +func (v *VorpalGrpcWrapper) ConfigurePort(port int) { + v.port = port + v.hostAddress = fmt.Sprintf(localHostAddress, port) + v.grpcClient = NewGRPCClientWithTimeout(v.hostAddress, 1*time.Second).(*ClientWithTimeout) + v.serving = false +} diff --git a/internal/wrappers/grpcs/vorpal.go b/internal/wrappers/grpcs/vorpal.go index ee42f477b..478f4802b 100644 --- a/internal/wrappers/grpcs/vorpal.go +++ b/internal/wrappers/grpcs/vorpal.go @@ -4,14 +4,16 @@ type VorpalWrapper interface { Scan(fileName, sourceCode string) (*ScanResult, error) HealthCheck() error ShutDown() error + GetPort() int + ConfigurePort(port int) } type ScanResult struct { - RequestID string `json:"request_id"` - Status bool `json:"status"` - Message string `json:"message"` + RequestID string `json:"request_id,omitempty"` + Status bool `json:"status,omitempty"` + Message string `json:"message,omitempty"` ScanDetails []ScanDetail `json:"scan_details"` - Error *Error `json:"error"` + Error *Error `json:"error,omitempty"` } type ScanDetail struct { @@ -28,8 +30,8 @@ type ScanDetail struct { } type Error struct { - Code ErrorCode `json:"code"` - Description string `json:"description"` + Code ErrorCode `json:"code,omitempty"` + Description string `json:"description,omitempty"` } type ErrorCode int32 diff --git a/internal/wrappers/jwt-helper.go b/internal/wrappers/jwt-helper.go index 0fa7ffb87..c65e127e7 100644 --- a/internal/wrappers/jwt-helper.go +++ b/internal/wrappers/jwt-helper.go @@ -31,6 +31,7 @@ var defaultEngines = map[string]bool{ type JWTWrapper interface { GetAllowedEngines(featureFlagsWrapper FeatureFlagsWrapper) (allowedEngines map[string]bool, err error) + IsAllowedEngine(engine string, featureFlagsWrapper FeatureFlagsWrapper) (bool, error) ExtractTenantFromToken() (tenant string, err error) } @@ -42,11 +43,7 @@ func NewJwtWrapper() JWTWrapper { func (*JWTStruct) GetAllowedEngines(featureFlagsWrapper FeatureFlagsWrapper) (allowedEngines map[string]bool, err error) { flagResponse, _ := GetSpecificFeatureFlag(featureFlagsWrapper, PackageEnforcementEnabled) if flagResponse.Status { - accessToken, err := GetAccessToken() - if err != nil { - return nil, err - } - jwtStruct, err := extractFromTokenToJwtStruct(accessToken) + jwtStruct, err := getJwtStruct() if err != nil { return nil, err } @@ -57,6 +54,32 @@ func (*JWTStruct) GetAllowedEngines(featureFlagsWrapper FeatureFlagsWrapper) (al return defaultEngines, nil } +func getJwtStruct() (*JWTStruct, error) { + accessToken, err := GetAccessToken() + if err != nil { + return nil, err + } + return extractFromTokenToJwtStruct(accessToken) +} + +// IsAllowedEngine will return if the engine is allowed in the user license +func (*JWTStruct) IsAllowedEngine(engine string, featureFlagsWrapper FeatureFlagsWrapper) (bool, error) { + flagResponse, _ := GetSpecificFeatureFlag(featureFlagsWrapper, PackageEnforcementEnabled) + if flagResponse.Status { + jwtStruct, err := getJwtStruct() + if err != nil { + return false, err + } + + for _, allowedEngine := range jwtStruct.AstLicense.LicenseData.AllowedEngines { + if strings.EqualFold(allowedEngine, engine) { + return true, nil + } + } + } + return false, nil +} + func prepareEngines(engines []string) map[string]bool { m := make(map[string]bool) for _, value := range engines { @@ -86,11 +109,7 @@ func extractFromTokenToJwtStruct(accessToken string) (*JWTStruct, error) { } func (*JWTStruct) ExtractTenantFromToken() (tenant string, err error) { - accessToken, err := GetAccessToken() - if err != nil { - return "", err - } - jwtStruct, err := extractFromTokenToJwtStruct(accessToken) + jwtStruct, err := getJwtStruct() if err != nil { return "", err } diff --git a/internal/wrappers/mock/jwt-helper-mock.go b/internal/wrappers/mock/jwt-helper-mock.go index de2b4f3d6..d6e423d55 100644 --- a/internal/wrappers/mock/jwt-helper-mock.go +++ b/internal/wrappers/mock/jwt-helper-mock.go @@ -6,7 +6,11 @@ import ( "github.com/checkmarx/ast-cli/internal/wrappers" ) -type JWTMockWrapper struct{} +type JWTMockWrapper struct { + AIEnabled int +} + +const AIProtectionDisabled = 1 // GetAllowedEngines mock for tests func (*JWTMockWrapper) GetAllowedEngines(featureFlagsWrapper wrappers.FeatureFlagsWrapper) (allowedEngines map[string]bool, err error) { @@ -21,3 +25,11 @@ func (*JWTMockWrapper) GetAllowedEngines(featureFlagsWrapper wrappers.FeatureFla func (*JWTMockWrapper) ExtractTenantFromToken() (tenant string, err error) { return "test-tenant", nil } + +// IsAllowedEngine mock for tests +func (j *JWTMockWrapper) IsAllowedEngine(engine string, featureFlagWrapper wrappers.FeatureFlagsWrapper) (bool, error) { + if j.AIEnabled == AIProtectionDisabled { + return false, nil + } + return true, nil +} diff --git a/internal/wrappers/mock/vorpal-mock.go b/internal/wrappers/mock/vorpal-mock.go new file mode 100644 index 000000000..b0ba8c539 --- /dev/null +++ b/internal/wrappers/mock/vorpal-mock.go @@ -0,0 +1,92 @@ +package mock + +import ( + "fmt" + + "github.com/checkmarx/ast-cli/internal/wrappers/grpcs" +) + +var ( + specialErrorPortNumber = 1 +) + +type VorpalMockWrapper struct { + Port int +} + +func NewVorpalMockWrapper(port int) *VorpalMockWrapper { + return &VorpalMockWrapper{Port: port} +} + +func (v *VorpalMockWrapper) Scan(fileName, sourceCode string) (*grpcs.ScanResult, error) { + if fileName == "csharp-no-vul.cs" { + return ReturnFailureResponseMock(), nil + } + return ReturnSuccessfulResponseMock(), nil +} + +func (v *VorpalMockWrapper) HealthCheck() error { + if v.Port == specialErrorPortNumber { + return fmt.Errorf("error %d", InternalError) + } + return nil +} + +func (v *VorpalMockWrapper) ShutDown() error { + return nil +} + +func (v *VorpalMockWrapper) GetPort() int { + return v.Port +} + +func ReturnSuccessfulResponseMock() *grpcs.ScanResult { + return &grpcs.ScanResult{ + RequestID: "1234567890", + Status: true, + Message: "Scan completed successfully.", + ScanDetails: []grpcs.ScanDetail{ + { + Language: "Python", + RuleName: "Stored XSS", + Severity: "High", + FileName: "python-vul-file.py", + Line: 37, + Remediation: "Fully encode all dynamic data, regardless of source, before embedding it in output.", + Description: "The method undefined embeds untrusted data in generated output with write, at line 80 of /python-vul-file.py." + + "This untrusted data is embedded into the output without proper sanitization or encoding, enabling an attacker to inject malicious code into the generated web-page." + + "The attacker would be able to alter the returned web page by saving malicious data in a data-store ahead of time." + + "The attacker's modified data is then read from the database by the undefined method with read, at line 37 of /python-vul-file.py." + + "This untrusted data then flows through the code straight to the output web page, without sanitization. This can enable a Stored Cross-Site Scripting (XSS) attack.", + }, + { + Language: "Python", + RuleName: "Missing HSTS Header", + Severity: "Medium", + FileName: "python-vul-file.py", + Line: 76, + Remediation: "Before setting the HSTS header - consider the implications it may have: Forcing HTTPS will prevent any future use of HTTP", + Description: "The web-application does not define an HSTS header, leaving it vulnerable to attack.", + }, + }, + } +} + +func ReturnFailureResponseMock() *grpcs.ScanResult { + return &grpcs.ScanResult{ + RequestID: "some-request-id", + Status: false, + Message: "Scan failed.", + Error: &grpcs.Error{Code: InternalError, Description: "An internal error occurred."}, + } +} + +func (v *VorpalMockWrapper) ConfigurePort(port int) { + +} + +const ( + UnknownError = 0 + InvalidRequest = 1 + InternalError = 2 +) diff --git a/test/integration/vorpal-engine_test.go b/test/integration/vorpal-engine_test.go index 7e085eced..c0e348af5 100644 --- a/test/integration/vorpal-engine_test.go +++ b/test/integration/vorpal-engine_test.go @@ -3,57 +3,222 @@ package integration import ( + "encoding/json" + "fmt" + "os" "testing" - "github.com/checkmarx/ast-cli/internal/commands" - errorConstants "github.com/checkmarx/ast-cli/internal/constants/errors" + "github.com/checkmarx/ast-cli/internal/commands/vorpal/vorpalconfig" commonParams "github.com/checkmarx/ast-cli/internal/params" + "github.com/checkmarx/ast-cli/internal/services" + "github.com/checkmarx/ast-cli/internal/wrappers/configuration" + "github.com/checkmarx/ast-cli/internal/wrappers/grpcs" + "github.com/spf13/viper" + asserts "github.com/stretchr/testify/assert" "gotest.tools/assert" ) func TestScanVorpal_NoFileSourceSent_ReturnSuccess(t *testing.T) { + configuration.LoadConfiguration() args := []string{ "scan", "vorpal", flag(commonParams.SourcesFlag), "", flag(commonParams.VorpalLatestVersion), } - err, _ := executeCommand(t, args...) + err, bytes := executeCommand(t, args...) assert.NilError(t, err, "Sending empty source file should not fail") + var scanResults grpcs.ScanResult + err = json.Unmarshal(bytes.Bytes(), &scanResults) + assert.NilError(t, err, "Failed to unmarshal scan result") + assert.Assert(t, scanResults.Message == services.FilePathNotProvided, "should return message: ", services.FilePathNotProvided) } -func TestScanVorpal_SentFileWithoutExtension_FailCommandWithError(t *testing.T) { - bindKeysToEnvAndDefault(t) +func TestExecuteVorpalScan_VorpalLatestVersionSetTrue_Success(t *testing.T) { + configuration.LoadConfiguration() args := []string{ "scan", "vorpal", - flag(commonParams.SourcesFlag), "data/python-vul-file", + flag(commonParams.SourcesFlag), "", flag(commonParams.VorpalLatestVersion), + flag(commonParams.AgentFlag), commonParams.DefaultAgent, } - err, _ := executeCommand(t, args...) - assert.ErrorContains(t, err, errorConstants.FileExtensionIsRequired) + err, bytes := executeCommand(t, args...) + assert.NilError(t, err, "Sending empty source file should not fail") + var scanResults grpcs.ScanResult + err = json.Unmarshal(bytes.Bytes(), &scanResults) + assert.NilError(t, err, "Failed to unmarshal scan result") + assert.Assert(t, scanResults.Message == services.FilePathNotProvided, "should return message: ", services.FilePathNotProvided) +} + +func TestExecuteVorpalScan_NoSourceAndVorpalLatestVersionSetFalse_Success(t *testing.T) { + configuration.LoadConfiguration() + vorpalWrapper := grpcs.NewVorpalGrpcWrapper(viper.GetInt(commonParams.VorpalPortKey)) + _ = vorpalWrapper.ShutDown() + _ = os.RemoveAll(vorpalconfig.Params.WorkingDir()) + args := []string{ + "scan", "vorpal", + flag(commonParams.SourcesFlag), "", + flag(commonParams.AgentFlag), commonParams.DefaultAgent, + } + + err, bytes := executeCommand(t, args...) + assert.NilError(t, err, "Sending empty source file should not fail") + var scanResults grpcs.ScanResult + err = json.Unmarshal(bytes.Bytes(), &scanResults) + assert.NilError(t, err, "Failed to unmarshal scan result") + assert.Assert(t, scanResults.Message == services.FilePathNotProvided, "should return message: ", services.FilePathNotProvided) +} + +func TestExecuteVorpalScan_NotExistingFile_Success(t *testing.T) { + configuration.LoadConfiguration() + args := []string{ + "scan", "vorpal", + flag(commonParams.SourcesFlag), "not-existing-file.py", + flag(commonParams.AgentFlag), commonParams.DefaultAgent, + } + + err, bytes := executeCommand(t, args...) + assert.NilError(t, err, "Sending empty source file should not fail") + var scanResults grpcs.ScanResult + err = json.Unmarshal(bytes.Bytes(), &scanResults) + assert.NilError(t, err, "Failed to unmarshal scan result") + assert.Assert(t, scanResults.Error.Description == fmt.Sprintf(services.FileNotFound, "not-existing-file.py"), "should return error: ", services.FileNotFound) } -func TestExecuteVorpalScan_VorpalLatestVersionSetTrue_SuccessfullyReturnMockData(t *testing.T) { +func TestExecuteVorpalScan_VorpalLatestVersionSetFalse_Success(t *testing.T) { + configuration.LoadConfiguration() + args := []string{ + "scan", "vorpal", + flag(commonParams.SourcesFlag), "data/python-vul-file.py", + flag(commonParams.AgentFlag), commonParams.DefaultAgent, + } - scanResult, _ := commands.ExecuteVorpalScan("data/python-vul-file.py", true) - expectedMockResult := commands.ReturnSuccessfulResponseMock() - //TODO: update mocks when there's a real engine - assert.DeepEqual(t, scanResult, expectedMockResult) + err, scanResults := executeCommand(t, args...) + assert.NilError(t, err, fmt.Sprintf("Should not fail with error: %v", err)) + asserts.NotNil(t, scanResults) + var scanResult grpcs.ScanResult + err = json.Unmarshal(scanResults.Bytes(), &scanResult) + assert.NilError(t, err, "Failed to unmarshal scan result") + asserts.Nil(t, scanResult.Error) + asserts.NotNil(t, scanResult.ScanDetails) } -func TestExecuteVorpalScan_VorpalLatestVersionSetFalse_SuccessfullyReturnMockData(t *testing.T) { +func TestExecuteVorpalScan_NoEngineInstalledAndVorpalLatestVersionSetFalse_Success(t *testing.T) { + configuration.LoadConfiguration() + + vorpalWrapper := grpcs.NewVorpalGrpcWrapper(viper.GetInt(commonParams.VorpalPortKey)) + _ = vorpalWrapper.ShutDown() + _ = os.RemoveAll(vorpalconfig.Params.WorkingDir()) - scanResult, _ := commands.ExecuteVorpalScan("data/python-vul-file.py", false) - expectedMockResult := commands.ReturnFailureResponseMock() - //TODO: update mocks when there's a real engine - assert.DeepEqual(t, scanResult, expectedMockResult) + args := []string{ + "scan", "vorpal", + flag(commonParams.SourcesFlag), "data/python-vul-file.py", + flag(commonParams.AgentFlag), commonParams.DefaultAgent, + } + + err, scanResults := executeCommand(t, args...) + assert.NilError(t, err, fmt.Sprintf("Should not fail with error: %v", err)) + asserts.NotNil(t, scanResults) + var scanResult grpcs.ScanResult + err = json.Unmarshal(scanResults.Bytes(), &scanResult) + assert.NilError(t, err, "Failed to unmarshal scan result") + asserts.Nil(t, scanResult.Error) + asserts.NotNil(t, scanResult.ScanDetails) } func TestExecuteVorpalScan_CorrectFlagsSent_SuccessfullyReturnMockData(t *testing.T) { + configuration.LoadConfiguration() + args := []string{ + "scan", "vorpal", + flag(commonParams.SourcesFlag), "data/python-vul-file.py", + flag(commonParams.AgentFlag), commonParams.DefaultAgent, + } - scanResult, _ := commands.ExecuteVorpalScan("data/python-vul-file.py", true) - expectedMockResult := commands.ReturnSuccessfulResponseMock() - //TODO: update mocks when there's a real engine - assert.DeepEqual(t, scanResult, expectedMockResult) + err, scanResults := executeCommand(t, args...) + assert.NilError(t, err, fmt.Sprintf("Should not fail with error: %v", err)) + asserts.NotNil(t, scanResults) + var scanResult grpcs.ScanResult + err = json.Unmarshal(scanResults.Bytes(), &scanResult) + assert.NilError(t, err, "Failed to unmarshal scan result") + asserts.Nil(t, scanResult.Error) + asserts.NotNil(t, scanResult.ScanDetails) +} + +func TestExecuteVorpalScan_UnsupportedLanguage_Fail(t *testing.T) { + configuration.LoadConfiguration() + args := []string{ + "scan", "vorpal", + flag(commonParams.SourcesFlag), "data/positive1.tf", + flag(commonParams.AgentFlag), commonParams.DefaultAgent, + } + + err, bytes := executeCommand(t, args...) + assert.NilError(t, err, "Scan should not fail with error") + var scanResult grpcs.ScanResult + err = json.Unmarshal(bytes.Bytes(), &scanResult) + assert.NilError(t, err, "Failed to unmarshal scan result") + asserts.NotNil(t, scanResult.Error) +} + +func TestExecuteVorpalScan_InitializeAndRunUpdateVersion_Success(t *testing.T) { + configuration.LoadConfiguration() + vorpalWrapper := grpcs.NewVorpalGrpcWrapper(viper.GetInt(commonParams.VorpalPortKey)) + _ = vorpalWrapper.ShutDown() + args := []string{ + "scan", "vorpal", + flag(commonParams.SourcesFlag), "", + flag(commonParams.VorpalLatestVersion), + flag(commonParams.AgentFlag), commonParams.DefaultAgent, + } + vorpalWrapper = grpcs.NewVorpalGrpcWrapper(viper.GetInt(commonParams.VorpalPortKey)) + healthCheckErr := vorpalWrapper.HealthCheck() + asserts.NotNil(t, healthCheckErr) + err, bytes := executeCommand(t, args...) + assert.NilError(t, err, "Sending empty source file should not fail") + var scanResults grpcs.ScanResult + err = json.Unmarshal(bytes.Bytes(), &scanResults) + assert.NilError(t, err, "Failed to unmarshal scan result") + assert.Assert(t, scanResults.Message == services.FilePathNotProvided, "should return message: ", services.FilePathNotProvided) +} + +func TestExecuteVorpalScan_InitializeAndShutdown_Success(t *testing.T) { + configuration.LoadConfiguration() + args := []string{ + "scan", "vorpal", + flag(commonParams.SourcesFlag), "", + flag(commonParams.AgentFlag), commonParams.DefaultAgent, + flag(commonParams.DebugFlag), + } + err, bytes := executeCommand(t, args...) + assert.NilError(t, err, "Sending empty source file should not fail") + var scanResults grpcs.ScanResult + err = json.Unmarshal(bytes.Bytes(), &scanResults) + assert.NilError(t, err, "Failed to unmarshal scan result") + assert.Assert(t, scanResults.Message == services.FilePathNotProvided, "should return message: ", services.FilePathNotProvided) + + vorpalWrapper := grpcs.NewVorpalGrpcWrapper(viper.GetInt(commonParams.VorpalPortKey)) + if healthCheckErr := vorpalWrapper.HealthCheck(); healthCheckErr != nil { + assert.Assert(t, healthCheckErr == nil, "Health check failed with error: ", healthCheckErr) + } + if shutdownErr := vorpalWrapper.ShutDown(); shutdownErr != nil { + assert.Assert(t, shutdownErr == nil, "Shutdown failed with error: ", shutdownErr) + } + err = vorpalWrapper.HealthCheck() + asserts.NotNil(t, err) +} + +func TestExecuteVorpalScan_EngineNotRunningWithLicense_Success(t *testing.T) { + configuration.LoadConfiguration() + vorpalWrapper := grpcs.NewVorpalGrpcWrapper(viper.GetInt(commonParams.VorpalPortKey)) + _ = vorpalWrapper.ShutDown() + _ = os.RemoveAll(vorpalconfig.Params.WorkingDir()) + args := []string{ + "scan", "vorpal", + flag(commonParams.SourcesFlag), "data/python-vul-file.py", + flag(commonParams.DebugFlag), + flag(commonParams.AgentFlag), "JetBrains", + } + err, _ := executeCommand(t, args...) + assert.NilError(t, err, "User has license, should not fail") }