From cdc17f999da0b6ebebde8add317abca7a9946f58 Mon Sep 17 00:00:00 2001 From: shivkumar dudhani Date: Tue, 23 Jul 2019 03:07:11 -0400 Subject: [PATCH] process existing for generate & annotations --- main.go | 2 +- pkg/controller/controller.go | 19 +++++++++++++- pkg/engine/engine.go | 25 +++++++++--------- pkg/engine/generation_new.go | 13 +++++----- pkg/engine/utils.go | 2 +- pkg/engine/validation.go | 1 + pkg/gencontroller/controller.go | 36 ++++++++++++++------------ pkg/gencontroller/generation.go | 46 ++++++++++++++++++++++++++++++++- 8 files changed, 105 insertions(+), 39 deletions(-) diff --git a/main.go b/main.go index d3fb1a6a8f..38effc7cdc 100644 --- a/main.go +++ b/main.go @@ -51,7 +51,7 @@ func main() { eventController, annotationsController) - genControler := gencontroller.NewGenController(client, eventController, policyInformerFactory, violationBuilder, kubeInformer.Core().V1().Namespaces()) + genControler := gencontroller.NewGenController(client, eventController, policyInformerFactory, violationBuilder, kubeInformer.Core().V1().Namespaces(), annotationsController) tlsPair, err := initTLSPemPair(clientConfig, client) if err != nil { glog.Fatalf("Failed to initialize TLS key/certificate pair: %v\n", err) diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index d66469c946..655b43cb60 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -225,7 +225,14 @@ func (pc *PolicyController) createAnnotations(policyInfos []*info.PolicyInfo) { if err != nil { glog.Error(err) } - if mpatch == nil && vpatch == nil { + + // Generation rules + ann, gpatch, err := annotations.AddPolicyJSONPatch(ann, pi, info.Validation) + if err != nil { + glog.Error(err) + } + + if mpatch == nil && vpatch == nil && gpatch == nil { //nothing to patch continue } @@ -237,11 +244,21 @@ func (pc *PolicyController) createAnnotations(policyInfos []*info.PolicyInfo) { continue } } + if mpatch == nil { patch = vpatch } else { patch = mpatch } + + // generation + if gpatch != nil { + patch, err = jsonpatch.MergePatch(patch, gpatch) + if err != nil { + glog.Error(err) + continue + } + } // add the anotation to the resource _, err = pc.client.PatchResource(pi.RKind, pi.RNamespace, pi.RName, patch) if err != nil { diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index e3e67a13ee..82cc53d462 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -10,22 +10,14 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// As the logic to process the policies in stateless, we do not need to define struct and implement behaviors for it -// Instead we expose them as standalone functions passing the required atrributes -// The each function returns the changes that need to be applied on the resource -// the caller is responsible to apply the changes to the resource // ProcessExisting checks for mutation and validation violations of existing resources func ProcessExisting(client *client.Client, policy *types.Policy) []*info.PolicyInfo { glog.Infof("Applying policy %s on existing resources", policy.Name) // key uid - resourceMap := map[string]*resourceInfo{} + resourceMap := map[string]resourceInfo{} for _, rule := range policy.Spec.Rules { for _, k := range rule.Kinds { - if k == "Namespace" { - // REWORK: will be handeled by namespace controller - continue - } // kind -> resource gvr := client.DiscoveryClient.GetGVRFromKind(k) // label selectors @@ -34,6 +26,10 @@ func ProcessExisting(client *client.Client, policy *types.Policy) []*info.Policy if rule.ResourceDescription.Namespace != nil { namespace = *rule.ResourceDescription.Namespace } + if k == "Namespace" { + namespace = "" + } + list, err := client.ListResource(k, namespace, rule.ResourceDescription.Selector) if err != nil { glog.Errorf("unable to list resource for %s with label selector %s", gvr.Resource, rule.Selector.String()) @@ -49,7 +45,7 @@ func ProcessExisting(client *client.Client, policy *types.Policy) []*info.Policy continue } } - ri := &resourceInfo{resource: &res, gvk: &metav1.GroupVersionKind{Group: gvk.Group, + ri := resourceInfo{resource: res, gvk: &metav1.GroupVersionKind{Group: gvk.Group, Version: gvk.Version, Kind: gvk.Kind}} @@ -74,7 +70,7 @@ func ProcessExisting(client *client.Client, policy *types.Policy) []*info.Policy return policyInfos } -func applyPolicy(client *client.Client, policy *types.Policy, res *resourceInfo) (*info.PolicyInfo, error) { +func applyPolicy(client *client.Client, policy *types.Policy, res resourceInfo) (*info.PolicyInfo, error) { policyInfo := info.NewPolicyInfo(policy.Name, res.gvk.Kind, res.resource.GetName(), res.resource.GetNamespace(), policy.Spec.ValidationFailureAction) glog.Infof("Applying policy %s with %d rules\n", policy.ObjectMeta.Name, len(policy.Spec.Rules)) rawResource, err := res.resource.MarshalJSON() @@ -93,7 +89,12 @@ func applyPolicy(client *client.Client, policy *types.Policy, res *resourceInfo) if err != nil { return nil, err } - // Generate rule managed by generation controller + if res.gvk.Kind == "Namespace" { + + // Generation + gruleInfos := GenerateNew(client, policy, res.resource) + policyInfo.AddRuleInfos(gruleInfos) + } return policyInfo, nil } diff --git a/pkg/engine/generation_new.go b/pkg/engine/generation_new.go index f67df2927f..c99fe8a6eb 100644 --- a/pkg/engine/generation_new.go +++ b/pkg/engine/generation_new.go @@ -9,13 +9,12 @@ import ( client "github.com/nirmata/kyverno/pkg/dclient" "github.com/nirmata/kyverno/pkg/info" "github.com/nirmata/kyverno/pkg/utils" - corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" ) //GenerateNew apply generation rules on a resource -func GenerateNew(client *client.Client, policy *v1alpha1.Policy, ns *corev1.Namespace) []*info.RuleInfo { +func GenerateNew(client *client.Client, policy *v1alpha1.Policy, ns unstructured.Unstructured) []*info.RuleInfo { ris := []*info.RuleInfo{} for _, rule := range policy.Spec.Rules { if rule.Generation == nil { @@ -35,14 +34,14 @@ func GenerateNew(client *client.Client, policy *v1alpha1.Policy, ns *corev1.Name return ris } -func applyRuleGeneratorNew(client *client.Client, ns *corev1.Namespace, gen *v1alpha1.Generation) error { +func applyRuleGeneratorNew(client *client.Client, ns unstructured.Unstructured, gen *v1alpha1.Generation) error { var err error resource := &unstructured.Unstructured{} var rdata map[string]interface{} if gen.Data != nil { // 1> Check if resource exists - obj, err := client.GetResource(gen.Kind, ns.Name, gen.Name) + obj, err := client.GetResource(gen.Kind, ns.GetName(), gen.Name) if err == nil { // 2> If already exsists, then verify the content is contained // found the resource @@ -64,7 +63,7 @@ func applyRuleGeneratorNew(client *client.Client, ns *corev1.Namespace, gen *v1a } if gen.Clone != nil { // 1> Check if resource exists - _, err := client.GetResource(gen.Kind, ns.Name, gen.Name) + _, err := client.GetResource(gen.Kind, ns.GetName(), gen.Name) if err == nil { return nil } @@ -77,11 +76,11 @@ func applyRuleGeneratorNew(client *client.Client, ns *corev1.Namespace, gen *v1a } resource.SetUnstructuredContent(rdata) resource.SetName(gen.Name) - resource.SetNamespace(ns.Name) + resource.SetNamespace(ns.GetName()) // Reset resource version resource.SetResourceVersion("") - _, err = client.CreateResource(gen.Kind, ns.Name, resource, false) + _, err = client.CreateResource(gen.Kind, ns.GetName(), resource, false) if err != nil { return err } diff --git a/pkg/engine/utils.go b/pkg/engine/utils.go index 7b76171adf..265ec93118 100644 --- a/pkg/engine/utils.go +++ b/pkg/engine/utils.go @@ -253,6 +253,6 @@ func convertToFloat(value interface{}) (float64, error) { } type resourceInfo struct { - resource *unstructured.Unstructured + resource unstructured.Unstructured gvk *metav1.GroupVersionKind } diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go index 832feffb35..8bc2df366b 100644 --- a/pkg/engine/validation.go +++ b/pkg/engine/validation.go @@ -197,6 +197,7 @@ func valFromReferenceToString(value interface{}, operator string) (string, error } } +//FormAbsolutePath returns absolute path func FormAbsolutePath(referencePath, absolutePath string) string { if filepath.IsAbs(referencePath) { return referencePath diff --git a/pkg/gencontroller/controller.go b/pkg/gencontroller/controller.go index f4853cd958..157e65b15b 100644 --- a/pkg/gencontroller/controller.go +++ b/pkg/gencontroller/controller.go @@ -7,6 +7,7 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "github.com/golang/glog" + "github.com/nirmata/kyverno/pkg/annotations" policyLister "github.com/nirmata/kyverno/pkg/client/listers/policy/v1alpha1" client "github.com/nirmata/kyverno/pkg/dclient" "github.com/nirmata/kyverno/pkg/event" @@ -22,13 +23,14 @@ import ( //Controller watches the 'Namespace' resource creation/update and applied the generation rules on them type Controller struct { - client *client.Client - namespaceLister v1CoreLister.NamespaceLister - namespaceSynced cache.InformerSynced - policyLister policyLister.PolicyLister - eventController event.Generator - violationBuilder violation.Generator - workqueue workqueue.RateLimitingInterface + client *client.Client + namespaceLister v1CoreLister.NamespaceLister + namespaceSynced cache.InformerSynced + policyLister policyLister.PolicyLister + eventController event.Generator + violationBuilder violation.Generator + annotationsController annotations.Controller + workqueue workqueue.RateLimitingInterface } //NewGenController returns a new Controller to manage generation rules @@ -36,17 +38,19 @@ func NewGenController(client *client.Client, eventController event.Generator, policyInformer policySharedInformer.PolicyInformer, violationBuilder violation.Generator, - namespaceInformer v1Informer.NamespaceInformer) *Controller { + namespaceInformer v1Informer.NamespaceInformer, + annotationsController annotations.Controller) *Controller { // create the controller controller := &Controller{ - client: client, - namespaceLister: namespaceInformer.Lister(), - namespaceSynced: namespaceInformer.Informer().HasSynced, - policyLister: policyInformer.GetLister(), - eventController: eventController, - violationBuilder: violationBuilder, - workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), wqNamespace), + client: client, + namespaceLister: namespaceInformer.Lister(), + namespaceSynced: namespaceInformer.Informer().HasSynced, + policyLister: policyInformer.GetLister(), + eventController: eventController, + violationBuilder: violationBuilder, + annotationsController: annotationsController, + workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), wqNamespace), } namespaceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: controller.createNamespaceHandler, @@ -77,7 +81,7 @@ func (c *Controller) enqueueNamespace(obj interface{}) { func (c *Controller) Run(stopCh <-chan struct{}) error { if ok := cache.WaitForCacheSync(stopCh, c.namespaceSynced); !ok { - return fmt.Errorf("faield to wait for caches to sync") + return fmt.Errorf("failed to wait for caches to sync") } for i := 0; i < workerCount; i++ { diff --git a/pkg/gencontroller/generation.go b/pkg/gencontroller/generation.go index c86b408294..a0adf99cc6 100644 --- a/pkg/gencontroller/generation.go +++ b/pkg/gencontroller/generation.go @@ -4,7 +4,10 @@ import ( "fmt" "strings" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "github.com/golang/glog" + "github.com/nirmata/kyverno/pkg/annotations" v1alpha1 "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1" "github.com/nirmata/kyverno/pkg/engine" event "github.com/nirmata/kyverno/pkg/event" @@ -12,6 +15,7 @@ import ( violation "github.com/nirmata/kyverno/pkg/violation" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" ) func (c *Controller) processNamespace(ns *corev1.Namespace) error { @@ -62,9 +66,19 @@ func (c *Controller) processPolicy(ns *corev1.Namespace, p *v1alpha1.Policy) { "", p.Spec.ValidationFailureAction) // Namespace has no namespace..WOW - ruleInfos := engine.GenerateNew(c.client, p, ns) + // convert to unstructured + unstrMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(ns) + if err != nil { + glog.Error(err) + return + } + unstObj := unstructured.Unstructured{Object: unstrMap} + ruleInfos := engine.GenerateNew(c.client, p, unstObj) policyInfo.AddRuleInfos(ruleInfos) + // generate annotations + c.createAnnotations(policyInfo) + if !policyInfo.IsSuccessful() { glog.Infof("Failed to apply policy %s on resource %s %s", p.Name, ns.Kind, ns.Name) for _, r := range ruleInfos { @@ -78,9 +92,11 @@ func (c *Controller) processPolicy(ns *corev1.Namespace, p *v1alpha1.Policy) { if onViolation { glog.Infof("Adding violation for generation rule of policy %s\n", policyInfo.Name) + // Policy Violation v := violation.BuldNewViolation(policyInfo.Name, policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName, event.PolicyViolation.String(), policyInfo.GetFailedRules()) c.violationBuilder.Add(v) } else { + // Event eventInfo = event.NewEvent(policyKind, "", policyInfo.Name, event.RequestBlocked, event.FPolicyApplyBlockCreate, policyInfo.RName, policyInfo.GetRuleNames(false)) @@ -100,3 +116,31 @@ func (c *Controller) processPolicy(ns *corev1.Namespace, p *v1alpha1.Policy) { c.eventController.Add(eventInfo) } + +func (c *Controller) createAnnotations(pi *info.PolicyInfo) { + //get resource + obj, err := c.client.GetResource(pi.RKind, pi.RNamespace, pi.RName) + if err != nil { + glog.Error(err) + return + } + // add annotation for policy application + ann := obj.GetAnnotations() + // Generation rules + ann, gpatch, err := annotations.AddPolicyJSONPatch(ann, pi, info.Mutation) + if err != nil { + glog.Error(err) + return + } + if gpatch == nil { + // nothing to patch + return + } + + // add the anotation to the resource + _, err = c.client.PatchResource(pi.RKind, pi.RNamespace, pi.RName, gpatch) + if err != nil { + glog.Error(err) + return + } +}