diff --git a/go.mod b/go.mod index 18d74cf4a..0abff1779 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/zclconf/go-cty v1.2.1 go.uber.org/zap v1.13.0 golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect - golang.org/x/tools v0.0.0-20201113202037-1643af1435f3 // indirect + golang.org/x/tools v0.0.0-20201201064407-fd09bd90d85c // indirect gopkg.in/src-d/go-git.v4 v4.13.1 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 helm.sh/helm/v3 v3.4.0 diff --git a/go.sum b/go.sum index 8d93c2c2f..f479c5c57 100644 --- a/go.sum +++ b/go.sum @@ -1227,6 +1227,10 @@ golang.org/x/tools v0.0.0-20201113164040-559c4acc06b6 h1:LTVgvEdikVZCooj7814/UBp golang.org/x/tools v0.0.0-20201113164040-559c4acc06b6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201113202037-1643af1435f3 h1:7R7+wzd5VuLvCNyHZ/MG511kkoP/DBEzkbh8qUsFbY8= golang.org/x/tools v0.0.0-20201113202037-1643af1435f3/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201121010211-780cb80bd7fb h1:z5+u0pkAUPUWd3taoTialQ2JAMo4Wo1Z3L25U4ZV9r0= +golang.org/x/tools v0.0.0-20201121010211-780cb80bd7fb/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201064407-fd09bd90d85c h1:D/mVYXCk6gUcyr7WuGlAk/ShHqgARUXc2VQxo27Hmws= +golang.org/x/tools v0.0.0-20201201064407-fd09bd90d85c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/iac-providers/kustomize/v3/load-dir.go b/pkg/iac-providers/kustomize/v3/load-dir.go index 2ce49f1b4..a34ead15d 100644 --- a/pkg/iac-providers/kustomize/v3/load-dir.go +++ b/pkg/iac-providers/kustomize/v3/load-dir.go @@ -1,7 +1,6 @@ package kustomizev3 import ( - "errors" "fmt" "path/filepath" @@ -17,6 +16,12 @@ const ( kustomizedirectory string = "kustomization" ) +var ( + errorKustomizeNotFound = fmt.Errorf("kustomization.y(a)ml file not found in the directory") + errorMultipleKustomizeFile = fmt.Errorf("multiple kustomization.y(a)ml found in the directory") + errorFromKustomize = fmt.Errorf("error from kustomization") +) + // LoadIacDir loads the kustomize directory and returns the ResourceConfig mapping which is evaluated by the policy engine func (k *KustomizeV3) LoadIacDir(absRootDir string) (output.AllResourceConfigs, error) { @@ -29,13 +34,13 @@ func (k *KustomizeV3) LoadIacDir(absRootDir string) (output.AllResourceConfigs, } if len(files) == 0 { - err = errors.New("could not find a kustomization.yaml/yml file in the directory") + err = errorKustomizeNotFound zap.S().Error("error while searching for iac files", zap.String("root dir", absRootDir), zap.Error(err)) return allResourcesConfig, err } if len(files) > 1 { - err = errors.New("a directory cannot have more than 1 kustomization.yaml/yml file") + err = errorMultipleKustomizeFile zap.S().Error("error while searching for iac files", zap.String("root dir", absRootDir), zap.Error(err)) return allResourcesConfig, err } @@ -43,9 +48,9 @@ func (k *KustomizeV3) LoadIacDir(absRootDir string) (output.AllResourceConfigs, kustomizeFileName := *files[0] yamlkustomizeobj, err := utils.ReadYamlFile(filepath.Join(absRootDir, kustomizeFileName)) - if len(yamlkustomizeobj) == 0 { - err = fmt.Errorf("unable to read any kustomization file in the directory : %v", err) - zap.S().Error("error while searching for iac files", zap.String("root dir", absRootDir), zap.Error(err)) + if err != nil { + err = fmt.Errorf("unable to read the kustomization file in the directory : %v", err) + zap.S().Error("error while reading the file", kustomizeFileName, zap.Error(err)) return allResourcesConfig, err } @@ -94,7 +99,7 @@ func LoadKustomize(basepath, filename string) ([]*utils.IacDocument, error) { m, err := k.Run(basepath) if err != nil { - return nil, err + return nil, errorFromKustomize } yaml, err := m.AsYaml() diff --git a/pkg/iac-providers/kustomize/v3/load-dir_test.go b/pkg/iac-providers/kustomize/v3/load-dir_test.go new file mode 100644 index 000000000..96160e2c9 --- /dev/null +++ b/pkg/iac-providers/kustomize/v3/load-dir_test.go @@ -0,0 +1,183 @@ +package kustomizev3 + +import ( + "fmt" + "os" + "reflect" + "syscall" + "testing" + + "github.com/accurics/terrascan/pkg/iac-providers/output" + "github.com/accurics/terrascan/pkg/utils" +) + +var errorReadKustomize = fmt.Errorf("unable to read the kustomization file in the directory : %s", utils.ErrYamlFileEmpty.Error()) + +func TestLoadIacDir(t *testing.T) { + + table := []struct { + name string + dirPath string + kustomize KustomizeV3 + want output.AllResourceConfigs + wantErr error + resourceCount int + }{ + { + name: "invalid dirPath", + dirPath: "not-there", + kustomize: KustomizeV3{}, + wantErr: &os.PathError{Err: syscall.ENOENT, Op: "open", Path: "not-there"}, + resourceCount: 0, + }, + { + name: "simple-deployment", + dirPath: "./testdata/simple-deployment", + kustomize: KustomizeV3{}, + wantErr: nil, + resourceCount: 4, + }, + { + name: "multibases", + dirPath: "./testdata/multibases/base", + kustomize: KustomizeV3{}, + wantErr: nil, + resourceCount: 2, + }, + { + name: "multibases", + dirPath: "./testdata/multibases/dev", + kustomize: KustomizeV3{}, + wantErr: nil, + resourceCount: 2, + }, + { + name: "multibases", + dirPath: "./testdata/multibases/prod", + kustomize: KustomizeV3{}, + wantErr: nil, + resourceCount: 2, + }, + + { + name: "multibases", + dirPath: "./testdata/multibases/stage", + kustomize: KustomizeV3{}, + wantErr: nil, + resourceCount: 2, + }, + { + name: "multibases", + dirPath: "./testdata/multibases", + kustomize: KustomizeV3{}, + wantErr: nil, + resourceCount: 4, + }, + { + name: "no-kustomize-directory", + dirPath: "./testdata/no-kustomizefile", + kustomize: KustomizeV3{}, + wantErr: errorKustomizeNotFound, + resourceCount: 0, + }, + { + name: "kustomize-file-empty", + dirPath: "./testdata/kustomize-file-empty", + kustomize: KustomizeV3{}, + wantErr: errorReadKustomize, + resourceCount: 0, + }, + } + + for _, tt := range table { + t.Run(tt.name, func(t *testing.T) { + resourceMap, gotErr := tt.kustomize.LoadIacDir(tt.dirPath) + if !reflect.DeepEqual(gotErr, tt.wantErr) { + t.Errorf("unexpected error; gotErr: '%v', wantErr: '%v'", gotErr, tt.wantErr) + } + + resCount := utils.GetResourceCount(resourceMap) + if resCount != tt.resourceCount { + t.Errorf("resource count (%d) does not match expected (%d)", resCount, tt.resourceCount) + } + }) + } + +} + +func TestLoadKustomize(t *testing.T) { + kustomizeYaml := "kustomization.yaml" + kustomizeYml := "kustomization.yml" + + table := []struct { + name string + basepath string + filename string + want output.AllResourceConfigs + wantErr error + }{ + { + name: "simple-deployment", + basepath: "./testdata/simple-deployment", + filename: kustomizeYaml, + wantErr: nil, + }, + { + name: "multibases", + basepath: "./testdata/multibases", + filename: kustomizeYaml, + wantErr: nil, + }, + { + name: "multibases/base", + basepath: "./testdata/multibases/base", + filename: kustomizeYml, + wantErr: nil, + }, + { + name: "multibases/dev", + basepath: "./testdata/multibases/dev", + filename: kustomizeYaml, + wantErr: nil, + }, + { + name: "multibases/prod", + basepath: "./testdata/multibases/prod", + filename: kustomizeYaml, + wantErr: nil, + }, + { + name: "multibases/stage", + basepath: "./testdata/multibases/stage", + filename: kustomizeYaml, + wantErr: nil, + }, + { + name: "multibases/zero-violation-base", + basepath: "./testdata/multibases/zero-violation-base", + filename: kustomizeYaml, + wantErr: nil, + }, + { + name: "erroneous-pod", + basepath: "./testdata/erroneous-pod", + filename: kustomizeYaml, + wantErr: errorFromKustomize, + }, + { + name: "erroneous-deployment", + basepath: "./testdata/erroneous-deployment/", + filename: kustomizeYaml, + wantErr: errorFromKustomize, + }, + } + + for _, tt := range table { + t.Run(tt.name, func(t *testing.T) { + _, gotErr := LoadKustomize(tt.basepath, tt.filename) + if !reflect.DeepEqual(gotErr, tt.wantErr) { + t.Errorf("unexpected error; gotErr: '%v', wantErr: '%v'", gotErr, tt.wantErr) + } + }) + } +} diff --git a/pkg/iac-providers/kustomize/v3/load-file_test.go b/pkg/iac-providers/kustomize/v3/load-file_test.go new file mode 100644 index 000000000..8d52917a8 --- /dev/null +++ b/pkg/iac-providers/kustomize/v3/load-file_test.go @@ -0,0 +1,38 @@ +package kustomizev3 + +import ( + "reflect" + "testing" + + "github.com/accurics/terrascan/pkg/iac-providers/output" +) + +func TestLoadIacFile(t *testing.T) { + + table := []struct { + name string + filePath string + kustomize KustomizeV3 + typeOnly bool + want output.AllResourceConfigs + wantErr error + }{ + { + name: "load iac file is not supported for kustomize", + filePath: "/dummyfilepath.yaml", + kustomize: KustomizeV3{}, + wantErr: errLoadIacFileNotSupported, + }, + } + + for _, tt := range table { + t.Run(tt.name, func(t *testing.T) { + _, gotErr := tt.kustomize.LoadIacFile(tt.filePath) + if !reflect.DeepEqual(gotErr, tt.wantErr) { + t.Errorf("unexpected error; gotErr: '%v', wantErr: '%v'", gotErr, tt.wantErr) + } else if tt.typeOnly && (reflect.TypeOf(gotErr)) != reflect.TypeOf(tt.wantErr) { + t.Errorf("unexpected error; gotErr: '%v', wantErr: '%v'", reflect.TypeOf(gotErr), reflect.TypeOf(tt.wantErr)) + } + }) + } +} diff --git a/pkg/iac-providers/kustomize/v3/testdata/erroneous-deployment/deployment.yaml b/pkg/iac-providers/kustomize/v3/testdata/erroneous-deployment/deployment.yaml new file mode 100644 index 000000000..c54230d93 --- /dev/null +++ b/pkg/iac-providers/kustomize/v3/testdata/erroneous-deployment/deployment.yaml @@ -0,0 +1,16 @@ +apiVersion: apps/v1beta1 +kind: Deployment +metadata: + labels: + app: myapp + test: someupdate + test2: someupdate3 +spec: + template: + spec: + containers: + - name: myapp-container2 + image: busybox + command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600'] + securityContext: + allowPrivilegeEscalation: true \ No newline at end of file diff --git a/pkg/iac-providers/kustomize/v3/testdata/erroneous-deployment/kustomization.yaml b/pkg/iac-providers/kustomize/v3/testdata/erroneous-deployment/kustomization.yaml new file mode 100644 index 000000000..775d2c72b --- /dev/null +++ b/pkg/iac-providers/kustomize/v3/testdata/erroneous-deployment/kustomization.yaml @@ -0,0 +1,5 @@ +commonLabels: + app: hello + +resources: +- deployment.yaml \ No newline at end of file diff --git a/pkg/iac-providers/kustomize/v3/testdata/erroneous-pod/kustomization.yaml b/pkg/iac-providers/kustomize/v3/testdata/erroneous-pod/kustomization.yaml new file mode 100644 index 000000000..503556567 --- /dev/null +++ b/pkg/iac-providers/kustomize/v3/testdata/erroneous-pod/kustomization.yaml @@ -0,0 +1,5 @@ +commonLabels: + app: hello + +resources: +- pod.yaml \ No newline at end of file diff --git a/pkg/iac-providers/kustomize/v3/testdata/erroneous-pod/pod.yaml b/pkg/iac-providers/kustomize/v3/testdata/erroneous-pod/pod.yaml new file mode 100644 index 000000000..4f8836600 --- /dev/null +++ b/pkg/iac-providers/kustomize/v3/testdata/erroneous-pod/pod.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +metadata: + name: myapp-pod + labels: + app: myapp + test: someupdate + test2: someupdate3 +spec: + containers: + - name: myapp-container + image: busybox + command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600'] + securityContext: + allowPrivilegeEscalation: true \ No newline at end of file diff --git a/pkg/iac-providers/kustomize/v3/testdata/kustomize-file-empty/configMap.yaml b/pkg/iac-providers/kustomize/v3/testdata/kustomize-file-empty/configMap.yaml new file mode 100644 index 000000000..e335ab8cc --- /dev/null +++ b/pkg/iac-providers/kustomize/v3/testdata/kustomize-file-empty/configMap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: the-map +data: + altGreeting: "Good Morning!" + enableRisky: "false" diff --git a/pkg/iac-providers/kustomize/v3/testdata/kustomize-file-empty/deployment.yaml b/pkg/iac-providers/kustomize/v3/testdata/kustomize-file-empty/deployment.yaml new file mode 100644 index 000000000..00e5eb937 --- /dev/null +++ b/pkg/iac-providers/kustomize/v3/testdata/kustomize-file-empty/deployment.yaml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: the-deployment +spec: + replicas: 3 + selector: + matchLabels: + deployment: hello + template: + metadata: + labels: + deployment: hello + spec: + containers: + - name: the-container + image: monopole/hello:1 + command: ["/hello", + "--port=8080", + "--enableRiskyFeature=$(ENABLE_RISKY)"] + ports: + - containerPort: 8080 + env: + - name: ALT_GREETING + valueFrom: + configMapKeyRef: + name: the-map + key: altGreeting + - name: ENABLE_RISKY + valueFrom: + configMapKeyRef: + name: the-map + key: enableRisky diff --git a/pkg/iac-providers/kustomize/v3/testdata/kustomize-file-empty/kustomization.yaml b/pkg/iac-providers/kustomize/v3/testdata/kustomize-file-empty/kustomization.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/iac-providers/kustomize/v3/testdata/kustomize-file-empty/service.yaml b/pkg/iac-providers/kustomize/v3/testdata/kustomize-file-empty/service.yaml new file mode 100644 index 000000000..e238f7002 --- /dev/null +++ b/pkg/iac-providers/kustomize/v3/testdata/kustomize-file-empty/service.yaml @@ -0,0 +1,12 @@ +kind: Service +apiVersion: v1 +metadata: + name: the-service +spec: + selector: + deployment: hello + type: LoadBalancer + ports: + - protocol: TCP + port: 8666 + targetPort: 8080 diff --git a/pkg/iac-providers/kustomize/v3/testdata/multibases/zero-violation-base/kustomization.yaml b/pkg/iac-providers/kustomize/v3/testdata/multibases/zero-violation-base/kustomization.yaml new file mode 100644 index 000000000..d577d5e5f --- /dev/null +++ b/pkg/iac-providers/kustomize/v3/testdata/multibases/zero-violation-base/kustomization.yaml @@ -0,0 +1,2 @@ +resources: +- pod.yaml \ No newline at end of file diff --git a/pkg/iac-providers/kustomize/v3/testdata/multibases/zero-violation-base/pod.yaml b/pkg/iac-providers/kustomize/v3/testdata/multibases/zero-violation-base/pod.yaml new file mode 100644 index 000000000..08bffc1df --- /dev/null +++ b/pkg/iac-providers/kustomize/v3/testdata/multibases/zero-violation-base/pod.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Pod +metadata: + name: myapp-pod + namespace: sample-ns + labels: + app: myapp +spec: + containers: + - name: nginx + image: nginx:1.7.9 + resources: + requests: + memory: "64Mi" + cpu: "250m" + limits: + memory: "128Mi" + cpu: "500m" \ No newline at end of file diff --git a/pkg/iac-providers/kustomize/v3/testdata/no-kustomizefile/configMap.yaml b/pkg/iac-providers/kustomize/v3/testdata/no-kustomizefile/configMap.yaml new file mode 100644 index 000000000..e335ab8cc --- /dev/null +++ b/pkg/iac-providers/kustomize/v3/testdata/no-kustomizefile/configMap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: the-map +data: + altGreeting: "Good Morning!" + enableRisky: "false" diff --git a/pkg/iac-providers/kustomize/v3/testdata/no-kustomizefile/deployment.yaml b/pkg/iac-providers/kustomize/v3/testdata/no-kustomizefile/deployment.yaml new file mode 100644 index 000000000..00e5eb937 --- /dev/null +++ b/pkg/iac-providers/kustomize/v3/testdata/no-kustomizefile/deployment.yaml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: the-deployment +spec: + replicas: 3 + selector: + matchLabels: + deployment: hello + template: + metadata: + labels: + deployment: hello + spec: + containers: + - name: the-container + image: monopole/hello:1 + command: ["/hello", + "--port=8080", + "--enableRiskyFeature=$(ENABLE_RISKY)"] + ports: + - containerPort: 8080 + env: + - name: ALT_GREETING + valueFrom: + configMapKeyRef: + name: the-map + key: altGreeting + - name: ENABLE_RISKY + valueFrom: + configMapKeyRef: + name: the-map + key: enableRisky diff --git a/pkg/iac-providers/kustomize/v3/testdata/no-kustomizefile/service.yaml b/pkg/iac-providers/kustomize/v3/testdata/no-kustomizefile/service.yaml new file mode 100644 index 000000000..e238f7002 --- /dev/null +++ b/pkg/iac-providers/kustomize/v3/testdata/no-kustomizefile/service.yaml @@ -0,0 +1,12 @@ +kind: Service +apiVersion: v1 +metadata: + name: the-service +spec: + selector: + deployment: hello + type: LoadBalancer + ports: + - protocol: TCP + port: 8666 + targetPort: 8080 diff --git a/pkg/utils/resource.go b/pkg/utils/resource.go index 9912d0b5b..5e0efe7f3 100644 --- a/pkg/utils/resource.go +++ b/pkg/utils/resource.go @@ -49,3 +49,15 @@ func FindResourceByID(resourceID string, normalizedResources *output.AllResource return &resource, nil } + +// GetResourceCount gives out the total number of resources present in a output.ResourceConfig object. +// Since the ResourceConfig mapping stores resources in lists which can be located resourceMapping[Type], +// `len(resourceMapping)` does not give the count of the resources but only gives out the total number of +// the type of resources inside the object. +func GetResourceCount(resourceMapping map[string][]output.ResourceConfig) (count int) { + count = 0 + for _, list := range resourceMapping { + count = count + len(list) + } + return +} diff --git a/pkg/utils/testdata/empty.yaml b/pkg/utils/testdata/empty.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/utils/testdata/pod.yaml b/pkg/utils/testdata/pod.yaml new file mode 100644 index 000000000..fe5ee411a --- /dev/null +++ b/pkg/utils/testdata/pod.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: myapp-pod + labels: + app: myapp +spec: + containers: + - name: nginx + image: nginx:1.7.9 diff --git a/pkg/utils/yaml.go b/pkg/utils/yaml.go index 1fab53a25..8adc55dd4 100644 --- a/pkg/utils/yaml.go +++ b/pkg/utils/yaml.go @@ -19,6 +19,9 @@ const ( var ( errHighDocumentCount = fmt.Errorf("document count was higher than expected count") + + // ErrYamlFileEmpty is return when empty yaml file is being read. + ErrYamlFileEmpty = fmt.Errorf("yaml file is empty") ) // LoadYAML loads a YAML file. Can return one or more IaC Documents. @@ -57,6 +60,9 @@ func ReadYamlFile(path string) (map[string]interface{}, error) { if err != nil { return nil, err } + if len(output) == 0 { + return nil, ErrYamlFileEmpty + } return output, nil } diff --git a/pkg/utils/yaml_test.go b/pkg/utils/yaml_test.go new file mode 100644 index 000000000..5ea07cdc8 --- /dev/null +++ b/pkg/utils/yaml_test.go @@ -0,0 +1,28 @@ +package utils + +import ( + "reflect" + "testing" +) + +func TestReadYamlFile(t *testing.T) { + + table := []struct { + path string + empty bool + }{ + {path: "./testdata/empty.yaml", empty: true}, + {path: "./testdata/pod.yaml"}, + } + + for _, tt := range table { + t.Run(tt.path, func(t *testing.T) { + _, gotErr := ReadYamlFile(tt.path) + + if !reflect.DeepEqual(gotErr, ErrYamlFileEmpty) && tt.empty { + t.Errorf("unexpected error; gotErr: '%v', wantErr: '%v'", gotErr, ErrYamlFileEmpty) + } + + }) + } +}