diff --git a/docs/usage/command_line_mode.md b/docs/usage/command_line_mode.md index 9f5f2815d..d79b4c28a 100644 --- a/docs/usage/command_line_mode.md +++ b/docs/usage/command_line_mode.md @@ -268,15 +268,17 @@ aws_ecr_repository: | -d | Use this to scan a specific directory. Use "." for current directory | AWS, GCP, Azure, and GitHub| | -f | Use this command to scan a specific file | | | -i type | Use this to change the IaC provider | arm, cft, docker, helm, k8s, kustomize, **terraform**| -| -i version | Use this in conjunction with `- i type` to specify the version of IaC provider | Supported versions of each IaC are: `arm: v1, cft: v1, docker: v1, helm: v3, k8s: v1, kustomize: v2, v3, v4, terraform: v12, v13, v14, v15`| +| --iac-version version | Use this in conjunction with `- i type` to specify the version of IaC provider | Supported versions of each IaC are: `arm: v1, cft: v1, docker: v1, helm: v3, k8s: v1, kustomize: v2, v3, v4, terraform: v12, v13, v14, v15`| | -p | Use this to specify directory path for policies | By default policies are installed here: | | -t | Use this to specify individual cloud providers | **all**, aws, azure, gcp, github, k8s| | -r | Use this to specify directory path for remote backend | git, s3, gcs, http | | -u | Use this to specify directory URL for remote IaC repositories | see options below | | |scan-rules|Specify rules to scan, example: --scan-rules="ruleID1,ruleID2"| | |skip-rules|Specify one or more rules to skip while scanning. Example: --skip-rules="ruleID1,ruleID2"| -| |use-colours |Configure the color for output (**auto**, t, f) | +| |use-colors |Configure the color for output (**auto**, t, f) | |--non-recursive |Use this for non recursive directories and modules scan | By default directory is scanned recursively, if this flag is used then only provided root directory will be scanned| +|--webhook-token string| Token used for sending authenticated requests to the notification webhook | This flag is optional when using the notification webhook| +|--webhook-url | A webhook URL where Terrascan will send JSON scan report and normalized IaC JSON | This overrides any notification webhook URLs configured in config TOML file specified with the `-c` flag| |--use-terraform-cache |Use this to refer terraform remote modules from terraform init cache rather than downloading | By default remote module will be downloaded in temporary directory. If this flag is set then modules will be refered from terraform init cache if module is not present in terraform init cache it will be downloaded. Directory will be scanned non recurively if this flag is used.(applicable only with terraform IaC provider)| | --find-vuln | find vulnerbilities | Use this to fetch vulnerabilities identified on the registry for docker images present in IaC the files scanned | | -v | verbose | Displays violations with all details | @@ -303,26 +305,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, 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 + --webhook-token string the auth token to call the notification webhook URL + --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 diff --git a/docs/usage/config_options.md b/docs/usage/config_options.md index 03cb90875..b0e26f63a 100644 --- a/docs/usage/config_options.md +++ b/docs/usage/config_options.md @@ -18,7 +18,7 @@ $ terrascan scan -c [notifications.webhook] url = "https://httpbin.org/post" token = "my_auth_token" - + [severity] level = "medium" [rules] @@ -41,7 +41,7 @@ You can specify the following configurations: * **skip-rules** - Specify one or more rules to skip while scanning. All other rules in the policy pack will be applied. * **severity** - the minimal level of severity of the policies to be scanned and displayed. Options are high, medium and low * **category** - the list of type of categories of the policies to be scanned and displayed -* **notifications** - Use these configuration as seen in the example above to send the output of scans as a webhook to a remote server. +* **notifications** - This configuration can be used, as seen in the example above, to send the output of scans as a webhook to a remote server. Note that the `--webhook-url` CLI flag will override any URLs configured through a configuration file. **k8s-admission-control** - Config options for K8s Admission Controllers and GitOps workflows: 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..d7ce95637 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, "webhook-url", "", "", "webhook URL where Terrascan will send JSON scan report and normalized IaC JSON") + scanCmd.Flags().StringVarP(&scanOptions.notificationWebhookToken, "webhook-token", "", "", "optional token used when sending authenticated requests to the notification webhook") RegisterCommand(rootCmd, scanCmd) } diff --git a/pkg/http-server/file-scan.go b/pkg/http-server/file-scan.go index d061cf344..b5c8ce111 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("webhook_url") + notificationWebhookToken := r.FormValue("webhook_token") // 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/file-scan_test.go b/pkg/http-server/file-scan_test.go index 771e8c412..81af1f44d 100644 --- a/pkg/http-server/file-scan_test.go +++ b/pkg/http-server/file-scan_test.go @@ -42,6 +42,8 @@ func TestUpload(t *testing.T) { categories []string invalidFindVulnerabilities bool findVulnerabilities bool + notificationWebhookURL string + notificationWebhookToken string }{ { name: "valid file scan", @@ -260,6 +262,17 @@ func TestUpload(t *testing.T) { wantStatus: http.StatusBadRequest, invalidFindVulnerabilities: true, }, + { + name: "valid file scan with notification webhook", + path: testFilePath, + param: testParamName, + iacType: testIacType, + iacVersion: testIacVersion, + cloudType: testCloudType, + wantStatus: http.StatusOK, + notificationWebhookURL: "https://httpbin.org/post", + notificationWebhookToken: "token", + }, } for _, tt := range table { @@ -352,6 +365,19 @@ func TestUpload(t *testing.T) { } } + if tt.notificationWebhookURL != "" { + if err = writer.WriteField("webhook_url", tt.notificationWebhookURL); err != nil { + writer.Close() + t.Error(err) + } + } + if tt.notificationWebhookToken != "" { + if err = writer.WriteField("webhook_token", tt.notificationWebhookToken); err != nil { + writer.Close() + t.Error(err) + } + } + writer.Close() // http request of the type "/v1/{iacType}/{iacVersion}/{cloudType}/file/scan" diff --git a/pkg/http-server/remote-repo.go b/pkg/http-server/remote-repo.go index 0b833b568..dc1737e62 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:"webhook_url"` + NotificationWebhookToken string `json:"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/http-server/webhook-scan-logs.go b/pkg/http-server/webhook-scan-logs.go index 677aa4ea9..3372a0f69 100644 --- a/pkg/http-server/webhook-scan-logs.go +++ b/pkg/http-server/webhook-scan-logs.go @@ -82,7 +82,7 @@ func (g *APIHandler) getLogs(w http.ResponseWriter, r *http.Request) { ) // Validate if authorized (API key is specified and matched the server one (saved in an environment variable) - validatingWebhook := admissionWebhook.NewValidatingWebhook([]byte("")) + validatingWebhook := admissionWebhook.NewValidatingWebhook([]byte(""), "", "") if err := validatingWebhook.Authorize(apiKey); err != nil { switch err { case admissionWebhook.ErrAPIKeyMissing: diff --git a/pkg/http-server/webhook-scan.go b/pkg/http-server/webhook-scan.go index 5b0c1c2a4..28720ce04 100644 --- a/pkg/http-server/webhook-scan.go +++ b/pkg/http-server/webhook-scan.go @@ -34,8 +34,11 @@ func (g *APIHandler) validateK8SWebhook(w http.ResponseWriter, r *http.Request) zap.S().Debug("handle: validating webhook request") var ( - params = mux.Vars(r) - apiKey = params["apiKey"] + params = mux.Vars(r) + apiKey = params["apiKey"] + qP = r.URL.Query() + notificationWebhookURL = qP.Get("webhook-url") + notificationWebhookToken = qP.Get("webhook-token") ) // Read the request into byte array @@ -47,7 +50,7 @@ func (g *APIHandler) validateK8SWebhook(w http.ResponseWriter, r *http.Request) } zap.S().Debugf("scanning configuration webhook request: %+v", string(body)) - validatingWebhook := admissionWebhook.NewValidatingWebhook(body) + validatingWebhook := admissionWebhook.NewValidatingWebhook(body, notificationWebhookURL, notificationWebhookToken) // Validate if authorized (API key is specified and matched the server one (saved in an environment variable) if err := validatingWebhook.Authorize(apiKey); err != nil { switch err { diff --git a/pkg/k8s/admission-webhook/validating-webhook.go b/pkg/k8s/admission-webhook/validating-webhook.go index 51f063e56..2b16fbecf 100644 --- a/pkg/k8s/admission-webhook/validating-webhook.go +++ b/pkg/k8s/admission-webhook/validating-webhook.go @@ -42,15 +42,19 @@ import ( // the kubernetes API server and decides whether the admission request from // the kubernetes client should be allowed or not type ValidatingWebhook struct { - requestBody []byte - dblogger *dblogs.WebhookScanLogger + requestBody []byte + dblogger *dblogs.WebhookScanLogger + notificationWebhookURL string + notificationWebhookToken string } // NewValidatingWebhook returns a new, empty ValidatingWebhook struct -func NewValidatingWebhook(body []byte) AdmissionWebhook { +func NewValidatingWebhook(body []byte, notificationWebhookURL, notificationWebhookToken string) AdmissionWebhook { return ValidatingWebhook{ - dblogger: dblogs.NewWebhookScanLogger(), - requestBody: body, + dblogger: dblogs.NewWebhookScanLogger(), + requestBody: body, + notificationWebhookURL: notificationWebhookURL, + notificationWebhookToken: notificationWebhookToken, } } @@ -191,10 +195,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, w.notificationWebhookURL, w.notificationWebhookToken) } 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, w.notificationWebhookURL, w.notificationWebhookToken) } 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..4adc2df96 100644 --- a/pkg/runtime/executor.go +++ b/pkg/runtime/executor.go @@ -19,6 +19,7 @@ package runtime import ( "sort" + "github.com/accurics/terrascan/pkg/notifications/webhook" "github.com/accurics/terrascan/pkg/policy/opa" "github.com/accurics/terrascan/pkg/vulnerability" @@ -39,38 +40,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 @@ -147,13 +152,22 @@ func (e *Executor) Init() error { } // create new notifiers - e.notifiers, err = notifications.NewNotifiers() - if err != nil { - zap.S().Debug("failed to create notifier(s).", zap.Error(err)) - // do not return an error if a key is not present in the config file - if err != notifications.ErrNotificationNotPresent { - zap.S().Error("failed to create notifier(s).", zap.Error(err)) - return err + if e.notificationWebhookURL != "" { + e.notifiers = []notifications.Notifier{ + &webhook.Webhook{ + URL: e.notificationWebhookURL, + Token: e.notificationWebhookToken, + }, + } + } else { + e.notifiers, err = notifications.NewNotifiers() + if err != nil { + zap.S().Debug("failed to create notifier(s).", zap.Error(err)) + // do not return an error if a key is not present in the config file + if err != notifications.ErrNotificationNotPresent { + zap.S().Error("failed to create notifier(s).", zap.Error(err)) + return err + } } } diff --git a/pkg/runtime/executor_test.go b/pkg/runtime/executor_test.go index 2d8d65ec2..55205cf71 100644 --- a/pkg/runtime/executor_test.go +++ b/pkg/runtime/executor_test.go @@ -418,6 +418,22 @@ func TestInit(t *testing.T) { wantErr: config.ErrNotPresent, wantIacProvider: []iacProvider.IacProvider{&tfv12.TfV12{}}, }, + { + name: "notification webhook configs passed as CLI args", + executor: Executor{ + filePath: filepath.Join(testDataDir, "testfile"), + dirPath: "", + policyTypes: []string{"aws"}, + iacType: "terraform", + iacVersion: "v12", + notificationWebhookURL: "http://some-host.url", + notificationWebhookToken: "token", + }, + configFile: filepath.Join(testDataDir, "webhook.toml"), + wantErr: nil, + wantIacProvider: []iacProvider.IacProvider{&tfv12.TfV12{}}, + wantNotifiers: []notifications.Notifier{&webhook.Webhook{}}, + }, } for _, tt := range table { @@ -449,16 +465,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 +605,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..ca096d629 100644 --- a/pkg/runtime/notifications.go +++ b/pkg/runtime/notifications.go @@ -23,6 +23,7 @@ import ( // SendNotifications sends notifications via all the configured notifiers func (e *Executor) SendNotifications(data interface{}) error { var allErrs error + // 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..3b214c27d 100644 --- a/test/e2e/help/golden/help_scan.txt +++ b/test/e2e/help/golden/help_scan.txt @@ -26,6 +26,8 @@ Flags: --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) + --webhook-token string optional token used when sending authenticated requests to the notification webhook + --webhook-url string webhook URL where Terrascan will send JSON scan report and normalized IaC JSON Global Flags: -c, --config-path string config file path diff --git a/test/e2e/scan/scan_webhook_test.go b/test/e2e/scan/scan_webhook_test.go new file mode 100644 index 000000000..941d344ed --- /dev/null +++ b/test/e2e/scan/scan_webhook_test.go @@ -0,0 +1,77 @@ +/* + Copyright (C) 2020 Accurics, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package scan_test + +import ( + "path/filepath" + + scanUtils "github.com/accurics/terrascan/test/e2e/scan" + "github.com/accurics/terrascan/test/helper" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" +) + +var _ = Describe("Scan Command using webhook args", func() { + BeforeEach(func() { + outWriter = gbytes.NewBuffer() + errWriter = gbytes.NewBuffer() + }) + + AfterEach(func() { + outWriter = nil + errWriter = nil + }) + + var policyDir, iacDir string + var err error + + notificationURL := "https://httpbin.org/post" + notificationToken := "token" + + Describe("terrascan scan command is run with --webhook-url and --webhook-token flag", func() { + tfGoldenRelPath := filepath.Join("golden", "terraform_scans") + tfAwsAmiGoldenRelPath := filepath.Join(tfGoldenRelPath, "aws", "aws_ami_violations") + + iacDir, err = filepath.Abs(filepath.Join(awsIacRelPath, "aws_ami_violation")) + It("should not error out while getting absolute path", func() { + Expect(err).NotTo(HaveOccurred()) + }) + + policyDir, err = filepath.Abs(policyRootRelPath) + It("should not error out while getting absolute path", func() { + Expect(err).NotTo(HaveOccurred()) + }) + + Context("valid --webhook-url and --webhook-token flag is supplied", func() { + It("should exit with status code 5", func() { + scanArgs := []string{"-p", policyDir, "-i", "terraform", "-d", iacDir, "--webhook-url", notificationURL, "--webhook-token", notificationToken} + scanUtils.RunScanAndAssertGoldenOutputRegex(terrascanBinaryPath, filepath.Join(tfAwsAmiGoldenRelPath, "aws_ami_violation_human.txt"), helper.ExitCodeThree, false, true, outWriter, errWriter, scanArgs...) + + }) + }) + + Context("only --webhook-url flag is supplied", func() { + It("should exit with status code 5", func() { + scanArgs := []string{"-p", policyDir, "-i", "terraform", "-d", iacDir, "--webhook-url", notificationURL} + scanUtils.RunScanAndAssertGoldenOutputRegex(terrascanBinaryPath, filepath.Join(tfAwsAmiGoldenRelPath, "aws_ami_violation_human.txt"), helper.ExitCodeThree, false, true, outWriter, errWriter, scanArgs...) + + }) + }) + }) + +})