diff --git a/pkg/cli/run.go b/pkg/cli/run.go index 78b1a4c0f..8683676e6 100644 --- a/pkg/cli/run.go +++ b/pkg/cli/run.go @@ -102,6 +102,12 @@ type ScanOptions struct { // FindVulnerabilities gives option to scan container images for vulnerabilities findVulnerabilities bool + + // notificationWebhookURL is the URL where terrascan will send the scan report and normalized config json + notificationWebhookURL string + + // notificationWebhookToken is the auth token to call the notification webhook URL + notificationWebhookToken string } // NewScanOptions returns a new pointer to ScanOptions @@ -187,7 +193,7 @@ func (s *ScanOptions) Run() error { // create a new runtime executor for processing IaC executor, err := runtime.NewExecutor(s.iacType, s.iacVersion, s.policyType, - s.iacFilePath, s.iacDirPath, s.policyPath, s.scanRules, s.skipRules, s.categories, s.severity, s.nonRecursive, s.useTerraformCache, s.findVulnerabilities) + s.iacFilePath, s.iacDirPath, s.policyPath, s.scanRules, s.skipRules, s.categories, s.severity, s.nonRecursive, s.useTerraformCache, s.findVulnerabilities, s.notificationWebhookURL, s.notificationWebhookToken) if err != nil { return err } diff --git a/pkg/cli/scan.go b/pkg/cli/scan.go index f2a71ac55..68e6d4c5f 100644 --- a/pkg/cli/scan.go +++ b/pkg/cli/scan.go @@ -74,5 +74,7 @@ func init() { scanCmd.Flags().BoolVarP(&scanOptions.nonRecursive, "non-recursive", "", false, "do not scan directories and modules recursively") scanCmd.Flags().BoolVarP(&scanOptions.useTerraformCache, "use-terraform-cache", "", false, "use terraform init cache for remote modules (when used directory scan will be non recursive, flag applicable only with terraform IaC provider)") scanCmd.Flags().BoolVarP(&scanOptions.findVulnerabilities, "find-vuln", "", false, "fetches vulnerabilities identified in Docker images") + scanCmd.Flags().StringVarP(&scanOptions.notificationWebhookURL, "notification-webhook-url", "", "", "the URL where terrascan will send the scan report and normalized config json") + scanCmd.Flags().StringVarP(&scanOptions.notificationWebhookToken, "notification-webhook-token", "", "", "the auth token to call the notification webhook URL") RegisterCommand(rootCmd, scanCmd) } diff --git a/pkg/http-server/file-scan.go b/pkg/http-server/file-scan.go index d061cf344..5c8de6ff9 100644 --- a/pkg/http-server/file-scan.go +++ b/pkg/http-server/file-scan.go @@ -102,6 +102,8 @@ func (g *APIHandler) scanFile(w http.ResponseWriter, r *http.Request) { // scan and skip rules are comma separated rule id's in the request body scanRulesValue := r.FormValue("scan_rules") skipRulesValue := r.FormValue("skip_rules") + notificationWebhookURL := r.FormValue("notificationWebhookURL") + notificationWebhookToken := r.FormValue("notificationWebhookToken") // categories is the list categories of violations that the user want to get informed about: low, medium or high categoriesValue := r.FormValue("categories") @@ -164,10 +166,10 @@ func (g *APIHandler) scanFile(w http.ResponseWriter, r *http.Request) { var executor *runtime.Executor if g.test { executor, err = runtime.NewExecutor(iacType, iacVersion, cloudType, - tempFile.Name(), "", []string{"./testdata/testpolicies"}, scanRules, skipRules, categories, severity, false, false, false) + tempFile.Name(), "", []string{"./testdata/testpolicies"}, scanRules, skipRules, categories, severity, false, false, false, notificationWebhookURL, notificationWebhookToken) } else { executor, err = runtime.NewExecutor(iacType, iacVersion, cloudType, - tempFile.Name(), "", getPolicyPathFromConfig(), scanRules, skipRules, categories, severity, false, false, findVulnerabilities) + tempFile.Name(), "", getPolicyPathFromConfig(), scanRules, skipRules, categories, severity, false, false, findVulnerabilities, notificationWebhookURL, notificationWebhookToken) } if err != nil { zap.S().Error(err) diff --git a/pkg/http-server/remote-repo.go b/pkg/http-server/remote-repo.go index 0b833b568..d9e6228ce 100644 --- a/pkg/http-server/remote-repo.go +++ b/pkg/http-server/remote-repo.go @@ -35,17 +35,19 @@ import ( // scanRemoteRepoReq contains request body for remote repository scanning type scanRemoteRepoReq struct { - RemoteType string `json:"remote_type"` - RemoteURL string `json:"remote_url"` - ConfigOnly bool `json:"config_only"` - ScanRules []string `json:"scan_rules"` - SkipRules []string `json:"skip_rules"` - Categories []string `json:"categories"` - Severity string `json:"severity"` - ShowPassed bool `json:"show_passed"` - NonRecursive bool `json:"non_recursive"` - FindVulnerabilities bool `json:"find_vulnerabilities"` - d downloader.Downloader + RemoteType string `json:"remote_type"` + RemoteURL string `json:"remote_url"` + ConfigOnly bool `json:"config_only"` + ScanRules []string `json:"scan_rules"` + SkipRules []string `json:"skip_rules"` + Categories []string `json:"categories"` + Severity string `json:"severity"` + ShowPassed bool `json:"show_passed"` + NonRecursive bool `json:"non_recursive"` + FindVulnerabilities bool `json:"find_vulnerabilities"` + d downloader.Downloader + notificationWebhookURL string `json:"notification_webhook_url"` + notificationWebhookToken string `json:"notification_webhook_token"` } // scanRemoteRepo downloads the remote Iac repository and scans it for @@ -129,7 +131,7 @@ func (s *scanRemoteRepoReq) ScanRemoteRepo(iacType, iacVersion string, cloudType // create a new runtime executor for scanning the remote repo executor, err := runtime.NewExecutor(iacType, iacVersion, cloudType, - "", iacDirPath, policyPath, s.ScanRules, s.SkipRules, s.Categories, s.Severity, s.NonRecursive, false, s.FindVulnerabilities) + "", iacDirPath, policyPath, s.ScanRules, s.SkipRules, s.Categories, s.Severity, s.NonRecursive, false, s.FindVulnerabilities, s.notificationWebhookURL, s.notificationWebhookToken) if err != nil { zap.S().Error(err) return output, isAdmissionDenied, err diff --git a/pkg/k8s/admission-webhook/validating-webhook.go b/pkg/k8s/admission-webhook/validating-webhook.go index 51f063e56..233cd72ff 100644 --- a/pkg/k8s/admission-webhook/validating-webhook.go +++ b/pkg/k8s/admission-webhook/validating-webhook.go @@ -191,10 +191,10 @@ func (w ValidatingWebhook) scanK8sFile(filePath string) (runtime.Output, error) if flag.Lookup("test.v") != nil { executor, err = runtime.NewExecutor("k8s", "v1", []string{"k8s"}, - filePath, "", []string{testPoliciesPath}, []string{}, []string{}, []string{}, "", false, false, false) + filePath, "", []string{testPoliciesPath}, []string{}, []string{}, []string{}, "", false, false, false, "", "") } else { executor, err = runtime.NewExecutor("k8s", "v1", []string{"k8s"}, - filePath, "", []string{}, []string{}, []string{}, []string{}, "", false, false, false) + filePath, "", []string{}, []string{}, []string{}, []string{}, "", false, false, false, "", "") } if err != nil { zap.S().Errorf("failed to create runtime executer: '%v'", err) diff --git a/pkg/runtime/executor.go b/pkg/runtime/executor.go index f4eee9395..3b5213f41 100644 --- a/pkg/runtime/executor.go +++ b/pkg/runtime/executor.go @@ -39,38 +39,42 @@ const ( // Executor object type Executor struct { - filePath string - dirPath string - policyPath []string - iacType string - iacVersion string - scanRules []string - skipRules []string - iacProviders []iacProvider.IacProvider - policyEngines []policy.Engine - notifiers []notifications.Notifier - categories []string - policyTypes []string - severity string - nonRecursive bool - useTerraformCache bool - findVulnerabilities bool - vulnerabilityEngine vulnerability.Engine + filePath string + dirPath string + policyPath []string + iacType string + iacVersion string + scanRules []string + skipRules []string + iacProviders []iacProvider.IacProvider + policyEngines []policy.Engine + notifiers []notifications.Notifier + categories []string + policyTypes []string + severity string + nonRecursive bool + useTerraformCache bool + findVulnerabilities bool + vulnerabilityEngine vulnerability.Engine + notificationWebhookURL string + notificationWebhookToken string } // NewExecutor creates a runtime object -func NewExecutor(iacType, iacVersion string, policyTypes []string, filePath, dirPath string, policyPath, scanRules, skipRules, categories []string, severity string, nonRecursive, useTerraformCache, findVulnerabilities bool) (e *Executor, err error) { +func NewExecutor(iacType, iacVersion string, policyTypes []string, filePath, dirPath string, policyPath, scanRules, skipRules, categories []string, severity string, nonRecursive, useTerraformCache, findVulnerabilities bool, notificationWebhookURL, notificationWebhookToken string) (e *Executor, err error) { e = &Executor{ - filePath: filePath, - dirPath: dirPath, - policyPath: policyPath, - policyTypes: policyTypes, - iacType: iacType, - iacVersion: iacVersion, - iacProviders: make([]iacProvider.IacProvider, 0), - nonRecursive: nonRecursive, - useTerraformCache: useTerraformCache, - findVulnerabilities: findVulnerabilities, + filePath: filePath, + dirPath: dirPath, + policyPath: policyPath, + policyTypes: policyTypes, + iacType: iacType, + iacVersion: iacVersion, + iacProviders: make([]iacProvider.IacProvider, 0), + nonRecursive: nonRecursive, + useTerraformCache: useTerraformCache, + findVulnerabilities: findVulnerabilities, + notificationWebhookURL: notificationWebhookURL, + notificationWebhookToken: notificationWebhookToken, } // assigning vulnerabilityEngine diff --git a/pkg/runtime/executor_test.go b/pkg/runtime/executor_test.go index 2d8d65ec2..1acaa789e 100644 --- a/pkg/runtime/executor_test.go +++ b/pkg/runtime/executor_test.go @@ -449,16 +449,18 @@ func TestInit(t *testing.T) { } type flagSet struct { - iacType string - iacVersion string - filePath string - dirPath string - policyPath []string - policyTypes []string - categories []string - severity string - scanRules []string - skipRules []string + iacType string + iacVersion string + filePath string + dirPath string + policyPath []string + policyTypes []string + categories []string + severity string + scanRules []string + skipRules []string + notificationWebhookURL string + notificationWebhookToken string } func TestNewExecutor(t *testing.T) { @@ -587,7 +589,7 @@ func TestNewExecutor(t *testing.T) { t.Run(tt.name, func(t *testing.T) { config.LoadGlobalConfig(tt.configfile) - gotExecutor, gotErr := NewExecutor(tt.flags.iacType, tt.flags.iacVersion, tt.flags.policyTypes, tt.flags.filePath, tt.flags.dirPath, tt.flags.policyPath, tt.flags.scanRules, tt.flags.skipRules, tt.flags.categories, tt.flags.severity, false, false, false) + gotExecutor, gotErr := NewExecutor(tt.flags.iacType, tt.flags.iacVersion, tt.flags.policyTypes, tt.flags.filePath, tt.flags.dirPath, tt.flags.policyPath, tt.flags.scanRules, tt.flags.skipRules, tt.flags.categories, tt.flags.severity, false, false, false, tt.flags.notificationWebhookURL, tt.flags.notificationWebhookToken) if !reflect.DeepEqual(tt.wantErr, gotErr) { t.Errorf("Mismatch in error => got: '%v', want: '%v'", gotErr, tt.wantErr) diff --git a/pkg/runtime/notifications.go b/pkg/runtime/notifications.go index 8cda3bf50..d6f47ab35 100644 --- a/pkg/runtime/notifications.go +++ b/pkg/runtime/notifications.go @@ -17,12 +17,27 @@ package runtime import ( + "github.com/accurics/terrascan/pkg/notifications/webhook" "github.com/accurics/terrascan/pkg/utils" ) // SendNotifications sends notifications via all the configured notifiers func (e *Executor) SendNotifications(data interface{}) error { var allErrs error + + // send notifications using CLI arguments + if e.notificationWebhookURL != "" { + w := webhook.Webhook{ + URL: e.notificationWebhookURL, + Token: e.notificationWebhookToken, + } + err := w.SendNotification(data) + if err != nil { + allErrs = utils.WrapError(err, allErrs) + } + return allErrs + } + // send notifications using configured notifiers for _, notifier := range e.notifiers { err := notifier.SendNotification(data) diff --git a/test/e2e/help/golden/help_scan.txt b/test/e2e/help/golden/help_scan.txt index 3a05fb01b..43a65a406 100644 --- a/test/e2e/help/golden/help_scan.txt +++ b/test/e2e/help/golden/help_scan.txt @@ -6,26 +6,28 @@ Usage: terrascan scan [flags] Flags: - --categories strings list of categories of violations to be reported by terrascan (example: --categories="category1,category2") - --config-only will output resource config (should only be used for debugging purposes) - --find-vuln fetches vulnerabilities identified in Docker images - -h, --help help for scan - -d, --iac-dir string path to a directory containing one or more IaC files (default ".") - -f, --iac-file string path to a single IaC file - -i, --iac-type string iac type (arm, cft, docker, helm, k8s, kustomize, terraform, tfplan) - --iac-version string iac version (arm: v1, cft: v1, docker: v1, helm: v3, k8s: v1, kustomize: v2, v3, v4, terraform: v12, v13, v14, v15, tfplan: v1) - --non-recursive do not scan directories and modules recursively - -p, --policy-path stringArray policy path directory - -t, --policy-type strings policy type (all, aws, azure, docker, gcp, github, k8s) (default [all]) - -r, --remote-type string type of remote backend (git, s3, gcs, http, terraform-registry) - -u, --remote-url string url pointing to remote IaC repository - --scan-rules strings one or more rules to scan (example: --scan-rules="ruleID1,ruleID2") - --severity string minimum severity level of the policy violations to be reported by terrascan - --show-passed display passed rules, along with violations - --skip-rules strings one or more rules to skip while scanning (example: --skip-rules="ruleID1,ruleID2") - --use-colors string color output (auto, t, f) (default "auto") - --use-terraform-cache use terraform init cache for remote modules (when used directory scan will be non recursive, flag applicable only with terraform IaC provider) - -v, --verbose will show violations with details (applicable for default output) + --categories strings list of categories of violations to be reported by terrascan (example: --categories="category1,category2") + --config-only will output resource config (should only be used for debugging purposes) + --find-vuln fetches vulnerabilities identified in Docker images + -h, --help help for scan + -d, --iac-dir string path to a directory containing one or more IaC files (default ".") + -f, --iac-file string path to a single IaC file + -i, --iac-type string iac type (arm, cft, docker, helm, k8s, kustomize, terraform, tfplan) + --iac-version string iac version (arm: v1, cft: v1, docker: v1, helm: v3, k8s: v1, kustomize: v2, v3, v4, terraform: v12, v13, v14, v15, tfplan: v1) + --non-recursive do not scan directories and modules recursively + --notification-webhook-token string the auth token to call the notification webhook URL + --notification-webhook-url string the URL where terrascan will send the scan report and normalized config json + -p, --policy-path stringArray policy path directory + -t, --policy-type strings policy type (all, aws, azure, docker, gcp, github, k8s) (default [all]) + -r, --remote-type string type of remote backend (git, s3, gcs, http, terraform-registry) + -u, --remote-url string url pointing to remote IaC repository + --scan-rules strings one or more rules to scan (example: --scan-rules="ruleID1,ruleID2") + --severity string minimum severity level of the policy violations to be reported by terrascan + --show-passed display passed rules, along with violations + --skip-rules strings one or more rules to skip while scanning (example: --skip-rules="ruleID1,ruleID2") + --use-colors string color output (auto, t, f) (default "auto") + --use-terraform-cache use terraform init cache for remote modules (when used directory scan will be non recursive, flag applicable only with terraform IaC provider) + -v, --verbose will show violations with details (applicable for default output) Global Flags: -c, --config-path string config file path