Skip to content

Commit

Permalink
Merge pull request #262 from accurics/terrascan-v1.0-add-webhook-noti…
Browse files Browse the repository at this point in the history
…fications

Terrascan v1.0 add webhook notifications
  • Loading branch information
kanchwala-yusuf committed Aug 10, 2020
2 parents 600a6e6 + 3a8a314 commit e8a70f6
Show file tree
Hide file tree
Showing 28 changed files with 809 additions and 18 deletions.
5 changes: 4 additions & 1 deletion cmd/terrascan/main.go
Expand Up @@ -45,6 +45,9 @@ func main() {
// logging flags
logLevel = flag.String("log-level", "info", "logging level (debug, info, warn, error, panic, fatal)")
logType = flag.String("log-type", "console", "log type (json, console)")

// config file
configFile = flag.String("config", "", "config file path")
)
flag.Parse()

Expand All @@ -61,6 +64,6 @@ func main() {
} else {
logging.Init(*logType, *logLevel)
zap.S().Debug("running terrascan in cli mode")
cli.Run(*iacType, *iacVersion, *cloudType, *iacFilePath, *iacDirPath)
cli.Run(*iacType, *iacVersion, *cloudType, *iacFilePath, *iacDirPath, *configFile)
}
}
6 changes: 6 additions & 0 deletions config/terrascan.toml
@@ -0,0 +1,6 @@
# terrascan configuration file

# notifications configuration
[notifications]
[notifications.webhook]
url = "https://httpbin.org/post"
3 changes: 3 additions & 0 deletions go.mod
Expand Up @@ -4,9 +4,12 @@ go 1.14

