diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a93ef3615..a496501fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,6 +89,7 @@ jobs: BITBUCKET_USERNAME: ${{ secrets.BITBUCKET_USERNAME }} BITBUCKET_PASSWORD: ${{ secrets.BITBUCKET_PASSWORD }} GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} + GITHUB_ACTOR: ${{ github.actor }} PR_BITBUCKET_TOKEN: ${{ secrets.PR_BITBUCKET_TOKEN }} PR_BITBUCKET_NAMESPACE: "AstSystemTest" PR_BITBUCKET_REPO_NAME: "cliIntegrationTest" diff --git a/.golangci.yml b/.golangci.yml index 68da6d7ea..7b5749fa3 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -49,6 +49,8 @@ linters-settings: - github.com/CheckmarxDev/containers-resolver/pkg/containerResolver - github.com/Checkmarx/manifest-parser/pkg/parser/models - github.com/Checkmarx/manifest-parser/pkg/parser + - github.com/Checkmarx/secret-detection/pkg/hooks/pre-commit + - github.com/Checkmarx/secret-detection/pkg/hooks/pre-receive - github.com/Checkmarx/gen-ai-prompts/prompts/sast_result_remediation - github.com/spf13/viper - github.com/checkmarx/2ms/v3/lib/reporting diff --git a/go.mod b/go.mod index 8b3dfb396..c11f7b687 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,10 @@ require ( github.com/Checkmarx/gen-ai-prompts v0.0.0-20240807143411-708ceec12b63 github.com/Checkmarx/gen-ai-wrapper v1.0.2 github.com/Checkmarx/manifest-parser v0.0.9 - github.com/Checkmarx/secret-detection v0.0.3-0.20250327150305-31c2c3be9edf + github.com/Checkmarx/secret-detection v1.2.1 github.com/MakeNowJust/heredoc v1.0.0 github.com/bouk/monkey v1.0.0 - github.com/checkmarx/2ms/v3 v3.20.0 + github.com/checkmarx/2ms/v3 v3.20.1 github.com/gofrs/flock v0.12.1 github.com/golang-jwt/jwt/v5 v5.2.2 github.com/gomarkdown/markdown v0.0.0-20241102151059-6bc1ffdc6e8c @@ -85,7 +85,6 @@ require ( github.com/charmbracelet/x/ansi v0.8.0 // indirect github.com/charmbracelet/x/cellbuf v0.0.13 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect - github.com/checkmarx/2ms v1.4.1-0.20250327145719-b78804cb08c7 // indirect github.com/cloudflare/circl v1.6.1 // indirect github.com/containerd/cgroups/v3 v3.0.5 // indirect github.com/containerd/containerd v1.7.27 // indirect @@ -127,7 +126,7 @@ require ( github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/github/go-spdx/v2 v2.3.2 // indirect - github.com/gitleaks/go-gitdiff v0.9.0 // indirect + github.com/gitleaks/go-gitdiff v0.9.1 // indirect github.com/go-errors/errors v1.5.1 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect diff --git a/go.sum b/go.sum index ed1170351..575846965 100644 --- a/go.sum +++ b/go.sum @@ -77,8 +77,8 @@ github.com/Checkmarx/gen-ai-wrapper v1.0.2 h1:T6X40+4hYnwfDsvkjWs9VIcE6s1O+8DUu0 github.com/Checkmarx/gen-ai-wrapper v1.0.2/go.mod h1:xwRLefezwNNnRGu1EjGS6wNiR9FVV/eP9D+oXwLViVM= github.com/Checkmarx/manifest-parser v0.0.9 h1:+H63riEKjhOuneTZ0eYzvwABHia04RZTuojtz2EJKz8= github.com/Checkmarx/manifest-parser v0.0.9/go.mod h1:hh5FX5FdDieU8CKQEkged4hfOaSylpJzub8PRFXa4kA= -github.com/Checkmarx/secret-detection v0.0.3-0.20250327150305-31c2c3be9edf h1:lKiogedU3WzWBc/xI6Xj1BhX2Gp1QBJj8C+czY7CcaE= -github.com/Checkmarx/secret-detection v0.0.3-0.20250327150305-31c2c3be9edf/go.mod h1:mtAHOm1mHGh7MVu6JdYUyitANsLcHNLUTBIh9pTERNI= +github.com/Checkmarx/secret-detection v1.2.1 h1:Hzpz74dcN/L14Q86ARvPOZpKBnERzGTpy6sl1RXKOTo= +github.com/Checkmarx/secret-detection v1.2.1/go.mod h1:kbXbtIQisDdB/TNuV7r9HPclEznUyBHLQ5yr7IX7vBQ= github.com/CycloneDX/cyclonedx-go v0.9.2 h1:688QHn2X/5nRezKe2ueIVCt+NRqf7fl3AVQk+vaFcIo= github.com/CycloneDX/cyclonedx-go v0.9.2/go.mod h1:vcK6pKgO1WanCdd61qx4bFnSsDJQ6SbM2ZuMIgq86Jg= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= @@ -217,10 +217,8 @@ github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= -github.com/checkmarx/2ms v1.4.1-0.20250327145719-b78804cb08c7 h1:COsC3skOJeJaSoCPuhLZ0byRGKm+ZHlyw5qm9ydlab0= -github.com/checkmarx/2ms v1.4.1-0.20250327145719-b78804cb08c7/go.mod h1:Bnd2YSh8LQSc4fHAFN0BKz8LYThB6qHg3Wn/+H+WZ4I= -github.com/checkmarx/2ms/v3 v3.20.0 h1:dr3vSVUoYXwS40DUGR5ueXaPROKTkMs+9xAij2/vzUA= -github.com/checkmarx/2ms/v3 v3.20.0/go.mod h1:f5ZwVFEDBr8WRa/9aXluPnufi2wuq6tGWCROcW3CdbA= +github.com/checkmarx/2ms/v3 v3.20.1 h1:WJaC8XTbtFHJyGKTiUL/OokI9SCT91XKoMQ1dfc32/E= +github.com/checkmarx/2ms/v3 v3.20.1/go.mod h1:8VIXk2A6F9TfTP3nfH1yOI00aUfmMhEnBedvXusWu6w= github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= @@ -372,8 +370,8 @@ github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrP github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/github/go-spdx/v2 v2.3.2 h1:IfdyNHTqzs4zAJjXdVQfRnxt1XMfycXoHBE2Vsm1bjs= github.com/github/go-spdx/v2 v2.3.2/go.mod h1:2ZxKsOhvBp+OYBDlsGnUMcchLeo2mrpEBn2L1C+U3IQ= -github.com/gitleaks/go-gitdiff v0.9.0 h1:SHAU2l0ZBEo8g82EeFewhVy81sb7JCxW76oSPtR/Nqg= -github.com/gitleaks/go-gitdiff v0.9.0/go.mod h1:pKz0X4YzCKZs30BL+weqBIG7mx0jl4tF1uXV9ZyNvrA= +github.com/gitleaks/go-gitdiff v0.9.1 h1:ni6z6/3i9ODT685OLCTf+s/ERlWUNWQF4x1pvoNICw0= +github.com/gitleaks/go-gitdiff v0.9.1/go.mod h1:pKz0X4YzCKZs30BL+weqBIG7mx0jl4tF1uXV9ZyNvrA= github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4= github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= diff --git a/internal/commands/hooks.go b/internal/commands/hooks.go new file mode 100644 index 000000000..0651f8629 --- /dev/null +++ b/internal/commands/hooks.go @@ -0,0 +1,49 @@ +package commands + +import ( + "github.com/MakeNowJust/heredoc" + "github.com/checkmarx/ast-cli/internal/params" + "github.com/checkmarx/ast-cli/internal/wrappers" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +// NewHooksCommand creates the hooks command with pre-commit subcommand +func NewHooksCommand(jwtWrapper wrappers.JWTWrapper) *cobra.Command { + hooksCmd := &cobra.Command{ + Use: "hooks", + Short: "Manage Git hooks", + Long: "The hooks command enables the ability to manage Git hooks for Checkmarx One.", + Example: heredoc.Doc( + ` + $ cx hooks pre-commit secrets-install-git-hook + $ cx hooks pre-commit secrets-scan + $ cx hooks pre-receive secrets-scan + `, + ), + Annotations: map[string]string{ + "command:doc": heredoc.Doc( + ` + https://checkmarx.com/resource/documents/en/34965-365503-hooks.html + `, + ), + }, + } + + // Add pre-commit and pre-receive subcommand + hooksCmd.AddCommand(PreCommitCommand(jwtWrapper)) + hooksCmd.AddCommand(PreReceiveCommand(jwtWrapper)) + + return hooksCmd +} + +func validateLicense(jwtWrapper wrappers.JWTWrapper) error { + allowed, err := jwtWrapper.IsAllowedEngine(params.EnterpriseSecretsLabel) + if err != nil { + return errors.Wrapf(err, "Failed checking license") + } + if !allowed { + return errors.New("Error: License validation failed. Please verify your CxOne license includes Enterprise Secrets.") + } + return nil +} diff --git a/internal/commands/pre-receive.go b/internal/commands/pre-receive.go new file mode 100644 index 000000000..3a68ecb87 --- /dev/null +++ b/internal/commands/pre-receive.go @@ -0,0 +1,49 @@ +package commands + +import ( + prereceive "github.com/Checkmarx/secret-detection/pkg/hooks/pre-receive" + "github.com/MakeNowJust/heredoc" + "github.com/checkmarx/ast-cli/internal/wrappers" + "github.com/spf13/cobra" +) + +func PreReceiveCommand(jwtWrapper wrappers.JWTWrapper) *cobra.Command { + preReceiveCmd := &cobra.Command{ + Use: "pre-receive", + Short: "Manage pre-receive hooks and run secret detection scans", + Long: "The pre-receive command is used for managing Git pre-receive hooks for secret detection", + Example: heredoc.Doc( + ` + $ cx hooks pre-receive secrets-scan + `, + ), + } + preReceiveCmd.AddCommand(scanSecretsPreReceiveCommand(jwtWrapper)) + + return preReceiveCmd +} + +func scanSecretsPreReceiveCommand(jwtWrapper wrappers.JWTWrapper) *cobra.Command { + var configFile string + scanPrereceiveCmd := &cobra.Command{ + Use: "secrets-scan", + Short: "Run a pre-receive secret detection scan on the pushed branch", + Long: "Runs pre-receive secret detection scans on each pushed branch that is about to enter the remote git repository", + Example: heredoc.Doc( + ` + $ cx hooks pre-receive secrets-scan + $ cx hooks pre-receive secrets-scan --config /path/to/config.yaml + `, + ), + PreRunE: func(cmd *cobra.Command, args []string) error { + return validateLicense(jwtWrapper) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return prereceive.Scan(configFile) + }, + } + + scanPrereceiveCmd.Flags().StringVarP(&configFile, "config", "c", "", "path to config.yaml file") + + return scanPrereceiveCmd +} diff --git a/internal/commands/pre_commit.go b/internal/commands/pre_commit.go index a3dfcfde3..08c2d20ea 100644 --- a/internal/commands/pre_commit.go +++ b/internal/commands/pre_commit.go @@ -2,48 +2,19 @@ package commands import ( "fmt" - precommit "github.com/Checkmarx/secret-detection/pkg/hooks" + precommit "github.com/Checkmarx/secret-detection/pkg/hooks/pre-commit" "github.com/MakeNowJust/heredoc" - "github.com/checkmarx/ast-cli/internal/params" "github.com/checkmarx/ast-cli/internal/wrappers" - "github.com/pkg/errors" "github.com/spf13/cobra" "strings" ) -// NewHooksCommand creates the hooks command with pre-commit subcommand -func NewHooksCommand(jwtWrapper wrappers.JWTWrapper) *cobra.Command { - hooksCmd := &cobra.Command{ - Use: "hooks", - Short: "Manage Git hooks", - Long: "The hooks command enables the ability to manage Git hooks for Checkmarx One", - Example: heredoc.Doc( - ` - $ cx hooks pre-commit secrets-install-git-hook - $ cx hooks pre-commit secrets-scan - `, - ), - Annotations: map[string]string{ - "command:doc": heredoc.Doc( - ` - https://checkmarx.com/resource/documents/en/xxxxx-xxxxx-hooks.html - `, - ), - }, - } - - // Add pre-commit subcommand - hooksCmd.AddCommand(PreCommitCommand(jwtWrapper)) - - return hooksCmd -} - // PreCommitCommand creates the pre-commit subcommand func PreCommitCommand(jwtWrapper wrappers.JWTWrapper) *cobra.Command { preCommitCmd := &cobra.Command{ Use: "pre-commit", Short: "Manage pre-commit hooks and run secret detection scans", - Long: "The pre-commit command enables the ability to manage Git pre-commit hooks for secret detection", + Long: "The pre-commit command enables the ability to manage Git pre-commit hooks for secret detection.", Example: heredoc.Doc( ` $ cx hooks pre-commit secrets-install-git-hook @@ -64,23 +35,12 @@ func PreCommitCommand(jwtWrapper wrappers.JWTWrapper) *cobra.Command { } // / validateLicense verifies the user has the required license for secret detection -func validateLicense(jwtWrapper wrappers.JWTWrapper) error { - - allowed, err := jwtWrapper.IsAllowedEngine(params.EnterpriseSecretsLabel) - if err != nil { - return errors.Wrapf(err, "Failed checking license") - } - if !allowed { - return errors.New("Error: License validation failed. Please verify your CxOne license includes Enterprise Secrets.") - } - return nil -} func secretsInstallGitHookCommand(jwtWrapper wrappers.JWTWrapper) *cobra.Command { cmd := &cobra.Command{ Use: "secrets-install-git-hook", Short: "Install the pre-commit hook", - Long: "Install the pre-commit hook for secret detection in your repository", + Long: "Install the pre-commit hook for secret detection in your repository.", Example: heredoc.Doc( ` $ cx hooks pre-commit secrets-install-git-hook @@ -102,7 +62,7 @@ func secretsUninstallGitHookCommand(jwtWrapper wrappers.JWTWrapper) *cobra.Comma cmd := &cobra.Command{ Use: "secrets-uninstall-git-hook", Short: "Uninstall the pre-commit hook", - Long: "Uninstall the pre-commit hook for secret detection from your repository", + Long: "Uninstall the pre-commit hook for secret detection from your repository.", Example: heredoc.Doc( ` $ cx hooks pre-commit secrets-uninstall-git-hook @@ -121,7 +81,7 @@ func secretsUpdateGitHookCommand(jwtWrapper wrappers.JWTWrapper) *cobra.Command cmd := &cobra.Command{ Use: "secrets-update-git-hook", Short: "Update the pre-commit hook", - Long: "Update the pre-commit hook for secret detection to the latest version", + Long: "Update the pre-commit hook for secret detection to the latest version.", Example: heredoc.Doc( ` $ cx hooks pre-commit secrets-update-git-hook @@ -143,7 +103,7 @@ func secretsScanCommand(jwtWrapper wrappers.JWTWrapper) *cobra.Command { return &cobra.Command{ Use: "secrets-scan", Short: "Run the real-time secret detection scan", - Long: "Run a real-time scan to detect secrets in your code before committing", + Long: "Run a real-time scan to detect secrets in your code before committing.", Example: heredoc.Doc( ` $ cx hooks pre-commit secrets-scan @@ -165,7 +125,7 @@ func secretsIgnoreCommand(jwtWrapper wrappers.JWTWrapper) *cobra.Command { cmd := &cobra.Command{ Use: "secrets-ignore", Short: "Ignore one or more detected secrets", - Long: "Add detected secrets to the ignore list so they won't be flagged in future scans", + Long: "Add detected secrets to the ignore list so they won't be flagged in future scans.", Example: heredoc.Doc( ` $ cx hooks pre-commit secrets-ignore --resultIds=a1b2c3d4e5f6,f1e2d3c4b5a6 @@ -209,7 +169,7 @@ func secretsHelpCommand() *cobra.Command { return &cobra.Command{ Use: "secrets-help", Short: "Display help for pre-commit commands", - Long: "Display detailed information about the pre-commit commands and options", + Long: "Display detailed information about the pre-commit commands and options.", RunE: func(cmd *cobra.Command, args []string) error { return cmd.Parent().Help() }, diff --git a/internal/commands/pre_receive_test.go b/internal/commands/pre_receive_test.go new file mode 100644 index 000000000..573eb0571 --- /dev/null +++ b/internal/commands/pre_receive_test.go @@ -0,0 +1,49 @@ +package commands + +import ( + "os" + "path/filepath" + "testing" + + "github.com/checkmarx/ast-cli/internal/wrappers/mock" + "github.com/stretchr/testify/assert" +) + +func TestPreReceiveCommand(t *testing.T) { + mockJWT := &mock.JWTMockWrapper{} + cmd := PreReceiveCommand(mockJWT) + assert.NotNil(t, cmd) + assert.Equal(t, "pre-receive", cmd.Use) + subCmds := cmd.Commands() + subCmdName := make([]string, len(subCmds)) + for i, subCmd := range subCmds { + subCmdName[i] = subCmd.Name() + } + expectedSubCmds := []string{ + "secrets-scan", + } + + for i, expectedSubCmd := range expectedSubCmds { + assert.Contains(t, expectedSubCmd, subCmdName[i]) + } +} + +func TestPreReceiveCommand_withConfig(t *testing.T) { + cmd := createASTTestCommand() + workDir, _ := os.Getwd() + configFile := filepath.Join(workDir, "config.yaml") + _ = os.WriteFile(configFile, []byte(""), 0644) + err := executeTestCommand( + cmd, + "hooks", "pre-receive", "secrets-scan", "--config", "config.yaml", + ) + assert.Nil(t, err) +} + +func TestPreReceiveCommand_withWrongFlagConfig(t *testing.T) { + err := execCmdNotNilAssertion( + t, + "hooks", "pre-receive", "secrets-scan", "--cf", "/path/config.yaml", + ) + assert.NotNil(t, err) +} diff --git a/test/integration/data/pre-receive-data/excludeFile.yaml b/test/integration/data/pre-receive-data/excludeFile.yaml new file mode 100644 index 000000000..d27190473 --- /dev/null +++ b/test/integration/data/pre-receive-data/excludeFile.yaml @@ -0,0 +1,2 @@ +exclude_path: + - "*.txt" \ No newline at end of file diff --git a/test/integration/data/pre-receive-data/excludeFolder.yaml b/test/integration/data/pre-receive-data/excludeFolder.yaml new file mode 100644 index 000000000..9add6cdef --- /dev/null +++ b/test/integration/data/pre-receive-data/excludeFolder.yaml @@ -0,0 +1,2 @@ +exclude_path: + - "integration/*" \ No newline at end of file diff --git a/test/integration/data/pre-receive-data/ignoreResultId.yaml b/test/integration/data/pre-receive-data/ignoreResultId.yaml new file mode 100644 index 000000000..9c2c68f37 --- /dev/null +++ b/test/integration/data/pre-receive-data/ignoreResultId.yaml @@ -0,0 +1,3 @@ +ignore_result_id: + - "986e5d5a73bcc5972e2d697ec0846db79b03dd75" + - "976014fdd1ef77be2c2ff1c1b6a3fe63d2b237f4" \ No newline at end of file diff --git a/test/integration/data/pre-receive-data/ignoreRuleId.yaml b/test/integration/data/pre-receive-data/ignoreRuleId.yaml new file mode 100644 index 000000000..f03b09fad --- /dev/null +++ b/test/integration/data/pre-receive-data/ignoreRuleId.yaml @@ -0,0 +1,2 @@ +ignore_rule_id: + - "github-pat" diff --git a/test/integration/pre-receive_test.go b/test/integration/pre-receive_test.go new file mode 100644 index 000000000..b8c15e41f --- /dev/null +++ b/test/integration/pre-receive_test.go @@ -0,0 +1,317 @@ +//go:build integration + +package integration + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +const ( + ignoreRuleId = "ignoreRuleId.yaml" + ignoreResultId = "ignoreResultId.yaml" + ignoreFiles = "excludeFile.yaml" + ignoreFolder = "excludeFolder.yaml" +) + +func TestPreReceive_PushSecrets(t *testing.T) { + workDir, cleanUp := setUpPreReceiveHookDir(t, "") + defer cleanUp() + assert.NoError(t, os.Chdir(workDir)) + setGlobalGitAccount(t, workDir) + + secretFile := filepath.Join(workDir, "secret.txt") + err := os.WriteFile(secretFile, []byte("ghp_DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"), 0644) + assert.NoError(t, err) + // Git add + outputCmd := exec.Command("git", "add", "secret.txt") + + // making it workingDir + outputCmd.Dir = workDir + + output, err := outputCmd.CombinedOutput() + assert.NoError(t, err, "failed to add files in staging :%s", string(output)) + + // Add commit + commitCmd := exec.Command("git", "commit", "-m", "added secrets file") + commitCmd.Dir = workDir + output, err = commitCmd.CombinedOutput() + assert.NoError(t, err, "Filed to commit :%s", string(output)) + //Pushing + + cmdPush := exec.Command("git", "push") + cmdPush.Dir = workDir + output, err = cmdPush.CombinedOutput() + outputString := string(output) + assert.Error(t, err, "Failed to push: %s", outputString) + assert.Contains(t, outputString, "[remote rejected]") + assert.Contains(t, outputString, "(pre-receive hook declined)") +} + +func TestPreReceive_PushWithoutSecrets(t *testing.T) { + workDir, cleanUp := setUpPreReceiveHookDir(t, "") + defer cleanUp() + assert.NoError(t, os.Chdir(workDir)) + setGlobalGitAccount(t, workDir) + + noSecretFile := filepath.Join(workDir, "without_secret.txt") + err := os.WriteFile(noSecretFile, []byte("Hello World"), 0644) + assert.NoError(t, err) + // Git add + outputCmd := exec.Command("git", "add", "without_secret.txt") + + // making it workingDir + outputCmd.Dir = workDir + + output, err := outputCmd.CombinedOutput() + assert.NoError(t, err, "failed to add files in staging :%s", string(output)) + + // Add commit + commitCmd := exec.Command("git", "commit", "-m", "added without secrets file") + commitCmd.Dir = workDir + output, err = commitCmd.CombinedOutput() + assert.NoError(t, err, "Filed to commit :%s", string(output)) + //Pushing + + cmdPush := exec.Command("git", "push") + cmdPush.Dir = workDir + output, err = cmdPush.CombinedOutput() + outputString := string(output) + assert.NotContains(t, outputString, "[remote rejected]") + assert.NotContains(t, outputString, "(pre-receive hook declined)") +} + +func TestPreReceive_PushSecrets_and_NoSecretsFile(t *testing.T) { + workDir, cleanUp := setUpPreReceiveHookDir(t, "") + defer cleanUp() + assert.NoError(t, os.Chdir(workDir)) + setGlobalGitAccount(t, workDir) + //create non-secret file + nonSecretFile := filepath.Join(workDir, "without_secret.txt") + err := os.WriteFile(nonSecretFile, []byte("Hello World"), 0644) + assert.NoError(t, err) + //create a secret file + secretFile := filepath.Join(workDir, "secret1.txt") + err = os.WriteFile(secretFile, []byte("ghp_DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"), 0644) + assert.NoError(t, err) + // Git add + outputCmd := exec.Command("git", "add", "without_secret.txt", "secret1.txt") + // making it workingDir + outputCmd.Dir = workDir + + output, err := outputCmd.CombinedOutput() + assert.NoError(t, err, "failed to add files in staging :%s", string(output)) + + // Add commit + commitCmd := exec.Command("git", "commit", "-m", "added without secrets file") + commitCmd.Dir = workDir + output, err = commitCmd.CombinedOutput() + assert.NoError(t, err, "Filed to commit :%s", string(output)) + //Pushing + cmdPush := exec.Command("git", "push") + cmdPush.Dir = workDir + output, err = cmdPush.CombinedOutput() + outputString := string(output) + assert.Contains(t, outputString, "[remote rejected]") + assert.Contains(t, outputString, "(pre-receive hook declined)") + assert.Contains(t, outputString, "Detected 1 secret across 1 commit") +} + +func TestPreReceive_IgnoreRuleId_ConfigFile(t *testing.T) { + configFileName := ignoreRuleId + workDir, cleanUp := setUpPreReceiveHookDir(t, configFileName) + defer cleanUp() + assert.NoError(t, os.Chdir(workDir)) + setGlobalGitAccount(t, workDir) + + //create a secret file + secretFile := filepath.Join(workDir, "secret1.txt") + err := os.WriteFile(secretFile, []byte("ghp_DDDDDDDDDDDDDDDDDDDDDDDDDDDADDADDDAD"), 0644) + assert.NoError(t, err) + // Git add + outputCmd := exec.Command("git", "add", "secret1.txt") + // making it workingDir + outputCmd.Dir = workDir + + output, err := outputCmd.CombinedOutput() + assert.NoError(t, err, "failed to add files in staging :%s", string(output)) + + // Add commit + commitCmd := exec.Command("git", "commit", "-m", "added without secrets file") + commitCmd.Dir = workDir + output, err = commitCmd.CombinedOutput() + assert.NoError(t, err, "Filed to commit :%s", string(output)) + //Pushing + cmdPush := exec.Command("git", "push") + cmdPush.Dir = workDir + output, err = cmdPush.CombinedOutput() + outputString := string(output) + // ignoring the secrets as per ruleId and successfully pushing + assert.NotContains(t, outputString, "[remote rejected]") + assert.NotContains(t, outputString, "(pre-receive hook declined)") + assert.NotContains(t, outputString, "Detected 1 secret across 1 commit") +} + +func TestPreReceive_IgnoreResultId_ConfigFile(t *testing.T) { + configFileName := ignoreResultId + workDir, cleanUp := setUpPreReceiveHookDir(t, configFileName) + defer cleanUp() + assert.NoError(t, os.Chdir(workDir)) + setGlobalGitAccount(t, workDir) + + //create a secret file + file1 := filepath.Join(workDir, "secretsFile.txt") + err := os.WriteFile(file1, []byte("ghp_DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"), 0644) + assert.NoError(t, err) + // Git add + outputCmd := exec.Command("git", "add", "secretsFile.txt") + // making it workingDir + outputCmd.Dir = workDir + + output, err := outputCmd.CombinedOutput() + assert.NoError(t, err, "failed to add files in staging :%s", string(output)) + + // Add commit + commitCmd := exec.Command("git", "commit", "-m", "added without secrets file") + commitCmd.Dir = workDir + output, err = commitCmd.CombinedOutput() + assert.NoError(t, err, "Filed to commit :%s", string(output)) + //Pushing + cmdPush := exec.Command("git", "push") + cmdPush.Dir = workDir + output, err = cmdPush.CombinedOutput() + outputString := string(output) + // ignoring the secrets as resultId matches in configFile and successfully pushing + assert.NotContains(t, outputString, "[remote rejected]") + assert.NotContains(t, outputString, "(pre-receive hook declined)") + assert.NotContains(t, outputString, "Detected 1 secret across 1 commit") +} + +func TestPreReceive_IgnoreFileExclusion_ConfigFile(t *testing.T) { + //Adding config file with file exclusion params + configFileName := ignoreFiles + workDir, cleanUp := setUpPreReceiveHookDir(t, configFileName) + defer cleanUp() + assert.NoError(t, os.Chdir(workDir)) + setGlobalGitAccount(t, workDir) + + //create a secret file + file1 := filepath.Join(workDir, "secretsFile.txt") + err := os.WriteFile(file1, []byte("ghp_DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"), 0644) + assert.NoError(t, err) + // Git add + outputCmd := exec.Command("git", "add", "secretsFile.txt") + // making it workingDir + outputCmd.Dir = workDir + + output, err := outputCmd.CombinedOutput() + assert.NoError(t, err, "failed to add files in staging :%s", string(output)) + + // Add commit + commitCmd := exec.Command("git", "commit", "-m", "added without secrets file") + commitCmd.Dir = workDir + output, err = commitCmd.CombinedOutput() + assert.NoError(t, err, "Filed to commit :%s", string(output)) + //Pushing + cmdPush := exec.Command("git", "push") + cmdPush.Dir = workDir + output, err = cmdPush.CombinedOutput() + outputString := string(output) + // ignoring the secrets as resultId matches in configFile and successfully pushing + assert.NotContains(t, outputString, "[remote rejected]") + assert.NotContains(t, outputString, "(pre-receive hook declined)") + assert.NotContains(t, outputString, "Detected 1 secret across 1 commit") + assert.Contains(t, outputString, "No secrets detected by Cx Secret Scanner") + +} + +func TestPreReceive_IgnoreFolderExclusion_ConfigFile(t *testing.T) { + //Adding config file with folder exclusion params + configFileName := ignoreFolder + workDir, cleanUp := setUpPreReceiveHookDir(t, configFileName) + defer cleanUp() + assert.NoError(t, os.Chdir(workDir)) + setGlobalGitAccount(t, workDir) + + //create a secret file + folderPath := filepath.Join(workDir, "integration") + err := os.MkdirAll(folderPath, os.ModePerm) + assert.NoError(t, err) + file1 := filepath.Join(folderPath, "secretsFile.txt") + err = os.WriteFile(file1, []byte("ghp_DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"), 0644) + assert.NoError(t, err) + // Git add + outputCmd := exec.Command("git", "add", "integration/secretsFile.txt") + // making it workingDir + outputCmd.Dir = workDir + + output, err := outputCmd.CombinedOutput() + assert.NoError(t, err, "failed to add files in staging :%s", string(output)) + + // Add commit + commitCmd := exec.Command("git", "commit", "-m", "added without secrets file") + commitCmd.Dir = workDir + output, err = commitCmd.CombinedOutput() + assert.NoError(t, err, "Filed to commit :%s", string(output)) + //Pushing + cmdPush := exec.Command("git", "push") + cmdPush.Dir = workDir + output, err = cmdPush.CombinedOutput() + outputString := string(output) + // ignoring the secrets as resultId matches in configFile and successfully pushing + assert.NotContains(t, outputString, "[remote rejected]") + assert.NotContains(t, outputString, "(pre-receive hook declined)") + assert.NotContains(t, outputString, "Detected 1 secret across 1 commit") + assert.Contains(t, outputString, "No secrets detected by Cx Secret Scanner") +} + +func setGlobalGitAccount(t *testing.T, repoName string) { + // Set global git config + username := os.Getenv("GITHUB_ACTOR") + err := exec.Command("git", "-C", repoName, "config", "user.email", username+"@users.noreply.github.com").Run() + err = exec.Command("git", "-C", repoName, "config", "user.name", username).Run() + assert.NoError(t, err) +} + +func setUpPreReceiveHookDir(t *testing.T, fileName string) (workdir string, cleanup func()) { + orgWorkDir, err := os.Getwd() + assert.NoError(t, err) + tempDir := t.TempDir() + + //Init a bare repo + + err = exec.Command("git", "init", "--bare", filepath.Join(tempDir, "server")).Run() + assert.NoError(t, err) + cxPath := filepath.Join(orgWorkDir, "..", "..", "bin", "cx") + yamlPath := filepath.Join(orgWorkDir, "data", "pre-receive-data", fileName) + fmt.Println("yaml path" + yamlPath) + fmt.Println("the current dir" + cxPath) + + preReceivePath := filepath.Join(tempDir, "server", "hooks", "pre-receive") + configFlags := "" + if fileName != "" { + configFlags = fmt.Sprintf(` --config "%s"`, yamlPath) + } + + script := fmt.Sprintf(`#!/bin/bash +"%s" hooks pre-receive secrets-scan%s`, cxPath, configFlags) + + err = os.WriteFile(preReceivePath, []byte(script), 0755) + assert.NoError(t, err) + + err = exec.Command("git", "clone", filepath.Join(tempDir, "server"), filepath.Join(tempDir, "client")).Run() + + mainDir := filepath.Join(tempDir, "client") + err = os.Chdir(mainDir) + assert.NoError(t, err) + cleanUp := func() { + assert.NoError(t, os.Chdir(orgWorkDir)) + } + + return mainDir, cleanUp +}