From c4a9e339f8f33e442a7228f710870649c1077b04 Mon Sep 17 00:00:00 2001
From: Maxim Goncharenko <kacejot@fex.net>
Date: Tue, 14 May 2019 18:10:25 +0300
Subject: [PATCH] Implemented Validation Pattern base. Updated Webhooks
 registration logic. Updated project for using TLS package

---
 init.go                                 |  20 +--
 kubeclient/certificates.go              |  24 +--
 kubeclient/kubeclient.go                |   2 +-
 main.go                                 |  16 +-
 pkg/policyengine/mutation.go            |   7 +-
 pkg/policyengine/mutation/checkRules.go |  44 -----
 pkg/policyengine/mutation/patches.go    |   1 -
 pkg/policyengine/mutation/utils.go      |  39 ++++
 pkg/policyengine/policyengine.go        |  10 +-
 pkg/policyengine/validation.go          |  99 ++++++++++-
 pkg/webhooks/registration.go            | 167 +++++++++++------
 pkg/webhooks/server.go                  | 227 ++++++++++++------------
 pkg/webhooks/utils.go                   |  18 +-
 policycontroller/processPolicy.go       |   2 +-
 14 files changed, 410 insertions(+), 266 deletions(-)
 delete mode 100644 pkg/policyengine/mutation/checkRules.go

diff --git a/init.go b/init.go
index f3c7913d92..358e7b2d29 100644
--- a/init.go
+++ b/init.go
@@ -7,7 +7,7 @@ import (
 
 	"github.com/nirmata/kube-policy/config"
 	"github.com/nirmata/kube-policy/kubeclient"
-	"github.com/nirmata/kube-policy/utils"
+	"github.com/nirmata/kube-policy/pkg/tls"
 
 	rest "k8s.io/client-go/rest"
 	clientcmd "k8s.io/client-go/tools/clientcmd"
@@ -23,27 +23,23 @@ func createClientConfig(kubeconfig string) (*rest.Config, error) {
 	}
 }
 
