From 0ddf0a0b39576840e103d2672a61110e633806b7 Mon Sep 17 00:00:00 2001 From: Yusuf Kanchwala Date: Tue, 4 Aug 2020 23:34:40 +0530 Subject: [PATCH] add support for sending webhook notifications --- pkg/notifications/interface.go | 8 ++++ pkg/notifications/notifiers.go | 57 +++++++++++++++++++++++++++ pkg/notifications/register.go | 29 ++++++++++++++ pkg/notifications/types.go | 20 ++++++++++ pkg/notifications/webhook.go | 35 +++++++++++++++++ pkg/notifications/webhook/types.go | 23 +++++++++++ pkg/notifications/webhook/webhook.go | 51 ++++++++++++++++++++++++ pkg/runtime/executor.go | 18 ++++++++- pkg/utils/http/request.go | 58 ++++++++++++++++++++++++++++ 9 files changed, 297 insertions(+), 2 deletions(-) create mode 100644 pkg/notifications/interface.go create mode 100644 pkg/notifications/notifiers.go create mode 100644 pkg/notifications/register.go create mode 100644 pkg/notifications/types.go create mode 100644 pkg/notifications/webhook.go create mode 100644 pkg/notifications/webhook/types.go create mode 100644 pkg/notifications/webhook/webhook.go create mode 100644 pkg/utils/http/request.go diff --git a/pkg/notifications/interface.go b/pkg/notifications/interface.go new file mode 100644 index 000000000..255c4c9eb --- /dev/null +++ b/pkg/notifications/interface.go @@ -0,0 +1,8 @@ +package notifications + +// Notifier defines the interface which every type of notification provider +// needs to implement to claim support in terrascan +type Notifier interface { + Init() error + SendNotification() error +} diff --git a/pkg/notifications/notifiers.go b/pkg/notifications/notifiers.go new file mode 100644 index 000000000..54713454f --- /dev/null +++ b/pkg/notifications/notifiers.go @@ -0,0 +1,57 @@ +/* + 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 notifications + +import ( + "fmt" + "reflect" + + "go.uber.org/zap" +) + +var ( + errNotifierNotSupported = fmt.Errorf("notifier not supported") +) + +// NewNotifier returns a new notifier +func NewNotifier(notifierType string) (notifier Notifier, err error) { + + // get notifier from supportedNotifierss + notifierObject, supported := supportedNotifiers[supportedNotifierType(notifierType)] + if !supported { + zap.S().Errorf("notifier type '%s' not supported", notifierType) + return notifier, errNotifierNotSupported + } + + // notifier + notifier = reflect.New(notifierObject).Interface().(Notifier) + + // initialize notifier + notifier.Init() + + // successful + return notifier, nil +} + +// IsNotifierSupported returns true/false depending on whether the notifier +// is supported in terrascan or not +func IsNotifierSupported(notifierType string) bool { + if _, supported := supportedNotifiers[supportedNotifierType(notifierType)]; !supported { + return false + } + return true +} diff --git a/pkg/notifications/register.go b/pkg/notifications/register.go new file mode 100644 index 000000000..c875a7fda --- /dev/null +++ b/pkg/notifications/register.go @@ -0,0 +1,29 @@ +/* + 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 notifications + +import ( + "reflect" +) + +// map of supported notifier types +var supportedNotifiers = make(map[supportedNotifierType]reflect.Type) + +// RegisterNotifier registers an notifier provider for terrascan +func RegisterNotifier(notifierType supportedNotifierType, notifierProvider reflect.Type) { + supportedNotifiers[notifierType] = notifierProvider +} diff --git a/pkg/notifications/types.go b/pkg/notifications/types.go new file mode 100644 index 000000000..ce13c45f5 --- /dev/null +++ b/pkg/notifications/types.go @@ -0,0 +1,20 @@ +/* + 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 notifications + +// SupportedNotifierType data type for supported IaC provider +type supportedNotifierType string diff --git a/pkg/notifications/webhook.go b/pkg/notifications/webhook.go new file mode 100644 index 000000000..695b4a1cd --- /dev/null +++ b/pkg/notifications/webhook.go @@ -0,0 +1,35 @@ +/* + 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 notifications + +import ( + "reflect" + + webhookNotifier "github.com/accurics/terrascan/pkg/notifications/webhook" +) + +// terraform specific constants +const ( + terraform supportedNotifierType = "webhook" +) + +// register terraform as an IaC provider with terrascan +func init() { + + // register iac provider + RegisterNotifier(terraform, reflect.TypeOf(webhookNotifier.Webhook{})) +} diff --git a/pkg/notifications/webhook/types.go b/pkg/notifications/webhook/types.go new file mode 100644 index 000000000..a1c131ad1 --- /dev/null +++ b/pkg/notifications/webhook/types.go @@ -0,0 +1,23 @@ +/* + 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 webhook + +// Webhook implements the Notifier interface +type Webhook struct { + url string + authToken string +} diff --git a/pkg/notifications/webhook/webhook.go b/pkg/notifications/webhook/webhook.go new file mode 100644 index 000000000..5eaf5e1e9 --- /dev/null +++ b/pkg/notifications/webhook/webhook.go @@ -0,0 +1,51 @@ +/* + 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 webhook + +import ( + "go.uber.org/zap" +) + +// Init initalizes the webhook notifier, reads config file and configures the +// necessary parameters for webhook notifications to work +func (w *Webhook) Init() error { + + // check if conf file exists + + // parse conf file + + // read webhook url and auth token + + // initalize Webhook struct with url and token + + // succesful + zap.S().Debug("initialized webhook notifier") + return nil +} + +// SendNotification sends webhook notification i.e sends a http POST request +// to the configured URL +func (w *Webhook) SendNotification() error { + + // make http POST request + + // validate http response + + // successful + zap.S().Debug("sent webhook notification") + return nil +} diff --git a/pkg/runtime/executor.go b/pkg/runtime/executor.go index 1f40cf967..d33855647 100644 --- a/pkg/runtime/executor.go +++ b/pkg/runtime/executor.go @@ -20,6 +20,7 @@ import ( "go.uber.org/zap" iacProvider "github.com/accurics/terrascan/pkg/iac-providers" + "github.com/accurics/terrascan/pkg/notifications" ) // Executor object @@ -29,7 +30,9 @@ type Executor struct { cloudType string iacType string iacVersion string + configFile string iacProvider iacProvider.IacProvider + notifiers notifications.Notifier } // NewExecutor creates a runtime object @@ -66,23 +69,34 @@ func (e *Executor) Init() error { return err } + // create new notifiers + e.notifiers, err = notifications.NewNotifier("webhook") + if err != nil { + zap.S().Errorf("failed to create notifier(s). error: '%s'", err) + return err + } + + zap.S().Debug("initialized executor") return nil } // Execute validates the inputs, processes the IaC, creates json output func (e *Executor) Execute() (normalized interface{}, err error) { + // create normalized output from Iac if e.dirPath != "" { normalized, err = e.iacProvider.LoadIacDir(e.dirPath) } else { - // create config from IaC normalized, err = e.iacProvider.LoadIacFile(e.filePath) } if err != nil { return normalized, err } - // write output + // evaluate policies + + // send notifications, if configured + e.notifiers.SendNotification() // successful return normalized, nil diff --git a/pkg/utils/http/request.go b/pkg/utils/http/request.go new file mode 100644 index 000000000..b0ce6d960 --- /dev/null +++ b/pkg/utils/http/request.go @@ -0,0 +1,58 @@ +package httputils + +import ( + "bytes" + "encoding/json" + "log" + "net/http" + + "github.com/hashicorp/go-retryablehttp" + "go.uber.org/zap" +) + +const ( + errNewRequest = fmt.Errorf("failed to create http request") + errDoRequest = fmt.Errorf("failed to make http request") +) + +// default global http client +var client *http.Client = &http.Client{} + +// init creates a http client which retries on errors like connection timeouts, +// server too slow respond etc. +func init() { + retryClient := retryablehttp.NewClient() + retryClient.RetryMax = 10 + client = retryClient.StandardClient() +} + +// SendRequest sends a http request on the given url +func SendRequest(method, url, token string, data []byte) (*http.Response, error) { + + var resp *http.Response + + // new http request + req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) + if err != nil { + zap.S().Errorf("failed to create http request; method: '%v', url: '%v'") + return resp, errNewRequest + } + req.Header.Set("Content-Type", "application/json") + if token != nil { + req.Header.Set("Authorization", fmt.Sprintf("Bearer: '%s'", token)) + } + + // make request + resp, err := client.Do(req) + if err != nil { + zap.S().Errorf("failed to make http request; method: '%v', url: '%v'") + return resp, errDoRequest + } + + return resp, err +} + +// SendPOSTRequest sends a http POST request +func SendPOSTRequest(url, token string) (*http.Response, error) { + return SendRequest("POST", url, token) +}