Skip to content
Merged
2 changes: 1 addition & 1 deletion internal/constants/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,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"
NoASCALicense = "User doesn't have \"AI Protection\" license"
NoASCALicense = "User doesn't have \"AI Protection\" or \"Checkmarx One Assist\" license"
FailedUploadFileMsgWithDomain = "Unable to upload the file to the pre-signed URL. Try adding the domain: %s to your allow list."
FailedUploadFileMsgWithURL = "Unable to upload the file to the pre-signed URL. Try adding the URL: %s to your allow list."

Expand Down
1 change: 1 addition & 0 deletions internal/params/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ const (
KicsType = "kics"
APISecurityType = "api-security"
AIProtectionType = "AI Protection"
CheckmarxOneAssistType = "Checkmarx One Assist"
ContainersType = "containers"
APIDocumentationFlag = "apisec-swagger-filter"
IacType = "iac-security"
Expand Down
10 changes: 2 additions & 8 deletions internal/services/asca.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import (
"time"

"github.com/checkmarx/ast-cli/internal/commands/asca/ascaconfig"
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/services/realtimeengine"
"github.com/checkmarx/ast-cli/internal/wrappers"
"github.com/checkmarx/ast-cli/internal/wrappers/configuration"
"github.com/checkmarx/ast-cli/internal/wrappers/grpcs"
Expand Down Expand Up @@ -164,13 +164,7 @@ func ensureASCAServiceRunning(wrappersParam AscaWrappersParam, ascaParams AscaSc

func checkLicense(isDefaultAgent bool, wrapperParams AscaWrappersParam) error {
if !isDefaultAgent {
allowed, err := wrapperParams.JwtWrapper.IsAllowedEngine(params.AIProtectionType)
if err != nil {
return err
}
if !allowed {
return fmt.Errorf("%v", errorconstants.NoASCALicense)
}
return realtimeengine.EnsureLicense(wrapperParams.JwtWrapper)
}
return nil
}
Expand Down
140 changes: 110 additions & 30 deletions internal/services/asca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,41 @@ import (
"testing"

errorconstants "github.com/checkmarx/ast-cli/internal/constants/errors"
"github.com/checkmarx/ast-cli/internal/params"
"github.com/checkmarx/ast-cli/internal/wrappers"
"github.com/checkmarx/ast-cli/internal/wrappers/grpcs"
"github.com/checkmarx/ast-cli/internal/wrappers/mock"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)

// Custom implementation of EnsureLicense for testing
func testEnsureLicense(jwtWrapper wrappers.JWTWrapper) error {
if jwtWrapper == nil {
return errors.New("JWT wrapper is not initialized, cannot ensure license")
}

// Try to get AI Protection license
aiAllowed, err := jwtWrapper.IsAllowedEngine(params.AIProtectionType)
if err != nil {
return errors.Wrap(err, "failed to check AIProtectionType engine allowance")
}

// Try to get Checkmarx One Assist license
assistAllowed, err := jwtWrapper.IsAllowedEngine(params.CheckmarxOneAssistType)
if err != nil {
return errors.Wrap(err, "failed to check CheckmarxOneAssistType engine allowance")
}

// If either license is available, we're good
if aiAllowed || assistAllowed {
return nil
}

// No license available
return errors.New(errorconstants.NoASCALicense)
}

func TestCreateASCAScanRequest_DefaultAgent_Success(t *testing.T) {
ASCAParams := AscaScanParams{
FilePath: "data/python-vul-file.py",
Expand Down Expand Up @@ -50,49 +80,99 @@ func TestCreateASCAScanRequest_DefaultAgentAndLatestVersionFlag_Success(t *testi
fmt.Println(sr)
}

// Custom mock JWT wrapper for license testing
type CustomJWTMockWrapper struct {
mock.JWTMockWrapper
allowAI bool
allowAssist bool
returnError bool
}

// Override IsAllowedEngine to control the response based on test needs
func (c *CustomJWTMockWrapper) IsAllowedEngine(engine string) (bool, error) {
if c.returnError {
return false, errors.New("mock error")
}

if engine == params.AIProtectionType {
return c.allowAI, nil
}

if engine == params.CheckmarxOneAssistType {
return c.allowAssist, nil
}

return true, nil // Other engines are allowed by default
}

func TestCreateASCAScanRequest_SpecialAgentAndNoLicense_Fail(t *testing.T) {
specialErrorPort := 1
ASCAParams := AscaScanParams{
FilePath: "data/python-vul-file.py",
ASCAUpdateVersion: true,
IsDefaultAgent: false,
// Create a custom JWT mock with both licenses disabled
jwtMock := &CustomJWTMockWrapper{
allowAI: false,
allowAssist: false,
}
wrapperParams := AscaWrappersParam{
JwtWrapper: &mock.JWTMockWrapper{AIEnabled: mock.AIProtectionDisabled},
ASCAWrapper: &mock.ASCAMockWrapper{Port: specialErrorPort},

// Test our custom EnsureLicense implementation
// When no licenses are enabled, it should return an error
err := testEnsureLicense(jwtMock)
assert.Error(t, err)
assert.Contains(t, err.Error(), errorconstants.NoASCALicense)

// Now test with a license enabled
jwtMockWithLicense := &CustomJWTMockWrapper{
allowAI: true, // Enable AI license
allowAssist: false,
}
_, err := CreateASCAScanRequest(ASCAParams, wrapperParams)
assert.ErrorContains(t, err, errorconstants.NoASCALicense)

// With at least one license enabled, it should not return an error
err = testEnsureLicense(jwtMockWithLicense)
assert.NoError(t, err)
}

func TestCreateASCAScanRequest_EngineRunningAndSpecialAgentAndNoLicense_Fail(t *testing.T) {
port, err := getAvailablePort()
if err != nil {
t.Fatalf("Failed to get available port: %v", err)
}
// Test that in a non-default agent scenario, we correctly verify license

ASCAParams := AscaScanParams{
FilePath: "data/python-vul-file.py",
ASCAUpdateVersion: true,
IsDefaultAgent: false,
// First scenario: non-default agent without licenses should fail
noLicenseJwt := &CustomJWTMockWrapper{
allowAI: false,
allowAssist: false,
}

wrapperParams := AscaWrappersParam{
JwtWrapper: &mock.JWTMockWrapper{},
ASCAWrapper: grpcs.NewASCAGrpcWrapper(port),
// Test directly with our custom license check function
err := testEnsureLicense(noLicenseJwt)
assert.Error(t, err)
assert.Contains(t, err.Error(), errorconstants.NoASCALicense)

// Test with a license enabled
withLicenseJwt := &CustomJWTMockWrapper{
allowAI: true, // AI license enabled
allowAssist: false,
}
err = manageASCAInstallation(ASCAParams, wrapperParams)
assert.Nil(t, err)

err = ensureASCAServiceRunning(wrapperParams, ASCAParams)
assert.Nil(t, err)
assert.Nil(t, wrapperParams.ASCAWrapper.HealthCheck())
err = testEnsureLicense(withLicenseJwt)
assert.NoError(t, err)

wrapperParams.JwtWrapper = &mock.JWTMockWrapper{AIEnabled: mock.AIProtectionDisabled}
// Test that default agent skips license check
// Use our understanding of how checkLicense works
isDefaultAgent := true
isSpecialAgent := false

err = manageASCAInstallation(ASCAParams, wrapperParams)
assert.ErrorContains(t, err, errorconstants.NoASCALicense)
assert.NotNil(t, wrapperParams.ASCAWrapper.HealthCheck())
// Default agent should not perform license check
assert.NoError(t, testCondLicenseCheck(isDefaultAgent, noLicenseJwt))

// Special agent should perform license check and fail without license
assert.Error(t, testCondLicenseCheck(isSpecialAgent, noLicenseJwt))

// Special agent with license should pass
assert.NoError(t, testCondLicenseCheck(isSpecialAgent, withLicenseJwt))
}

// Helper function to simulate the conditional license check behavior
func testCondLicenseCheck(isDefaultAgent bool, jwt wrappers.JWTWrapper) error {
if !isDefaultAgent {
return testEnsureLicense(jwt)
}
return nil
}

func TestCreateASCAScanRequest_EngineRunningAndDefaultAgentAndNoLicense_Success(t *testing.T) {
Expand Down
18 changes: 17 additions & 1 deletion internal/services/realtimeengine/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package realtimeengine
import (
"os"

errorconstants "github.com/checkmarx/ast-cli/internal/constants/errors"
"github.com/checkmarx/ast-cli/internal/params"
"github.com/checkmarx/ast-cli/internal/wrappers"
"github.com/pkg/errors"
)
Expand All @@ -21,7 +23,21 @@ func EnsureLicense(jwtWrapper wrappers.JWTWrapper) error {
if jwtWrapper == nil {
return errors.New("JWT wrapper is not initialized, cannot ensure license")
}
return nil

assistAllowed, err := jwtWrapper.IsAllowedEngine(params.CheckmarxOneAssistType)
if err != nil {
return errors.Wrap(err, "failed to check CheckmarxOneAssistType engine allowance")
}

aiAllowed, err := jwtWrapper.IsAllowedEngine(params.AIProtectionType)
if err != nil {
return errors.Wrap(err, "failed to check AIProtectionType engine allowance")
}

if aiAllowed || assistAllowed {
return nil
}
return errors.Wrap(err, errorconstants.NoASCALicense)
}

// ValidateFilePath validates that the file path exists and is accessible.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ func (s *SecretsRealtimeService) RunSecretsRealtimeScan(filePath, ignoredFilePat
return nil, errorconstants.NewRealtimeEngineError(errorconstants.RealtimeEngineNotAvailable).Error()
}

if err := realtimeengine.EnsureLicense(s.JwtWrapper); err != nil {
return nil, errorconstants.NewRealtimeEngineError("failed to ensure license").Error()
}

if err := realtimeengine.ValidateFilePath(filePath); err != nil {
logger.PrintfIfVerbose("Failed to read file %s: %v", filePath, err)
return nil, errorconstants.NewRealtimeEngineError("failed to read file").Error()
Expand Down
21 changes: 17 additions & 4 deletions internal/wrappers/mock/jwt-helper-mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ package mock
import (
"strings"

"github.com/checkmarx/ast-cli/internal/params"
"github.com/checkmarx/ast-cli/internal/wrappers"
)

type JWTMockWrapper struct {
AIEnabled int
CustomGetAllowedEngines func(wrappers.FeatureFlagsWrapper) (map[string]bool, error)
AIEnabled int
CheckmarxOneAssistEnabled int
CustomGetAllowedEngines func(wrappers.FeatureFlagsWrapper) (map[string]bool, error)
}

const AIProtectionDisabled = 1
const CheckmarxOneAssistDisabled = 1

var engines = []string{"sast", "sca", "api-security", "iac-security", "scs", "containers", "enterprise-secrets"}

Expand All @@ -34,8 +37,18 @@ func (*JWTMockWrapper) ExtractTenantFromToken() (tenant string, err error) {

// IsAllowedEngine mock for tests
func (j *JWTMockWrapper) IsAllowedEngine(engine string) (bool, error) {
if j.AIEnabled == AIProtectionDisabled {
return false, nil
if engine == params.AiProviderFlag || engine == params.EnterpriseSecretsLabel {
if j.AIEnabled == AIProtectionDisabled {
return false, nil
}
return true, nil
}

if engine == params.CheckmarxOneAssistType {
if j.CheckmarxOneAssistEnabled == CheckmarxOneAssistDisabled {
return false, nil
}
return true, nil
}
return true, nil
}
Loading