Skip to content

Commit

Permalink
accept notification webhook configs as CLI args
Browse files Browse the repository at this point in the history
  • Loading branch information
nasir-rabbani committed Oct 8, 2021
1 parent db18509 commit 6bb2cff
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 92 deletions.
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, "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)
}
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("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")
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: 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:"notification_webhook_url"`
NotificationWebhookToken string `json:"notification_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("notificationWebhookURL")
notificationWebhookToken = qP.Get("notificationWebhookToken")
)

// 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
84 changes: 49 additions & 35 deletions pkg/runtime/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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
Expand Down Expand Up @@ -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
}
}
}

Expand Down
40 changes: 29 additions & 11 deletions pkg/runtime/executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions pkg/runtime/notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 6bb2cff

Please sign in to comment.