diff --git a/cmd/kyverno/main.go b/cmd/kyverno/main.go index ab39ffae77..075ef77a52 100755 --- a/cmd/kyverno/main.go +++ b/cmd/kyverno/main.go @@ -9,6 +9,7 @@ import ( "os" "time" + backwardcompatibility "github.com/kyverno/kyverno/pkg/backward_compatibility" kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned" kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions" "github.com/kyverno/kyverno/pkg/config" @@ -328,6 +329,7 @@ func main() { log.Log.WithName("WebhookServer"), openAPIController, rCache, + grc, ) if err != nil { @@ -355,6 +357,9 @@ func main() { // verifies if the admission control is enabled and active server.RunAsync(stopCh) + + go backwardcompatibility.AddLabels(pclient, pInformer.Kyverno().V1().GenerateRequests()) + go backwardcompatibility.AddCloneLabel(client, pInformer.Kyverno().V1().ClusterPolicies()) <-stopCh // by default http.Server waits indefinitely for connections to return to idle and then shuts down diff --git a/go.mod b/go.mod index ac51bb99b3..5e8e4bb71f 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/onsi/gomega v1.8.1 github.com/ory/go-acc v0.2.6 // indirect github.com/pkg/errors v0.9.1 + github.com/prometheus/common v0.4.1 github.com/sirupsen/logrus v1.6.0 // indirect github.com/spf13/cobra v1.0.0 github.com/stretchr/testify v1.5.1 diff --git a/go.sum b/go.sum index 876f0dddb0..307d39c413 100644 --- a/go.sum +++ b/go.sum @@ -40,7 +40,9 @@ github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrU github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/ahmetb/gen-crd-api-reference-docs v0.1.5/go.mod h1:P/XzJ+c2+khJKNKABcm2biRwk2QAuwbLf8DlXuaL7WM= github.com/alecthomas/participle v0.2.1/go.mod h1:SW6HZGeZgSIpcUWX3fXpfZhuaWHnmoD5KCVaqSaNTkk= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= @@ -583,6 +585,7 @@ github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7q github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -896,6 +899,7 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/backward_compatibility/add_labels.go b/pkg/backward_compatibility/add_labels.go new file mode 100644 index 0000000000..6408a6452b --- /dev/null +++ b/pkg/backward_compatibility/add_labels.go @@ -0,0 +1,146 @@ +package backwardcompatibility + +import ( + "context" + "fmt" + "strings" + "time" + + kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned" + kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1" + "github.com/kyverno/kyverno/pkg/config" + dclient "github.com/kyverno/kyverno/pkg/dclient" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +// AddLabels - adds labels to all the existing generate requests +func AddLabels(client *kyvernoclient.Clientset, grInformer kyvernoinformer.GenerateRequestInformer) { + // Get all the GR's that are existing + // Extract and Update all of them with the with the labels + grList, err := grInformer.Lister().List(labels.NewSelector()) + if err != nil { + log.Log.Error(err, "failed to get generate request list") + return + } + + for _, gr := range grList { + + grLabels := gr.Labels + if grLabels == nil || len(grLabels) == 0 { + grLabels = make(map[string]string) + } + grLabels["generate.kyverno.io/policy-name"] = gr.Spec.Policy + grLabels["generate.kyverno.io/resource-name"] = gr.Spec.Resource.Name + grLabels["generate.kyverno.io/resource-kind"] = gr.Spec.Resource.Kind + grLabels["generate.kyverno.io/resource-namespace"] = gr.Spec.Resource.Namespace + + gr.SetLabels(grLabels) + + _, err = client.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Update(context.TODO(), gr, metav1.UpdateOptions{}) + if err != nil { + log.Log.V(4).Info(fmt.Sprintf("failed to update the GR %v. error: %v", gr.Name, err)) + for n := 0; n <= 3; n++ { + log.Log.V(4).Info(fmt.Sprintf("retrying to get GR %v", gr.Name)) + time.Sleep(100 * time.Millisecond) + errInGettingGR := addLabelForGR(gr.Name, gr.Namespace, client, grInformer) + if errInGettingGR != nil { + continue + } else { + break + } + } + } + } + return +} + +func addLabelForGR(name string, namespace string, client *kyvernoclient.Clientset, grInformer kyvernoinformer.GenerateRequestInformer) error { + gr, err := grInformer.Lister().GenerateRequests(namespace).Get(name) + if err != nil { + log.Log.Error(err, fmt.Sprintf("failed to update the GR %v", name)) + return err + } + + grLabels := gr.Labels + if grLabels == nil || len(grLabels) == 0 { + grLabels = make(map[string]string) + } + grLabels["generate.kyverno.io/policy-name"] = gr.Spec.Policy + grLabels["generate.kyverno.io/resource-name"] = gr.Spec.Resource.Name + grLabels["generate.kyverno.io/resource-kind"] = gr.Spec.Resource.Kind + grLabels["generate.kyverno.io/resource-namespace"] = gr.Spec.Resource.Namespace + + gr.SetLabels(grLabels) + + _, err = client.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Update(context.TODO(), gr, metav1.UpdateOptions{}) + if err != nil { + log.Log.Error(err, fmt.Sprintf("failed to update the GR %v", gr.Name)) + return err + } + + return nil +} + +// AddCloneLabel - add label to the source resource about the new clone +func AddCloneLabel(client *dclient.Client, pInformer kyvernoinformer.ClusterPolicyInformer) { + // Get all the Generate Policies which has clone + // Get the resource with Kind, NameSpace, Name + // Add Policy name if label not found + policies, err := pInformer.Lister().List(labels.NewSelector()) + if err != nil { + log.Log.Error(err, "failed to get policies") + return + } + + for _, policy := range policies { + for _, rule := range policy.Spec.Rules { + if rule.HasGenerate() { + clone := rule.Generation.Clone + if clone.Name != "" { + namespace := clone.Namespace + name := clone.Name + kind := rule.Generation.Kind + + obj, err := client.GetResource("", kind, namespace, name) + if err != nil { + log.Log.Error(err, fmt.Sprintf("source not found name:%v namespace:%v kind:%v", name, namespace, kind)) + continue + } + updateSource := true + + // add label + label := obj.GetLabels() + if len(label) == 0 { + label = make(map[string]string) + label["generate.kyverno.io/clone-policy-name"] = policy.GetName() + } else { + if label["generate.kyverno.io/clone-policy-name"] != "" { + policyNames := label["generate.kyverno.io/clone-policy-name"] + if !strings.Contains(policyNames, policy.GetName()) { + policyNames = policyNames + "," + policy.GetName() + label["generate.kyverno.io/clone-policy-name"] = policyNames + } else { + updateSource = false + } + } else { + label["generate.kyverno.io/clone-policy-name"] = policy.GetName() + } + } + + if updateSource { + log.Log.V(4).Info("updating existing clone source") + obj.SetLabels(label) + _, err = client.UpdateResource(obj.GetAPIVersion(), kind, namespace, obj, false) + if err != nil { + log.Log.Error(err, fmt.Sprintf("failed to update source name:%v namespace:%v kind:%v\n", obj.GetName(), obj.GetNamespace(), obj.GetKind())) + return + } + log.Log.V(4).Info(fmt.Sprintf("updated source name:%v namespace:%v kind:%v\n", obj.GetName(), obj.GetNamespace(), obj.GetKind())) + } + } + } + } + } +} diff --git a/pkg/generate/generate.go b/pkg/generate/generate.go index 9540c1bb43..ac2acb8b3d 100644 --- a/pkg/generate/generate.go +++ b/pkg/generate/generate.go @@ -144,10 +144,10 @@ func (c *Controller) applyGenerate(resource unstructured.Unstructured, gr kyvern if !r.Success { logger.V(4).Info("querying all generate requests") selector := labels.SelectorFromSet(labels.Set(map[string]string{ - "policyName": engineResponse.PolicyResponse.Policy, - "resourceName": engineResponse.PolicyResponse.Resource.Name, - "resourceKind": engineResponse.PolicyResponse.Resource.Kind, - "ResourceNamespace": engineResponse.PolicyResponse.Resource.Namespace, + "generate.kyverno.io/policy-name": engineResponse.PolicyResponse.Policy, + "generate.kyverno.io/resource-name": engineResponse.PolicyResponse.Resource.Name, + "generate.kyverno.io/resource-kind": engineResponse.PolicyResponse.Resource.Kind, + "generate.kyverno.io/resource-namespace": engineResponse.PolicyResponse.Resource.Namespace, })) grList, err := c.grLister.List(selector) if err != nil { @@ -179,9 +179,7 @@ func updateStatus(statusControl StatusControlInterface, gr kyverno.GenerateReque return statusControl.Success(gr, genResources) } -func (c *Controller) applyGeneratePolicy(log logr.Logger, policyContext engine.PolicyContext, gr kyverno.GenerateRequest, applicableRules []string) ([]kyverno.ResourceSpec, error) { - // List of generatedResources - var genResources []kyverno.ResourceSpec +func (c *Controller) applyGeneratePolicy(log logr.Logger, policyContext engine.PolicyContext, gr kyverno.GenerateRequest, applicableRules []string) (genResources []kyverno.ResourceSpec, err error) { // Get the response as the actions to be performed on the resource // - - substitute values policy := policyContext.Policy @@ -203,14 +201,13 @@ func (c *Controller) applyGeneratePolicy(log logr.Logger, policyContext engine.P startTime := time.Now() processExisting := false + var genResource kyverno.ResourceSpec if len(rule.MatchResources.Kinds) > 0 { if len(rule.MatchResources.Annotations) == 0 && rule.MatchResources.Selector == nil { - processExisting = func() bool { - rcreationTime := resource.GetCreationTimestamp() - pcreationTime := policy.GetCreationTimestamp() - return rcreationTime.Before(&pcreationTime) - }() + rcreationTime := resource.GetCreationTimestamp() + pcreationTime := policy.GetCreationTimestamp() + processExisting = rcreationTime.Before(&pcreationTime) } } @@ -220,15 +217,16 @@ func (c *Controller) applyGeneratePolicy(log logr.Logger, policyContext engine.P return nil, err } - genResource, err := applyRule(log, c.client, rule, resource, jsonContext, policy.Name, gr, processExisting) - if err != nil { - log.Error(err, "failed to apply generate rule", "policy", policy.Name, - "rule", rule.Name, "resource", resource.GetName()) - return nil, err + if !processExisting { + genResource, err = applyRule(log, c.client, rule, resource, jsonContext, policy.Name, gr) + if err != nil { + log.Error(err, "failed to apply generate rule", "policy", policy.Name, + "rule", rule.Name, "resource", resource.GetName()) + return nil, err + } + ruleNameToProcessingTime[rule.Name] = time.Since(startTime) + genResources = append(genResources, genResource) } - - ruleNameToProcessingTime[rule.Name] = time.Since(startTime) - genResources = append(genResources, genResource) } if gr.Status.State == "" && len(genResources) > 0 { @@ -300,12 +298,11 @@ func getResourceInfo(object map[string]interface{}) (kind, name, namespace, apiv return } -func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resource unstructured.Unstructured, ctx context.EvalInterface, policy string, gr kyverno.GenerateRequest, processExisting bool) (kyverno.ResourceSpec, error) { +func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resource unstructured.Unstructured, ctx context.EvalInterface, policy string, gr kyverno.GenerateRequest) (kyverno.ResourceSpec, error) { var rdata map[string]interface{} var err error var mode ResourceMode var noGenResource kyverno.ResourceSpec - genUnst, err := getUnstrRule(rule.Generation.DeepCopy()) if err != nil { return noGenResource, err @@ -347,7 +344,7 @@ func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resou } if genClone != nil && len(genClone) != 0 { - rdata, mode, err = manageClone(logger, genAPIVersion, genKind, genNamespace, genName, genClone, client) + rdata, mode, err = manageClone(logger, genAPIVersion, genKind, genNamespace, genName, policy, genClone, client) } else { rdata, mode, err = manageData(logger, genAPIVersion, genKind, genNamespace, genName, genData, client) } @@ -364,10 +361,6 @@ func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resou return newGenResource, nil } - if processExisting { - return noGenResource, nil - } - // build the resource template newResource := &unstructured.Unstructured{} newResource.SetUnstructuredContent(rdata) @@ -386,7 +379,7 @@ func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resou label := newResource.GetLabels() label["policy.kyverno.io/policy-name"] = policy label["policy.kyverno.io/gr-name"] = gr.Name - newResource.SetLabels(label) + delete(label, "generate.kyverno.io/clone-policy-name") if mode == Create { if rule.Generation.Synchronize { label["policy.kyverno.io/synchronize"] = "enable" @@ -396,6 +389,7 @@ func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resou // Reset resource version newResource.SetResourceVersion("") + newResource.SetLabels(label) // Create the resource _, err = client.CreateResource(genAPIVersion, genKind, genNamespace, newResource, false) if err != nil { @@ -405,7 +399,6 @@ func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resou logger.V(2).Info("generated target resource") } else if mode == Update { - label := newResource.GetLabels() if rule.Generation.Synchronize { label["policy.kyverno.io/synchronize"] = "enable" } else { @@ -417,7 +410,7 @@ func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resou newResource.SetLabels(label) _, err := client.UpdateResource(genAPIVersion, genKind, genNamespace, newResource, false) if err != nil { - logger.Error(err, "updating existing resource") + logger.Error(err, "failed to update resource") return noGenResource, err } logger.V(2).Info("updated target resource") @@ -450,7 +443,7 @@ func manageData(log logr.Logger, apiVersion, kind, namespace, name string, data return updateObj.UnstructuredContent(), Update, nil } -func manageClone(log logr.Logger, apiVersion, kind, namespace, name string, clone map[string]interface{}, client *dclient.Client) (map[string]interface{}, ResourceMode, error) { +func manageClone(log logr.Logger, apiVersion, kind, namespace, name, policy string, clone map[string]interface{}, client *dclient.Client) (map[string]interface{}, ResourceMode, error) { rNamespace, _, err := unstructured.NestedString(clone, "namespace") if err != nil { return nil, Skip, fmt.Errorf("failed to find source namespace: %v", err) diff --git a/pkg/generate/generate_controller.go b/pkg/generate/generate_controller.go index 3254173983..228d366de2 100644 --- a/pkg/generate/generate_controller.go +++ b/pkg/generate/generate_controller.go @@ -343,3 +343,8 @@ func (c *Controller) syncGenerateRequest(key string) error { return c.processGR(gr) } + +// EnqueueGenerateRequestFromWebhook - enqueing generate requests from webhook +func (c *Controller) EnqueueGenerateRequestFromWebhook(gr *kyverno.GenerateRequest) { + c.enqueueGenerateRequest(gr) +} diff --git a/pkg/policy/validate.go b/pkg/policy/validate.go index 8a659d6a74..c587e7fb46 100644 --- a/pkg/policy/validate.go +++ b/pkg/policy/validate.go @@ -120,6 +120,46 @@ func Validate(policyRaw []byte, client *dclient.Client, mock bool, openAPIContro if !isLabelAndAnnotationsString(rule) { return fmt.Errorf("labels and annotations supports only string values, \"use double quotes around the non string values\"") } + + // add label to source mentioned in policy + if !mock && rule.Generation.Clone.Name != "" { + obj, err := client.GetResource("", rule.Generation.Kind, rule.Generation.Clone.Namespace, rule.Generation.Clone.Name) + if err != nil { + log.Log.Error(err, fmt.Sprintf("source resource %s/%s/%s not found.", rule.Generation.Kind, rule.Generation.Clone.Namespace, rule.Generation.Clone.Name)) + continue + } + + updateSource := true + label := obj.GetLabels() + + if len(label) == 0 { + label = make(map[string]string) + label["generate.kyverno.io/clone-policy-name"] = p.GetName() + } else { + if label["generate.kyverno.io/clone-policy-name"] != "" { + policyNames := label["generate.kyverno.io/clone-policy-name"] + if !strings.Contains(policyNames, p.GetName()) { + policyNames = policyNames + "," + p.GetName() + label["generate.kyverno.io/clone-policy-name"] = policyNames + } else { + updateSource = false + } + } else { + label["generate.kyverno.io/clone-policy-name"] = p.GetName() + } + } + + if updateSource { + log.Log.V(4).Info("updating existing clone source") + obj.SetLabels(label) + _, err = client.UpdateResource(obj.GetAPIVersion(), rule.Generation.Kind, rule.Generation.Clone.Namespace, obj, false) + if err != nil { + log.Log.Error(err, "failed to update source name:%v namespace:%v kind:%v", obj.GetName(), obj.GetNamespace(), obj.GetKind()) + continue + } + log.Log.V(4).Info("updated source name:%v namespace:%v kind:%v", obj.GetName(), obj.GetNamespace(), obj.GetKind()) + } + } } if !mock { diff --git a/pkg/policy/validate_controller.go b/pkg/policy/validate_controller.go index 55f83f1c8e..a3d3a3ab4b 100644 --- a/pkg/policy/validate_controller.go +++ b/pkg/policy/validate_controller.go @@ -437,14 +437,20 @@ func deleteGR(kyvernoClient *kyvernoclient.Clientset, policyKey string, grList [ } func updateGR(kyvernoClient *kyvernoclient.Clientset, policyKey string, grList []*kyverno.GenerateRequest, logger logr.Logger) { - for _, v := range grList { - if policyKey == v.Spec.Policy { - v.SetLabels(map[string]string{ - "policy-update": fmt.Sprintf("revision-count-%d", rand.Intn(100000)), - }) - _, err := kyvernoClient.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Update(context.TODO(), v, metav1.UpdateOptions{}) + for _, gr := range grList { + if policyKey == gr.Spec.Policy { + grLabels := gr.Labels + if grLabels == nil || len(grLabels) == 0 { + grLabels = make(map[string]string) + } + grLabels["policy-update"] = fmt.Sprintf("revision-count-%d", rand.Intn(100000)) + // gr.SetLabels(map[string]string{ + // "policy-update": fmt.Sprintf("revision-count-%d", rand.Intn(100000)), + // }) + gr.SetLabels(grLabels) + _, err := kyvernoClient.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Update(context.TODO(), gr, metav1.UpdateOptions{}) if err != nil { - logger.Error(err, "failed to update gr", "name", v.GetName()) + logger.Error(err, "failed to update gr", "name", gr.GetName()) } } } diff --git a/pkg/webhooks/generate/generate.go b/pkg/webhooks/generate/generate.go index e264007c43..031bffb01b 100644 --- a/pkg/webhooks/generate/generate.go +++ b/pkg/webhooks/generate/generate.go @@ -141,10 +141,10 @@ func retryApplyResource(client *kyvernoclient.Clientset, grSpec kyverno.Generate if action == v1beta1.Create || action == v1beta1.Update { log.V(4).Info("querying all generate requests") selector := labels.SelectorFromSet(labels.Set(map[string]string{ - "policyName": grSpec.Policy, - "resourceName": grSpec.Resource.Name, - "resourceKind": grSpec.Resource.Kind, - "ResourceNamespace": grSpec.Resource.Namespace, + "generate.kyverno.io/policy-name": grSpec.Policy, + "generate.kyverno.io/resource-name": grSpec.Resource.Name, + "generate.kyverno.io/resource-kind": grSpec.Resource.Kind, + "generate.kyverno.io/resource-namespace": grSpec.Resource.Namespace, })) grList, err := grLister.List(selector) if err != nil { @@ -153,28 +153,31 @@ func retryApplyResource(client *kyvernoclient.Clientset, grSpec kyverno.Generate } for _, v := range grList { - if grSpec.Policy == v.Spec.Policy && grSpec.Resource.Name == v.Spec.Resource.Name && grSpec.Resource.Kind == v.Spec.Resource.Kind && grSpec.Resource.Namespace == v.Spec.Resource.Namespace { - gr.SetLabels(map[string]string{ - "resources-update": "true", - }) - v.Spec.Context = gr.Spec.Context - v.Spec.Policy = gr.Spec.Policy - v.Spec.Resource = gr.Spec.Resource - _, err = client.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Update(context.TODO(), v, metav1.UpdateOptions{}) - if err != nil { - return err - } - isExist = true + grLabels := gr.Labels + if grLabels == nil || len(grLabels) == 0 { + grLabels = make(map[string]string) } + grLabels["resources-update"] = "true" + gr.SetLabels(grLabels) + v.Spec.Context = gr.Spec.Context + v.Spec.Policy = gr.Spec.Policy + v.Spec.Resource = gr.Spec.Resource + + _, err = client.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Update(context.TODO(), v, metav1.UpdateOptions{}) + if err != nil { + return err + } + isExist = true } + if !isExist { gr.SetGenerateName("gr-") gr.SetLabels(map[string]string{ - "policyName": grSpec.Policy, - "resourceName": grSpec.Resource.Name, - "resourceKind": grSpec.Resource.Kind, - "ResourceNamespace": grSpec.Resource.Namespace, + "generate.kyverno.io/policy-name": grSpec.Policy, + "generate.kyverno.io/resource-name": grSpec.Resource.Name, + "generate.kyverno.io/resource-kind": grSpec.Resource.Kind, + "generate.kyverno.io/resource-namespace": grSpec.Resource.Namespace, }) _, err = client.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Create(context.TODO(), &gr, metav1.CreateOptions{}) if err != nil { diff --git a/pkg/webhooks/generation.go b/pkg/webhooks/generation.go index 03eef0bdc3..a11a44a76f 100644 --- a/pkg/webhooks/generation.go +++ b/pkg/webhooks/generation.go @@ -2,6 +2,7 @@ package webhooks import ( contextdefault "context" + "encoding/json" "fmt" "reflect" "sort" @@ -16,6 +17,7 @@ import ( "github.com/kyverno/kyverno/pkg/engine" "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/engine/response" + enginutils "github.com/kyverno/kyverno/pkg/engine/utils" "github.com/kyverno/kyverno/pkg/event" kyvernoutils "github.com/kyverno/kyverno/pkg/utils" "github.com/kyverno/kyverno/pkg/webhooks/generate" @@ -30,68 +32,122 @@ func (ws *WebhookServer) HandleGenerate(request *v1beta1.AdmissionRequest, polic logger := ws.log.WithValues("action", "generation", "uid", request.UID, "kind", request.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation) logger.V(4).Info("incoming request") var engineResponses []*response.EngineResponse + if request.Operation == v1beta1.Create || request.Operation == v1beta1.Update { + if len(policies) == 0 { + return + } + // convert RAW to unstructured + new, old, err := kyvernoutils.ExtractResources(nil, request) + if err != nil { + logger.Error(err, "failed to extract resource") + } - if len(policies) == 0 { - return - } - // convert RAW to unstructured - new, old, err := kyvernoutils.ExtractResources(nil, request) - if err != nil { - logger.Error(err, "failed to extract resource") - } + policyContext := engine.PolicyContext{ + NewResource: new, + OldResource: old, + AdmissionInfo: userRequestInfo, + ExcludeGroupRole: dynamicConfig.GetExcludeGroupRole(), + ExcludeResourceFunc: ws.configHandler.ToFilter, + ResourceCache: ws.resCache, + JSONContext: ctx, + } - policyContext := engine.PolicyContext{ - NewResource: new, - OldResource: old, - AdmissionInfo: userRequestInfo, - ExcludeGroupRole: dynamicConfig.GetExcludeGroupRole(), - ExcludeResourceFunc: ws.configHandler.ToFilter, - ResourceCache: ws.resCache, - JSONContext: ctx, - } - - for _, policy := range policies { - var rules []response.RuleResponse - policyContext.Policy = *policy - engineResponse := engine.Generate(policyContext) - for _, rule := range engineResponse.PolicyResponse.Rules { - if !rule.Success { - ws.deleteGR(logger, engineResponse) - continue + for _, policy := range policies { + var rules []response.RuleResponse + policyContext.Policy = *policy + engineResponse := engine.Generate(policyContext) + for _, rule := range engineResponse.PolicyResponse.Rules { + if !rule.Success { + ws.deleteGR(logger, engineResponse) + continue + } + rules = append(rules, rule) } - rules = append(rules, rule) + if len(rules) > 0 { + engineResponse.PolicyResponse.Rules = rules + // some generate rules do apply to the resource + engineResponses = append(engineResponses, engineResponse) + ws.statusListener.Update(generateStats{ + resp: engineResponse, + }) + } } - if len(rules) > 0 { - engineResponse.PolicyResponse.Rules = rules - // some generate rules do apply to the resource - engineResponses = append(engineResponses, engineResponse) - ws.statusListener.Update(generateStats{ - resp: engineResponse, - }) + // Adds Generate Request to a channel(queue size 1000) to generators + if failedResponse := applyGenerateRequest(ws.grGenerator, userRequestInfo, request.Operation, engineResponses...); err != nil { + // report failure event + for _, failedGR := range failedResponse { + events := failedEvents(fmt.Errorf("failed to create Generate Request: %v", failedGR.err), failedGR.gr, new) + ws.eventGen.Add(events...) + } } } - // Adds Generate Request to a channel(queue size 1000) to generators - if failedResponse := applyGenerateRequest(ws.grGenerator, userRequestInfo, request.Operation, engineResponses...); err != nil { - // report failure event - for _, failedGR := range failedResponse { - events := failedEvents(fmt.Errorf("failed to create Generate Request: %v", failedGR.err), failedGR.gr, new) - ws.eventGen.Add(events...) - } + if request.Operation == v1beta1.Update { + ws.handleUpdate(request) } return } +//HandleUpdate handles admission-requests for update +func (ws *WebhookServer) handleUpdate(request *v1beta1.AdmissionRequest) { + logger := ws.log.WithValues("action", "generation", "uid", request.UID, "kind", request.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation) + resource, err := enginutils.ConvertToUnstructured(request.OldObject.Raw) + if err != nil { + logger.Error(err, "failed to convert object resource to unstructured format") + } + + resLabels := resource.GetLabels() + if resLabels["generate.kyverno.io/clone-policy-name"] != "" { + policyNames := strings.Split(resLabels["generate.kyverno.io/clone-policy-name"], ",") + for _, policyName := range policyNames { + selector := labels.SelectorFromSet(labels.Set(map[string]string{ + "generate.kyverno.io/policy-name": policyName, + })) + + grList, err := ws.grLister.List(selector) + if err != nil { + logger.Error(err, "failed to get generate request for the resource", "label", "generate.kyverno.io/policy-name") + + } + for _, gr := range grList { + ws.grController.EnqueueGenerateRequestFromWebhook(gr) + } + } + } +} + +//HandleDelete handles admission-requests for delete +func (ws *WebhookServer) handleDelete(request *v1beta1.AdmissionRequest) { + logger := ws.log.WithValues("action", "generation", "uid", request.UID, "kind", request.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation) + resource, err := enginutils.ConvertToUnstructured(request.OldObject.Raw) + if err != nil { + logger.Error(err, "failed to convert object resource to unstructured format") + } + + r, _ := json.Marshal(resource) + fmt.Println(string(r)) + + resLabels := resource.GetLabels() + if resLabels["app.kubernetes.io/managed-by"] == "kyverno" && resLabels["policy.kyverno.io/synchronize"] == "enable" && request.Operation == v1beta1.Delete { + grName := resLabels["policy.kyverno.io/gr-name"] + gr, err := ws.grLister.Get(grName) + if err != nil { + logger.Error(err, "failed to get generate request", "name", grName) + } + ws.grController.EnqueueGenerateRequestFromWebhook(gr) + } +} + func (ws *WebhookServer) deleteGR(logger logr.Logger, engineResponse *response.EngineResponse) { logger.V(4).Info("querying all generate requests") selector := labels.SelectorFromSet(labels.Set(map[string]string{ - "policyName": engineResponse.PolicyResponse.Policy, - "resourceName": engineResponse.PolicyResponse.Resource.Name, - "resourceKind": engineResponse.PolicyResponse.Resource.Kind, - "ResourceNamespace": engineResponse.PolicyResponse.Resource.Namespace, + "generate.kyverno.io/policy-name": engineResponse.PolicyResponse.Policy, + "generate.kyverno.io/resource-name": engineResponse.PolicyResponse.Resource.Name, + "generate.kyverno.io/resource-kind": engineResponse.PolicyResponse.Resource.Kind, + "generate.kyverno.io/resource-namespace": engineResponse.PolicyResponse.Resource.Namespace, })) grList, err := ws.grLister.List(selector) diff --git a/pkg/webhooks/server.go b/pkg/webhooks/server.go index 00585f5bb3..600f067b37 100644 --- a/pkg/webhooks/server.go +++ b/pkg/webhooks/server.go @@ -20,6 +20,7 @@ import ( client "github.com/kyverno/kyverno/pkg/dclient" context2 "github.com/kyverno/kyverno/pkg/engine/context" "github.com/kyverno/kyverno/pkg/event" + "github.com/kyverno/kyverno/pkg/generate" "github.com/kyverno/kyverno/pkg/openapi" "github.com/kyverno/kyverno/pkg/policycache" "github.com/kyverno/kyverno/pkg/policyreport" @@ -29,7 +30,7 @@ import ( userinfo "github.com/kyverno/kyverno/pkg/userinfo" "github.com/kyverno/kyverno/pkg/utils" "github.com/kyverno/kyverno/pkg/webhookconfig" - "github.com/kyverno/kyverno/pkg/webhooks/generate" + webhookgenerate "github.com/kyverno/kyverno/pkg/webhooks/generate" v1beta1 "k8s.io/api/admission/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" rbacinformer "k8s.io/client-go/informers/rbac/v1" @@ -104,7 +105,7 @@ type WebhookServer struct { prGenerator policyreport.GeneratorInterface // generate request generator - grGenerator *generate.Generator + grGenerator *webhookgenerate.Generator auditHandler AuditHandler @@ -116,6 +117,8 @@ type WebhookServer struct { // resCache - controls creation and fetching of resource informer cache resCache resourcecache.ResourceCacheIface + + grController *generate.Controller } // NewWebhookServer creates new instance of WebhookServer accordingly to given configuration @@ -137,13 +140,14 @@ func NewWebhookServer( statusSync policystatus.Listener, configHandler config.Interface, prGenerator policyreport.GeneratorInterface, - grGenerator *generate.Generator, + grGenerator *webhookgenerate.Generator, auditHandler AuditHandler, supportMutateValidate bool, cleanUp chan<- struct{}, log logr.Logger, openAPIController *openapi.Controller, resCache resourcecache.ResourceCacheIface, + grc *generate.Controller, ) (*WebhookServer, error) { if tlsPair == nil { @@ -182,6 +186,7 @@ func NewWebhookServer( webhookMonitor: webhookMonitor, prGenerator: prGenerator, grGenerator: grGenerator, + grController: grc, auditHandler: auditHandler, log: log, openAPIController: openAPIController, @@ -372,6 +377,9 @@ func (ws *WebhookServer) ResourceMutation(request *v1beta1.AdmissionRequest) *v1 func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse { logger := ws.log.WithName("Validate").WithValues("uid", request.UID, "kind", request.Kind.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation) + if request.Operation == v1beta1.Delete { + ws.handleDelete(request) + } if !ws.supportMutateValidate { logger.Info("mutate and validate rules are not supported prior to Kubernetes 1.14.0")