Repository: kyverno/kyverno
Pull request #1707 (2021-03-14 18:07:20)

Kyverno CLI gives error on applying any policy on a resource 
Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

## Related issue
closes https://github.com/kyverno/kyverno/issues/1706

## What type of PR is this
> /kind bug

## Checklist

- [X] I have read the [contributing guidelines](https://github.com/kyverno/kyverno/blob/main/CONTRIBUTING.md).
- [] I have added tests that prove my fix is effective or that my feature works.
- [] I have added or changed [the documentation](https://github.com/kyverno/website).



Patch:
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 874b6e55e..6ee986481 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -41,6 +41,9 @@ them, don't hesitate to ask. We're here to help! This is simply a reminder of wh
 - [] I have read the [contributing guidelines](https://github.com/kyverno/kyverno/blob/main/CONTRIBUTING.md).
 - [] I have added tests that prove my fix is effective or that my feature works.
 - [] I have added or changed [the documentation](https://github.com/kyverno/website).
+  - If not, I have raised an issue in [kyverno/website](https://github.com/kyverno/website) to track the doc update:
+  <!-- Uncomment to link to the issue -->
+  <!-- https://github.com/kyverno/website/issues/1 -->
 
 ## Further comments
 
diff --git a/cmd/initContainer/main.go b/cmd/initContainer/main.go
index 7e09ecff8..4f0dc8986 100644
--- a/cmd/initContainer/main.go
+++ b/cmd/initContainer/main.go
@@ -10,7 +10,6 @@ import (
 	"sync"
 	"time"
 
-	"github.com/kyverno/kyverno/pkg/config"
 	client "github.com/kyverno/kyverno/pkg/dclient"
 	"github.com/kyverno/kyverno/pkg/signal"
 	"github.com/kyverno/kyverno/pkg/utils"
@@ -28,8 +27,6 @@ var (
 )
 
 const (
-	mutatingWebhookConfigKind      string = "MutatingWebhookConfiguration"
-	validatingWebhookConfigKind    string = "ValidatingWebhookConfiguration"
 	policyReportKind               string = "PolicyReport"
 	clusterPolicyReportKind        string = "ClusterPolicyReport"
 	reportChangeRequestKind        string = "ReportChangeRequest"
@@ -72,16 +69,6 @@ func main() {
 	}
 
 	requests := []request{
-		{validatingWebhookConfigKind, config.ValidatingWebhookConfigurationName},
-		{validatingWebhookConfigKind, config.ValidatingWebhookConfigurationDebugName},
-		{mutatingWebhookConfigKind, config.MutatingWebhookConfigurationName},
-		{mutatingWebhookConfigKind, config.MutatingWebhookConfigurationDebugName},
-
-		{validatingWebhookConfigKind, config.PolicyValidatingWebhookConfigurationName},
-		{validatingWebhookConfigKind, config.PolicyValidatingWebhookConfigurationDebugName},
-		{mutatingWebhookConfigKind, config.PolicyMutatingWebhookConfigurationName},
-		{mutatingWebhookConfigKind, config.PolicyMutatingWebhookConfigurationDebugName},
-
 		{policyReportKind, ""},
 		{clusterPolicyReportKind, ""},
 
@@ -120,8 +107,6 @@ func main() {
 
 func executeRequest(client *client.Client, req request) error {
 	switch req.kind {
-	case mutatingWebhookConfigKind, validatingWebhookConfigKind:
-		return removeWebhookIfExists(client, req.kind, req.name)
 	case policyReportKind:
 		return removePolicyReport(client, req.kind)
 	case clusterPolicyReportKind:
@@ -236,29 +221,6 @@ func merge(done <-chan struct{}, stopCh <-chan struct{}, processes ...<-chan err
 	return out
 }
 
-func removeWebhookIfExists(client *client.Client, kind string, name string) error {
-	logger := log.Log.WithName("removeExistingWebhook").WithValues("kind", kind, "name", name)
-	var err error
-	// Get resource
-	_, err = client.GetResource("", kind, "", name)
-	if errors.IsNotFound(err) {
-		logger.V(4).Info("resource not found")
-		return nil
-	}
-	if err != nil {
-		logger.Error(err, "failed to get resource")
-		return err
-	}
-	// Delete resource
-	err = client.DeleteResource("", kind, "", name, false)
-	if err != nil {
-		logger.Error(err, "failed to delete resource")
-		return err
-	}
-	logger.Info("removed the resource")
-	return nil
-}
-
 func removeClusterPolicyReport(client *client.Client, kind string) error {
 	logger := log.Log.WithName("removeClusterPolicyReport")
 
diff --git a/cmd/kyverno/localDockerfile b/cmd/kyverno/localDockerfile
index 42309392d..e58ab3904 100644
--- a/cmd/kyverno/localDockerfile
+++ b/cmd/kyverno/localDockerfile
@@ -1,3 +1,7 @@
 FROM scratch
+
 ADD kyverno /kyverno
+
+USER 10001
+
 ENTRYPOINT ["/kyverno"]
\ No newline at end of file
diff --git a/cmd/kyverno/main.go b/cmd/kyverno/main.go
index 61e8548ac..01ab10270 100755
--- a/cmd/kyverno/main.go
+++ b/cmd/kyverno/main.go
@@ -24,6 +24,7 @@ import (
 	"github.com/kyverno/kyverno/pkg/policystatus"
 	"github.com/kyverno/kyverno/pkg/resourcecache"
 	"github.com/kyverno/kyverno/pkg/signal"
+	ktls "github.com/kyverno/kyverno/pkg/tls"
 	"github.com/kyverno/kyverno/pkg/utils"
 	"github.com/kyverno/kyverno/pkg/version"
 	"github.com/kyverno/kyverno/pkg/webhookconfig"
@@ -144,7 +145,7 @@ func main() {
 		log.Log)
 
 	// Resource Mutating Webhook Watcher
-	webhookMonitor := webhookconfig.NewMonitor(log.Log.WithName("WebhookMonitor"))
+	webhookMonitor := webhookconfig.NewMonitor(rCache, log.Log.WithName("WebhookMonitor"))
 
 	// KYVERNO CRD INFORMER
 	// watches CRD resources:
@@ -283,8 +284,9 @@ func main() {
 		client,
 	)
 
+	certRenewer := ktls.NewCertRenewer(client, clientConfig, ktls.CertRenewalInterval, ktls.CertValidityDuration, log.Log.WithName("CertRenewer"))
 	// Configure certificates
-	tlsPair, err := client.InitTLSPemPair(clientConfig, serverIP)
+	tlsPair, err := certRenewer.InitTLSPemPair(serverIP)
 	if err != nil {
 		setupLog.Error(err, "Failed to initialize TLS key/certificate pair")
 		os.Exit(1)
@@ -329,6 +331,7 @@ func main() {
 		pCacheController.Cache,
 		webhookCfg,
 		webhookMonitor,
+		certRenewer,
 		statusSync.Listener,
 		configData,
 		reportReqGen,
diff --git a/pkg/dclient/certificates.go b/pkg/dclient/certificates.go
deleted file mode 100644
index 784fa5c94..000000000
--- a/pkg/dclient/certificates.go
+++ /dev/null
@@ -1,243 +0,0 @@
-package client
-
-import (
-	"encoding/base64"
-	"fmt"
-	"net/url"
-
-	"github.com/kyverno/kyverno/pkg/config"
-	tls "github.com/kyverno/kyverno/pkg/tls"
-	v1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/client-go/rest"
-)
-
-// InitTLSPemPair 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 (c *Client) InitTLSPemPair(configuration *rest.Config, serverIP string) (*tls.PemPair, error) {
-	logger := c.log
-	certProps, err := c.GetTLSCertProps(configuration)
-	if err != nil {
-		return nil, err
-	}
-
-	logger.Info("Building key/certificate pair for TLS")
-	tlsPair, err := c.buildTLSPemPair(certProps, serverIP)
-	if err != nil {
-		return nil, err
-	}
-	if err = c.WriteTLSPairToSecret(certProps, tlsPair); err != nil {
-		return nil, fmt.Errorf("Unable to save TLS pair to the cluster: %v", err)
-	}
-
-	return tlsPair, nil
-}
-
-// buildTLSPemPair Issues TLS certificate for webhook server using self-signed CA cert
-// Returns signed and approved TLS certificate in PEM format
-func (c *Client) buildTLSPemPair(props tls.CertificateProps, serverIP string) (*tls.PemPair, error) {
-	caCert, caPEM, err := tls.GenerateCACert()
-	if err != nil {
-		return nil, err
-	}
-
-	if err := c.WriteCACertToSecret(caPEM, props); err != nil {
-		return nil, fmt.Errorf("failed to write CA cert to secret: %v", err)
-	}
-
-	return tls.GenerateCertPem(caCert, props, serverIP)
-}
-
-//ReadRootCASecret returns the RootCA from the pre-defined secret
-func (c *Client) ReadRootCASecret() (result []byte) {
-	logger := c.log.WithName("ReadRootCASecret")
-	certProps, err := c.GetTLSCertProps(c.clientConfig)
-	if err != nil {
-		logger.Error(err, "failed to get TLS Cert Properties")
-		return result
-	}
-	sname := generateRootCASecretName(certProps)
-	stlsca, err := c.GetResource("", Secrets, certProps.Namespace, sname)
-	if err != nil {
-		return result
-	}
-	tlsca, err := convertToSecret(stlsca)
-	if err != nil {
-		logger.Error(err, "failed to convert secret", "name", sname, "namespace", certProps.Namespace)
-		return result
-	}
-
-	result = tlsca.Data[rootCAKey]
-	if len(result) == 0 {
-		logger.Info("root CA certificate not found in secret", "name", tlsca.Name, "namespace", certProps.Namespace)
-		return result
-	}
-	logger.V(4).Info("using CA bundle defined in secret to validate the webhook's server certificate", "name", tlsca.Name, "namespace", certProps.Namespace)
-	return result
-}
-
-const selfSignedAnnotation string = "self-signed-cert"
-const rootCAKey string = "rootCA.crt"
-
-// ReadTLSPair Reads the pair of TLS certificate and key from the specified secret.
-func (c *Client) ReadTLSPair(props tls.CertificateProps) *tls.PemPair {
-	logger := c.log.WithName("ReadTLSPair")
-	sname := generateTLSPairSecretName(props)
-	unstrSecret, err := c.GetResource("", Secrets, props.Namespace, sname)
-	if err != nil {
-		logger.Error(err, "Failed to get secret", "name", sname, "namespace", props.Namespace)
-		return nil
-	}
-
-	// If secret contains annotation 'self-signed-cert', then it's created using helper scripts to setup self-signed certificates.
-	// As the root CA used to sign the certificate is required for webhook cnofiguration, check if the corresponding secret is created
-	annotations := unstrSecret.GetAnnotations()
-	if _, ok := annotations[selfSignedAnnotation]; ok {
-		sname := generateRootCASecretName(props)
-		_, err := c.GetResource("", Secrets, props.Namespace, sname)
-		if err != nil {
-			logger.Error(err, "Root CA secret is required while using self-signed certificates TLS pair, defaulting to generating new TLS pair", "name", sname, "namespace", props.Namespace)
-			return nil
-		}
-	}
-	secret, err := convertToSecret(unstrSecret)
-	if err != nil {
-		return nil
-	}
-	pemPair := tls.PemPair{
-		Certificate: secret.Data[v1.TLSCertKey],
-		PrivateKey:  secret.Data[v1.TLSPrivateKeyKey],
-	}
-	if len(pemPair.Certificate) == 0 {
-		logger.Info("TLS Certificate not found in secret", "name", sname, "namespace", props.Namespace)
-		return nil
-	}
-	if len(pemPair.PrivateKey) == 0 {
-		logger.Info("TLS PrivateKey not found in secret", "name", sname, "namespace", props.Namespace)
-		return nil
-	}
-	return &pemPair
-}
-
-// WriteCACertToSecret stores the CA cert in secret
-func (c *Client) WriteCACertToSecret(caPEM *tls.PemPair, props tls.CertificateProps) error {
-	logger := c.log.WithName("CAcert")
-	name := generateRootCASecretName(props)
-
-	secretUnstr, err := c.GetResource("", Secrets, props.Namespace, name)
-	if err != nil {
-		secret := &v1.Secret{
-			TypeMeta: metav1.TypeMeta{
-				Kind:       "Secret",
-				APIVersion: "v1",
-			},
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      name,
-				Namespace: props.Namespace,
-				Annotations: map[string]string{
-					selfSignedAnnotation: "true",
-				},
-			},
-			Data: map[string][]byte{
-				rootCAKey: caPEM.Certificate,
-			},
-			Type: v1.SecretTypeOpaque,
-		}
-
-		_, err := c.CreateResource("", Secrets, props.Namespace, secret, false)
-		if err == nil {
-			logger.Info("secret created", "name", name, "namespace", props.Namespace)
-		}
-		return err
-	}
-
-	if _, ok := secretUnstr.GetAnnotations()[selfSignedAnnotation]; !ok {
-		secretUnstr.SetAnnotations(map[string]string{selfSignedAnnotation: "true"})
-	}
-
-	dataMap := map[string]interface{}{
-		rootCAKey: base64.StdEncoding.EncodeToString(caPEM.Certificate)}
-
-	if err := unstructured.SetNestedMap(secretUnstr.Object, dataMap, "data"); err != nil {
-		return err
-	}
-
-	_, err = c.UpdateResource("", Secrets, props.Namespace, secretUnstr, false)
-	if err != nil {
-		return err
-	}
-	logger.Info("secret updated", "name", name, "namespace", props.Namespace)
-	return nil
-}
-
-// WriteTLSPairToSecret Writes the pair of TLS certificate and key to the specified secret.
-// Updates existing secret or creates new one.
-func (c *Client) WriteTLSPairToSecret(props tls.CertificateProps, pemPair *tls.PemPair) error {
-	logger := c.log.WithName("WriteTLSPair")
-	name := generateTLSPairSecretName(props)
-	secretUnstr, err := c.GetResource("", Secrets, props.Namespace, name)
-	if err != nil {
-		secret := &v1.Secret{
-			TypeMeta: metav1.TypeMeta{
-				Kind:       "Secret",
-				APIVersion: "v1",
-			},
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      name,
-				Namespace: props.Namespace,
-			},
-			Data: map[string][]byte{
-				v1.TLSCertKey:       pemPair.Certificate,
-				v1.TLSPrivateKeyKey: pemPair.PrivateKey,
-			},
-			Type: v1.SecretTypeTLS,
-		}
-
-		_, err := c.CreateResource("", Secrets, props.Namespace, secret, false)
-		if err == nil {
-			logger.Info("secret created", "name", name, "namespace", props.Namespace)
-		}
-		return err
-	}
-
-	dataMap := map[string]interface{}{
-		v1.TLSCertKey:       base64.StdEncoding.EncodeToString(pemPair.Certificate),
-		v1.TLSPrivateKeyKey: base64.StdEncoding.EncodeToString(pemPair.PrivateKey),
-	}
-
-	if err := unstructured.SetNestedMap(secretUnstr.Object, dataMap, "data"); err != nil {
-		return err
-	}
-
-	_, err = c.UpdateResource("", Secrets, props.Namespace, secretUnstr, false)
-	if err != nil {
-		return err
-	}
-
-	logger.Info("secret updated", "name", name, "namespace", props.Namespace)
-	return nil
-}
-
-func generateTLSPairSecretName(props tls.CertificateProps) string {
-	return tls.GenerateInClusterServiceName(props) + ".kyverno-tls-pair"
-}
-
-func generateRootCASecretName(props tls.CertificateProps) string {
-	return tls.GenerateInClusterServiceName(props) + ".kyverno-tls-ca"
-}
-
-//GetTLSCertProps provides the TLS Certificate Properties
-func (c *Client) GetTLSCertProps(configuration *rest.Config) (certProps tls.CertificateProps, err error) {
-	apiServerURL, err := url.Parse(configuration.Host)
-	if err != nil {
-		return certProps, err
-	}
-	certProps = tls.CertificateProps{
-		Service:       config.KyvernoServiceName,
-		Namespace:     config.KyvernoNamespace,
-		APIServerHost: apiServerURL.Hostname(),
-	}
-	return certProps, nil
-}
diff --git a/pkg/dclient/client.go b/pkg/dclient/client.go
index 3fa1cf66b..cbd31c42e 100644
--- a/pkg/dclient/client.go
+++ b/pkg/dclient/client.go
@@ -9,7 +9,6 @@ import (
 	"github.com/go-logr/logr"
 	openapiv2 "github.com/googleapis/gnostic/openapiv2"
 	certificates "k8s.io/api/certificates/v1beta1"
-	v1 "k8s.io/api/core/v1"
 	helperv1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	meta "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -198,15 +197,6 @@ func convertToUnstructured(obj interface{}) *unstructured.Unstructured {
 	return &unstructured.Unstructured{Object: unstructuredObj}
 }
 
-//To-Do remove this to use unstructured type
-func convertToSecret(obj *unstructured.Unstructured) (v1.Secret, error) {
-	secret := v1.Secret{}
-	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), &secret); err != nil {
-		return secret, err
-	}
-	return secret, nil
-}
-
 //To-Do remove this to use unstructured type
 func convertToCSR(obj *unstructured.Unstructured) (*certificates.CertificateSigningRequest, error) {
 	csr := certificates.CertificateSigningRequest{}
diff --git a/pkg/dclient/utils.go b/pkg/dclient/utils.go
index d4cf657eb..6b6dc19b0 100644
--- a/pkg/dclient/utils.go
+++ b/pkg/dclient/utils.go
@@ -15,17 +15,6 @@ import (
 	kubernetesfake "k8s.io/client-go/kubernetes/fake"
 )
 
-const (
-	// CSRs CertificateSigningRequest
-	CSRs string = "CertificateSigningRequest"
-	// Secrets Secret
-	Secrets string = "Secret"
-	// ConfigMaps ConfigMap
-	ConfigMaps string = "ConfigMap"
-	// Namespaces Namespace
-	Namespaces string = "Namespace"
-)
-
 //NewMockClient ---testing utilities
 func NewMockClient(scheme *runtime.Scheme, gvrToListKind map[schema.GroupVersionResource]string, objects ...runtime.Object) (*Client, error) {
 	client := fake.NewSimpleDynamicClientWithCustomListKinds(scheme, gvrToListKind, objects...)
diff --git a/pkg/engine/apiPath.go b/pkg/engine/apiPath.go
index 3d64e66d9..691891138 100644
--- a/pkg/engine/apiPath.go
+++ b/pkg/engine/apiPath.go
@@ -81,7 +81,7 @@ func NewAPIPath(path string) (*APIPath, error) {
 		return &APIPath{
 			Root:         paths[0],
 			Group:        paths[1],
-			Version:      paths[2],
+			Version:      paths[1] + "/" + paths[2],
 			ResourceType: paths[3],
 		}, nil
 	}
@@ -91,7 +91,7 @@ func NewAPIPath(path string) (*APIPath, error) {
 		return &APIPath{
 			Root:         paths[0],
 			Group:        paths[1],
-			Version:      paths[2],
+			Version:      paths[1] + "/" + paths[2],
 			ResourceType: paths[3],
 			Name:         paths[4],
 		}, nil
@@ -102,7 +102,7 @@ func NewAPIPath(path string) (*APIPath, error) {
 		return &APIPath{
 			Root:         paths[0],
 			Group:        paths[1],
-			Version:      paths[2],
+			Version:      paths[1] + "/" + paths[2],
 			Namespace:    paths[4],
 			ResourceType: paths[5],
 		}, nil
@@ -113,7 +113,7 @@ func NewAPIPath(path string) (*APIPath, error) {
 		return &APIPath{
 			Root:         paths[0],
 			Group:        paths[1],
-			Version:      paths[2],
+			Version:      paths[1] + "/" + paths[2],
 			Namespace:    paths[4],
 			ResourceType: paths[5],
 			Name:         paths[6],
@@ -125,17 +125,33 @@ func NewAPIPath(path string) (*APIPath, error) {
 
 func (a *APIPath) String() string {
 	var paths []string
-	if a.Namespace != "" {
-		if a.Name == "" {
-			paths = []string{a.Root, a.Group, a.Version, "namespaces", a.Namespace, a.ResourceType}
+	if a.Root == "apis" {
+		if a.Namespace != "" {
+			if a.Name == "" {
+				paths = []string{a.Root, a.Version, "namespaces", a.Namespace, a.ResourceType}
+			} else {
+				paths = []string{a.Root, a.Version, "namespaces", a.Namespace, a.ResourceType, a.Name}
+			}
 		} else {
-			paths = []string{a.Root, a.Group, a.Version, "namespaces", a.Namespace, a.ResourceType, a.Name}
+			if a.Name != "" {
+				paths = []string{a.Root, a.Version, a.ResourceType, a.Name}
+			} else {
+				paths = []string{a.Root, a.Version, a.ResourceType}
+			}
 		}
 	} else {
-		if a.Name != "" {
-			paths = []string{a.Root, a.Group, a.Version, a.ResourceType, a.Name}
+		if a.Namespace != "" {
+			if a.Name == "" {
+				paths = []string{a.Root, a.Group, "namespaces", a.Namespace, a.ResourceType}
+			} else {
+				paths = []string{a.Root, a.Group, "namespaces", a.Namespace, a.ResourceType, a.Name}
+			}
 		} else {
-			paths = []string{a.Root, a.Group, a.Version, a.ResourceType}
+			if a.Name != "" {
+				paths = []string{a.Root, a.Group, a.ResourceType, a.Name}
+			} else {
+				paths = []string{a.Root, a.Group, a.ResourceType}
+			}
 		}
 	}
 
diff --git a/pkg/engine/mutate/mutation.go b/pkg/engine/mutate/mutation.go
index abeb26a00..4b32c5b89 100644
--- a/pkg/engine/mutate/mutation.go
+++ b/pkg/engine/mutate/mutation.go
@@ -64,6 +64,7 @@ func (h patchStrategicMergeHandler) Handle() (response.RuleResponse, unstructure
 	var err error
 	if PatchStrategicMerge, err = variables.SubstituteVars(log, h.evalCtx, PatchStrategicMerge); err != nil {
 		// variable subsitution failed
+		ruleResponse.Name = h.ruleName
 		ruleResponse.Success = false
 		ruleResponse.Message = err.Error()
 		return ruleResponse, h.patchedResource
@@ -121,16 +122,6 @@ func (h patchesJSON6902Handler) Handle() (resp response.RuleResponse, patchedRes
 		return resp, h.patchedResource
 	}
 
-	skip, err := preProcessJSONPatches(patchesJSON6902, h.patchedResource, h.logger)
-	if err != nil {
-		h.logger.Error(err, "failed to preProcessJSONPatches")
-	}
-
-	if skip {
-		resp.Success = true
-		return resp, h.patchedResource
-	}
-
 	return ProcessPatchJSON6902(h.ruleName, patchesJSON6902, h.patchedResource, h.logger)
 }
 
@@ -173,25 +164,6 @@ func (h patchesHandler) Handle() (resp response.RuleResponse, patchedResource un
 	resp.Name = h.ruleName
 	resp.Type = utils.Mutation.String()
 
-	// patches is already converted to patchesJSON6902
-	patchesJSON6902, err := convertPatchesToJSON(h.mutation.PatchesJSON6902)
-	if err != nil {
-		resp.Success = false
-		h.logger.Error(err, "error in type conversion")
-		resp.Message = err.Error()
-		return resp, h.patchedResource
-	}
-
-	skip, err := preProcessJSONPatches(patchesJSON6902, h.patchedResource, h.logger)
-	if err != nil {
-		h.logger.Error(err, "failed to preProcessJSONPatches")
-	}
-
-	if skip {
-		resp.Success = true
-		return resp, h.patchedResource
-	}
-
 	return ProcessPatches(h.logger, h.ruleName, *h.mutation, h.patchedResource)
 }
 
diff --git a/pkg/engine/mutate/patchesUtils.go b/pkg/engine/mutate/patchesUtils.go
index 4568eb2fd..f5deacc93 100644
--- a/pkg/engine/mutate/patchesUtils.go
+++ b/pkg/engine/mutate/patchesUtils.go
@@ -1,18 +1,12 @@
 package mutate
 
 import (
-	"fmt"
 	"path/filepath"
-	"reflect"
 	"regexp"
-	"strconv"
 	"strings"
 
-	evanjsonpatch "github.com/evanphx/json-patch/v5"
-	"github.com/go-logr/logr"
 	"github.com/mattbaird/jsonpatch"
 	"github.com/minio/minio/pkg/wildcard"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 )
 
 func generatePatches(src, dst []byte) ([][]byte, error) {
@@ -152,135 +146,3 @@ func ignorePatch(path string) bool {
 
 	return false
 }
-
-// preProcessJSONPatches deals with the JsonPatch when reinvocation
-// policy is set in webhook, to avoid generating duplicate values.
-// This duplicate error only occurs on type array, if it's adding to a map
-// the value will be added to the map if nil, otherwise it overwrites the old value
-// return skip == true to skip the json patch application
-func preProcessJSONPatches(patchesJSON6902 []byte, resource unstructured.Unstructured,
-	log logr.Logger) (skip bool, err error) {
-	var patches evanjsonpatch.Patch
-	log = log.WithName("preProcessJSONPatches")
-
-	patches, err = evanjsonpatch.DecodePatch(patchesJSON6902)
-	if err != nil {
-		return false, fmt.Errorf("cannot decode patches as an RFC 6902 patch: %v", err)
-	}
-
-	for _, patch := range patches {
-		if patch.Kind() != "add" {
-			continue
-		}
-
-		path, err := patch.Path()
-		if err != nil {
-			return false, fmt.Errorf("failed to get path in JSON Patch: %v", err)
-		}
-
-		// check if the target is the list
-		if tail := filepath.Base(path); tail != "-" {
-			_, err := strconv.Atoi(tail)
-			if err != nil {
-				log.V(4).Info("JSON patch does not add to the list, skipping", "path", path)
-				continue
-			}
-		}
-
-		resourceObj, err := getObject(path, resource.UnstructuredContent())
-		if err != nil {
-			log.V(4).Info("unable to get object by the given path, proceed patchesJson6902 without preprocessing", "path", path, "error", err.Error())
-			continue
-		}
-
-		val, err := patch.ValueInterface()
-		if err != nil {
-			log.V(4).Info("unable to get value by the given path, proceed patchesJson6902 without preprocessing", "path", path, "error", err.Error())
-			continue
-		}
-
-		// if there's one patch exist in the resource, which indicates
-		// this is re-invoked JSON patches, skip application
-		if isSubsetObject(val, resourceObj) {
-			return true, nil
-		}
-	}
-
-	return false, nil
-}
-
-// - insert to the end of the list
-// {"op": "add", "path": "/spec/containers/-", {"value": "{"name":"busyboxx","image":"busybox:latest"}"}
-
-// - insert value to the certain element of the list
-// {"op": "add", "path": "/spec/containers/1", {"value": "{"name":"busyboxx","image":"busybox:latest"}"}
-func getObject(path string, resource map[string]interface{}) (interface{}, error) {
-	var strippedResource interface{}
-	strippedResource = resource
-	var ok bool
-
-	if strings.HasPrefix(path, "/") {
-		path = path[1:]
-	}
-	paths := strings.Split(path, "/")
-
-	for i, key := range paths {
-		switch strippedResource.(type) {
-		case map[string]interface{}:
-			strippedResource, ok = strippedResource.(map[string]interface{})[key]
-			if !ok {
-				return nil, fmt.Errorf("referenced value does not exist at %s", strings.Join(paths[:i+1], "/"))
-			}
-
-		case []interface{}:
-			var idx int
-
-			if key == "-" {
-				idx = len(strippedResource.([]interface{})) - 1
-			} else {
-				var err error
-				idx, err = strconv.Atoi(key)
-				if err != nil {
-					return nil, fmt.Errorf("cannot parse index in JSON Patch at %s: %v", strings.Join(paths[:i+1], "/"), err)
-				}
-				if idx < 0 {
-					idx = len(strippedResource.([]interface{})) - 1
-				}
-			}
-
-			if len(strippedResource.([]interface{})) <= idx {
-				return nil, nil
-			}
-
-			strippedResource = strippedResource.([]interface{})[idx]
-		}
-	}
-	return strippedResource, nil
-}
-
-// isSubsetObject returns true if object is subset of resource
-// the object/resource is the element inside the list, return false
-// if the type is mismatched (not map)
-func isSubsetObject(object, resource interface{}) bool {
-	objectMap, ok := object.(map[string]interface{})
-	if !ok {
-		return false
-	}
-
-	resourceMap, ok := resource.(map[string]interface{})
-	if !ok {
-		return false
-	}
-
-	for objKey, objVal := range objectMap {
-		rsrcVal, ok := resourceMap[objKey]
-		if !ok {
-			return false
-		}
-
-		if !reflect.DeepEqual(objVal, rsrcVal) {
-			return false
-		}
-	}
-	return true
-}
diff --git a/pkg/engine/variables/operator/in.go b/pkg/engine/variables/operator/in.go
index 055abd36d..bd510e101 100644
--- a/pkg/engine/variables/operator/in.go
+++ b/pkg/engine/variables/operator/in.go
@@ -110,19 +110,20 @@ func keyExistsInArray(key string, value interface{}, log logr.Logger) (invalidTy
 }
 
 func (in InHandler) validateValueWithStringSetPattern(key []string, value interface{}) (keyExists bool) {
-	invalidType, keyExists := setExistsInArray(key, value, in.log)
+	invalidType, isIn := setExistsInArray(key, value, in.log, false)
 	if invalidType {
 		in.log.Info("expected type []string", "value", value, "type", fmt.Sprintf("%T", value))
 		return false
 	}
 
-	return keyExists
+	return isIn
 }
 
 // setExistsInArray checks if the key is a subset of value
 // The value can be a string, an array of strings, or a JSON format
 // array of strings (e.g. ["val1", "val2", "val3"].
-func setExistsInArray(key []string, value interface{}, log logr.Logger) (invalidType bool, keyExists bool) {
+// notIn argument if set to true will check for NotIn
+func setExistsInArray(key []string, value interface{}, log logr.Logger, notIn bool) (invalidType bool, keyExists bool) {
 	switch valuesAvailable := value.(type) {
 
 	case []interface{}:
@@ -134,8 +135,10 @@ func setExistsInArray(key []string, value interface{}, log logr.Logger) (invalid
 			}
 			valueSlice = append(valueSlice, v)
 		}
-
-		return false, isSubset(key, valueSlice)
+		if notIn {
+			return false, isNotIn(key, valueSlice)
+		}
+		return false, isIn(key, valueSlice)
 
 	case string:
 
@@ -148,30 +151,47 @@ func setExistsInArray(key []string, value interface{}, log logr.Logger) (invalid
 			log.Error(err, "failed to unmarshal value to JSON string array", "key", key, "value", value)
 			return true, false
 		}
+		if notIn {
+			return false, isNotIn(key, arr)
+		}
 
-		return false, isSubset(key, arr)
+		return false, isIn(key, arr)
 
 	default:
 		return true, false
 	}
 }
 
-// isSubset checks if S1 is a subset of S2 i.e. ALL values of S1 are in S2
-func isSubset(key []string, value []string) bool {
-	set := make(map[string]int)
+// isIn checks if all values in S1 are in S2
+func isIn(key []string, value []string) bool {
+	set := make(map[string]bool)
 
 	for _, val := range value {
-		set[val]++
+		set[val] = true
 	}
 
 	for _, val := range key {
-		count, found := set[val]
+		_, found := set[val]
 		if !found {
 			return false
-		} else if count < 1 {
+		}
+	}
+
+	return true
+}
+
+// isNotIn checks if none of the values in S1 is in S2
+func isNotIn(key []string, value []string) bool {
+	set := make(map[string]bool)
+
+	for _, val := range value {
+		set[val] = true
+	}
+
+	for _, val := range key {
+		_, found := set[val]
+		if found {
 			return false
-		} else {
-			set[val] = count - 1
 		}
 	}
 
diff --git a/pkg/engine/variables/operator/notin.go b/pkg/engine/variables/operator/notin.go
index e75b89e86..d604c4b0d 100644
--- a/pkg/engine/variables/operator/notin.go
+++ b/pkg/engine/variables/operator/notin.go
@@ -64,13 +64,13 @@ func (nin NotInHandler) validateValueWithStringPattern(key string, value interfa
 }
 
 func (nin NotInHandler) validateValueWithStringSetPattern(key []string, value interface{}) bool {
-	invalidType, keyExists := setExistsInArray(key, value, nin.log)
+	invalidType, isNotIn := setExistsInArray(key, value, nin.log, true)
 	if invalidType {
 		nin.log.Info("expected type []string", "value", value, "type", fmt.Sprintf("%T", value))
 		return false
 	}
 
-	return !keyExists
+	return isNotIn
 }
 
 func (nin NotInHandler) validateValueWithBoolPattern(_ bool, _ interface{}) bool {
diff --git a/pkg/kyverno/apply/command.go b/pkg/kyverno/apply/command.go
index 749e2b77b..772423bb2 100644
--- a/pkg/kyverno/apply/command.go
+++ b/pkg/kyverno/apply/command.go
@@ -207,7 +207,7 @@ func applyCommandHelper(resourcePaths []string, cluster bool, policyReport bool,
 
 	// empty the previous contents of the file just in case if the file already existed before with some content(so as to perform overwrites)
 	// the truncation of files for the case when mutateLogPath is dir, is handled under pkg/kyverno/apply/common.go
-	if !mutateLogPathIsDir {
+	if !mutateLogPathIsDir && mutateLogPath != "" {
 		_, err := os.OpenFile(mutateLogPath, os.O_TRUNC|os.O_WRONLY, 0644)
 		if err != nil {
 			if !sanitizederror.IsErrorSanitized(err) {
diff --git a/pkg/resourcecache/gvrcache.go b/pkg/resourcecache/gvrcache.go
index 8d8d76d0c..befc315a8 100644
--- a/pkg/resourcecache/gvrcache.go
+++ b/pkg/resourcecache/gvrcache.go
@@ -4,6 +4,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/client-go/dynamic/dynamiclister"
 	"k8s.io/client-go/informers"
+	"k8s.io/client-go/tools/cache"
 )
 
 // GenericCache - allows operation on a single resource
@@ -13,6 +14,7 @@ type GenericCache interface {
 	Lister() dynamiclister.Lister
 	NamespacedLister(namespace string) dynamiclister.NamespaceLister
 	GVR() schema.GroupVersionResource
+	GetInformer() cache.SharedIndexInformer
 }
 
 type genericCache struct {
@@ -56,3 +58,8 @@ func (gc *genericCache) Lister() dynamiclister.Lister {
 func (gc *genericCache) NamespacedLister(namespace string) dynamiclister.NamespaceLister {
 	return dynamiclister.New(gc.genericInformer.Informer().GetIndexer(), gc.GVR()).Namespace(namespace)
 }
+
+// GetInformer gets SharedIndexInformer
+func (gc *genericCache) GetInformer() cache.SharedIndexInformer {
+	return gc.genericInformer.Informer()
+}
diff --git a/pkg/resourcecache/main.go b/pkg/resourcecache/main.go
index b902c2f94..f8cc3c5f1 100644
--- a/pkg/resourcecache/main.go
+++ b/pkg/resourcecache/main.go
@@ -10,11 +10,15 @@ import (
 )
 
 // ResourceCache - allows the creation, deletion and saving the resource informers as a cache
+// the resource cache can be registered by gvks as follows:
+// - group/version/kind
+// - group/kind
+// - kind
 type ResourceCache interface {
-	CreateInformers(resources ...string) []error
-	CreateGVKInformer(kind string) (GenericCache, error)
-	StopResourceInformer(resource string)
-	GetGVRCache(resource string) (GenericCache, bool)
+	CreateInformers(gvks ...string) []error
+	CreateGVKInformer(gvk string) (GenericCache, error)
+	StopResourceInformer(gvk string)
+	GetGVRCache(gvk string) (GenericCache, bool)
 }
 
 type resourceCache struct {
diff --git a/pkg/tls/certRenewer.go b/pkg/tls/certRenewer.go
new file mode 100644
index 000000000..f4cb20581
--- /dev/null
+++ b/pkg/tls/certRenewer.go
@@ -0,0 +1,348 @@
+package tls
+
+import (
+	"crypto/tls"
+	"crypto/x509"
+	"encoding/base64"
+	"encoding/pem"
+	"fmt"
+	"time"
+
+	"github.com/cenkalti/backoff"
+	"github.com/go-logr/logr"
+	"github.com/kyverno/kyverno/pkg/config"
+	client "github.com/kyverno/kyverno/pkg/dclient"
+	"github.com/pkg/errors"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/client-go/rest"
+)
+
+const (
+	// ManagedByLabel is added to Kyverno managed secrets
+	ManagedByLabel string = "cert.kyverno.io/managed-by"
+
+	SelfSignedAnnotation    string = "self-signed-cert"
+	RootCAKey               string = "rootCA.crt"
+	rollingUpdateAnnotation string = "update.kyverno.io/force-rolling-update"
+)
+
+// CertRenewer creates rootCA and pem pair to register
+// webhook configurations and webhook server
+// renews RootCA at the given interval
+type CertRenewer struct {
+	client               *client.Client
+	clientConfig         *rest.Config
+	certRenewalInterval  time.Duration
+	certValidityDuration time.Duration
+	log                  logr.Logger
+}
+
+// NewCertRenewer returns an instance of CertRenewer
+func NewCertRenewer(client *client.Client, clientConfig *rest.Config, certRenewalInterval, certValidityDuration time.Duration, log logr.Logger) *CertRenewer {
+	return &CertRenewer{
+		client:               client,
+		clientConfig:         clientConfig,
+		certRenewalInterval:  certRenewalInterval,
+		certValidityDuration: certValidityDuration,
+		log:                  log,
+	}
+}
+
+// InitTLSPemPair 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 (c *CertRenewer) InitTLSPemPair(serverIP string) (*PemPair, error) {
+	logger := c.log.WithName("InitTLSPemPair")
+	certProps, err := GetTLSCertProps(c.clientConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	if valid, err := c.ValidCert(); err == nil && valid {
+		if tlsPair, err := ReadTLSPair(c.clientConfig, c.client); err == nil {
+			logger.Info("using existing TLS key/certificate pair")
+			return tlsPair, nil
+		}
+	} else {
+		logger.V(3).Info("unable to find TLS pair", "reason", err.Error())
+	}
+
+	logger.Info("building key/certificate pair for TLS")
+	return c.buildTLSPemPairAndWriteToSecrets(certProps, serverIP)
+}
+
+// buildTLSPemPairAndWriteToSecrets Issues TLS certificate for webhook server using self-signed CA cert
+// Returns signed and approved TLS certificate in PEM format
+func (c *CertRenewer) buildTLSPemPairAndWriteToSecrets(props CertificateProps, serverIP string) (*PemPair, error) {
+	caCert, caPEM, err := GenerateCACert(c.certValidityDuration)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := c.WriteCACertToSecret(caPEM, props); err != nil {
+		return nil, fmt.Errorf("failed to write CA cert to secret: %v", err)
+	}
+
+	tlsPair, err := GenerateCertPem(caCert, props, serverIP, c.certValidityDuration)
+	if err != nil {
+		return nil, err
+	}
+
+	if err = c.WriteTLSPairToSecret(props, tlsPair); err != nil {
+		return nil, fmt.Errorf("unable to save TLS pair to the cluster: %v", err)
+	}
+
+	return tlsPair, nil
+}
+
+// ReadTLSPair Reads the pair of TLS certificate and key from the specified secret.
+
+// WriteCACertToSecret stores the CA cert in secret
+func (c *CertRenewer) WriteCACertToSecret(caPEM *PemPair, props CertificateProps) error {
+	logger := c.log.WithName("CAcert")
+	name := generateRootCASecretName(props)
+
+	secretUnstr, err := c.client.GetResource("", "Secret", props.Namespace, name)
+	if err != nil {
+		secret := &v1.Secret{
+			TypeMeta: metav1.TypeMeta{
+				Kind:       "Secret",
+				APIVersion: "v1",
+			},
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      name,
+				Namespace: props.Namespace,
+				Annotations: map[string]string{
+					SelfSignedAnnotation: "true",
+				},
+				Labels: map[string]string{
+					ManagedByLabel: "kyverno",
+				},
+			},
+			Data: map[string][]byte{
+				RootCAKey: caPEM.Certificate,
+			},
+			Type: v1.SecretTypeOpaque,
+		}
+
+		_, err := c.client.CreateResource("", "Secret", props.Namespace, secret, false)
+		if err == nil {
+			logger.Info("secret created", "name", name, "namespace", props.Namespace)
+		}
+		return err
+	}
+
+	if _, ok := secretUnstr.GetAnnotations()[SelfSignedAnnotation]; !ok {
+		secretUnstr.SetAnnotations(map[string]string{SelfSignedAnnotation: "true"})
+	}
+
+	dataMap := map[string]interface{}{
+		RootCAKey: base64.StdEncoding.EncodeToString(caPEM.Certificate)}
+
+	if err := unstructured.SetNestedMap(secretUnstr.Object, dataMap, "data"); err != nil {
+		return err
+	}
+
+	_, err = c.client.UpdateResource("", "Secret", props.Namespace, secretUnstr, false)
+	if err != nil {
+		return err
+	}
+	logger.Info("secret updated", "name", name, "namespace", props.Namespace)
+	return nil
+}
+
+// WriteTLSPairToSecret Writes the pair of TLS certificate and key to the specified secret.
+// Updates existing secret or creates new one.
+func (c *CertRenewer) WriteTLSPairToSecret(props CertificateProps, pemPair *PemPair) error {
+	logger := c.log.WithName("WriteTLSPair")
+
+	name := generateTLSPairSecretName(props)
+	secretUnstr, err := c.client.GetResource("", "Secret", props.Namespace, name)
+	if err != nil {
+		secret := &v1.Secret{
+			TypeMeta: metav1.TypeMeta{
+				Kind:       "Secret",
+				APIVersion: "v1",
+			},
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      name,
+				Namespace: props.Namespace,
+				Labels: map[string]string{
+					ManagedByLabel: "kyverno",
+				},
+			},
+			Data: map[string][]byte{
+				v1.TLSCertKey:       pemPair.Certificate,
+				v1.TLSPrivateKeyKey: pemPair.PrivateKey,
+			},
+			Type: v1.SecretTypeTLS,
+		}
+
+		_, err := c.client.CreateResource("", "Secret", props.Namespace, secret, false)
+		if err == nil {
+			logger.Info("secret created", "name", name, "namespace", props.Namespace)
+		}
+		return err
+	}
+
+	dataMap := map[string][]byte{
+		v1.TLSCertKey:       pemPair.Certificate,
+		v1.TLSPrivateKeyKey: pemPair.PrivateKey,
+	}
+
+	secret, err := convertToSecret(secretUnstr)
+	if err != nil {
+		return err
+	}
+
+	secret.Data = dataMap
+	_, err = c.client.UpdateResource("", "Secret", props.Namespace, secret, false)
+	if err != nil {
+		return err
+	}
+
+	logger.Info("secret updated", "name", name, "namespace", props.Namespace)
+	return nil
+}
+
+// RollingUpdate triggers a rolling update of Kyverno pod.
+// It is used when the rootCA is renewed, the restart of
+// Kyverno pod will register webhook server with new cert
+func (c *CertRenewer) RollingUpdate() error {
+
+	update := func() error {
+		deploy, err := c.client.GetResource("", "Deployment", config.KyvernoNamespace, config.KyvernoDeploymentName)
+		if err != nil {
+			return errors.Wrap(err, "failed to find Kyverno")
+		}
+
+		if IsKyvernoIsInRollingUpdate(deploy.UnstructuredContent(), c.log) {
+			return nil
+		}
+
+		annotations, ok, err := unstructured.NestedStringMap(deploy.UnstructuredContent(), "spec", "template", "metadata", "annotations")
+		if err != nil {
+			return errors.Wrap(err, "bad annotations")
+		}
+
+		if !ok {
+			annotations = map[string]string{}
+		}
+
+		annotations[rollingUpdateAnnotation] = time.Now().String()
+		if err = unstructured.SetNestedStringMap(deploy.UnstructuredContent(),
+			annotations,
+			"spec", "template", "metadata", "annotations",
+		); err != nil {
+			return errors.Wrapf(err, "set annotation %s", rollingUpdateAnnotation)
+		}
+
+		if _, err = c.client.UpdateResource("", "Deployment", config.KyvernoNamespace, deploy, false); err != nil {
+			return errors.Wrap(err, "update Kyverno deployment")
+		}
+		return nil
+	}
+
+	exbackoff := &backoff.ExponentialBackOff{
+		InitialInterval:     500 * time.Millisecond,
+		RandomizationFactor: 0.5,
+		Multiplier:          1.5,
+		MaxInterval:         time.Second,
+		MaxElapsedTime:      3 * time.Second,
+		Clock:               backoff.SystemClock,
+	}
+
+	exbackoff.Reset()
+	return backoff.Retry(update, exbackoff)
+}
+
+// ValidCert validates the CA Cert
+func (c *CertRenewer) ValidCert() (bool, error) {
+	logger := c.log.WithName("ValidCert")
+
+	rootCA, err := ReadRootCASecret(c.clientConfig, c.client)
+	if err != nil {
+		return false, errors.Wrap(err, "unable to read CA from secret")
+	}
+
+	tlsPair, err := ReadTLSPair(c.clientConfig, c.client)
+	if err != nil {
+		// wait till next reconcile
+		logger.Info("unable to read TLS PEM Pair from secret", "reason", err.Error())
+		return false, errors.Wrap(err, "unable to read TLS PEM Pair from secret")
+	}
+
+	// build cert pool
+	pool := x509.NewCertPool()
+	caPem, _ := pem.Decode(rootCA)
+	if caPem == nil {
+		logger.Error(err, "bad certificate")
+		return false, nil
+	}
+
+	cac, err := x509.ParseCertificate(caPem.Bytes)
+	if err != nil {
+		logger.Error(err, "failed to parse CA cert")
+		return false, nil
+	}
+	pool.AddCert(cac)
+
+	// valid PEM pair
+	_, err = tls.X509KeyPair(tlsPair.Certificate, tlsPair.PrivateKey)
+	if err != nil {
+		logger.Error(err, "invalid PEM pair")
+		return false, nil
+	}
+
+	certPem, _ := pem.Decode(tlsPair.Certificate)
+	if certPem == nil {
+		logger.Error(err, "bad private key")
+		return false, nil
+	}
+
+	cert, err := x509.ParseCertificate(certPem.Bytes)
+	if err != nil {
+		logger.Error(err, "failed to parse cert")
+		return false, nil
+	}
+
+	if _, err = cert.Verify(x509.VerifyOptions{
+		Roots:       pool,
+		CurrentTime: time.Now().Add(c.certRenewalInterval),
+	}); err != nil {
+		logger.Error(err, "invalid cert")
+		return false, nil
+	}
+
+	return true, nil
+}
+
+// IsKyvernoIsInRollingUpdate returns true if Kyverno is in rolling update
+func IsKyvernoIsInRollingUpdate(deploy map[string]interface{}, logger logr.Logger) bool {
+	replicas, _, err := unstructured.NestedInt64(deploy, "spec", "replicas")
+	if err != nil {
+		logger.Error(err, "unable to fetch spec.replicas")
+	}
+
+	nonTerminatedReplicas, _, err := unstructured.NestedInt64(deploy, "status", "replicas")
+	if err != nil {
+		logger.Error(err, "unable to fetch status.replicas")
+	}
+
+	if nonTerminatedReplicas > replicas {
+		logger.Info("detect Kyverno is in rolling update, won't trigger the update again")
+		return true
+	}
+
+	return false
+}
+
+func generateTLSPairSecretName(props CertificateProps) string {
+	return generateInClusterServiceName(props) + ".kyverno-tls-pair"
+}
+
+func generateRootCASecretName(props CertificateProps) string {
+	return generateInClusterServiceName(props) + ".kyverno-tls-ca"
+}
diff --git a/pkg/tls/reader.go b/pkg/tls/reader.go
new file mode 100644
index 000000000..4ddebc9a2
--- /dev/null
+++ b/pkg/tls/reader.go
@@ -0,0 +1,106 @@
+package tls
+
+import (
+	"fmt"
+	"net/url"
+
+	"github.com/kyverno/kyverno/pkg/config"
+	client "github.com/kyverno/kyverno/pkg/dclient"
+	"github.com/pkg/errors"
+	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/client-go/rest"
+)
+
+// ReadRootCASecret returns the RootCA from the pre-defined secret
+func ReadRootCASecret(restConfig *rest.Config, client *client.Client) (result []byte, err error) {
+	certProps, err := GetTLSCertProps(restConfig)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to get TLS Cert Properties")
+	}
+
+	sname := generateRootCASecretName(certProps)
+	stlsca, err := client.GetResource("", "Secret", certProps.Namespace, sname)
+	if err != nil {
+		return nil, err
+	}
+
+	tlsca, err := convertToSecret(stlsca)
+	if err != nil {
+		return nil, errors.Wrapf(err, "failed to convert secret %s/%s", certProps.Namespace, sname)
+	}
+
+	result = tlsca.Data[RootCAKey]
+	if len(result) == 0 {
+		return nil, errors.Errorf("root CA certificate not found in secret %s/%s", certProps.Namespace, tlsca.Name)
+	}
+
+	return result, nil
+}
+
+// ReadTLSPair returns the pem pair from the pre-defined secret
+func ReadTLSPair(restConfig *rest.Config, client *client.Client) (*PemPair, error) {
+	certProps, err := GetTLSCertProps(restConfig)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to get TLS Cert Properties")
+	}
+
+	sname := generateTLSPairSecretName(certProps)
+	unstrSecret, err := client.GetResource("", "Secret", certProps.Namespace, sname)
+	if err != nil {
+		return nil, fmt.Errorf("failed to get secret %s/%s: %v", certProps.Namespace, sname, err)
+	}
+
+	// If secret contains annotation 'self-signed-cert', then it's created using helper scripts to setup self-signed certificates.
+	// As the root CA used to sign the certificate is required for webhook configuration, check if the corresponding secret is created
+	annotations := unstrSecret.GetAnnotations()
+	if _, ok := annotations[SelfSignedAnnotation]; ok {
+		sname := generateRootCASecretName(certProps)
+		_, err := client.GetResource("", "Secret", certProps.Namespace, sname)
+		if err != nil {
+			return nil, fmt.Errorf("rootCA secret is required while using self-signed certificate TLS pair, defaulting to generating new TLS pair  %s/%s", certProps.Namespace, sname)
+		}
+	}
+	secret, err := convertToSecret(unstrSecret)
+	if err != nil {
+		return nil, err
+	}
+
+	pemPair := PemPair{
+		Certificate: secret.Data[v1.TLSCertKey],
+		PrivateKey:  secret.Data[v1.TLSPrivateKeyKey],
+	}
+
+	if len(pemPair.Certificate) == 0 {
+		return nil, fmt.Errorf("TLS Certificate not found in secret %s/%s", certProps.Namespace, sname)
+	}
+	if len(pemPair.PrivateKey) == 0 {
+		return nil, fmt.Errorf("TLS PrivateKey not found in secret %s/%s", certProps.Namespace, sname)
+	}
+
+	return &pemPair, nil
+}
+
+//GetTLSCertProps provides the TLS Certificate Properties
+func GetTLSCertProps(configuration *rest.Config) (certProps CertificateProps, err error) {
+	apiServerURL, err := url.Parse(configuration.Host)
+	if err != nil {
+		return certProps, err
+	}
+
+	certProps = CertificateProps{
+		Service:       config.KyvernoServiceName,
+		Namespace:     config.KyvernoNamespace,
+		APIServerHost: apiServerURL.Hostname(),
+	}
+	return certProps, nil
+}
+
+func convertToSecret(obj *unstructured.Unstructured) (v1.Secret, error) {
+	secret := v1.Secret{}
+	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), &secret); err != nil {
+		return secret, err
+	}
+	return secret, nil
+}
diff --git a/pkg/tls/tls.go b/pkg/tls/tls.go
index 023db9e69..880dc9f6e 100644
--- a/pkg/tls/tls.go
+++ b/pkg/tls/tls.go
@@ -14,7 +14,11 @@ import (
 	"time"
 )
 
-const certValidityDuration = 10 * 365 * 24 * time.Hour
+// CertRenewalInterval is the renewal interval for rootCA
+const CertRenewalInterval time.Duration = 12 * time.Hour
+
+// CertValidityDuration is the valid duration for a new cert
+const CertValidityDuration time.Duration = 365 * 24 * time.Hour
 
 // CertificateProps Properties of TLS certificate which should be issued for webhook server
 type CertificateProps struct {
@@ -63,7 +67,7 @@ func CertificateToPem(certificateDER []byte) []byte {
 
 // GenerateCACert creates the self-signed CA cert and private key
 // it will be used to sign the webhook server certificate
-func GenerateCACert() (*KeyPair, *PemPair, error) {
+func GenerateCACert(certValidityDuration time.Duration) (*KeyPair, *PemPair, error) {
 	now := time.Now()
 	begin := now.Add(-1 * time.Hour)
 	end := now.Add(certValidityDuration)
@@ -110,7 +114,7 @@ func GenerateCACert() (*KeyPair, *PemPair, error) {
 
 // GenerateCertPem takes the results of GenerateCACert and uses it to create the
 // PEM-encoded public certificate and private key, respectively
-func GenerateCertPem(caCert *KeyPair, props CertificateProps, serverIP string) (*PemPair, error) {
+func GenerateCertPem(caCert *KeyPair, props CertificateProps, serverIP string, certValidityDuration time.Duration) (*PemPair, error) {
 	now := time.Now()
 	begin := now.Add(-1 * time.Hour)
 	end := now.Add(certValidityDuration)
@@ -121,7 +125,7 @@ func GenerateCertPem(caCert *KeyPair, props CertificateProps, serverIP string) (
 
 	dnsNames[1] = fmt.Sprintf("%s.%s", props.Service, props.Namespace)
 	// The full service name is the CommonName for the certificate
-	commonName := GenerateInClusterServiceName(props)
+	commonName := generateInClusterServiceName(props)
 	dnsNames[2] = fmt.Sprintf("%s", commonName)
 
 	var ips []net.IP
@@ -174,7 +178,7 @@ func GenerateCertPem(caCert *KeyPair, props CertificateProps, serverIP string) (
 }
 
 //GenerateInClusterServiceName The generated service name should be the common name for TLS certificate
-func GenerateInClusterServiceName(props CertificateProps) string {
+func generateInClusterServiceName(props CertificateProps) string {
 	return props.Service + "." + props.Namespace + ".svc"
 }
 
diff --git a/pkg/webhookconfig/common.go b/pkg/webhookconfig/common.go
index 0ec76c895..58a3a2b0d 100644
--- a/pkg/webhookconfig/common.go
+++ b/pkg/webhookconfig/common.go
@@ -4,28 +4,34 @@ import (
 	"io/ioutil"
 
 	"github.com/kyverno/kyverno/pkg/config"
+	"github.com/kyverno/kyverno/pkg/tls"
 	admregapi "k8s.io/api/admissionregistration/v1beta1"
 	apps "k8s.io/api/apps/v1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
 	rest "k8s.io/client-go/rest"
 )
 
 func (wrc *Register) readCaData() []byte {
-	logger := wrc.log
+	logger := wrc.log.WithName("readCaData")
 	var caData []byte
+	var err error
+
 	// Check if ca is defined in the secret tls-ca
 	// assume the key and signed cert have been defined in secret tls.kyverno
-	if caData = wrc.client.ReadRootCASecret(); len(caData) != 0 {
+	if caData, err = tls.ReadRootCASecret(wrc.clientConfig, wrc.client); err == nil {
 		logger.V(4).Info("read CA from secret")
 		return caData
 	}
-	logger.V(4).Info("failed to read CA from secret, reading from kubeconfig")
+
+	logger.V(4).Info("failed to read CA from secret, reading from kubeconfig", "reason", err.Error())
 	// load the CA from kubeconfig
 	if caData = extractCA(wrc.clientConfig); len(caData) != 0 {
 		logger.V(4).Info("read CA from kubeconfig")
 		return caData
 	}
+
 	logger.V(4).Info("failed to read CA from kubeconfig")
 	return nil
 }
@@ -49,8 +55,8 @@ func extractCA(config *rest.Config) (result []byte) {
 
 func (wrc *Register) constructOwner() v1.OwnerReference {
 	logger := wrc.log
-	kubePolicyDeployment, err := wrc.getKubePolicyDeployment()
 
+	kubePolicyDeployment, _, err := wrc.GetKubePolicyDeployment()
 	if err != nil {
 		logger.Error(err, "failed to construct OwnerReference")
 		return v1.OwnerReference{}
@@ -64,17 +70,18 @@ func (wrc *Register) constructOwner() v1.OwnerReference {
 	}
 }
 
-func (wrc *Register) getKubePolicyDeployment() (*apps.Deployment, error) {
+// GetKubePolicyDeployment gets Kyverno deployment
+func (wrc *Register) GetKubePolicyDeployment() (*apps.Deployment, *unstructured.Unstructured, error) {
 	lister, _ := wrc.resCache.GetGVRCache("Deployment")
 	kubePolicyDeployment, err := lister.NamespacedLister(config.KyvernoNamespace).Get(config.KyvernoDeploymentName)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	deploy := apps.Deployment{}
 	if err = runtime.DefaultUnstructuredConverter.FromUnstructured(kubePolicyDeployment.UnstructuredContent(), &deploy); err != nil {
-		return nil, err
+		return nil, kubePolicyDeployment, err
 	}
-	return &deploy, nil
+	return &deploy, kubePolicyDeployment, nil
 }
 
 // debug mutating webhook
diff --git a/pkg/webhookconfig/monitor.go b/pkg/webhookconfig/monitor.go
index af59210c9..b39ecbe96 100644
--- a/pkg/webhookconfig/monitor.go
+++ b/pkg/webhookconfig/monitor.go
@@ -2,12 +2,18 @@ package webhookconfig
 
 import (
 	"fmt"
+	"os"
+	"reflect"
 	"sync"
 	"time"
 
 	"github.com/go-logr/logr"
-	dclient "github.com/kyverno/kyverno/pkg/dclient"
+	"github.com/kyverno/kyverno/pkg/config"
 	"github.com/kyverno/kyverno/pkg/event"
+	"github.com/kyverno/kyverno/pkg/resourcecache"
+	"github.com/kyverno/kyverno/pkg/tls"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/client-go/tools/cache"
 )
 
 //maxRetryCount defines the max deadline count
@@ -28,17 +34,34 @@ const (
 // like the webhook settings.
 //
 type Monitor struct {
-	t   time.Time
-	mu  sync.RWMutex
-	log logr.Logger
+	t           time.Time
+	mu          sync.RWMutex
+	secretQueue chan bool
+	log         logr.Logger
 }
 
-//NewMonitor returns a new instance of LastRequestTime store
-func NewMonitor(log logr.Logger) *Monitor {
-	return &Monitor{
-		t:   time.Now(),
-		log: log,
+//NewMonitor returns a new instance of webhook monitor
+func NewMonitor(resCache resourcecache.ResourceCache, log logr.Logger) *Monitor {
+	monitor := &Monitor{
+		t:           time.Now(),
+		secretQueue: make(chan bool, 1),
+		log:         log,
 	}
+
+	var err error
+	secretCache, ok := resCache.GetGVRCache("Secret")
+	if !ok {
+		if secretCache, err = resCache.CreateGVKInformer("Secret"); err != nil {
+			log.Error(err, "unable to start Secret's informer")
+		}
+	}
+
+	secretCache.GetInformer().AddEventHandler(cache.ResourceEventHandlerFuncs{
+		AddFunc:    monitor.addSecretFunc,
+		UpdateFunc: monitor.updateSecretFunc,
+	})
+
+	return monitor
 }
 
 //Time returns the last request time
@@ -56,18 +79,59 @@ func (t *Monitor) SetTime(tm time.Time) {
 	t.t = tm
 }
 
+func (t *Monitor) addSecretFunc(obj interface{}) {
+	secret := obj.(*unstructured.Unstructured)
+	if secret.GetNamespace() != config.KyvernoNamespace {
+		return
+	}
+
+	val, ok := secret.GetAnnotations()[tls.SelfSignedAnnotation]
+	if !ok || val != "true" {
+		return
+	}
+
+	t.secretQueue <- true
+}
+
+func (t *Monitor) updateSecretFunc(oldObj interface{}, newObj interface{}) {
+	old := oldObj.(*unstructured.Unstructured)
+	new := newObj.(*unstructured.Unstructured)
+	if new.GetNamespace() != config.KyvernoNamespace {
+		return
+	}
+
+	val, ok := new.GetAnnotations()[tls.SelfSignedAnnotation]
+	if !ok || val != "true" {
+		return
+	}
+
+	if reflect.DeepEqual(old.UnstructuredContent()["data"], new.UnstructuredContent()["data"]) {
+		return
+	}
+
+	t.secretQueue <- true
+	t.log.V(4).Info("secret updated, reconciling webhook configurations")
+}
+
 //Run runs the checker and verify the resource update
-func (t *Monitor) Run(register *Register, eventGen event.Interface, client *dclient.Client, stopCh <-chan struct{}) {
+func (t *Monitor) Run(register *Register, certRenewer *tls.CertRenewer, eventGen event.Interface, stopCh <-chan struct{}) {
 	logger := t.log
 	logger.V(4).Info("starting webhook monitor", "interval", idleCheckInterval)
-	status := newStatusControl(client, eventGen, logger.WithName("WebhookStatusControl"))
+	status := newStatusControl(register.client, eventGen, logger.WithName("WebhookStatusControl"))
 
 	ticker := time.NewTicker(tickerInterval)
 	defer ticker.Stop()
 
+	certsRenewalTicker := time.NewTicker(tls.CertRenewalInterval)
+	defer certsRenewalTicker.Stop()
+
 	for {
 		select {
 		case <-ticker.C:
+			if skipWebhookCheck(register, logger.WithName("statusCheck/skipWebhookCheck")) {
+				logger.Info("skip validating webhook status, Kyverno is in rolling update")
+				continue
+			}
 
 			if err := register.Check(); err != nil {
 				t.log.Error(err, "missing webhooks")
@@ -95,6 +159,10 @@ func (t *Monitor) Run(register *Register, eventGen event.Interface, client *dcli
 
 			if timeDiff > idleCheckInterval {
 				logger.V(1).Info("webhook idle time exceeded", "deadline", idleCheckInterval)
+				if skipWebhookCheck(register, logger.WithName("skipWebhookCheck")) {
+					logger.Info("skip validating webhook status, Kyverno is in rolling update")
+					continue
+				}
 
 				// send request to update the Kyverno deployment
 				if err := status.IncrementAnnotation(); err != nil {
@@ -110,6 +178,40 @@ func (t *Monitor) Run(register *Register, eventGen event.Interface, client *dcli
 				logger.Error(err, "failed to annotate deployment webhook status to success")
 			}
 
+		case <-certsRenewalTicker.C:
+			valid, err := certRenewer.ValidCert()
+			if err != nil {
+				logger.Error(err, "failed to validate cert")
+				continue
+			}
+
+			if valid {
+				continue
+			}
+
+			logger.Info("rootCA is about to expire, trigger a rolling update to renew the cert")
+			if err := certRenewer.RollingUpdate(); err != nil {
+				logger.Error(err, "unable to trigger a rolling update to renew rootCA, force restarting")
+				os.Exit(1)
+			}
+
+		case <-t.secretQueue:
+			valid, err := certRenewer.ValidCert()
+			if err != nil {
+				logger.Error(err, "failed to validate cert")
+				continue
+			}
+
+			if valid {
+				continue
+			}
+
+			logger.Info("rootCA has changed, updating webhook configurations")
+			if err := certRenewer.RollingUpdate(); err != nil {
+				logger.Error(err, "unable to trigger a rolling update to re-register webhook server, force restarting")
+				os.Exit(1)
+			}
+
 		case <-stopCh:
 			// handler termination signal
 			logger.V(2).Info("stopping webhook monitor")
@@ -117,3 +219,14 @@ func (t *Monitor) Run(register *Register, eventGen event.Interface, client *dcli
 		}
 	}
 }
+
+// skipWebhookCheck returns true if Kyverno is in rolling update
+func skipWebhookCheck(register *Register, logger logr.Logger) bool {
+	_, deploy, err := register.GetKubePolicyDeployment()
+	if err != nil {
+		logger.Info("unable to get Kyverno deployment", "reason", err.Error())
+		return false
+	}
+
+	return tls.IsKyvernoIsInRollingUpdate(deploy.UnstructuredContent(), logger)
+}
diff --git a/pkg/webhookconfig/registration.go b/pkg/webhookconfig/registration.go
index 64ace56bf..aef5331a4 100644
--- a/pkg/webhookconfig/registration.go
+++ b/pkg/webhookconfig/registration.go
@@ -11,9 +11,11 @@ import (
 	"github.com/kyverno/kyverno/pkg/config"
 	client "github.com/kyverno/kyverno/pkg/dclient"
 	"github.com/kyverno/kyverno/pkg/resourcecache"
+	"github.com/kyverno/kyverno/pkg/tls"
 	admregapi "k8s.io/api/admissionregistration/v1beta1"
 	errorsapi "k8s.io/apimachinery/pkg/api/errors"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	rest "k8s.io/client-go/rest"
 )
 
@@ -64,24 +66,29 @@ func (wrc *Register) Register() error {
 
 	wrc.removeWebhookConfigurations()
 
+	caData := wrc.readCaData()
+	if caData == nil {
+		return errors.New("Unable to extract CA data from configuration")
+	}
+
 	errors := make([]string, 0)
-	if err := wrc.createVerifyMutatingWebhookConfiguration(); err != nil {
+	if err := wrc.createVerifyMutatingWebhookConfiguration(caData); err != nil {
 		errors = append(errors, err.Error())
 	}
 
-	if err := wrc.createPolicyValidatingWebhookConfiguration(); err != nil {
+	if err := wrc.createPolicyValidatingWebhookConfiguration(caData); err != nil {
 		errors = append(errors, err.Error())
 	}
 
-	if err := wrc.createPolicyMutatingWebhookConfiguration(); err != nil {
+	if err := wrc.createPolicyMutatingWebhookConfiguration(caData); err != nil {
 		errors = append(errors, err.Error())
 	}
 
-	if err := wrc.createResourceValidatingWebhookConfiguration(); err != nil {
+	if err := wrc.createResourceValidatingWebhookConfiguration(caData); err != nil {
 		errors = append(errors, err.Error())
 	}
 
-	if err := wrc.createResourceMutatingWebhookConfiguration(); err != nil {
+	if err := wrc.createResourceMutatingWebhookConfiguration(caData); err != nil {
 		errors = append(errors, err.Error())
 	}
 
@@ -122,19 +129,46 @@ func (wrc *Register) Check() error {
 
 // Remove removes all webhook configurations
 func (wrc *Register) Remove(cleanUp chan<- struct{}) {
+	defer close(cleanUp)
+	if !wrc.cleanupKyvernoResource() {
+		return
+	}
+
 	wrc.removeWebhookConfigurations()
-	close(cleanUp)
+	wrc.removeSecrets()
 }
 
-func (wrc *Register) createResourceMutatingWebhookConfiguration() error {
+// cleanupKyvernoResource returns true if Kyverno deployment is terminating
+func (wrc *Register) cleanupKyvernoResource() bool {
+	logger := wrc.log.WithName("cleanupKyvernoResource")
+	deploy, err := wrc.client.GetResource("", "Deployment", deployNamespace, deployName)
+	if err != nil {
+		logger.Error(err, "failed to get deployment")
+		return false
+	}
 
-	var caData []byte
-	var config *admregapi.MutatingWebhookConfiguration
+	if deploy.GetDeletionTimestamp() != nil {
+		logger.Info("Kyverno is terminating, clean up Kyverno resources")
+		return true
+	}
 
-	if caData = wrc.readCaData(); caData == nil {
-		return errors.New("Unable to extract CA data from configuration")
+	replicas, _, err := unstructured.NestedInt64(deploy.UnstructuredContent(), "spec", "replicas")
+	if err != nil {
+		logger.Error(err, "unable to fetch spec.replicas of Kyverno deployment")
 	}
 
+	if replicas == 0 {
+		logger.Info("Kyverno is scaled to zero, clean up Kyverno resources")
+		return true
+	}
+
+	logger.Info("updating Kyverno Pod, won't clean up Kyverno resources")
+	return false
+}
+
+func (wrc *Register) createResourceMutatingWebhookConfiguration(caData []byte) error {
+	var config *admregapi.MutatingWebhookConfiguration
+
 	if wrc.serverIP != "" {
 		config = wrc.constructDebugMutatingWebhookConfig(caData)
 	} else {
@@ -158,13 +192,9 @@ func (wrc *Register) createResourceMutatingWebhookConfiguration() error {
 	return nil
 }
 
-func (wrc *Register) createResourceValidatingWebhookConfiguration() error {
-	var caData []byte
+func (wrc *Register) createResourceValidatingWebhookConfiguration(caData []byte) error {
 	var config *admregapi.ValidatingWebhookConfiguration
 
-	if caData = wrc.readCaData(); caData == nil {
-		return errors.New("Unable to extract CA data from configuration")
-	}
 	if wrc.serverIP != "" {
 		config = wrc.constructDebugValidatingWebhookConfig(caData)
 	} else {
@@ -189,15 +219,9 @@ func (wrc *Register) createResourceValidatingWebhookConfiguration() error {
 }
 
 //registerPolicyValidatingWebhookConfiguration create a Validating webhook configuration for Policy CRD
-func (wrc *Register) createPolicyValidatingWebhookConfiguration() error {
-	var caData []byte
+func (wrc *Register) createPolicyValidatingWebhookConfiguration(caData []byte) error {
 	var config *admregapi.ValidatingWebhookConfiguration
 
-	// read certificate data
-	if caData = wrc.readCaData(); caData == nil {
-		return errors.New("Unable to extract CA data from configuration")
-	}
-
 	if wrc.serverIP != "" {
 		config = wrc.contructDebugPolicyValidatingWebhookConfig(caData)
 	} else {
@@ -217,14 +241,9 @@ func (wrc *Register) createPolicyValidatingWebhookConfiguration() error {
 	return nil
 }
 
-func (wrc *Register) createPolicyMutatingWebhookConfiguration() error {
-	var caData []byte
+func (wrc *Register) createPolicyMutatingWebhookConfiguration(caData []byte) error {
 	var config *admregapi.MutatingWebhookConfiguration
 
-	if caData = wrc.readCaData(); caData == nil {
-		return errors.New("Unable to extract CA data from configuration")
-	}
-
 	if wrc.serverIP != "" {
 		config = wrc.contructDebugPolicyMutatingWebhookConfig(caData)
 	} else {
@@ -245,14 +264,9 @@ func (wrc *Register) createPolicyMutatingWebhookConfiguration() error {
 	return nil
 }
 
-func (wrc *Register) createVerifyMutatingWebhookConfiguration() error {
-	var caData []byte
+func (wrc *Register) createVerifyMutatingWebhookConfiguration(caData []byte) error {
 	var config *admregapi.MutatingWebhookConfiguration
 
-	if caData = wrc.readCaData(); caData == nil {
-		return errors.New("Unable to extract CA data from configuration")
-	}
-
 	if wrc.serverIP != "" {
 		config = wrc.constructDebugVerifyMutatingWebhookConfig(caData)
 	} else {
@@ -435,3 +449,24 @@ func (wrc *Register) getVerifyWebhookMutatingWebhookName() string {
 func (wrc *Register) GetWebhookTimeOut() time.Duration {
 	return time.Duration(wrc.timeoutSeconds)
 }
+
+// removeSecrets removes Kyverno managed secrets
+func (wrc *Register) removeSecrets() {
+	selector := &v1.LabelSelector{
+		MatchLabels: map[string]string{
+			tls.ManagedByLabel: "kyverno",
+		},
+	}
+
+	secretList, err := wrc.client.ListResource("", "Secret", config.KyvernoNamespace, selector)
+	if err != nil && errorsapi.IsNotFound(err) {
+		wrc.log.Error(err, "failed to clean up Kyverno managed secrets")
+		return
+	}
+
+	for _, secret := range secretList.Items {
+		if err := wrc.client.DeleteResource("", "Secret", secret.GetNamespace(), secret.GetName(), false); err != nil {
+			wrc.log.Error(err, "failed to delete secret", "ns", secret.GetNamespace(), "name", secret.GetName())
+		}
+	}
+}
diff --git a/pkg/webhooks/mutation.go b/pkg/webhooks/mutation.go
index 5efad0131..ccc590fd1 100644
--- a/pkg/webhooks/mutation.go
+++ b/pkg/webhooks/mutation.go
@@ -1,6 +1,7 @@
 package webhooks
 
 import (
+	"errors"
 	"reflect"
 	"sort"
 	"time"
@@ -68,7 +69,7 @@ func (ws *WebhookServer) HandleMutation(
 		}
 
 		if !engineResponse.IsSuccessful() && len(engineResponse.GetFailedRules()) > 0 {
-			logger.Info("failed to apply policy", "policy", policy.Name, "failed rules", engineResponse.GetFailedRules())
+			logger.Error(errors.New("some rules failed"), "failed to apply policy", "policy", policy.Name, "failed rules", engineResponse.GetFailedRules())
 			continue
 		}
 
@@ -112,7 +113,7 @@ func (ws *WebhookServer) HandleMutation(
 
 		// if any of the policies fails, print out the error
 		if !isResponseSuccessful(engineResponses) {
-			logger.Info("failed to apply mutation rules on the resource, reporting policy violation", "errors", getErrorMsg(engineResponses))
+			logger.Error(errors.New(getErrorMsg(engineResponses)), "failed to apply mutation rules on the resource, reporting policy violation")
 		}
 	}()
 
diff --git a/pkg/webhooks/server.go b/pkg/webhooks/server.go
index 14d279135..7a4873067 100644
--- a/pkg/webhooks/server.go
+++ b/pkg/webhooks/server.go
@@ -27,6 +27,7 @@ import (
 	"github.com/kyverno/kyverno/pkg/policyreport"
 	"github.com/kyverno/kyverno/pkg/policystatus"
 	"github.com/kyverno/kyverno/pkg/resourcecache"
+	ktls "github.com/kyverno/kyverno/pkg/tls"
 	tlsutils "github.com/kyverno/kyverno/pkg/tls"
 	userinfo "github.com/kyverno/kyverno/pkg/userinfo"
 	"github.com/kyverno/kyverno/pkg/utils"
@@ -104,6 +105,8 @@ type WebhookServer struct {
 	// last request time
 	webhookMonitor *webhookconfig.Monitor
 
+	certRenewer *ktls.CertRenewer
+
 	// policy report generator
 	prGenerator policyreport.GeneratorInterface
 
@@ -148,6 +151,7 @@ func NewWebhookServer(
 	pCache policycache.Interface,
 	webhookRegistrationClient *webhookconfig.Register,
 	webhookMonitor *webhookconfig.Monitor,
+	certRenewer *ktls.CertRenewer,
 	statusSync policystatus.Listener,
 	configHandler config.Interface,
 	prGenerator policyreport.GeneratorInterface,
@@ -198,6 +202,7 @@ func NewWebhookServer(
 		configHandler:         configHandler,
 		cleanUp:               cleanUp,
 		webhookMonitor:        webhookMonitor,
+		certRenewer:           certRenewer,
 		prGenerator:           prGenerator,
 		grGenerator:           grGenerator,
 		grController:          grc,
@@ -512,7 +517,7 @@ func (ws *WebhookServer) RunAsync(stopCh <-chan struct{}) {
 	logger.Info("starting service")
 
 	if !ws.debug {
-		go ws.webhookMonitor.Run(ws.webhookRegister, ws.eventGen, ws.client, stopCh)
+		go ws.webhookMonitor.Run(ws.webhookRegister, ws.certRenewer, ws.eventGen, stopCh)
 	}
 }
 


Test patch:
diff --git a/pkg/engine/apiPath_test.go b/pkg/engine/apiPath_test.go
index 8879b377f..8942c12b4 100644
--- a/pkg/engine/apiPath_test.go
+++ b/pkg/engine/apiPath_test.go
@@ -26,3 +26,26 @@ func Test_Paths(t *testing.T) {
 	f("/apis/gloo.solo.io/v1/namespaces/gloo-system/upstreams/  ", "/apis/gloo.solo.io/v1/namespaces/gloo-system/upstreams")
 	f("  /apis/gloo.solo.io/v1/namespaces/gloo-system/upstreams", "/apis/gloo.solo.io/v1/namespaces/gloo-system/upstreams")
 }
+
+func Test_GroupVersions(t *testing.T) {
+	f := func(path, expected string) {
+		p, err := NewAPIPath(path)
+		if err != nil {
+			t.Error(err)
+			return
+		}
+
+		if p.Root == "api" {
+			if p.Group != expected {
+				t.Errorf("expected %s got %s", expected, p.Group)
+			}
+		} else {
+			if p.Version != expected {
+				t.Errorf("expected %s got %s", expected, p.Version)
+			}
+		}
+	}
+
+	f("/api/v1/namespace/{{ request.namespace }}", "v1")
+	f("/apis/extensions/v1beta1/namespaces/example/ingresses", "extensions/v1beta1")
+}
diff --git a/pkg/engine/mutate/patchesUtils_test.go b/pkg/engine/mutate/patchesUtils_test.go
index 75ad93a06..c49ee138e 100644
--- a/pkg/engine/mutate/patchesUtils_test.go
+++ b/pkg/engine/mutate/patchesUtils_test.go
@@ -1,16 +1,12 @@
 package mutate
 
 import (
-	"encoding/json"
 	"fmt"
 	"testing"
 
 	"github.com/mattbaird/jsonpatch"
 	assertnew "github.com/stretchr/testify/assert"
 	"gotest.tools/assert"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"sigs.k8s.io/controller-runtime/pkg/log"
-	yaml "sigs.k8s.io/yaml"
 )
 
 func Test_GeneratePatches(t *testing.T) {
@@ -139,120 +135,6 @@ var overlayBytes = []byte(`
 
 var expectBytes = []byte(`{"apiVersion": "apps/v1","kind": "Deployment","metadata": {"name": "wordpress","labels": {"app": "wordpress"}},"spec": {"selector": {"matchLabels": {"app": "wordpress"}},"strategy": {"type": "Recreate"},"template": {"metadata": {"labels": {"app": "wordpress"}},"spec": {"containers": [{"name": "nginx","image": "nginx"},{"image": "wordpress:4.8-apache","name": "wordpress","ports": [{"containerPort": 80,"name": "wordpress"}],"volumeMounts": [{"name": "wordpress-persistent-storage","mountPath": "/var/www/html"}],"env": [{"name": "WORDPRESS_DB_HOST","value": "$(MYSQL_SERVICE)"},{"name": "WORDPRESS_DB_PASSWORD","valueFrom": {"secretKeyRef": {"name": "mysql-pass","key": "password"}}}]}],"volumes": [{"name": "wordpress-persistent-storage"}],"initContainers": [{"name": "init-command","image": "debian","command": ["echo $(WORDPRESS_SERVICE)","echo $(MYSQL_SERVICE)"]}]}}}}`)
 
-var podBytes = []byte(`
-{
-  "kind": "Pod",
-  "apiVersion": "v1",
-  "metadata": {
-      "name": "nginx"
-  },
-  "spec": {
-      "containers": [
-          {
-              "name": "nginx",
-              "image": "nginx:latest"
-          },
-          {
-            "name": "nginx-new",
-            "image": "nginx:latest"
-          }
-      ]
-  }
-}
-`)
-
-func Test_preProcessJSONPatches_skip(t *testing.T) {
-	patchesJSON6902 := []byte(`
-- op: add
-  path: /spec/containers/1
-  value: {"name":"nginx-new","image":"nginx:latest"}
-`)
-	var pod unstructured.Unstructured
-	assertnew.Nil(t, json.Unmarshal(podBytes, &pod))
-
-	patches, err := yaml.YAMLToJSON(patchesJSON6902)
-	assertnew.Nil(t, err)
-
-	skip, err := preProcessJSONPatches(patches, pod, log.Log)
-	assertnew.Nil(t, err)
-	assertnew.Equal(t, true, skip)
-}
-
-func Test_preProcessJSONPatches_not_skip(t *testing.T) {
-	patchesJSON6902 := []byte(`
-- op: add
-  path: /spec/containers/1
-  value: {"name":"my-new-container","image":"nginx:latest"}
-`)
-
-	patches, err := yaml.YAMLToJSON(patchesJSON6902)
-	assertnew.Nil(t, err)
-
-	var pod unstructured.Unstructured
-	assertnew.Nil(t, json.Unmarshal(podBytes, &pod))
-
-	skip, err := preProcessJSONPatches(patches, pod, log.Log)
-	assertnew.Nil(t, err)
-	assertnew.Equal(t, false, skip)
-}
-
-func Test_isSubsetObject_true(t *testing.T) {
-	var object, resource interface{}
-
-	objectRaw := []byte(`{"image": "nginx:latest","name": "nginx-new"}`)
-	resourceRaw := []byte(`{"image": "nginx:latest","name": "random-name"}`)
-	assertnew.Nil(t, json.Unmarshal(objectRaw, &object))
-	assertnew.Nil(t, json.Unmarshal(resourceRaw, &resource))
-	assertnew.Equal(t, false, isSubsetObject(object, resource))
-
-	resourceRawNew := []byte(`{"image": "nginx:latest","name": "nginx-new"}`)
-	assertnew.Nil(t, json.Unmarshal(resourceRawNew, &resource))
-	assertnew.Equal(t, true, isSubsetObject(object, resource))
-}
-
-func Test_getObject_notPresent(t *testing.T) {
-	path := "/spec/random/1"
-	var pod unstructured.Unstructured
-
-	assertnew.Nil(t, json.Unmarshal(podBytes, &pod))
-	_, err := getObject(path, pod.UnstructuredContent())
-	expectedErr := "referenced value does not exist at spec/random"
-	assertnew.Equal(t, err.Error(), expectedErr)
-}
-
-func Test_getObject_outOfIndex(t *testing.T) {
-	path := "/spec/containers/2"
-	var pod unstructured.Unstructured
-
-	assertnew.Nil(t, json.Unmarshal(podBytes, &pod))
-	object, err := getObject(path, pod.UnstructuredContent())
-	assertnew.Nil(t, err)
-	assertnew.Nil(t, object)
-
-}
-
-func Test_getObject_success(t *testing.T) {
-	path := "/spec/containers/1"
-	var pod unstructured.Unstructured
-	expectedObject := map[string]interface{}{"image": "nginx:latest", "name": "nginx-new"}
-
-	assertnew.Nil(t, json.Unmarshal(podBytes, &pod))
-	object, err := getObject(path, pod.UnstructuredContent())
-	assertnew.Nil(t, err)
-	assertnew.Equal(t, expectedObject, object)
-}
-
-func Test_getObject_get_last_element(t *testing.T) {
-	path := "/spec/containers/-"
-	var pod unstructured.Unstructured
-	expectedObject := map[string]interface{}{"image": "nginx:latest", "name": "nginx-new"}
-
-	assertnew.Nil(t, json.Unmarshal(podBytes, &pod))
-	object, err := getObject(path, pod.UnstructuredContent())
-	assertnew.Nil(t, err)
-	assertnew.Equal(t, expectedObject, object)
-}
-
 func Test_ignorePath(t *testing.T) {
 	tests := []struct {
 		path   string
diff --git a/pkg/engine/variables/evaluate_test.go b/pkg/engine/variables/evaluate_test.go
index bbdcace2a..723c21319 100644
--- a/pkg/engine/variables/evaluate_test.go
+++ b/pkg/engine/variables/evaluate_test.go
@@ -1069,7 +1069,7 @@ func Test_Eval_In_String_Set_Fail(t *testing.T) {
 // test passes if ALL of the values in "key" are NOT in "value" ("key" is not a subset of "value")
 func Test_Eval_NotIn_String_Set_Pass(t *testing.T) {
 	ctx := context.NewContext()
-	key := [2]string{"1.1.1.1", "4.4.4.4"}
+	key := [2]string{"4.4.4.4", "5.5.5.5"}
 	keyInterface := make([]interface{}, len(key), len(key))
 	for i := range key {
 		keyInterface[i] = key[i]
@@ -1094,7 +1094,7 @@ func Test_Eval_NotIn_String_Set_Pass(t *testing.T) {
 // test passes if ALL of the values in "key" are in "value" ("key" is a subset of "value")
 func Test_Eval_NotIn_String_Set_Fail(t *testing.T) {
 	ctx := context.NewContext()
-	key := [2]string{"1.1.1.1", "2.2.2.2"}
+	key := [2]string{"1.1.1.1", "4.4.4.4"}
 	keyInterface := make([]interface{}, len(key), len(key))
 	for i := range key {
 		keyInterface[i] = key[i]