Repository: datreeio/datree
Pull request #510 (2022-03-20 13:45:41)

feat: policy-config flag


Patch:
diff --git a/bl/evaluation/evaluator.go b/bl/evaluation/evaluator.go
index 58982d3..85fe154 100644
--- a/bl/evaluation/evaluator.go
+++ b/bl/evaluation/evaluator.go
@@ -9,7 +9,7 @@ import (
 	"github.com/datreeio/datree/pkg/ciContext"
 	"github.com/datreeio/datree/pkg/cliClient"
 	"github.com/datreeio/datree/pkg/extractor"
-	"github.com/datreeio/datree/pkg/yamlSchemaValidator"
+	"github.com/datreeio/datree/pkg/jsonSchemaValidator"
 )
 
 type CLIClient interface {
@@ -106,7 +106,7 @@ func (e *Evaluator) Evaluate(policyCheckData PolicyCheckData) (PolicyCheckResult
 		return PolicyCheckResultData{FormattedResults{}, []cliClient.RuleData{}, []cliClient.FileData{}, FailedRulesByFiles{}, rulesCount}, nil
 	}
 
-	yamlSchemaValidator := yamlSchemaValidator.New()
+	jsonSchemaValidator := jsonSchemaValidator.New()
 
 	// map of files paths to map of rules to failed rule data
 	failedRulesByFiles := make(FailedRulesByFiles)
@@ -135,7 +135,7 @@ func (e *Evaluator) Evaluate(policyCheckData PolicyCheckData) (PolicyCheckResult
 					return emptyPolicyCheckResult, err
 				}
 
-				validationResult, err := yamlSchemaValidator.Validate(string(ruleSchemaJson), string(configurationJson))
+				validationResult, err := jsonSchemaValidator.ValidateYamlSchema(string(ruleSchemaJson), string(configurationJson))
 
 				if err != nil {
 					return emptyPolicyCheckResult, err
diff --git a/bl/policy/policy_factory.go b/bl/policy/policy_factory.go
index 61b7a34..092830e 100644
--- a/bl/policy/policy_factory.go
+++ b/bl/policy/policy_factory.go
@@ -1,6 +1,7 @@
 package policy
 
 import (
+	_ "embed"
 	"fmt"
 
 	"github.com/datreeio/datree/pkg/cliClient"
@@ -117,5 +118,4 @@ func createDefaultPolicy(defaultRules *internal_policy.DefaultRulesDefinitions)
 	}
 
 	return Policy{"Default", rules}
-
 }
diff --git a/cmd/root.go b/cmd/root.go
index 2d84978..73294f1 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -15,9 +15,9 @@ import (
 	"github.com/datreeio/datree/pkg/deploymentConfig"
 	"github.com/datreeio/datree/pkg/executor"
 	"github.com/datreeio/datree/pkg/fileReader"
+	"github.com/datreeio/datree/pkg/jsonSchemaValidator"
 	"github.com/datreeio/datree/pkg/localConfig"
 	"github.com/datreeio/datree/pkg/printer"
-	"github.com/datreeio/datree/pkg/yamlSchemaValidator"
 	"github.com/spf13/cobra"
 )
 
@@ -77,8 +77,8 @@ func init() {
 
 	rootCmd.AddCommand(completion.New())
 
-	rootCmd.AddCommand(schema_validator.New(&schema_validator.YamlSchemaValidatorCommandContext{
-		YamlSchemaValidator: app.context.YamlSchemaValidator,
+	rootCmd.AddCommand(schema_validator.New(&schema_validator.JSONSchemaValidatorCommandContext{
+		JSONSchemaValidator: app.context.JSONSchemaValidator,
 		Printer:             app.context.Printer,
 	}))
 }
@@ -95,7 +95,7 @@ type context struct {
 	Printer             *printer.Printer
 	Reader              *fileReader.FileReader
 	K8sValidator        *validation.K8sValidator
-	YamlSchemaValidator *yamlSchemaValidator.YamlSchemaValidator
+	JSONSchemaValidator *jsonSchemaValidator.JSONSchemaValidator
 	CommandRunner       *executor.CommandRunner
 }
 
@@ -117,7 +117,7 @@ func startup() *app {
 			Printer:             printer,
 			Reader:              fileReader.CreateFileReader(nil),
 			K8sValidator:        validation.New(),
-			YamlSchemaValidator: yamlSchemaValidator.New(),
+			JSONSchemaValidator: jsonSchemaValidator.New(),
 			CommandRunner:       executor.CreateNewCommandRunner(),
 		},
 	}
diff --git a/cmd/schema-validator/main.go b/cmd/schema-validator/main.go
index 5324d42..eacb9c1 100644
--- a/cmd/schema-validator/main.go
+++ b/cmd/schema-validator/main.go
@@ -10,17 +10,17 @@ import (
 
 type Result = gojsonschema.Result
 
-type YamlSchemaValidator interface {
-	Validate(yamlSchema string, yaml string) (*Result, error)
+type JSONSchemaValidator interface {
+	ValidateYamlSchema(yamlSchema string, yaml string) (*Result, error)
 }
 
-type YamlSchemaValidationPrinter interface {
+type JSONSchemaValidationPrinter interface {
 	PrintYamlSchemaResults(result *Result, error error)
 }
 
-type YamlSchemaValidatorCommandContext struct {
-	YamlSchemaValidator YamlSchemaValidator
-	Printer             YamlSchemaValidationPrinter
+type JSONSchemaValidatorCommandContext struct {
+	JSONSchemaValidator JSONSchemaValidator
+	Printer             JSONSchemaValidationPrinter
 }
 
 func ExtractYamlFilesContent(schemaPath string, yamlPath string) (string, string, error) {
@@ -40,7 +40,7 @@ func ExtractYamlFilesContent(schemaPath string, yamlPath string) (string, string
 	return schemaContent, yamlContent, nil
 }
 
-func New(ctx *YamlSchemaValidatorCommandContext) *cobra.Command {
+func New(ctx *JSONSchemaValidatorCommandContext) *cobra.Command {
 	schemaValidator := &cobra.Command{
 		Use:    "schema-validator",
 		Short:  "Execute schema validation to yaml files for given yaml schema",
@@ -62,7 +62,7 @@ func New(ctx *YamlSchemaValidatorCommandContext) *cobra.Command {
 				ctx.Printer.PrintYamlSchemaResults(nil, err)
 				return err
 			}
-			result, err := ctx.YamlSchemaValidator.Validate(schemaContent, yamlContent)
+			result, err := ctx.JSONSchemaValidator.ValidateYamlSchema(schemaContent, yamlContent)
 			ctx.Printer.PrintYamlSchemaResults(result, err)
 			if err != nil {
 				return err
diff --git a/internal/fixtures/policyAsCode/policies.yaml b/internal/fixtures/policyAsCode/policies.yaml
new file mode 100644
index 0000000..196e218
--- /dev/null
+++ b/internal/fixtures/policyAsCode/policies.yaml
@@ -0,0 +1,180 @@
+apiVersion: v1
+policies:
+  - name: Default
+    isDefault: true
+    rules:
+      - identifier: INGRESS_INCORRECT_HOST_VALUE_PERMISSIVE
+        messageOnFailure: Incorrect value for key `host` - specify host instead of using a wildcard character ("*")
+      - identifier: CONTAINERS_MISSING_IMAGE_VALUE_VERSION
+        messageOnFailure: Incorrect value for key `image` - specify an image version to avoid unpleasant "version surprises" in the future
+      - identifier: DEPLOYMENT_MISSING_LABEL_ENV_VALUE
+        messageOnFailure: 'Missing label object `env` - add a proper environment description (e.g. "prod", "testing", etc.) to the Deployment config'
+      - identifier: CONTAINERS_MISSING_CPU_REQUEST_KEY
+        messageOnFailure: Missing property object `requests.cpu` - value should be within the accepted boundaries recommended by the organization
+      - identifier: CRONJOB_INVALID_SCHEDULE_VALUE
+        messageOnFailure: 'Incorrect value for key `schedule` - the (cron) schedule expressions is not valid and therefor, will not work as expected'
+      - identifier: WORKLOAD_INCORRECT_RESTARTPOLICY_VALUE_ALWAYS
+        messageOnFailure: Incorrect value for key `restartPolicy` - any other value than `Always` is not supported by this resource
+      - identifier: CONTAINERS_MISSING_CPU_LIMIT_KEY
+        messageOnFailure: Missing property object `limits.cpu` - value should be within the accepted boundaries recommended by the organization
+      - identifier: HPA_MISSING_MINREPLICAS_KEY
+        messageOnFailure: Missing property object `minReplicas` - the value should be within the accepted boundaries recommended by the organization
+      - identifier: SERVICE_INCORRECT_TYPE_VALUE_NODEPORT
+        messageOnFailure: Incorrect value for key `type` - `NodePort` will open a port on all nodes where it can be reached by the network external to the cluster
+      - identifier: CONTAINERS_INCORRECT_HOSTPID_VALUE_TRUE
+        messageOnFailure: Incorrect value for key `hostPID` - running on the host’s PID namespace enables access to sensitive information from processes running outside the container
+      - identifier: K8S_DEPRECATED_APIVERSION_1.16
+        messageOnFailure: Incorrect value for key `apiVersion` - the version you are trying to use is not supported by the Kubernetes cluster version (>=1.16)
+      - identifier: CONTAINERS_MISSING_IMAGE_VALUE_DIGEST
+        messageOnFailure: 'Incorrect value for key `image` - add a digest tag (starts with `@sha256:`) to represent an immutable version of the image'
+      - identifier: CONTAINERS_INCORRECT_HOSTIPC_VALUE_TRUE
+        messageOnFailure: Incorrect value for key `hostIPC` - running on the host’s IPC namespace can be (maliciously) used to interact with other processes running outside the container
+      - identifier: DEPLOYMENT_INCORRECT_REPLICAS_VALUE
+        messageOnFailure: Incorrect value for key `replicas` - don't relay on a single pod to do all of the work. Running 2 or more replicas will increase the availability of the service
+      - identifier: HPA_MISSING_MAXREPLICAS_KEY
+        messageOnFailure: Missing property object `maxReplicas` - the value should be within the accepted boundaries recommended by the organization
+      - identifier: CONTAINERS_MISSING_READINESSPROBE_KEY
+        messageOnFailure: Missing property object `readinessProbe` - add a properly configured readinessProbe to notify kubelet your Pods are ready for traffic
+      - identifier: CRONJOB_MISSING_STARTINGDEADLINESECOND_KEY
+        messageOnFailure: Missing property object `startingDeadlineSeconds` - set a time limit to the cron execution to allow killing it if exceeded
+      - identifier: CRONJOB_MISSING_CONCURRENCYPOLICY_KEY
+        messageOnFailure: Missing property object `concurrencyPolicy` - the behavior will be more deterministic if jobs won't run concurrently
+      - identifier: CONTAINERS_INCORRECT_PRIVILEGED_VALUE_TRUE
+        messageOnFailure: Incorrect value for key `privileged` - this mode will allow the container the same access as processes running on the host
+      - identifier: K8S_DEPRECATED_APIVERSION_1.17
+        messageOnFailure: Incorrect value for key `apiVersion` - the version you are trying to use is not supported by the Kubernetes cluster version (>=1.17)
+      - identifier: CONTAINERS_MISSING_MEMORY_REQUEST_KEY
+        messageOnFailure: Missing property object `requests.memory` - value should be within the accepted boundaries recommended by the organization
+      - identifier: K8S_INCORRECT_KIND_VALUE_POD
+        messageOnFailure: Incorrect value for key `kind` - raw pod won't be rescheduled in the event of a node failure
+      - identifier: WORKLOAD_INCORRECT_NAMESPACE_VALUE_DEFAULT
+        messageOnFailure: Incorrect value for key `namespace` - use an explicit namespace instead of the default one (`default`)
+      - identifier: CONTAINERS_INCORRECT_RUNASUSER_VALUE_LOWUID
+        messageOnFailure: Incorrect value for key `runAsUser` - value should be above 10000 to reduce the chances the UID is already taken
+      - identifier: CONTAINERS_INCORRECT_PATH_VALUE_DOCKERSOCKET
+        messageOnFailure: Incorrect value for key `path` - avoid mounting the docker.socket becasue it can allow container breakout
+      - identifier: WORKLOAD_MISSING_LABEL_OWNER_VALUE
+        messageOnFailure: Missing label object `owner` - add a proper owner label in order to know which person/team to ping when needed
+      - identifier: CONTAINERS_MISSING_LIVENESSPROBE_KEY
+        messageOnFailure: Missing property object `livenessProbe` - add a properly configured livenessProbe to catch possible deadlocks
+      - identifier: CONTAINERS_MISSING_MEMORY_LIMIT_KEY
+        messageOnFailure: Missing property object `limits.memory` - value should be within the accepted boundaries recommended by the organization
+      - identifier: CONTAINERS_INCORRECT_HOSTNETWORK_VALUE_TRUE
+        messageOnFailure: Incorrect value for key `hostNetwork` - running on the host’s network namespace can allow a compromised container to sniff network traffic
+      - identifier: WORKLOAD_INVALID_LABELS_VALUE
+        messageOnFailure: Incorrect value for key(s) under `labels` - the vales syntax is not valid so the Kubernetes engine will not accept it
+  - name: Governance
+    rules:
+      - identifier: PODDISRUPTIONBUDGET_DENY_ZERO_VOLUNTARY_DISRUPTION
+        messageOnFailure: Setting minAvailable to 100 and maxUnavailable to 0 is not allowed by the organization, as node on which the pod is running can never be drained.
+      - identifier: WORKLOAD_MISSING_NAMESPACE_FOR_NAMESPACED_RESOURCES
+        messageOnFailure: Namespaced resources without namespace is not allowed by the organization, please specify a valid namespace in the cluster.
+      - identifier: INGRESS_INCORRECT_DOMAIN_NAME
+        messageOnFailure: Incorrect value specified for domain name, please provide a valid name
+customRules:
+  - identifier: PODDISRUPTIONBUDGET_DENY_ZERO_VOLUNTARY_DISRUPTION
+    name: Allows pods to undergo least voluntary disruption
+    defaultMessageOnFailure: Setting minAvailable to 100 and maxUnavailable to 0 is not allowed by the organization, as node on which the pod is running can never be drained.
+    schema:
+      type: object
+      if:
+       properties:
+         kind:
+           enum:
+             - PodDisruptionBudget
+      then:
+       required:
+       - spec
+       properties:
+         metadata:
+           type: object
+         spec:
+           type: object
+           properties:
+             minAvailable:
+               not:
+                 const: '100%'
+             maxUnavailable:
+               allOf:
+               - not:
+                   const: 0
+               - not:
+                   const: '0%'
+  - identifier: WORKLOAD_MISSING_NAMESPACE_FOR_NAMESPACED_RESOURCES
+    name: Ensure namespaced resources have namespace specified
+    defaultMessageOnFailure: Namespaced resources without namespace is not allowed by the organization, please specify a valid namespace in the cluster
+    schema:
+      type: object
+      if:
+        properties:
+          kind:
+            type: string
+            enum:
+              - Binding
+              - ConfigMap
+              - Endpoints
+              - Event
+              - LimitRange
+              - PersistentVolumeClaim
+              - Pod
+              - PodTemplate
+              - ReplicationController
+              - ResourceQuota
+              - Secret
+              - ServiceAccount
+              - Service
+              - ControllerRevision
+              - DaemonSet
+              - Deployment
+              - ReplicaSet
+              - StatefulSet
+              - LocalSubjectAccessReview
+              - HorizontalPodAutoscaler
+              - CronJob
+              - Job
+              - Lease
+              - EndpointSlice
+              - Ingress
+              - NetworkPolicy
+              - PodDisruptionBudget
+              - RoleBinding
+              - Role
+      then:
+        required:
+          - metadata
+        properties:
+          metadata:
+            type: object
+            required:
+            - namespace
+            properties:
+              namespace:
+                type: string
+  - identifier: INGRESS_INCORRECT_DOMAIN_NAME
+    name: Deny invalid domain names in ingress hosts
+    defaultMessageOnFailure: Incorrect value specified for domain name, please provide a valid name
+    schema:
+      type: object
+      if:
+        properties:
+          kind:
+            type: string
+            enum:
+              - Ingress
+      then:
+       required:
+         - spec
+       properties:
+         spec:
+           type: object
+           required:
+             - rules
+           properties:
+             rules:
+               type: array
+               items:
+                 type: object
+                 properties:
+                   host:
+                     type: string
+                     pattern: "^.*$"
diff --git a/pkg/jsonSchemaValidator/validator.go b/pkg/jsonSchemaValidator/validator.go
new file mode 100644
index 0000000..53c8fcb
--- /dev/null
+++ b/pkg/jsonSchemaValidator/validator.go
@@ -0,0 +1,29 @@
+package jsonSchemaValidator
+
+import (
+	"github.com/ghodss/yaml"
+	"github.com/xeipuuv/gojsonschema"
+)
+
+type JSONSchemaValidator struct {
+}
+
+func New() *JSONSchemaValidator {
+	return &JSONSchemaValidator{}
+}
+
+type Result = gojsonschema.Result
+
+func (jsv *JSONSchemaValidator) ValidateYamlSchema(schemaContent string, yamlContent string) (*Result, error) {
+	jsonSchema, _ := yaml.YAMLToJSON([]byte(schemaContent))
+	return jsv.Validate(string(jsonSchema), yamlContent)
+}
+
+func (jsv *JSONSchemaValidator) Validate(schemaContent string, yamlContent string) (*Result, error) {
+	jsonContent, _ := yaml.YAMLToJSON([]byte(yamlContent))
+
+	schemaLoader := gojsonschema.NewStringLoader(schemaContent)
+	documentLoader := gojsonschema.NewStringLoader(string(jsonContent))
+
+	return gojsonschema.Validate(schemaLoader, documentLoader)
+}
diff --git a/pkg/policy/policiesSchema.json b/pkg/policy/policiesSchema.json
index ce16f55..6eb5bbe 100644
--- a/pkg/policy/policiesSchema.json
+++ b/pkg/policy/policiesSchema.json
@@ -32,7 +32,7 @@
                 "identifier": {
                   "uniqueItems": true,
                   "type": "string",
-                  "pattern": "^([A-Za-z0-9])+(_?[A-Za-z0-9])*$"
+                  "pattern": "^([A-Za-z0-9\\.])+(_?[A-Za-z0-9\\.])*$"
                 },
                 "messageOnFailure": {
                   "type": "string"
@@ -51,24 +51,28 @@
       "type": "array",
       "items": {
         "type": "object",
-        "identifier": {
-          "type": "string"
-        },
-        "pattern": "^([A-Za-z0-9])+(_?[A-Za-z0-9])*$",
-        "defaultMessageOnFailure": {
-          "type": "string"
-        },
-        "name": {
-          "type": "string"
-        },
-        "jsonSchema": {
-          "type": "string"
-        },
-        "schema": {
-          "type": "object"
+        "properties": {
+          "identifier": {
+            "type": "string",
+            "pattern": "^([A-Za-z0-9\\.])+(_?[A-Za-z0-9\\.])*$"
+          },
+          "defaultMessageOnFailure": {
+            "type": "string"
+          },
+          "name": {
+            "type": "string"
+          },
+          "jsonSchema": {
+            "type": "string"
+          },
+          "schema": {
+            "type": "object"
+          }
         },
         "required": [
-          "identifier"
+          "identifier",
+          "name",
+          "defaultMessageOnFailure"
         ]
       }
     }
diff --git a/pkg/policy/policy.go b/pkg/policy/policy.go
index 8e25687..1432ce2 100644
--- a/pkg/policy/policy.go
+++ b/pkg/policy/policy.go
@@ -2,13 +2,20 @@ package policy
 
 import (
 	_ "embed"
+	"fmt"
 
-	"gopkg.in/yaml.v3"
+	"github.com/datreeio/datree/pkg/cliClient"
+	"github.com/datreeio/datree/pkg/fileReader"
+	"github.com/datreeio/datree/pkg/jsonSchemaValidator"
+	"github.com/ghodss/yaml"
 )
 
 //go:embed defaultRules.yaml
 var defaultRulesYamlContent string
 
+//go:embed policiesSchema.json
+var policiesSchemaContent string
+
 type DefaultRulesDefinitions struct {
 	ApiVersion string                   `yaml:"apiVersion"`
 	Rules      []*DefaultRuleDefinition `yaml:"rules"`
@@ -30,6 +37,53 @@ func GetDefaultRules() (*DefaultRulesDefinitions, error) {
 	return defaultRulesDefinitions, err
 }
 
+func GetPoliciesFileFromPath(path string) (*cliClient.EvaluationPrerunPolicies, error) {
+	fileReader := fileReader.CreateFileReader(nil)
+	policiesStr, err := fileReader.ReadFileContent(path)
+	if err != nil {
+		return nil, err
+	}
+
+	err = validatePoliciesYaml(policiesStr, path)
+	if err != nil {
+		return nil, err
+	}
+
+	var policies *cliClient.EvaluationPrerunPolicies
+	policiesBytes, err := yaml.YAMLToJSON([]byte(policiesStr))
+	if err != nil {
+		return nil, err
+	}
+
+	err = yaml.Unmarshal(policiesBytes, &policies)
+	if err != nil {
+		return nil, err
+	}
+
+	return policies, nil
+}
+
+func validatePoliciesYaml(content string, policyYamlPath string) error {
+	jsonSchemaValidator := jsonSchemaValidator.New()
+	result, err := jsonSchemaValidator.Validate(policiesSchemaContent, content)
+
+	if err != nil {
+		return err
+	}
+
+	if !result.Valid() {
+		validationErrors := fmt.Errorf("Found errors in policies file %s:\n", policyYamlPath)
+
+		for _, validationError := range result.Errors() {
+			validationErrors = fmt.Errorf("%s\n%s", validationErrors, validationError)
+		}
+
+		return validationErrors
+	}
+
+	return nil
+}
+
 func yamlToStruct(content string) (*DefaultRulesDefinitions, error) {
 	var defaultRulesDefinitions DefaultRulesDefinitions
 	err := yaml.Unmarshal([]byte(content), &defaultRulesDefinitions)
diff --git a/pkg/printer/printer.go b/pkg/printer/printer.go
index 9ab574b..a2ba387 100644
--- a/pkg/printer/printer.go
+++ b/pkg/printer/printer.go
@@ -55,7 +55,7 @@ type Warning struct {
 	InvalidK8sInfo  InvalidK8sInfo
 	ExtraMessages   []ExtraMessage
 }
-type YamlSchemaValidatorResults = gojsonschema.Result
+type JSONSchemaValidatorResults = gojsonschema.Result
 
 func (p *Printer) SetTheme(theme *Theme) {
 	p.Theme = theme
@@ -95,7 +95,7 @@ func (p *Printer) printK8sValidationWarning(warning Warning) {
 	fmt.Fprintln(out)
 }
 
-func (p *Printer) PrintYamlSchemaResults(result *YamlSchemaValidatorResults, error error) {
+func (p *Printer) PrintYamlSchemaResults(result *JSONSchemaValidatorResults, error error) {
 	if result == nil {
 		p.printInColor("INVALID FILE PATH\n", p.Theme.Colors.RedBold)
 		return
diff --git a/pkg/yamlSchemaValidator/validator.go b/pkg/yamlSchemaValidator/validator.go
deleted file mode 100644
index 28d22d7..0000000
--- a/pkg/yamlSchemaValidator/validator.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package yamlSchemaValidator
-
-import (
-	"github.com/ghodss/yaml"
-	"github.com/xeipuuv/gojsonschema"
-)
-
-type YamlSchemaValidator struct {
-}
-
-func New() *YamlSchemaValidator {
-	return &YamlSchemaValidator{}
-}
-
-type Result = gojsonschema.Result
-
-func (jsv *YamlSchemaValidator) Validate(schemaContent string, yamlContent string) (*Result, error) {
-
-	jsonSchema, _ := yaml.YAMLToJSON([]byte(schemaContent))
-
-	json, _ := yaml.YAMLToJSON([]byte(yamlContent))
-
-	schemaLoader := gojsonschema.NewStringLoader(string(jsonSchema))
-	documentLoader := gojsonschema.NewStringLoader(string(json))
-
-	return gojsonschema.Validate(schemaLoader, documentLoader)
-}


Test patch:
diff --git a/bl/evaluation/evaluator_test.go b/bl/evaluation/evaluator_test.go
index 22dc7d4..070793d 100644
--- a/bl/evaluation/evaluator_test.go
+++ b/bl/evaluation/evaluator_test.go
@@ -276,7 +276,7 @@ func newFilesConfigurations(path string) []*extractor.FileConfigurations {
 }
 
 func mockGetPreRunData() *cliClient.EvaluationPrerunDataResponse {
-	const policiesJsonPath = "../../internal/fixtures/policyAsCode/policies.json"
+	const policiesJsonPath = "../../internal/fixtures/policyAsCode/prerun.json"
 
 	fileReader := fileReader.CreateFileReader(nil)
 	policiesJsonStr, err := fileReader.ReadFileContent(policiesJsonPath)
diff --git a/bl/policy/policy_factory_test.go b/bl/policy/policy_factory_test.go
index e110218..cf1d6fc 100644
--- a/bl/policy/policy_factory_test.go
+++ b/bl/policy/policy_factory_test.go
@@ -14,7 +14,7 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-const policiesJsonPath = "../../internal/fixtures/policyAsCode/policies.json"
+const policiesJsonPath = "../../internal/fixtures/policyAsCode/prerun.json"
 
 func TestCreatePolicy(t *testing.T) {
 	policiesJson := mockGetPreRunData()
diff --git a/cmd/test/main.go b/cmd/test/main.go
index 01537ff..f0a99c9 100644
--- a/cmd/test/main.go
+++ b/cmd/test/main.go
@@ -10,10 +10,10 @@ import (
 	"github.com/datreeio/datree/bl/evaluation"
 	"github.com/datreeio/datree/bl/files"
 	"github.com/datreeio/datree/bl/messager"
-	"github.com/datreeio/datree/bl/policy"
 	policy_factory "github.com/datreeio/datree/bl/policy"
 	"github.com/datreeio/datree/pkg/ciContext"
 	"github.com/datreeio/datree/pkg/cliClient"
+	"github.com/datreeio/datree/pkg/policy"
 	"github.com/pkg/errors"
 
 	"github.com/datreeio/datree/pkg/extractor"
@@ -48,6 +48,7 @@ type TestCommandFlags struct {
 	OnlyK8sFiles         bool
 	PolicyName           string
 	SchemaLocations      []string
+	PolicyConfig         string
 }
 
 // TestCommandFlags constructor
@@ -113,7 +114,7 @@ type TestCommandData struct {
 	K8sVersion            string
 	IgnoreMissingSchemas  bool
 	OnlyK8sFiles          bool
-	Policy                policy.Policy
+	Policy                policy_factory.Policy
 	SchemaLocations       []string
 	Token                 string
 	RegistrationURL       string
@@ -229,7 +230,9 @@ func (flags *TestCommandFlags) AddFlags(cmd *cobra.Command) {
 	}
 	cmd.Flags().StringVarP(&flags.K8sVersion, "schema-version", "s", "", "Set kubernetes version to validate against. Defaults to 1.19.0")
 	cmd.Flags().StringVarP(&flags.PolicyName, "policy", "p", "", "Policy name to run against")
-	cmd.Flags().BoolVarP(&flags.OnlyK8sFiles, "only-k8s-files", "", false, "Evaluate only valid yaml files with the properties 'apiVersion' and 'kind'. Ignore everything else")
+
+	cmd.Flags().StringVar(&flags.PolicyConfig, "policy-config", "", "Policy name to run against")
+	cmd.Flags().BoolVar(&flags.OnlyK8sFiles, "only-k8s-files", false, "Evaluate only valid yaml files with the properties 'apiVersion' and 'kind'. Ignore everything else")
 
 	// kubeconform flag
 	cmd.Flags().StringArrayVarP(&flags.SchemaLocations, "schema-location", "", []string{}, "Override schemas location search path (can be specified multiple times)")
@@ -249,7 +252,19 @@ func GenerateTestCommandData(testCommandFlags *TestCommandFlags, localConfigCont
 		k8sVersion = "1.19.0"
 	}
 
-	policy, err := policy_factory.CreatePolicy(evaluationPrerunDataResp.PoliciesJson, testCommandFlags.PolicyName)
+	var policies *cliClient.EvaluationPrerunPolicies
+	var err error
+
+	if testCommandFlags.PolicyConfig != "" {
+		policies, err = policy.GetPoliciesFileFromPath(testCommandFlags.PolicyConfig)
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		policies = evaluationPrerunDataResp.PoliciesJson
+	}
+
+	policy, err := policy_factory.CreatePolicy(policies, testCommandFlags.PolicyName)
 	if err != nil {
 		return nil, err
 	}
@@ -285,7 +300,6 @@ func validateK8sVersionFormatIfProvided(k8sVersion string) error {
 }
 
 func Test(ctx *TestCommandContext, paths []string, prerunData *TestCommandData) error {
-
 	if paths[0] == "-" {
 		if len(paths) > 1 {
 			return fmt.Errorf(fmt.Sprintf("Unexpected args: [%s]", strings.Join(paths[1:], ",")))
diff --git a/cmd/test/main_test.go b/cmd/test/main_test.go
index ed660b7..9904f29 100644
--- a/cmd/test/main_test.go
+++ b/cmd/test/main_test.go
@@ -379,7 +379,7 @@ func newErrorsChan() chan error {
 }
 
 func mockGetPreRunData() *cliClient.EvaluationPrerunDataResponse {
-	const policiesJsonPath = "../../internal/fixtures/policyAsCode/policies.json"
+	const policiesJsonPath = "../../internal/fixtures/policyAsCode/prerun.json"
 
 	fileReader := fileReader.CreateFileReader(nil)
 	policiesJsonStr, err := fileReader.ReadFileContent(policiesJsonPath)
diff --git a/internal/fixtures/policyAsCode/policies.json b/internal/fixtures/policyAsCode/prerun.json
similarity index 100%
rename from internal/fixtures/policyAsCode/policies.json
rename to internal/fixtures/policyAsCode/prerun.json
diff --git a/pkg/cliClient/cliClient_test.go b/pkg/cliClient/cliClient_test.go
index dfb3983..75e6426 100644
--- a/pkg/cliClient/cliClient_test.go
+++ b/pkg/cliClient/cliClient_test.go
@@ -342,7 +342,7 @@ func test_getVersionMessage_success() *GetVersionMessageTestCase {
 }
 
 func mockGetPreRunData() *EvaluationPrerunDataResponse {
-	const policiesJsonPath = "../../internal/fixtures/policyAsCode/policies.json"
+	const policiesJsonPath = "../../internal/fixtures/policyAsCode/prerun.json"
 
 	fileReader := fileReader.CreateFileReader(nil)
 	policiesJsonStr, err := fileReader.ReadFileContent(policiesJsonPath)
diff --git a/pkg/yamlSchemaValidator/validator_test.go b/pkg/policy/policy_test.go
similarity index 62%
rename from pkg/yamlSchemaValidator/validator_test.go
rename to pkg/policy/policy_test.go
index 25f6d80..517abc1 100644
--- a/pkg/yamlSchemaValidator/validator_test.go
+++ b/pkg/policy/policy_test.go
@@ -1,14 +1,17 @@
-package yamlSchemaValidator
+package policy
 
 import (
 	"os"
+	"reflect"
 	"strconv"
 	"strings"
 	"testing"
 
+	"github.com/datreeio/datree/pkg/cliClient"
 	"github.com/datreeio/datree/pkg/fileReader"
-	"github.com/datreeio/datree/pkg/policy"
-	"gopkg.in/yaml.v2"
+	"github.com/datreeio/datree/pkg/jsonSchemaValidator"
+	"github.com/ghodss/yaml"
+	"github.com/stretchr/testify/assert"
 )
 
 type TestFilesByRuleId = map[int]*FailAndPassTests
@@ -23,19 +26,30 @@ type FileWithName struct {
 	content string
 }
 
-func TestValidate(t *testing.T) {
+func TestGetPoliciesFileFromPath(t *testing.T) {
+	policiesYamlPath := "../../internal/fixtures/policyAsCode/policies.yaml"
+	policies, err := GetPoliciesFileFromPath(policiesYamlPath)
+	if err != nil {
+		panic(err)
+	}
+
+	expectedPoliciesJson := expectedPoliciesContent(t, policiesYamlPath)
+	assert.True(t, reflect.DeepEqual(policies, expectedPoliciesJson))
+}
+
+func TestDefaultRulesValidation(t *testing.T) {
 	err := os.Chdir("../../")
 	if err != nil {
 		panic(err)
 	}
 
-	defaultRules, err := policy.GetDefaultRules()
+	defaultRules, err := GetDefaultRules()
 	if err != nil {
 		panic(err)
 	}
 
 	testFilesByRuleId := getTestFilesByRuleId(t)
-	validator := New()
+	validator := jsonSchemaValidator.New()
 
 	for _, rule := range defaultRules.Rules {
 		validatePassing(t, validator, rule.Schema, rule.ID, testFilesByRuleId[rule.ID].passes, true)
@@ -43,14 +57,14 @@ func TestValidate(t *testing.T) {
 	}
 }
 
-func validatePassing(t *testing.T, validator *YamlSchemaValidator, schemaContent map[string]interface{}, ruleId int, files []*FileWithName, expectPass bool) {
+func validatePassing(t *testing.T, validator *jsonSchemaValidator.JSONSchemaValidator, schemaContent map[string]interface{}, ruleId int, files []*FileWithName, expectPass bool) {
 	for _, file := range files {
 		schemaBytes, err := yaml.Marshal(schemaContent)
 		if err != nil {
 			panic(err)
 		}
 
-		res, err := validator.Validate(string(schemaBytes), file.content)
+		res, err := validator.ValidateYamlSchema(string(schemaBytes), file.content)
 		if err != nil {
 			panic(err)
 		}
@@ -110,3 +124,17 @@ func getFileData(filename string) (int, bool) {
 	isPass := strings.Contains(parts[1], "pass")
 	return id, isPass
 }
+
+func expectedPoliciesContent(t *testing.T, path string) *cliClient.EvaluationPrerunPolicies {
+	fileReader := fileReader.CreateFileReader(nil)
+	policiesStr, _ := fileReader.ReadFileContent(path)
+
+	var policiesJson *cliClient.EvaluationPrerunPolicies
+	policiesBytes, _ := yaml.YAMLToJSON([]byte(policiesStr))
+
+	err := yaml.Unmarshal(policiesBytes, &policiesJson)
+	if err != nil {
+		panic(err)
+	}
+	return policiesJson
+}