Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accept notification webhook configs as CLI args #1042

Merged
merged 5 commits into from
Oct 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 26 additions & 22 deletions docs/usage/command_line_mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | <tbd any formats/limitations for example file size> |
| -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: <tbd specify a default path> |
| -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 |
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions docs/usage/config_options.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ $ terrascan scan -c <config file path>
[notifications.webhook]
url = "https://httpbin.org/post"
token = "my_auth_token"

[severity]
level = "medium"
[rules]
Expand All @@ -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:
Expand Down
8 changes: 7 additions & 1 deletion pkg/cli/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/cli/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
6 changes: 4 additions & 2 deletions pkg/http-server/file-scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand Down
26 changes: 26 additions & 0 deletions pkg/http-server/file-scan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ func TestUpload(t *testing.T) {
categories []string
invalidFindVulnerabilities bool
findVulnerabilities bool
notificationWebhookURL string
notificationWebhookToken string
}{
{
name: "valid file scan",
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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"
Expand Down
26 changes: 14 additions & 12 deletions pkg/http-server/remote-repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pkg/http-server/webhook-scan-logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 6 additions & 3 deletions pkg/http-server/webhook-scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down
18 changes: 11 additions & 7 deletions pkg/k8s/admission-webhook/validating-webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down Expand Up @@ -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)
Expand Down
Loading