-func initTlsPemPair(certFile, keyFile string, clientConfig *rest.Config, kubeclient *kubeclient.KubeClient) (*utils.TlsPemPair, error) {
-	var tlsPair *utils.TlsPemPair
+func initTlsPemPair(certFile, keyFile string, clientConfig *rest.Config, kubeclient *kubeclient.KubeClient) (*tls.TlsPemPair, error) {
+	var tlsPair *tls.TlsPemPair
 	if certFile != "" || keyFile != "" {
 		tlsPair = tlsPairFromFiles(certFile, keyFile)
 	}
 
 	var err error
 	if tlsPair != nil {
-		log.Print("Using given TLS key/certificate pair")
 		return tlsPair, nil
 	} else {
 		tlsPair, err = tlsPairFromCluster(clientConfig, kubeclient)
-		if err == nil {
-			log.Printf("Using TLS key/certificate from cluster")
-		}
 		return tlsPair, err
 	}
 }
 
 // Loads PEM private key and TLS certificate from given files
-func tlsPairFromFiles(certFile, keyFile string) *utils.TlsPemPair {
+func tlsPairFromFiles(certFile, keyFile string) *tls.TlsPemPair {
 	if certFile == "" || keyFile == "" {
 		return nil
 	}
@@ -60,7 +56,7 @@ func tlsPairFromFiles(certFile, keyFile string) *utils.TlsPemPair {
 		return nil
 	}
 
-	return &utils.TlsPemPair{
+	return &tls.TlsPemPair{
 		Certificate: certContent,
 		PrivateKey:  keyContent,
 	}
@@ -69,19 +65,19 @@ func tlsPairFromFiles(certFile, keyFile string) *utils.TlsPemPair {
 // Loads or creates PEM private key and TLS certificate for webhook server.
 // Created pair is stored in cluster's secret.
 // Returns struct with key/certificate pair.
-func tlsPairFromCluster(configuration *rest.Config, client *kubeclient.KubeClient) (*utils.TlsPemPair, error) {
+func tlsPairFromCluster(configuration *rest.Config, client *kubeclient.KubeClient) (*tls.TlsPemPair, error) {
 	apiServerUrl, err := url.Parse(configuration.Host)
 	if err != nil {
 		return nil, err
 	}
-	certProps := utils.TlsCertificateProps{
+	certProps := tls.TlsCertificateProps{
 		Service:       config.WebhookServiceName,
 		Namespace:     config.KubePolicyNamespace,
 		ApiServerHost: apiServerUrl.Hostname(),
 	}
 
 	tlsPair := client.ReadTlsPair(certProps)
-	if utils.IsTlsPairShouldBeUpdated(tlsPair) {
+	if tls.IsTlsPairShouldBeUpdated(tlsPair) {
 		log.Printf("Generating new key/certificate pair for TLS")
 		tlsPair, err = client.GenerateTlsPemPair(certProps)
 		if err != nil {
diff --git a/kubeclient/certificates.go b/kubeclient/certificates.go
index 2c5666cc68..9626c1004f 100644
--- a/kubeclient/certificates.go
+++ b/kubeclient/certificates.go
@@ -5,22 +5,22 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/nirmata/kube-policy/utils"
-
+	tls "github.com/nirmata/kube-policy/pkg/tls"
 	certificates "k8s.io/api/certificates/v1beta1"
+
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 // Issues TLS certificate for webhook server using given PEM private key
 // Returns signed and approved TLS certificate in PEM format
-func (kc *KubeClient) GenerateTlsPemPair(props utils.TlsCertificateProps) (*utils.TlsPemPair, error) {
-	privateKey, err := utils.TlsGeneratePrivateKey()
+func (kc *KubeClient) GenerateTlsPemPair(props tls.TlsCertificateProps) (*tls.TlsPemPair, error) {
+	privateKey, err := tls.TlsGeneratePrivateKey()
 	if err != nil {
 		return nil, err
 	}
 
-	certRequest, err := utils.TlsCertificateGenerateRequest(privateKey, props)
+	certRequest, err := tls.TlsCertificateGenerateRequest(privateKey, props)
 	if err != nil {
 		return nil, errors.New(fmt.Sprintf("Unable to create certificate request: %v", err))
 	}
@@ -35,9 +35,9 @@ func (kc *KubeClient) GenerateTlsPemPair(props utils.TlsCertificateProps) (*util
 		return nil, errors.New(fmt.Sprintf("Unable to fetch certificate from request: %v", err))
 	}
 
-	return &utils.TlsPemPair{
+	return &tls.TlsPemPair{
 		Certificate: tlsCert,
-		PrivateKey:  utils.TlsPrivateKeyToPem(privateKey),
+		PrivateKey:  tls.TlsPrivateKeyToPem(privateKey),
 	}, nil
 }
 
@@ -111,7 +111,7 @@ const privateKeyField string = "privateKey"
 const certificateField string = "certificate"
 
 // Reads the pair of TLS certificate and key from the specified secret.
-func (kc *KubeClient) ReadTlsPair(props utils.TlsCertificateProps) *utils.TlsPemPair {
+func (kc *KubeClient) ReadTlsPair(props tls.TlsCertificateProps) *tls.TlsPemPair {
 	name := generateSecretName(props)
 	secret, err := kc.client.CoreV1().Secrets(props.Namespace).Get(name, metav1.GetOptions{})
 	if err != nil {
@@ -119,7 +119,7 @@ func (kc *KubeClient) ReadTlsPair(props utils.TlsCertificateProps) *utils.TlsPem
 		return nil
 	}
 
-	pemPair := utils.TlsPemPair{
+	pemPair := tls.TlsPemPair{
 		Certificate: secret.Data[certificateField],
 		PrivateKey:  secret.Data[privateKeyField],
 	}
@@ -136,7 +136,7 @@ func (kc *KubeClient) ReadTlsPair(props utils.TlsCertificateProps) *utils.TlsPem
 
 // Writes the pair of TLS certificate and key to the specified secret.
 // Updates existing secret or creates new one.
-func (kc *KubeClient) WriteTlsPair(props utils.TlsCertificateProps, pemPair *utils.TlsPemPair) error {
+func (kc *KubeClient) WriteTlsPair(props tls.TlsCertificateProps, pemPair *tls.TlsPemPair) error {
 	name := generateSecretName(props)
 	secret, err := kc.client.CoreV1().Secrets(props.Namespace).Get(name, metav1.GetOptions{})
 
@@ -176,6 +176,6 @@ func (kc *KubeClient) WriteTlsPair(props utils.TlsCertificateProps, pemPair *uti
 	return err
 }
 
-func generateSecretName(props utils.TlsCertificateProps) string {
-	return utils.GenerateInClusterServiceName(props) + ".kube-policy-tls-pair"
+func generateSecretName(props tls.TlsCertificateProps) string {
+	return tls.GenerateInClusterServiceName(props) + ".kube-policy-tls-pair"
 }
diff --git a/kubeclient/kubeclient.go b/kubeclient/kubeclient.go
index eca5f406b6..d4cd0220d2 100644
--- a/kubeclient/kubeclient.go
+++ b/kubeclient/kubeclient.go
@@ -29,7 +29,7 @@ type KubeClient struct {
 // Checks parameters and creates new instance of KubeClient
 func NewKubeClient(config *rest.Config, logger *log.Logger) (*KubeClient, error) {
 	if logger == nil {
-		logger = log.New(os.Stdout, "Kubernetes client: ", log.LstdFlags|log.Lshortfile)
+		logger = log.New(os.Stdout, "Kubernetes client: ", log.LstdFlags)
 	}
 
 	client, err := kubernetes.NewForConfig(config)
diff --git a/main.go b/main.go
index 8eac1b64f1..e5f516fa89 100644
--- a/main.go
+++ b/main.go
@@ -68,18 +68,26 @@ func main() {
 	if err != nil {
 		log.Fatalf("Unable to create webhook server: %v\n", err)
 	}
-	server.RunAsync()
+
+	webhookRegistrationClient, err := webhooks.NewWebhookRegistrationClient(clientConfig, kubeclient)
+	if err != nil {
+		log.Fatalf("Unable to register admission webhooks on cluster: %v\n", err)
+	}
 
 	stopCh := signals.SetupSignalHandler()
+
 	policyInformerFactory.Start(stopCh)
-	if err = eventController.Run(stopCh); err != nil {
-		log.Fatalf("Error running EventController: %v\n", err)
-	}
+	eventController.Run(stopCh)
 
 	if err = policyController.Run(stopCh); err != nil {
 		log.Fatalf("Error running PolicyController: %v\n", err)
 	}
 
+	if err = webhookRegistrationClient.Register(); err != nil {
+		log.Fatalf("Failed registering Admission Webhooks: %v\n", err)
+	}
+
+	server.RunAsync()
 	<-stopCh
 	server.Stop()
 }
diff --git a/pkg/policyengine/mutation.go b/pkg/policyengine/mutation.go
index 75f20193b3..4ab71befcf 100644
--- a/pkg/policyengine/mutation.go
+++ b/pkg/policyengine/mutation.go
@@ -22,7 +22,7 @@ func (p *policyEngine) Mutate(policy kubepolicy.Policy, rawResource []byte) []mu
 			continue
 		}
 
-		ok, err := mutation.IsRuleApplicableToResource(rawResource, rule.ResourceDescription)
+		ok, err := mutation.ResourceMeetsRules(rawResource, rule.ResourceDescription)
 		if err != nil {
 			p.logger.Printf("Rule has invalid data: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
 			continue
@@ -33,6 +33,10 @@ func (p *policyEngine) Mutate(policy kubepolicy.Policy, rawResource []byte) []mu
 			continue
 		}
 
+		if rule.Mutation == nil {
+			continue
+		}
+
 		// Process Overlay
 
 		if rule.Mutation.Overlay != nil {
@@ -54,7 +58,6 @@ func (p *policyEngine) Mutate(policy kubepolicy.Policy, rawResource []byte) []mu
 				policyPatches = append(policyPatches, processedPatches...)
 			}
 		}
-
 	}
 
 	return policyPatches
diff --git a/pkg/policyengine/mutation/checkRules.go b/pkg/policyengine/mutation/checkRules.go
deleted file mode 100644
index bcd73a0840..0000000000
--- a/pkg/policyengine/mutation/checkRules.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package mutation
-
-import (
-	"github.com/minio/minio/pkg/wildcard"
-	types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-)
-
-// kind is the type of object being manipulated
-// Checks requests kind, name and labels to fit the policy
-func IsRuleApplicableToResource(resourceRaw []byte, description types.ResourceDescription) (bool, error) {
-	kind := ParseKindFromObject(resourceRaw)
-	if description.Kind != kind {
-		return false, nil
-	}
-
-	if resourceRaw != nil {
-		meta := ParseMetadataFromObject(resourceRaw)
-		name := ParseNameFromObject(resourceRaw)
-
-		if description.Name != nil {
-
-			if !wildcard.Match(*description.Name, name) {
-				return false, nil
-			}
-		}
-
-		if description.Selector != nil {
-			selector, err := metav1.LabelSelectorAsSelector(description.Selector)
-
-			if err != nil {
-				return false, err
-			}
-
-			labelMap := ParseLabelsFromMetadata(meta)
-
-			if !selector.Matches(labelMap) {
-				return false, nil
-			}
-
-		}
-	}
-	return true, nil
-}
diff --git a/pkg/policyengine/mutation/patches.go b/pkg/policyengine/mutation/patches.go
index 0b3015020d..bf86a73418 100644
--- a/pkg/policyengine/mutation/patches.go
+++ b/pkg/policyengine/mutation/patches.go
@@ -10,7 +10,6 @@ import (
 
 type PatchBytes []byte
 
-// Test patches on given document according to given sets.
 // Returns array from separate patches that can be applied to the document
 // Returns error ONLY in case when creation of resource should be denied.
 func ProcessPatches(patches []kubepolicy.Patch, resource []byte) ([]PatchBytes, error) {
diff --git a/pkg/policyengine/mutation/utils.go b/pkg/policyengine/mutation/utils.go
index 5abc2ee03c..eb1daf2156 100644
--- a/pkg/policyengine/mutation/utils.go
+++ b/pkg/policyengine/mutation/utils.go
@@ -4,6 +4,9 @@ import (
 	"encoding/json"
 	"strings"
 
+	"github.com/minio/minio/pkg/wildcard"
+	kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
 )
 
@@ -65,3 +68,39 @@ func ParseRegexPolicyResourceName(policyResourceName string) (string, bool) {
 	}
 	return strings.Trim(regex[1], " "), true
 }
+
+// ResourceMeetsRules checks requests kind, name and labels to fit the policy
+func ResourceMeetsRules(resourceRaw []byte, description kubepolicy.ResourceDescription) (bool, error) {
+	kind := ParseKindFromObject(resourceRaw)
+	if description.Kind != kind {
+		return false, nil
+	}
+
+	if resourceRaw != nil {
+		meta := ParseMetadataFromObject(resourceRaw)
+		name := ParseNameFromObject(resourceRaw)
+
+		if description.Name != nil {
+
+			if !wildcard.Match(*description.Name, name) {
+				return false, nil
+			}
+		}
+
+		if description.Selector != nil {
+			selector, err := metav1.LabelSelectorAsSelector(description.Selector)
+
+			if err != nil {
+				return false, err
+			}
+
+			labelMap := ParseLabelsFromMetadata(meta)
+
+			if !selector.Matches(labelMap) {
+				return false, nil
+			}
+
+		}
+	}
+	return true, nil
+}
diff --git a/pkg/policyengine/policyengine.go b/pkg/policyengine/policyengine.go
index cbe8367d78..76936b9913 100644
--- a/pkg/policyengine/policyengine.go
+++ b/pkg/policyengine/policyengine.go
@@ -12,15 +12,14 @@ import (
 )
 
 type PolicyEngine interface {
-	// ProcessMutation should be called from admission contoller
+	// Mutate should be called from admission contoller
 	// when there is an creation / update of the resource
 	// ProcessMutation(policy types.Policy, rawResource []byte) (patchBytes []byte, events []Events, err error)
 	Mutate(policy types.Policy, rawResource []byte) []mutation.PatchBytes
 
-	// ProcessValidation should be called from admission contoller
+	// Validate should be called from admission contoller
 	// when there is an creation / update of the resource
-	// TODO: Change name to Validate
-	ProcessValidation(policy types.Policy, rawResource []byte)
+	Validate(policy types.Policy, rawResource []byte) bool
 
 	// ProcessExisting should be called from policy controller
 	// when there is an create / update of the policy
@@ -36,6 +35,7 @@ type policyEngine struct {
 	logger     *log.Logger
 }
 
+// NewPolicyEngine creates new instance of policyEngine
 func NewPolicyEngine(kubeClient *kubeClient.KubeClient, logger *log.Logger) PolicyEngine {
 	return &policyEngine{
 		kubeClient: kubeClient,
@@ -54,7 +54,7 @@ func (p *policyEngine) ProcessExisting(policy types.Policy, rawResource []byte)
 			continue
 		}
 
-		if ok, err := mutation.IsRuleApplicableToResource(rawResource, rule.ResourceDescription); !ok {
+		if ok, err := mutation.ResourceMeetsRules(rawResource, rule.ResourceDescription); !ok {
 			p.logger.Printf("Rule %s of policy %s is not applicable to the request", rule.Name, policy.Name)
 			return nil, nil, err
 		}
diff --git a/pkg/policyengine/validation.go b/pkg/policyengine/validation.go
index 282a4496a4..acd4a83bc9 100644
--- a/pkg/policyengine/validation.go
+++ b/pkg/policyengine/validation.go
@@ -1,5 +1,100 @@
 package policyengine
 
-import types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
+import (
+	"encoding/json"
+	"fmt"
 
-func (p *policyEngine) ProcessValidation(policy types.Policy, rawResource []byte) {}
+	kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
+	"github.com/nirmata/kube-policy/pkg/policyengine/mutation"
+)
+
+func (p *policyEngine) Validate(policy kubepolicy.Policy, rawResource []byte) bool {
+	var resource interface{}
+	json.Unmarshal(rawResource, &resource)
+
+	allowed := true
+	for i, rule := range policy.Spec.Rules {
+
+		// Checks for preconditions
+		// TODO: Rework PolicyEngine interface that it receives not a policy, but mutation object for
+		// Mutate, validation for Validate and so on. It will allow to bring this checks outside of PolicyEngine
+		// to common part as far as they present for all: mutation, validation, generation
+
+		err := rule.Validate()
+		if err != nil {
+			p.logger.Printf("Rule has invalid structure: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
+			continue
+		}
+
+		ok, err := mutation.ResourceMeetsRules(rawResource, rule.ResourceDescription)
+		if err != nil {
+			p.logger.Printf("Rule has invalid data: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
+			continue
+		}
+
+		if !ok {
+			p.logger.Printf("Rule is not applicable t the request: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
+			continue
+		}
+
+		if rule.Validation == nil {
+			continue
+		}
+
+		if err := traverseAndValidate(resource, rule.Validation.Pattern); err != nil {
+			p.logger.Printf("Validation with the rule %s has failed %s: %s\n", rule.Name, err.Error(), *rule.Validation.Message)
+			allowed = false
+		} else {
+			p.logger.Printf("Validation rule %s is successful %s: %s\n", rule.Name, err.Error(), *rule.Validation.Message)
+		}
+	}
+
+	return allowed
+}
+
+func traverseAndValidate(resourcePart, patternPart interface{}) error {
+	switch pattern := patternPart.(type) {
+	case map[string]interface{}:
+		dictionary, ok := resourcePart.(map[string]interface{})
+
+		if !ok {
+			return fmt.Errorf("Validating error: expected %T, found %T", patternPart, resourcePart)
+		}
+
+		var err error
+		for key, value := range pattern {
+			err = traverseAndValidate(dictionary[key], value)
+		}
+		return err
+
+	case []interface{}:
+		array, ok := resourcePart.([]interface{})
+
+		if !ok {
+			return fmt.Errorf("Validating error: expected %T, found %T", patternPart, resourcePart)
+		}
+
+		var err error
+		for i, value := range pattern {
+			err = traverseAndValidate(array[i], value)
+		}
+		return err
+	case string:
+		str := resourcePart.(string)
+		if !checkForWildcard(str, pattern) {
+			return fmt.Errorf("Value %s has not passed wildcard check %s", str, pattern)
+		}
+	default:
+		return fmt.Errorf("Received unknown type: %T", patternPart)
+	}
+
+	return nil
+}
+
+func checkForWildcard(value, pattern string) bool {
+	return value == pattern
+}
+
+func checkForOperator(value int, pattern string) bool {
+	return true
+}
diff --git a/pkg/webhooks/registration.go b/pkg/webhooks/registration.go
index dda450dad7..6fe8a64f2b 100644
--- a/pkg/webhooks/registration.go
+++ b/pkg/webhooks/registration.go
@@ -13,32 +13,48 @@ import (
 	rest "k8s.io/client-go/rest"
 )
 
-type MutationWebhookRegistration struct {
+// WebhookRegistrationClient is client for registration webhooks on cluster
+type WebhookRegistrationClient struct {
 	registrationClient *admregclient.AdmissionregistrationV1beta1Client
 	kubeclient         *kubeclient.KubeClient
 	clientConfig       *rest.Config
 }
 
-func NewMutationWebhookRegistration(clientConfig *rest.Config, kubeclient *kubeclient.KubeClient) (*MutationWebhookRegistration, error) {
+// NewWebhookRegistrationClient creates new WebhookRegistrationClient instance
+func NewWebhookRegistrationClient(clientConfig *rest.Config, kubeclient *kubeclient.KubeClient) (*WebhookRegistrationClient, error) {
 	registrationClient, err := admregclient.NewForConfig(clientConfig)
 	if err != nil {
 		return nil, err
 	}
 
-	return &MutationWebhookRegistration{
+	return &WebhookRegistrationClient{
 		registrationClient: registrationClient,
 		kubeclient:         kubeclient,
 		clientConfig:       clientConfig,
 	}, nil
 }
 
-func (mwr *MutationWebhookRegistration) Register() error {
-	webhookConfig, err := mwr.constructWebhookConfig(mwr.clientConfig)
+// Register creates admission webhooks configs on cluster
+func (wrc *WebhookRegistrationClient) Register() error {
+	// For the case if cluster already has this configs
+	wrc.Deregister()
+
+	mutatingWebhookConfig, err := wrc.constructMutatingWebhookConfig(wrc.clientConfig)
 	if err != nil {
 		return err
 	}
 
-	_, err = mwr.registrationClient.MutatingWebhookConfigurations().Create(webhookConfig)
+	_, err = wrc.registrationClient.MutatingWebhookConfigurations().Create(mutatingWebhookConfig)
+	if err != nil {
+		return err
+	}
+
+	validationWebhookConfig, err := wrc.constructValidatingWebhookConfig(wrc.clientConfig)
+	if err != nil {
+		return err
+	}
+
+	_, err = wrc.registrationClient.ValidatingWebhookConfigurations().Create(validationWebhookConfig)
 	if err != nil {
 		return err
 	}
@@ -46,70 +62,109 @@ func (mwr *MutationWebhookRegistration) Register() error {
 	return nil
 }
 
-func (mwr *MutationWebhookRegistration) Deregister() error {
-	return mwr.registrationClient.MutatingWebhookConfigurations().Delete(config.MutationWebhookName, &meta.DeleteOptions{})
+// Deregister deletes webhook configs from cluster
+// This function does not fail on error:
+// Register will fail if the config exists, so there is no need to fail on error
+func (wrc *WebhookRegistrationClient) Deregister() {
+	wrc.registrationClient.MutatingWebhookConfigurations().Delete(config.MutatingWebhookConfigurationName, &meta.DeleteOptions{})
+	wrc.registrationClient.ValidatingWebhookConfigurations().Delete(config.ValidatingWebhookConfigurationName, &meta.DeleteOptions{})
 }
 
-func (mwr *MutationWebhookRegistration) constructWebhookConfig(configuration *rest.Config) (*admregapi.MutatingWebhookConfiguration, error) {
-	caData := ExtractCA(configuration)
+func (wrc *WebhookRegistrationClient) constructMutatingWebhookConfig(configuration *rest.Config) (*admregapi.MutatingWebhookConfiguration, error) {
+	caData := extractCA(configuration)
 	if len(caData) == 0 {
 		return nil, errors.New("Unable to extract CA data from configuration")
 	}
 
-	kubePolicyDeployment, err := mwr.kubeclient.GetKubePolicyDeployment()
-
-	if err != nil {
-		return nil, err
-	}
-
 	return &admregapi.MutatingWebhookConfiguration{
 		ObjectMeta: meta.ObjectMeta{
-			Name:   config.WebhookConfigName,
-			Labels: config.WebhookConfigLabels,
+			Name:   config.MutatingWebhookConfigurationName,
+			Labels: config.KubePolicyAppLabels,
 			OwnerReferences: []meta.OwnerReference{
-				meta.OwnerReference{
-					APIVersion: config.DeploymentAPIVersion,
-					Kind:       config.DeploymentKind,
-					Name:       kubePolicyDeployment.ObjectMeta.Name,
-					UID:        kubePolicyDeployment.ObjectMeta.UID,
-				},
+				wrc.constructOwner(),
 			},
 		},
 		Webhooks: []admregapi.Webhook{
-			admregapi.Webhook{
-				Name: config.MutationWebhookName,
-				ClientConfig: admregapi.WebhookClientConfig{
-					Service: &admregapi.ServiceReference{
-						Namespace: config.KubePolicyNamespace,
-						Name:      config.WebhookServiceName,
-						Path:      &config.WebhookServicePath,
-					},
-					CABundle: caData,
-				},
-				Rules: []admregapi.RuleWithOperations{
-					admregapi.RuleWithOperations{
-						Operations: []admregapi.OperationType{
-							admregapi.Create,
-						},
-						Rule: admregapi.Rule{
-							APIGroups: []string{
-								"*",
-							},
-							APIVersions: []string{
-								"*",
-							},
-							Resources: []string{
-								"*/*",
-							},
-						},
-					},
-				},
-			},
+			constructWebhook(
+				config.MutatingWebhookName,
+				config.MutatingWebhookServicePath,
+				caData),
 		},
 	}, nil
 }
 
-func ExtractCA(config *rest.Config) (result []byte) {
+func (wrc *WebhookRegistrationClient) constructValidatingWebhookConfig(configuration *rest.Config) (*admregapi.ValidatingWebhookConfiguration, error) {
+	caData := extractCA(configuration)
+	if len(caData) == 0 {
+		return nil, errors.New("Unable to extract CA data from configuration")
+	}
+
+	return &admregapi.ValidatingWebhookConfiguration{
+		ObjectMeta: meta.ObjectMeta{
+			Name:   config.ValidatingWebhookConfigurationName,
+			Labels: config.KubePolicyAppLabels,
+			OwnerReferences: []meta.OwnerReference{
+				wrc.constructOwner(),
+			},
+		},
+		Webhooks: []admregapi.Webhook{
+			constructWebhook(
+				config.ValidatingWebhookName,
+				config.ValidatingWebhookServicePath,
+				caData),
+		},
+	}, nil
+}
+
+func constructWebhook(name, servicePath string, caData []byte) admregapi.Webhook {
+	return admregapi.Webhook{
+		Name: name,
+		ClientConfig: admregapi.WebhookClientConfig{
+			Service: &admregapi.ServiceReference{
+				Namespace: config.KubePolicyNamespace,
+				Name:      config.WebhookServiceName,
+				Path:      &servicePath,
+			},
+			CABundle: caData,
+		},
+		Rules: []admregapi.RuleWithOperations{
+			admregapi.RuleWithOperations{
+				Operations: []admregapi.OperationType{
+					admregapi.Create,
+				},
+				Rule: admregapi.Rule{
+					APIGroups: []string{
+						"*",
+					},
+					APIVersions: []string{
+						"*",
+					},
+					Resources: []string{
+						"*/*",
+					},
+				},
+			},
+		},
+	}
+}
+
+func (wrc *WebhookRegistrationClient) constructOwner() meta.OwnerReference {
+	kubePolicyDeployment, err := wrc.kubeclient.GetKubePolicyDeployment()
+
+	if err != nil {
+		return meta.OwnerReference{}
+	}
+
+	return meta.OwnerReference{
+		APIVersion: config.DeploymentAPIVersion,
+		Kind:       config.DeploymentKind,
+		Name:       kubePolicyDeployment.ObjectMeta.Name,
+		UID:        kubePolicyDeployment.ObjectMeta.UID,
+	}
+}
+
+// ExtractCA used for extraction CA from config
+func extractCA(config *rest.Config) (result []byte) {
 	fileName := config.TLSClientConfig.CAFile
 
 	if fileName != "" {
@@ -120,7 +175,7 @@ func ExtractCA(config *rest.Config) (result []byte) {
 		}
 
 		return result
-	} else {
-		return config.TLSClientConfig.CAData
 	}
+
+	return config.TLSClientConfig.CAData
 }
diff --git a/pkg/webhooks/server.go b/pkg/webhooks/server.go
index 6a91b69ef6..637da97e27 100644
--- a/pkg/webhooks/server.go
+++ b/pkg/webhooks/server.go
@@ -10,16 +10,14 @@ import (
 	"log"
 	"net/http"
 	"os"
-	"sort"
 	"time"
 
 	"github.com/nirmata/kube-policy/config"
 	"github.com/nirmata/kube-policy/kubeclient"
-	kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
 	policylister "github.com/nirmata/kube-policy/pkg/client/listers/policy/v1alpha1"
 	"github.com/nirmata/kube-policy/pkg/policyengine"
 	"github.com/nirmata/kube-policy/pkg/policyengine/mutation"
-	"github.com/nirmata/kube-policy/utils"
+	tlsutils "github.com/nirmata/kube-policy/pkg/tls"
 	v1beta1 "k8s.io/api/admission/v1beta1"
 	"k8s.io/apimachinery/pkg/labels"
 	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
@@ -37,12 +35,12 @@ type WebhookServer struct {
 // NewWebhookServer creates new instance of WebhookServer accordingly to given configuration
 // Policy Controller and Kubernetes Client should be initialized in configuration
 func NewWebhookServer(
-	tlsPair *utils.TlsPemPair,
-	kubeclient *kubeclient.KubeClient,
+	tlsPair *tlsutils.TlsPemPair,
+	kubeClient *kubeclient.KubeClient,
 	policyLister policylister.PolicyLister,
 	logger *log.Logger) (*WebhookServer, error) {
 	if logger == nil {
-		logger = log.New(os.Stdout, "HTTPS Server: ", log.LstdFlags|log.Lshortfile)
+		logger = log.New(os.Stdout, "Webhook Server:    ", log.LstdFlags)
 	}
 
 	if tlsPair == nil {
@@ -55,7 +53,7 @@ func NewWebhookServer(
 		return nil, err
 	}
 	tlsConfig.Certificates = []tls.Certificate{pair}
-	policyEngine := policyengine.NewPolicyEngine(kubeclient, logger)
+	policyEngine := policyengine.NewPolicyEngine(kubeClient, logger)
 
 	ws := &WebhookServer{
 		policyEngine: policyEngine,
@@ -64,7 +62,8 @@ func NewWebhookServer(
 	}
 
 	mux := http.NewServeMux()
-	mux.HandleFunc(config.WebhookServicePath, ws.serve)
+	mux.HandleFunc(config.MutatingWebhookServicePath, ws.serve)
+	mux.HandleFunc(config.ValidatingWebhookServicePath, ws.serve)
 
 	ws.server = http.Server{
 		Addr:         ":443", // Listen on port for HTTPS requests
@@ -80,44 +79,125 @@ func NewWebhookServer(
 
 // Main server endpoint for all requests
 func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
-	if r.URL.Path == config.WebhookServicePath {
-		admissionReview := ws.parseAdmissionReview(r, w)
-		if admissionReview == nil {
-			return
-		}
+	admissionReview := ws.bodyToAdmissionReview(r, w)
+	if admissionReview == nil {
+		return
+	}
 
-		var admissionResponse *v1beta1.AdmissionResponse
-		if AdmissionIsRequired(admissionReview.Request) {
-			admissionResponse = ws.Mutate(admissionReview.Request)
-		}
+	admissionReview.Response = &v1beta1.AdmissionResponse{
+		Allowed: true,
+	}
 
-		if admissionResponse == nil {
-			admissionResponse = &v1beta1.AdmissionResponse{
-				Allowed: true,
-			}
+	if KindIsSupported(admissionReview.Request.Kind.Kind) {
+		switch r.URL.Path {
+		case config.MutatingWebhookServicePath:
+			admissionReview.Response = ws.HandleMutation(admissionReview.Request)
+		case config.ValidatingWebhookServicePath:
+			admissionReview.Response = ws.HandleValidation(admissionReview.Request)
 		}
+	}
 
-		admissionReview.Response = admissionResponse
-		admissionReview.Response.UID = admissionReview.Request.UID
+	admissionReview.Response.UID = admissionReview.Request.UID
 
-		responseJson, err := json.Marshal(admissionReview)
-		if err != nil {
-			http.Error(w, fmt.Sprintf("Could not encode response: %v", err), http.StatusInternalServerError)
-			return
-		}
+	responseJson, err := json.Marshal(admissionReview)
+	if err != nil {
+		http.Error(w, fmt.Sprintf("Could not encode response: %v", err), http.StatusInternalServerError)
+		return
+	}
 
-		ws.logger.Printf("Response body\n:%v", string(responseJson))
-		w.Header().Set("Content-Type", "application/json; charset=utf-8")
-		if _, err := w.Write(responseJson); err != nil {
-			http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
-		}
-	} else {
-		http.Error(w, fmt.Sprintf("Unexpected method path: %v", r.URL.Path), http.StatusNotFound)
+	w.Header().Set("Content-Type", "application/json; charset=utf-8")
+	if _, err := w.Write(responseJson); err != nil {
+		http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
 	}
 }
 
+// RunAsync TLS server in separate thread and returns control immediately
+func (ws *WebhookServer) RunAsync() {
+	go func(ws *WebhookServer) {
+		err := ws.server.ListenAndServeTLS("", "")
+		if err != nil {
+			ws.logger.Fatal(err)
+		}
+	}(ws)
+
+	ws.logger.Printf("Started Webhook Server")
+}
+
+// Stop TLS server and returns control after the server is shut down
+func (ws *WebhookServer) Stop() {
+	err := ws.server.Shutdown(context.Background())
+	if err != nil {
+		// Error from closing listeners, or context timeout:
+		ws.logger.Printf("Server Shutdown error: %v", err)
+		ws.server.Close()
+	}
+}
+
+// HandleMutation handles mutating webhook admission request
+func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
+	ws.logger.Printf("Handling mutation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
+		request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation)
+
+	policies, err := ws.policyLister.List(labels.NewSelector())
+	if err != nil {
+		utilruntime.HandleError(err)
+		return nil
+	}
+
+	var allPatches []mutation.PatchBytes
+	for _, policy := range policies {
+		ws.logger.Printf("Applying policy %s with %d rules\n", policy.ObjectMeta.Name, len(policy.Spec.Rules))
+
+		policyPatches := ws.policyEngine.Mutate(*policy, request.Object.Raw)
+		allPatches = append(allPatches, policyPatches...)
+
+		if len(policyPatches) > 0 {
+			namespace := mutation.ParseNamespaceFromObject(request.Object.Raw)
+			name := mutation.ParseNameFromObject(request.Object.Raw)
+			ws.logger.Printf("Policy %s applied to %s %s/%s", policy.Name, request.Kind.Kind, namespace, name)
+		}
+	}
+
+	patchType := v1beta1.PatchTypeJSONPatch
+	return &v1beta1.AdmissionResponse{
+		Allowed:   true,
+		Patch:     mutation.JoinPatches(allPatches),
+		PatchType: &patchType,
+	}
+}
+
+// HandleValidation handles validating webhook admission request
+func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
+	ws.logger.Printf("Handling validation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
+		request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation)
+
+	policies, err := ws.policyLister.List(labels.NewSelector())
+	if err != nil {
+		utilruntime.HandleError(err)
+		return nil
+	}
+
+	allowed := true
+	for _, policy := range policies {
+		ws.logger.Printf("Validating resource with policy %s with %d rules", policy.ObjectMeta.Name, len(policy.Spec.Rules))
+
+		if ok := ws.policyEngine.Validate(*policy, request.Object.Raw); !ok {
+			ws.logger.Printf("Validation has failed: %v\n", err)
+			utilruntime.HandleError(err)
+			allowed = false
+		} else {
+			ws.logger.Println("Validation is successful")
+		}
+	}
+
+	return &v1beta1.AdmissionResponse{
+		Allowed: allowed,
+	}
+}
+
+// bodyToAdmissionReview creates AdmissionReview object from request body
 // Answers to the http.ResponseWriter if request is not valid
-func (ws *WebhookServer) parseAdmissionReview(request *http.Request, writer http.ResponseWriter) *v1beta1.AdmissionReview {
+func (ws *WebhookServer) bodyToAdmissionReview(request *http.Request, writer http.ResponseWriter) *v1beta1.AdmissionReview {
 	var body []byte
 	if request.Body != nil {
 		if data, err := ioutil.ReadAll(request.Body); err == nil {
@@ -143,81 +223,6 @@ func (ws *WebhookServer) parseAdmissionReview(request *http.Request, writer http
 		http.Error(writer, "Can't decode body as AdmissionReview", http.StatusExpectationFailed)
 		return nil
 	} else {
-		ws.logger.Printf("Request body:\n%v", string(body))
 		return admissionReview
 	}
 }
-
-// Runs TLS server in separate thread and returns control immediately
-func (ws *WebhookServer) RunAsync() {
-	go func(ws *WebhookServer) {
-		err := ws.server.ListenAndServeTLS("", "")
-		if err != nil {
-			ws.logger.Fatal(err)
-		}
-	}(ws)
-}
-
-// Stops TLS server and returns control after the server is shut down
-func (ws *WebhookServer) Stop() {
-	err := ws.server.Shutdown(context.Background())
-	if err != nil {
-		// Error from closing listeners, or context timeout:
-		ws.logger.Printf("Server Shutdown error: %v", err)
-		ws.server.Close()
-	}
-}
-
-func (ws *WebhookServer) Mutate(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
-	ws.logger.Printf("AdmissionReview for Kind=%v, Namespace=%v Name=%v UID=%v patchOperation=%v UserInfo=%v",
-		request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation, request.UserInfo)
-
-	policies, err := ws.getPolicies()
-	if err != nil {
-		utilruntime.HandleError(err)
-		return nil
-	}
-	if len(policies) == 0 {
-		return nil
-	}
-
-	var allPatches []mutation.PatchBytes
-	for _, policy := range policies {
-		ws.logger.Printf("Applying policy %s with %d rules", policy.ObjectMeta.Name, len(policy.Spec.Rules))
-
-		policyPatches := ws.policyEngine.Mutate(policy, request.Object.Raw)
-		allPatches = append(allPatches, policyPatches...)
-
-		if len(policyPatches) > 0 {
-			namespace := mutation.ParseNamespaceFromObject(request.Object.Raw)
-			name := mutation.ParseNameFromObject(request.Object.Raw)
-			ws.logger.Printf("Policy %s applied to %s %s/%s", policy.Name, request.Kind.Kind, namespace, name)
-		}
-	}
-
-	patchType := v1beta1.PatchTypeJSONPatch
-	return &v1beta1.AdmissionResponse{
-		Allowed:   true,
-		Patch:     mutation.JoinPatches(allPatches),
-		PatchType: &patchType,
-	}
-}
-
-func (ws *WebhookServer) getPolicies() ([]kubepolicy.Policy, error) {
-	selector := labels.NewSelector()
-	cachedPolicies, err := ws.policyLister.List(selector)
-	if err != nil {
-		ws.logger.Printf("Error: %v", err)
-		return nil, err
-	}
-
-	var policies []kubepolicy.Policy
-	for _, elem := range cachedPolicies {
-		policies = append(policies, *elem.DeepCopy())
-	}
-
-	sort.Slice(policies, func(i, j int) bool {
-		return policies[i].CreationTimestamp.Time.Before(policies[j].CreationTimestamp.Time)
-	})
-	return policies, nil
-}
diff --git a/pkg/webhooks/utils.go b/pkg/webhooks/utils.go
index a3323ff917..da970e58c9 100644
--- a/pkg/webhooks/utils.go
+++ b/pkg/webhooks/utils.go
@@ -2,12 +2,11 @@ package webhooks
 
 import (
 	kubeclient "github.com/nirmata/kube-policy/kubeclient"
-	types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
-	mutation "github.com/nirmata/kube-policy/pkg/policyengine/mutation"
-	"k8s.io/api/admission/v1beta1"
 )
 
-func kindIsSupported(kind string) bool {
+// KindIsSupported checks kind to be prensent in
+// SupportedKinds defined in config
+func KindIsSupported(kind string) bool {
 	for _, k := range kubeclient.GetSupportedKinds() {
 		if k == kind {
 			return true
@@ -15,14 +14,3 @@ func kindIsSupported(kind string) bool {
 	}
 	return false
 }
-
-// Checks for admission if kind is supported
-func AdmissionIsRequired(request *v1beta1.AdmissionRequest) bool {
-	// Here you can make additional hardcoded checks
-	return kindIsSupported(request.Kind.Kind)
-}
-
-// Checks requests kind, name and labels to fit the policy
-func IsRuleApplicableToRequest(policyResource types.ResourceDescription, request *v1beta1.AdmissionRequest) (bool, error) {
-	return mutation.IsRuleApplicableToResource(request.Object.Raw, policyResource)
-}
diff --git a/policycontroller/processPolicy.go b/policycontroller/processPolicy.go
index a707f7325d..7165361cce 100644
--- a/policycontroller/processPolicy.go
+++ b/policycontroller/processPolicy.go
@@ -97,7 +97,7 @@ func (pc *PolicyController) filterResourceByRule(rule types.Rule) ([]runtime.Obj
 		}
 
 		// filter the resource by name and label
-		if ok, _ := mutation.IsRuleApplicableToResource(rawResource, rule.ResourceDescription); ok {
+		if ok, _ := mutation.ResourceMeetsRules(rawResource, rule.ResourceDescription); ok {
 			targetResources = append(targetResources, resource)
 		}
 	}