diff --git a/internal/commands/data/manifests/requirements.txt b/internal/commands/data/manifests/requirements.txt index 9e27501c6..9f6f443a3 100644 --- a/internal/commands/data/manifests/requirements.txt +++ b/internal/commands/data/manifests/requirements.txt @@ -52,6 +52,7 @@ tzdata==2025.1 # Exact version flask==3.1.2 +werkzeug>=3.0.6 # Range: greater than or equal and less than diff --git a/internal/services/realtimeengine/iacrealtime/constants.go b/internal/services/realtimeengine/iacrealtime/constants.go index e0947f4fb..5d8c1f449 100644 --- a/internal/services/realtimeengine/iacrealtime/constants.go +++ b/internal/services/realtimeengine/iacrealtime/constants.go @@ -7,6 +7,7 @@ const ( KicsContainerPrefix = "cli-iac-realtime-" ContainerResultsFileName = "results.json" InfoSeverity = "info" + IacEnginePath = "/usr/local/bin" ) var KicsErrorCodes = []string{"60", "50", "40", "30", "20"} diff --git a/internal/services/realtimeengine/iacrealtime/container-manager.go b/internal/services/realtimeengine/iacrealtime/container-manager.go index f8bdc5d99..f42b8c870 100644 --- a/internal/services/realtimeengine/iacrealtime/container-manager.go +++ b/internal/services/realtimeengine/iacrealtime/container-manager.go @@ -30,6 +30,10 @@ func (dm *ContainerManager) GenerateContainerID() string { } func (dm *ContainerManager) RunKicsContainer(engine, volumeMap string) error { + engine, err := engineNameResolution(engine, IacEnginePath) + if err != nil { + return err + } args := []string{ "run", "--rm", "-v", volumeMap, @@ -40,7 +44,7 @@ func (dm *ContainerManager) RunKicsContainer(engine, volumeMap string) error { "-o", ContainerPath, "--report-formats", ContainerFormat, } + _, err = exec.Command(engine, args...).CombinedOutput() - _, err := exec.Command(engine, args...).CombinedOutput() return err } diff --git a/internal/services/realtimeengine/iacrealtime/container-manager_test.go b/internal/services/realtimeengine/iacrealtime/container-manager_test.go index 037f2aaa6..f1d93ace6 100644 --- a/internal/services/realtimeengine/iacrealtime/container-manager_test.go +++ b/internal/services/realtimeengine/iacrealtime/container-manager_test.go @@ -176,6 +176,13 @@ func TestMockContainerManager_RunKicsContainer(t *testing.T) { volumeMap: "/tmp/test:/path", expectErr: false, // Mock doesn't validate parameters }, + { + name: "FallBack engine Path verification", + engine: "/usr/local/bin/docker", + volumeMap: "/tmp/test:/path", + expectErr: false, // Mock doesn't validate parameters + + }, } for _, tt := range tests { diff --git a/internal/services/realtimeengine/iacrealtime/iac-realtime.go b/internal/services/realtimeengine/iacrealtime/iac-realtime.go index 2e2faf120..17f46ffb2 100644 --- a/internal/services/realtimeengine/iacrealtime/iac-realtime.go +++ b/internal/services/realtimeengine/iacrealtime/iac-realtime.go @@ -4,10 +4,19 @@ import ( "encoding/json" "fmt" "os" + "os/exec" + "path/filepath" + "runtime" - errorconstants "github.com/checkmarx/ast-cli/internal/constants/errors" "github.com/checkmarx/ast-cli/internal/services/realtimeengine" "github.com/checkmarx/ast-cli/internal/wrappers" + "github.com/pkg/errors" + + errorconstants "github.com/checkmarx/ast-cli/internal/constants/errors" +) + +const ( + osWindows = "windows" ) type IacRealtimeService struct { @@ -136,3 +145,23 @@ func (svc *IacRealtimeService) validateFilePath(filePath string) error { } return nil } + +func engineNameResolution(engineName, fallBackDir string) (string, error) { + var err error + if _, err = exec.LookPath(engineName); err == nil { + return engineName, nil + } + if err != nil && getOS() == osWindows { + return "", errors.New(engineName + ": executable file not found in PATH") + } + fallbackPath := filepath.Join(fallBackDir, engineName) + info, err := os.Stat(fallbackPath) + if err == nil && !info.IsDir() { + return fallbackPath, nil + } + return "", errors.New(engineName + " not found in PATH or in " + IacEnginePath) +} + +var getOS = func() string { + return runtime.GOOS +} diff --git a/internal/services/realtimeengine/iacrealtime/iac-realtime_test.go b/internal/services/realtimeengine/iacrealtime/iac-realtime_test.go index 562726d37..1ce51c145 100644 --- a/internal/services/realtimeengine/iacrealtime/iac-realtime_test.go +++ b/internal/services/realtimeengine/iacrealtime/iac-realtime_test.go @@ -3,6 +3,7 @@ package iacrealtime import ( "os" "path/filepath" + "runtime" "testing" commonParams "github.com/checkmarx/ast-cli/internal/params" @@ -448,3 +449,64 @@ func TestFilterIgnoredFindings_WithOneIgnored(t *testing.T) { t.Errorf("Unexpected result after filtering: got %s, expected 'Memory Not Limited'", filtered[0].Title) } } + +func createExecutable(t *testing.T, tempDir, name string) string { + t.Helper() + path := filepath.Join(tempDir, name) + if runtime.GOOS == "windows" { + path += ".exe" + } + + err := os.WriteFile(path, []byte("#!/bin/sh\necho test"), 0755) + if err != nil { + t.Fatalf("failed to create executable: %v", err) + } + return filepath.Base(path) +} + +func TestEngineName_Resolution_FoundInPATH(t *testing.T) { + tmpDir := t.TempDir() + engineName := createExecutable(t, tmpDir, "docker") + previousPath := os.Getenv("PATH") + + err := os.Setenv("PATH", tmpDir+string(os.PathListSeparator)+previousPath) + if err != nil { + t.Fatalf("Failed to set the PATH in env") + } + defer func() { + _ = os.Setenv("PATH", previousPath) + }() + res, err := engineNameResolution(engineName, IacEnginePath) + if err != nil || res != engineName { + t.Fatalf("Expected enginename in return , got %v , err %d", res, err) + } +} + +func TestEngineName_Resolution_check_fallBackPath_for_MAC_Linux(t *testing.T) { + origGOOS := getOS + defer func() { getOS = origGOOS }() + getOS = func() string { return "darwin" } // or "linux" + + testPath := IacEnginePath + testFile := filepath.Join(testPath, "docker") + + err := os.WriteFile(testFile, []byte("#!/bin/sh\necho test"), 0755) + if err != nil { + t.Skipf("skipping test, cannot write file: %v", err) + } + defer func() { _ = os.Remove(testFile) }() + + oldPATH := os.Getenv("PATH") + defer func() { _ = os.Setenv("PATH", oldPATH) }() + _ = os.Setenv("PATH", "") + + result, err := engineNameResolution("docker", IacEnginePath) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + expected := filepath.Join(IacEnginePath, "docker") + if result != expected { + t.Fatalf("expected %q, got %q", expected, result) + } +} diff --git a/test/integration/data/manifests/requirements.txt b/test/integration/data/manifests/requirements.txt index 7ee12e2f8..bc288b7a6 100644 --- a/test/integration/data/manifests/requirements.txt +++ b/test/integration/data/manifests/requirements.txt @@ -52,6 +52,7 @@ tzdata==2025.1 # Exact version flask==3.1.2 +werkzeug>=3.0.6 # Range: greater than or equal and less than diff --git a/test/integration/iac-realtime_test.go b/test/integration/iac-realtime_test.go index 705655030..612c3178c 100644 --- a/test/integration/iac-realtime_test.go +++ b/test/integration/iac-realtime_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "os" "path/filepath" + "runtime" "testing" commonParams "github.com/checkmarx/ast-cli/internal/params" @@ -258,4 +259,55 @@ func TestIacRealtimeScan_ResultsValidation_DetailedCheck(t *testing.T) { "EndIndex should be >= StartIndex") } } + +} + +func TestEngineNameResolution_engine_NotFound(t *testing.T) { + oldPath := os.Getenv("PATH") + t.Cleanup(func() { + _ = os.Setenv("PATH", oldPath) + }) + _ = os.Setenv("PATH", "") + + args := []string{ + "scan", "iac-realtime", + flag(commonParams.SourcesFlag), "data/positive1.tf", + flag(commonParams.EngineFlag), "docker", + } + err, _ := executeCommand(t, args...) + + if err == nil { + t.Fatalf("expected error, got nil") + } + assert.NotNil(t, err, "docker executables not set in PATH or usr/local/bin") +} + +func TestEngineNameResolution_containerEngine_Found_inPATH_exists(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skipping test on windows") + } + oldPath := os.Getenv("PATH") + t.Cleanup(func() { + _ = os.Setenv("PATH", oldPath) + }) + _ = os.Setenv("PATH", "/usr/local/bin:"+os.Getenv("PATH")) + path := "/usr/local/bin" + testFile := filepath.Join(path, "docker.exe") + + err := os.WriteFile(testFile, []byte("#!/bin/sh\necho test"), 0755) + if err != nil { + t.Skipf("skipping test , cannot write the file %s", err) + } + defer func() { + _ = os.Remove(testFile) + }() + + args := []string{ + "scan", "iac-realtime", + flag(commonParams.SourcesFlag), "data/positive1.tf", + flag(commonParams.EngineFlag), "docker", + } + err, _ = executeCommand(t, args...) + + assert.Nil(t, err, "docker executables are found in PATH or usr/local/bin") }