From afc2a183553d2f0c4da108ed7513e815f0609d56 Mon Sep 17 00:00:00 2001 From: Yusuf Kanchwala <30405568+kanchwala-yusuf@users.noreply.github.com> Date: Fri, 28 May 2021 12:22:20 +0530 Subject: [PATCH] add support for YAML format for terrascan config file (#816) --- config/terrascan.toml | 2 + config/terrascan.yaml | 16 ++++ pkg/config/config-reader.go | 22 ++++- pkg/config/config-reader_test.go | 89 +++++++++++++++++++ pkg/config/testdata/invalid.yaml | 1 + .../testdata/terrascan-config-all-fields.yaml | 19 ++++ .../testdata/terrascan-config-category.yaml | 23 +++++ .../testdata/terrascan-config-severity.yml | 21 +++++ pkg/config/types.go | 40 ++++----- 9 files changed, 212 insertions(+), 21 deletions(-) create mode 100644 config/terrascan.yaml create mode 100644 pkg/config/testdata/invalid.yaml create mode 100644 pkg/config/testdata/terrascan-config-all-fields.yaml create mode 100644 pkg/config/testdata/terrascan-config-category.yaml create mode 100644 pkg/config/testdata/terrascan-config-severity.yml diff --git a/config/terrascan.toml b/config/terrascan.toml index 045087c05..31ff68ea7 100644 --- a/config/terrascan.toml +++ b/config/terrascan.toml @@ -3,6 +3,8 @@ # notifications configuration [notifications] [notifications.webhook] + type = "webhook" + [notifications.webhook.config] url = "https://httpbin.org/post" # scan and skip rules configuration diff --git a/config/terrascan.yaml b/config/terrascan.yaml new file mode 100644 index 000000000..342bff15e --- /dev/null +++ b/config/terrascan.yaml @@ -0,0 +1,16 @@ +notifications: + webhook: + type: webhook + config: + url: 'https://httpbin.org/post' + webhook1: + type: webhook + config: + url: 'https://httpbin.org/post' +rules: + scan-rules: + - AWS.S3Bucket.DS.High.1043 + - AWS.S3Bucket.IAM.High.0370 + skip-rules: + - AWS.S3Bucket.DS.High.1043 + - AWS.S3Bucket.IAM.High.0370 diff --git a/pkg/config/config-reader.go b/pkg/config/config-reader.go index 520e7b097..dd45a7c2f 100644 --- a/pkg/config/config-reader.go +++ b/pkg/config/config-reader.go @@ -20,9 +20,17 @@ import ( "fmt" "io/ioutil" "os" + "path/filepath" "github.com/pelletier/go-toml" "go.uber.org/zap" + "gopkg.in/yaml.v3" +) + +const ( + tomlExtension = ".toml" + yamlExtension1 = ".yaml" + yamlExtension2 = ".yml" ) var ( @@ -62,9 +70,21 @@ func NewTerrascanConfigReader(fileName string) (*TerrascanConfigReader, error) { return configReader, ErrTomlLoadConfig } - if err = toml.Unmarshal(data, &configReader.config); err != nil { + // check the extension of the file and decode using the file contents + // using the relevant package + switch filepath.Ext(fileName) { + case tomlExtension: + err = toml.Unmarshal(data, &configReader.config) + case yamlExtension1, yamlExtension2: + err = yaml.Unmarshal(data, &configReader.config) + default: + err = fmt.Errorf("file format %q not support for terrascan config file", + filepath.Ext(fileName)) + } + if err != nil { return configReader, err } + return configReader, nil } diff --git a/pkg/config/config-reader_test.go b/pkg/config/config-reader_test.go index 931c83366..845051353 100644 --- a/pkg/config/config-reader_test.go +++ b/pkg/config/config-reader_test.go @@ -71,6 +71,14 @@ func TestNewTerrascanConfigReader(t *testing.T) { wantErr: true, want: &TerrascanConfigReader{}, }, + { + name: "invalid config file format", + args: args{ + fileName: "test.invalid", + }, + wantErr: true, + want: &TerrascanConfigReader{}, + }, { name: "invalid toml config file", args: args{ @@ -79,6 +87,14 @@ func TestNewTerrascanConfigReader(t *testing.T) { wantErr: true, want: &TerrascanConfigReader{}, }, + { + name: "invalid yaml config file", + args: args{ + fileName: "testdata/invalid.yaml", + }, + wantErr: true, + want: &TerrascanConfigReader{}, + }, { name: "valid toml config file with partial fields", args: args{ @@ -155,6 +171,79 @@ func TestNewTerrascanConfigReader(t *testing.T) { Policy: testPolicy, Rules: testRules, }, + { + name: "invalid yaml config file", + args: args{ + fileName: "testdata/invalid.yaml", + }, + wantErr: true, + want: &TerrascanConfigReader{}, + }, + { + name: "valid yaml config file with all fields", + args: args{ + fileName: "testdata/terrascan-config-all-fields.yaml", + }, + want: &TerrascanConfigReader{ + config: TerrascanConfig{ + Policy: testPolicy, + Notifications: map[string]Notifier{ + "webhook1": testNotifier, + }, + Rules: testRules, + }, + }, + assertGetters: true, + notifications: map[string]Notifier{ + "webhook1": testNotifier, + }, + Policy: testPolicy, + Rules: testRules, + }, + { + name: "valid yaml config file with all fields and severity defined", + args: args{ + fileName: "testdata/terrascan-config-severity.yml", + }, + want: &TerrascanConfigReader{ + config: TerrascanConfig{ + Policy: testPolicy, + Notifications: map[string]Notifier{ + "webhook1": testNotifier, + }, + Rules: testRules, + Severity: highSeverity, + }, + }, + assertGetters: true, + notifications: map[string]Notifier{ + "webhook1": testNotifier, + }, + Policy: testPolicy, + Rules: testRules, + }, + { + name: "valid yaml config file with all fields and categories defined", + args: args{ + fileName: "testdata/terrascan-config-category.yaml", + }, + want: &TerrascanConfigReader{ + config: TerrascanConfig{ + Policy: testPolicy, + Notifications: map[string]Notifier{ + "webhook1": testNotifier, + }, + Rules: testRules, + Category: categoryList, + }, + }, + assertGetters: true, + notifications: map[string]Notifier{ + "webhook1": testNotifier, + }, + Policy: testPolicy, + Rules: testRules, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/config/testdata/invalid.yaml b/pkg/config/testdata/invalid.yaml new file mode 100644 index 000000000..8b0ce0c41 --- /dev/null +++ b/pkg/config/testdata/invalid.yaml @@ -0,0 +1 @@ +invalid yaml file \ No newline at end of file diff --git a/pkg/config/testdata/terrascan-config-all-fields.yaml b/pkg/config/testdata/terrascan-config-all-fields.yaml new file mode 100644 index 000000000..8d91676f5 --- /dev/null +++ b/pkg/config/testdata/terrascan-config-all-fields.yaml @@ -0,0 +1,19 @@ +policy: + path: custom-path + rego_subdir: rego-subdir + repo_url: 'https://repository/url' + branch: branch-name +notifications: + webhook1: + type: webhook + config: + url: testurl1 +rules: + scan-rules: + - rule.1 + - rule.2 + - rule.3 + - rule.4 + - rule.5 + skip-rules: + - rule.1 diff --git a/pkg/config/testdata/terrascan-config-category.yaml b/pkg/config/testdata/terrascan-config-category.yaml new file mode 100644 index 000000000..390bbb8db --- /dev/null +++ b/pkg/config/testdata/terrascan-config-category.yaml @@ -0,0 +1,23 @@ +policy: + path: custom-path + rego_subdir: rego-subdir + repo_url: 'https://repository/url' + branch: branch-name +notifications: + webhook1: + type: webhook + config: + url: testurl1 +rules: + scan-rules: + - rule.1 + - rule.2 + - rule.3 + - rule.4 + - rule.5 + skip-rules: + - rule.1 +category: + list: + - category.1 + - category.2 diff --git a/pkg/config/testdata/terrascan-config-severity.yml b/pkg/config/testdata/terrascan-config-severity.yml new file mode 100644 index 000000000..f25954c7b --- /dev/null +++ b/pkg/config/testdata/terrascan-config-severity.yml @@ -0,0 +1,21 @@ +policy: + path: custom-path + rego_subdir: rego-subdir + repo_url: 'https://repository/url' + branch: branch-name +notifications: + webhook1: + type: webhook + config: + url: testurl1 +rules: + scan-rules: + - rule.1 + - rule.2 + - rule.3 + - rule.4 + - rule.5 + skip-rules: + - rule.1 +severity: + level: high diff --git a/pkg/config/types.go b/pkg/config/types.go index c5853a6f8..954a0dcc8 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -21,52 +21,52 @@ var global *TerrascanConfig // TerrascanConfig struct defines global variables/configurations across terrascan type TerrascanConfig struct { - Policy `toml:"policy,omitempty"` - Notifications map[string]Notifier `toml:"notifications,omitempty"` - Rules `toml:"rules,omitempty"` - Category `toml:"category,omitempty"` - Severity `toml:"severity,omitempty"` - K8sAdmissionControl `toml:"k8s-admission-control,omitempty"` + Policy `toml:"policy,omitempty" yaml:"policy,omitempty"` + Notifications map[string]Notifier `toml:"notifications,omitempty" yaml:"notifications,omitempty"` + Rules `toml:"rules,omitempty" yaml:"rules,omitempty"` + Category `toml:"category,omitempty" yaml:"category,omitempty"` + Severity `toml:"severity,omitempty" yaml:"severity,omitempty"` + K8sAdmissionControl `toml:"k8s-admission-control,omitempty" yaml:"k8s-admission-control,omitempty"` } // Category defines the categories of violations that you want to be reported type Category struct { - List []string `toml:"list"` + List []string `toml:"list" yaml:"list"` } // Severity defines the minimum level of severity of violations that you want to be reported type Severity struct { - Level string `toml:"level"` + Level string `toml:"level" yaml:"level"` } // Policy struct defines policy specific configurations type Policy struct { // policy repo local path - BasePath string `toml:"path,omitempty"` + BasePath string `toml:"path,omitempty" yaml:"path,omitempty"` // local filepath where repository containing policies is cached at - RepoPath string `toml:"rego_subdir,omitempty"` + RepoPath string `toml:"rego_subdir,omitempty" yaml:"rego_subdir,omitempty"` // policy git url and branch - RepoURL string `toml:"repo_url,omitempty"` - Branch string `toml:"branch,omitempty"` + RepoURL string `toml:"repo_url,omitempty" yaml:"repo_url,omitempty"` + Branch string `toml:"branch,omitempty" yaml:"branch,omitempty"` } // Notifier represent a single notification in the terrascan config file type Notifier struct { - NotifierType string `toml:"type"` - NotifierConfig interface{} `toml:"config"` + NotifierType string `toml:"type" yaml:"type"` + NotifierConfig interface{} `toml:"config" yaml:"config"` } // Rules represents scan and skip rules in the terrascan config file type Rules struct { - ScanRules []string `toml:"scan-rules,omitempty"` - SkipRules []string `toml:"skip-rules,omitempty"` + ScanRules []string `toml:"scan-rules,omitempty" yaml:"scan-rules,omitempty"` + SkipRules []string `toml:"skip-rules,omitempty" yaml:"skip-rules,omitempty"` } // K8sAdmissionControl deny rules in the terrascan config file type K8sAdmissionControl struct { - Dashboard bool `toml:"dashboard,omitempty"` - DeniedSeverity string `toml:"denied-severity,omitempty"` - Categories []string `toml:"denied-categories,omitempty"` - SaveRequests bool `toml:"save-requests,omitempty"` + Dashboard bool `toml:"dashboard,omitempty" yaml:"dashboard,omitempty"` + DeniedSeverity string `toml:"denied-severity,omitempty" yaml:"denied-severity,omitempty"` + Categories []string `toml:"denied-categories,omitempty" yaml:"denied-categories,omitempty"` + SaveRequests bool `toml:"save-requests,omitempty" yaml:"save-requests,omitempty"` }