require (
github.com/gorilla/mux v1.7.4
github.com/hashicorp/go-retryablehttp v0.6.6
github.com/hashicorp/go-version v1.2.0
github.com/hashicorp/hcl/v2 v2.3.0
github.com/hashicorp/terraform v0.12.28
github.com/pelletier/go-toml v1.8.0
github.com/pkg/errors v0.9.1
github.com/spf13/afero v1.3.2
github.com/zclconf/go-cty v1.2.1
go.uber.org/zap v1.9.1
Expand Down
7 changes: 7 additions & 0 deletions go.sum
Expand Up @@ -133,16 +133,20 @@ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv
github.com/hashicorp/go-azure-helpers v0.10.0/go.mod h1:YuAtHxm2v74s+IjQwUG88dHBJPd5jL+cXr5BGVzSKhE=
github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-getter v1.4.2-0.20200106182914-9813cbd4eb02/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY=
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
github.com/hashicorp/go-hclog v0.0.0-20181001195459-61d530d6c27f/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-immutable-radix v0.0.0-20180129170900-7f3cd4390caa/go.mod h1:6ij3Z20p+OhOkCSrA0gImAWoHYQRGbnlcuk6XYTiaRw=
github.com/hashicorp/go-msgpack v0.5.4/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-plugin v1.3.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0=
github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM=
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
github.com/hashicorp/go-slug v0.4.1/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8=
Expand Down Expand Up @@ -235,6 +239,8 @@ github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/packer-community/winrmcp v0.0.0-20180102160824-81144009af58/go.mod h1:f6Izs6JvFTdnRbziASagjZ2vmf55NSIkC/weStxCHqk=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw=
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down Expand Up @@ -438,6 +444,7 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
Expand Down
12 changes: 6 additions & 6 deletions pkg/cli/run.go
Expand Up @@ -17,24 +17,24 @@
package cli

import (
"os"
// "os"

"github.com/accurics/terrascan/pkg/runtime"
"github.com/accurics/terrascan/pkg/utils"
// "github.com/accurics/terrascan/pkg/utils"
)

// Run executes terrascan in CLI mode
func Run(iacType, iacVersion, cloudType, iacFilePath, iacDirPath string) {
func Run(iacType, iacVersion, cloudType, iacFilePath, iacDirPath, configFile string) {

// create a new runtime executor for processing IaC
executor, err := runtime.NewExecutor(iacType, iacVersion, cloudType, iacFilePath,
iacDirPath)
iacDirPath, configFile)
if err != nil {
return
}
normalized, err := executor.Execute()
_, err = executor.Execute()
if err != nil {
return
}
utils.PrintJSON(normalized, os.Stdout)
// utils.PrintJSON(normalized, os.Stdout)
}
2 changes: 1 addition & 1 deletion pkg/http-server/file-scan.go
Expand Up @@ -83,7 +83,7 @@ func (g *APIHandler) scanFile(w http.ResponseWriter, r *http.Request) {

// create a new runtime executor for scanning the uploaded file
executor, err := runtime.NewExecutor(iacType, iacVersion, cloudType,
tempFile.Name(), "")
tempFile.Name(), "", "")
if err != nil {
zap.S().Error(err)
apiErrorResponse(w, err.Error(), http.StatusBadRequest)
Expand Down
8 changes: 8 additions & 0 deletions 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(interface{}) error
SendNotification(interface{}) error
}
137 changes: 137 additions & 0 deletions pkg/notifications/notifiers.go
@@ -0,0 +1,137 @@
/*
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"
"os"
"reflect"

"github.com/accurics/terrascan/pkg/utils"
"github.com/pelletier/go-toml"
"go.uber.org/zap"
)

const (
notificationsConfigKey = "notifications"
)

var (
errNotPresent = fmt.Errorf("config file not present")
errNotifierNotSupported = fmt.Errorf("notifier not supported")
errTomlLoadConfig = fmt.Errorf("failed to load toml config")
errTomlKeyNotPresent = fmt.Errorf("key not present in toml config")
)

// 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
}

// successful
return reflect.New(notifierObject).Interface().(Notifier), nil
}

// NewNotifiers returns a list of notifiers configured in the config file
func NewNotifiers(configFile string) ([]Notifier, error) {

var notifiers []Notifier

// empty config file path
if configFile == "" {
zap.S().Infof("no config file specified")
return notifiers, nil
}

// check if file exists
_, err := os.Stat(configFile)
if err != nil {
zap.S().Errorf("config file '%s' not present", configFile)
return notifiers, errNotPresent
}

// parse toml config file
config, err := toml.LoadFile(configFile)
if err != nil {
zap.S().Errorf("failed to load toml config file '%s'. error: '%v'", err)
return notifiers, errTomlLoadConfig
}

// get config for 'notifications'
keyConfig := config.Get(notificationsConfigKey)
if keyConfig == nil {
zap.S().Infof("key '%s' not present in toml config", notificationsConfigKey)
return notifiers, errTomlKeyNotPresent
}

// get all the notifier types configured in TOML config
keyTomlConfig := keyConfig.(*toml.Tree)
notifierTypes := keyTomlConfig.Keys()

// create notifiers
var allErrs error
for _, nType := range notifierTypes {

if !IsNotifierSupported(nType) {
zap.S().Errorf("notifier type '%s' not supported", nType)
allErrs = utils.WrapError(errNotifierNotSupported, allErrs)
continue
}

// check if toml config present for notifier type
nTypeConfig := keyTomlConfig.Get(nType)
if nTypeConfig.(*toml.Tree).String() == "" {
zap.S().Errorf("notifier '%v' config not present", nType)
allErrs = utils.WrapError(errTomlKeyNotPresent, allErrs)
continue
}

// create a new notifier
n, err := NewNotifier(nType)
if err != nil {
allErrs = utils.WrapError(err, allErrs)
continue
}

// populate data
err = n.Init(nTypeConfig)
if err != nil {
allErrs = utils.WrapError(err, allErrs)
continue
}

// add to the list of notifiers
notifiers = append(notifiers, n)
}

// return list of notifiers
return notifiers, allErrs
}

// 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
}
91 changes: 91 additions & 0 deletions pkg/notifications/notifiers_test.go
@@ -0,0 +1,91 @@
package notifications

import (
"reflect"
"testing"

"github.com/accurics/terrascan/pkg/notifications/webhook"
)

func TestNewNotifier(t *testing.T) {

table := []struct {
name string
nType string
wantType Notifier
wantErr error
}{
{
name: "valid notifier",
nType: "webhook",
wantType: &webhook.Webhook{},
wantErr: nil,
},
{
name: "invalid notifier",
nType: "notthere",
wantErr: errNotifierNotSupported,
},
}

for _, tt := range table {
t.Run(tt.name, func(t *testing.T) {
gotType, gotErr := NewNotifier(tt.nType)
if !reflect.DeepEqual(gotType, tt.wantType) {
t.Errorf("got: '%v', want: '%v'", gotType, tt.wantType)
}
if !reflect.DeepEqual(gotErr, tt.wantErr) {
t.Errorf("incorrect error; got: '%v', want: '%v'", gotErr, tt.wantErr)
}
})
}
}

func TestNewNotifiers(t *testing.T) {

table := []struct {
name string
configFile string
wantErr error
}{
{
name: "config not present",
configFile: "notthere",
wantErr: errNotPresent,
},
{
name: "invalid toml",
configFile: "testdata/invalid.toml",
wantErr: errTomlLoadConfig,
},
{
name: "key not present",
configFile: "testdata/nokey.toml",
wantErr: errTomlKeyNotPresent,
},
{
name: "invalid notifier",
configFile: "testdata/invalid-notifier-type.toml",
wantErr: errNotifierNotSupported,
},
{
name: "empty notifier config",
configFile: "testdata/empty-notifier-config.toml",
wantErr: errTomlKeyNotPresent,
},
{
name: "invalid notifier config",
configFile: "testdata/invalid-notifier-config.toml",
wantErr: nil,
},
}

for _, tt := range table {
t.Run(tt.name, func(t *testing.T) {
_, gotErr := NewNotifiers(tt.configFile)
if !reflect.DeepEqual(gotErr, tt.wantErr) {
t.Errorf("incorrect error; got: '%v', want: '%v'", gotErr, tt.wantErr)
}
})
}
}
29 changes: 29 additions & 0 deletions 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
}
2 changes: 2 additions & 0 deletions pkg/notifications/testdata/empty-notifier-config.toml
@@ -0,0 +1,2 @@
[notifications]
[notifications.webhook]
4 changes: 4 additions & 0 deletions pkg/notifications/testdata/invalid-notifier-config.toml
@@ -0,0 +1,4 @@
[notifications]
[notifications.webhook]
key1 = "val1"
key2 = "val2"
6 changes: 6 additions & 0 deletions pkg/notifications/testdata/invalid-notifier-type.toml
@@ -0,0 +1,6 @@
# terrascan configuration file

# notifications configuration
[notifications]
[notifications.invalid]
url = "https://httpbin.org/post"

0 comments on commit e8a70f6

Please sign in to comment.