diff --git a/main.go b/main.go index 70dc30fd7f..8afe006da9 100644 --- a/main.go +++ b/main.go @@ -52,11 +52,14 @@ func main() { // - PolicyVolation // - cache resync time: 30 seconds pInformer := kyvernoinformer.NewSharedInformerFactoryWithOptions(pclient, 30) + // EVENT GENERATOR + // - generate event with retry + egen := event.NewEventGenerator(client, pInformer.Kyverno().V1alpha1().Policies()) // POLICY CONTROLLER // - reconciliation policy and policy violation // - status: violation count - pc, err := policy.NewPolicyController(pclient, client, pInformer.Kyverno().V1alpha1().Policies(), pInformer.Kyverno().V1alpha1().PolicyViolations()) + pc, err := policy.NewPolicyController(pclient, client, pInformer.Kyverno().V1alpha1().Policies(), pInformer.Kyverno().V1alpha1().PolicyViolations(), egen) if err != nil { glog.Fatalf("error creating policy controller: %v\n", err) } @@ -68,10 +71,6 @@ func main() { glog.Fatalf("error creating policy violation controller: %v\n", err) } - // EVENT GENERATOR - // - generate event with retry - egen := event.NewEventGenerator(client, pInformer.Kyverno().V1alpha1().Policies()) - // TODO : Process Existing tlsPair, err := initTLSPemPair(clientConfig, client) if err != nil { diff --git a/pkg/policy/controller.go b/pkg/policy/controller.go index 8cee3e168d..26fc4401b9 100644 --- a/pkg/policy/controller.go +++ b/pkg/policy/controller.go @@ -14,6 +14,7 @@ import ( informer "github.com/nirmata/kyverno/pkg/clientNew/informers/externalversions/kyverno/v1alpha1" lister "github.com/nirmata/kyverno/pkg/clientNew/listers/kyverno/v1alpha1" client "github.com/nirmata/kyverno/pkg/dclient" + "github.com/nirmata/kyverno/pkg/event" "github.com/nirmata/kyverno/pkg/utils" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -46,6 +47,7 @@ var controllerKind = kyverno.SchemeGroupVersion.WithKind("Policy") type PolicyController struct { client *client.Client kyvernoClient *kyvernoclient.Clientset + eventGen event.Interface eventRecorder record.EventRecorder syncHandler func(pKey string) error enqueuePolicy func(policy *kyverno.Policy) @@ -69,7 +71,7 @@ type PolicyController struct { } // NewPolicyController create a new PolicyController -func NewPolicyController(kyvernoClient *kyvernoclient.Clientset, client *client.Client, pInformer informer.PolicyInformer, pvInformer informer.PolicyViolationInformer) (*PolicyController, error) { +func NewPolicyController(kyvernoClient *kyvernoclient.Clientset, client *client.Client, pInformer informer.PolicyInformer, pvInformer informer.PolicyViolationInformer, eventGen event.Interface) (*PolicyController, error) { // Event broad caster eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(glog.Infof) @@ -82,6 +84,7 @@ func NewPolicyController(kyvernoClient *kyvernoclient.Clientset, client *client. pc := PolicyController{ client: client, kyvernoClient: kyvernoClient, + eventGen: eventGen, eventRecorder: eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "policy_controller"}), queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "policy"), } @@ -397,8 +400,9 @@ func (pc *PolicyController) syncPolicy(key string) error { return err } // process policies on existing resources - pc.processExistingResources(*p) - + policyInfos := pc.processExistingResources(*p) + // report errors + pc.report(policyInfos) return pc.syncStatusOnly(p, pvList) } diff --git a/pkg/policy/existing.go b/pkg/policy/existing.go index 0879a306da..629ad0fb58 100644 --- a/pkg/policy/existing.go +++ b/pkg/policy/existing.go @@ -14,11 +14,11 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -func (pc *PolicyController) processExistingResources(policy kyverno.Policy) { +func (pc *PolicyController) processExistingResources(policy kyverno.Policy) []info.PolicyInfo { // Parse through all the resources // drops the cache after configured rebuild time pc.rm.Drop() - + var policyInfos []info.PolicyInfo // get resource that are satisfy the resource description defined in the rules resourceMap := listResources(pc.client, policy, pc.filterK8Resources) for _, resource := range resourceMap { @@ -29,10 +29,12 @@ func (pc *PolicyController) processExistingResources(policy kyverno.Policy) { } // apply the policy on each glog.V(4).Infof("apply policy %s with resource version %s on resource %s/%s/%s with resource version %s", policy.Name, policy.ResourceVersion, resource.GetKind(), resource.GetNamespace(), resource.GetName(), resource.GetResourceVersion()) - applyPolicyOnResource(policy, resource) + policyInfo := applyPolicyOnResource(policy, resource) + policyInfos = append(policyInfos, *policyInfo) // post-processing, register the resource as processed pc.rm.RegisterResource(policy.GetName(), policy.GetResourceVersion(), resource.GetKind(), resource.GetNamespace(), resource.GetName(), resource.GetResourceVersion()) } + return policyInfos } func applyPolicyOnResource(policy kyverno.Policy, resource unstructured.Unstructured) *info.PolicyInfo { diff --git a/pkg/policy/report.go b/pkg/policy/report.go new file mode 100644 index 0000000000..c232494cbc --- /dev/null +++ b/pkg/policy/report.go @@ -0,0 +1,60 @@ +package policy + +import ( + "fmt" + + "github.com/nirmata/kyverno/pkg/event" + "github.com/nirmata/kyverno/pkg/info" + "github.com/nirmata/kyverno/pkg/policyviolation" +) + +func (pc *PolicyController) report(policyInfos []info.PolicyInfo) { + // generate events + // generate policy violations + + for _, policyInfo := range policyInfos { + // events + // success - policy applied on resource + // failure - policy/rule failed to apply on the resource + reportPolicy(policyInfo, pc.eventGen) + // policy violations + // failure - policy/rule failed to apply on the resource + } + + // generate policy violation + policyviolation.GeneratePolicyViolations(pc.pvListerSynced, pc.pvLister, pc.kyvernoClient, policyInfos) + +} + +func reportPolicy(policyInfo info.PolicyInfo, eventGen event.Interface) { + + if policyInfo.IsSuccessful() { + return + } + + for _, rule := range policyInfo.Rules { + if rule.IsSuccessful() { + continue + } + + // generate event on resource for each failed rule + e := &event.Info{} + e.Kind = policyInfo.RKind + e.Namespace = policyInfo.RNamespace + e.Name = policyInfo.RName + e.Reason = "Failure" + e.Message = fmt.Sprintf("policy %s (%s) rule %s failed to apply. %v", policyInfo.Name, rule.RuleType.String(), rule.Name, rule.GetErrorString()) + eventGen.Add(e) + + // generate policy violation for each failed rule + + } + // generate a event on policy for all failed rules + e := &event.Info{} + e.Kind = "Policy" + e.Namespace = "" + e.Name = policyInfo.Name + e.Reason = "Failure" + e.Message = fmt.Sprintf("failed to apply rules %s on resource %s/%s/%s", policyInfo.FailedRules(), policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName) + eventGen.Add(e) +} diff --git a/pkg/policyviolation/helpers.go b/pkg/policyviolation/helpers.go index cff6e6f0c6..ae62e87b5a 100644 --- a/pkg/policyviolation/helpers.go +++ b/pkg/policyviolation/helpers.go @@ -1,7 +1,16 @@ package policyviolation import ( + "fmt" + "reflect" + + "github.com/golang/glog" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1" + kyvernoclient "github.com/nirmata/kyverno/pkg/clientNew/clientset/versioned" + lister "github.com/nirmata/kyverno/pkg/clientNew/listers/kyverno/v1alpha1" + "github.com/nirmata/kyverno/pkg/info" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/cache" ) //BuildPolicyViolation returns an value of type PolicyViolation @@ -18,3 +27,116 @@ func BuildPolicyViolation(policy string, resource kyverno.ResourceSpec, fRules [ pv.SetGenerateName("pv-") return pv } + +// buildPolicyViolationsForAPolicy returns a policy violation object if there are any rules that fail +func buildPolicyViolationsForAPolicy(pi info.PolicyInfo) kyverno.PolicyViolation { + var fRules []kyverno.ViolatedRule + var pv kyverno.PolicyViolation + for _, r := range pi.Rules { + if !r.IsSuccessful() { + fRules = append(fRules, kyverno.ViolatedRule{Name: r.Name, Message: r.GetErrorString(), Type: r.RuleType.String()}) + } + } + if len(fRules) > 0 { + glog.V(4).Infof("building policy violation for policy %s on resource %s/%s/%s", pi.Name, pi.RKind, pi.RNamespace, pi.RName) + // there is an error + pv = BuildPolicyViolation(pi.Name, kyverno.ResourceSpec{ + Kind: pi.RKind, + Namespace: pi.RNamespace, + Name: pi.RName, + }, + fRules, + ) + + } + return pv +} + +//generatePolicyViolations generate policyViolation resources for the rules that failed +//TODO: check if pvListerSynced is needed +func GeneratePolicyViolations(pvListerSynced cache.InformerSynced, pvLister lister.PolicyViolationLister, client *kyvernoclient.Clientset, policyInfos []info.PolicyInfo) { + var pvs []kyverno.PolicyViolation + for _, policyInfo := range policyInfos { + if !policyInfo.IsSuccessful() { + if pv := buildPolicyViolationsForAPolicy(policyInfo); !reflect.DeepEqual(pv, kyverno.PolicyViolation{}) { + pvs = append(pvs, pv) + } + } + } + + if len(pvs) > 0 { + for _, newPv := range pvs { + // generate PolicyViolation objects + glog.V(4).Infof("creating policyViolation resource for policy %s and resource %s/%s/%s", newPv.Spec.Policy, newPv.Spec.Kind, newPv.Spec.Namespace, newPv.Spec.Name) + + // check if there was a previous violation for policy & resource combination + curPv, err := getExistingPolicyViolationIfAny(pvListerSynced, pvLister, newPv) + if err != nil { + continue + } + if curPv == nil { + // no existing policy violation, create a new one + _, err := client.KyvernoV1alpha1().PolicyViolations().Create(&newPv) + if err != nil { + glog.Error(err) + } + continue + } + // compare the policyviolation spec for existing resource if present else + if reflect.DeepEqual(curPv.Spec, newPv.Spec) { + // if they are equal there has been no change so dont update the polivy violation + glog.Infof("policy violation spec %v did not change so not updating it", newPv.Spec) + continue + } + // spec changed so update the policyviolation + //TODO: wont work, as name is not defined yet + _, err = client.KyvernoV1alpha1().PolicyViolations().Update(&newPv) + if err != nil { + glog.Error(err) + continue + } + } + } +} + +//TODO: change the name +func getExistingPolicyViolationIfAny(pvListerSynced cache.InformerSynced, pvLister lister.PolicyViolationLister, newPv kyverno.PolicyViolation) (*kyverno.PolicyViolation, error) { + // TODO: check for existing ov using label selectors on resource and policy + labelMap := map[string]string{"policy": newPv.Spec.Policy, "resource": newPv.Spec.ResourceSpec.ToKey()} + ls := &metav1.LabelSelector{} + err := metav1.Convert_Map_string_To_string_To_v1_LabelSelector(&labelMap, ls, nil) + if err != nil { + glog.Errorf("failed to generate label sector of Policy name %s: %v", newPv.Spec.Policy, err) + return nil, err + } + policyViolationSelector, err := metav1.LabelSelectorAsSelector(ls) + if err != nil { + glog.Errorf("invalid label selector: %v", err) + return nil, err + } + + //TODO: sync the cache before reading from it ? + // check is this is needed ? + // stopCh := make(chan struct{}, 0) + // if !cache.WaitForCacheSync(stopCh, pvListerSynced) { + // //TODO: can this be handled or avoided ? + // glog.Info("unable to sync policy violation shared informer cache, might be out of sync") + // } + + pvs, err := pvLister.List(policyViolationSelector) + if err != nil { + glog.Errorf("unable to list policy violations with label selector %v: %v", policyViolationSelector, err) + return nil, err + } + //TODO: ideally there should be only one policy violation returned + if len(pvs) > 1 { + glog.Errorf("more than one policy violation exists with labels %v", labelMap) + return nil, fmt.Errorf("more than one policy violation exists with labels %v", labelMap) + } + + if len(pvs) == 0 { + glog.Infof("policy violation does not exist with labels %v", labelMap) + return nil, nil + } + return pvs[0], nil +} diff --git a/pkg/webhooks/report.go b/pkg/webhooks/report.go index c81f867a03..d9b8042834 100644 --- a/pkg/webhooks/report.go +++ b/pkg/webhooks/report.go @@ -1,22 +1,14 @@ package webhooks import ( - "fmt" - "reflect" "strings" "github.com/nirmata/kyverno/pkg/annotations" - kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1" - kyvernoclient "github.com/nirmata/kyverno/pkg/clientNew/clientset/versioned" - lister "github.com/nirmata/kyverno/pkg/clientNew/listers/kyverno/v1alpha1" "github.com/nirmata/kyverno/pkg/violation" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/golang/glog" "github.com/nirmata/kyverno/pkg/event" "github.com/nirmata/kyverno/pkg/info" - "github.com/nirmata/kyverno/pkg/policyviolation" - "k8s.io/client-go/tools/cache" ) //TODO: change validation from bool -> enum(validation, mutation) @@ -101,114 +93,115 @@ func buildAnnotation(mAnn map[string]string, pi *info.PolicyInfo) { } } -// buildPolicyViolationsForAPolicy returns a policy violation object if there are any rules that fail -func buildPolicyViolationsForAPolicy(pi info.PolicyInfo) kyverno.PolicyViolation { - var fRules []kyverno.ViolatedRule - var pv kyverno.PolicyViolation - for _, r := range pi.Rules { - if !r.IsSuccessful() { - fRules = append(fRules, kyverno.ViolatedRule{Name: r.Name, Message: r.GetErrorString(), Type: r.RuleType.String()}) - } - } - if len(fRules) > 0 { - glog.V(4).Infof("building policy violation for policy %s on resource %s/%s/%s", pi.Name, pi.RKind, pi.RNamespace, pi.RName) - // there is an error - pv = policyviolation.BuildPolicyViolation(pi.Name, kyverno.ResourceSpec{ - Kind: pi.RKind, - Namespace: pi.RNamespace, - Name: pi.RName, - }, - fRules, - ) +// // buildPolicyViolationsForAPolicy returns a policy violation object if there are any rules that fail +// func buildPolicyViolationsForAPolicy(pi info.PolicyInfo) kyverno.PolicyViolation { +// var fRules []kyverno.ViolatedRule +// var pv kyverno.PolicyViolation +// for _, r := range pi.Rules { +// if !r.IsSuccessful() { +// fRules = append(fRules, kyverno.ViolatedRule{Name: r.Name, Message: r.GetErrorString(), Type: r.RuleType.String()}) +// } +// } +// if len(fRules) > 0 { +// glog.V(4).Infof("building policy violation for policy %s on resource %s/%s/%s", pi.Name, pi.RKind, pi.RNamespace, pi.RName) +// // there is an error +// pv = policyviolation.BuildPolicyViolation(pi.Name, kyverno.ResourceSpec{ +// Kind: pi.RKind, +// Namespace: pi.RNamespace, +// Name: pi.RName, +// }, +// fRules, +// ) - } - return pv -} +// } +// return pv +// } -//generatePolicyViolations generate policyViolation resources for the rules that failed -func generatePolicyViolations(pvListerSynced cache.InformerSynced, pvLister lister.PolicyViolationLister, client *kyvernoclient.Clientset, policyInfos []info.PolicyInfo) { - var pvs []kyverno.PolicyViolation - for _, policyInfo := range policyInfos { - if !policyInfo.IsSuccessful() { - if pv := buildPolicyViolationsForAPolicy(policyInfo); !reflect.DeepEqual(pv, kyverno.PolicyViolation{}) { - pvs = append(pvs, pv) - } - } - } +// //generatePolicyViolations generate policyViolation resources for the rules that failed +// //TODO: check if pvListerSynced is needed +// func generatePolicyViolations(pvListerSynced cache.InformerSynced, pvLister lister.PolicyViolationLister, client *kyvernoclient.Clientset, policyInfos []info.PolicyInfo) { +// var pvs []kyverno.PolicyViolation +// for _, policyInfo := range policyInfos { +// if !policyInfo.IsSuccessful() { +// if pv := buildPolicyViolationsForAPolicy(policyInfo); !reflect.DeepEqual(pv, kyverno.PolicyViolation{}) { +// pvs = append(pvs, pv) +// } +// } +// } - if len(pvs) > 0 { - for _, newPv := range pvs { - // generate PolicyViolation objects - glog.V(4).Infof("creating policyViolation resource for policy %s and resource %s/%s/%s", newPv.Spec.Policy, newPv.Spec.Kind, newPv.Spec.Namespace, newPv.Spec.Name) +// if len(pvs) > 0 { +// for _, newPv := range pvs { +// // generate PolicyViolation objects +// glog.V(4).Infof("creating policyViolation resource for policy %s and resource %s/%s/%s", newPv.Spec.Policy, newPv.Spec.Kind, newPv.Spec.Namespace, newPv.Spec.Name) - // check if there was a previous violation for policy & resource combination - curPv, err := getExistingPolicyViolationIfAny(pvListerSynced, pvLister, newPv) - if err != nil { - continue - } - if curPv == nil { - // no existing policy violation, create a new one - _, err := client.KyvernoV1alpha1().PolicyViolations().Create(&newPv) - if err != nil { - glog.Error(err) - } - continue - } - // compare the policyviolation spec for existing resource if present else - if reflect.DeepEqual(curPv.Spec, newPv.Spec) { - // if they are equal there has been no change so dont update the polivy violation - glog.Infof("policy violation spec %v did not change so not updating it", newPv.Spec) - continue - } - // spec changed so update the policyviolation - //TODO: wont work, as name is not defined yet - _, err = client.KyvernoV1alpha1().PolicyViolations().Update(&newPv) - if err != nil { - glog.Error(err) - continue - } - } - } -} +// // check if there was a previous violation for policy & resource combination +// curPv, err := getExistingPolicyViolationIfAny(pvListerSynced, pvLister, newPv) +// if err != nil { +// continue +// } +// if curPv == nil { +// // no existing policy violation, create a new one +// _, err := client.KyvernoV1alpha1().PolicyViolations().Create(&newPv) +// if err != nil { +// glog.Error(err) +// } +// continue +// } +// // compare the policyviolation spec for existing resource if present else +// if reflect.DeepEqual(curPv.Spec, newPv.Spec) { +// // if they are equal there has been no change so dont update the polivy violation +// glog.Infof("policy violation spec %v did not change so not updating it", newPv.Spec) +// continue +// } +// // spec changed so update the policyviolation +// //TODO: wont work, as name is not defined yet +// _, err = client.KyvernoV1alpha1().PolicyViolations().Update(&newPv) +// if err != nil { +// glog.Error(err) +// continue +// } +// } +// } +// } -//TODO: change the name -func getExistingPolicyViolationIfAny(pvListerSynced cache.InformerSynced, pvLister lister.PolicyViolationLister, newPv kyverno.PolicyViolation) (*kyverno.PolicyViolation, error) { - // TODO: check for existing ov using label selectors on resource and policy - labelMap := map[string]string{"policy": newPv.Spec.Policy, "resource": newPv.Spec.ResourceSpec.ToKey()} - ls := &metav1.LabelSelector{} - err := metav1.Convert_Map_string_To_string_To_v1_LabelSelector(&labelMap, ls, nil) - if err != nil { - glog.Errorf("failed to generate label sector of Policy name %s: %v", newPv.Spec.Policy, err) - return nil, err - } - policyViolationSelector, err := metav1.LabelSelectorAsSelector(ls) - if err != nil { - glog.Errorf("invalid label selector: %v", err) - return nil, err - } +// //TODO: change the name +// func getExistingPolicyViolationIfAny(pvListerSynced cache.InformerSynced, pvLister lister.PolicyViolationLister, newPv kyverno.PolicyViolation) (*kyverno.PolicyViolation, error) { +// // TODO: check for existing ov using label selectors on resource and policy +// labelMap := map[string]string{"policy": newPv.Spec.Policy, "resource": newPv.Spec.ResourceSpec.ToKey()} +// ls := &metav1.LabelSelector{} +// err := metav1.Convert_Map_string_To_string_To_v1_LabelSelector(&labelMap, ls, nil) +// if err != nil { +// glog.Errorf("failed to generate label sector of Policy name %s: %v", newPv.Spec.Policy, err) +// return nil, err +// } +// policyViolationSelector, err := metav1.LabelSelectorAsSelector(ls) +// if err != nil { +// glog.Errorf("invalid label selector: %v", err) +// return nil, err +// } - //TODO: sync the cache before reading from it ? - // check is this is needed ? - // stopCh := make(chan struct{}, 0) - // if !cache.WaitForCacheSync(stopCh, pvListerSynced) { - // //TODO: can this be handled or avoided ? - // glog.Info("unable to sync policy violation shared informer cache, might be out of sync") - // } +// //TODO: sync the cache before reading from it ? +// // check is this is needed ? +// // stopCh := make(chan struct{}, 0) +// // if !cache.WaitForCacheSync(stopCh, pvListerSynced) { +// // //TODO: can this be handled or avoided ? +// // glog.Info("unable to sync policy violation shared informer cache, might be out of sync") +// // } - pvs, err := pvLister.List(policyViolationSelector) - if err != nil { - glog.Errorf("unable to list policy violations with label selector %v: %v", policyViolationSelector, err) - return nil, err - } - //TODO: ideally there should be only one policy violation returned - if len(pvs) > 1 { - glog.Errorf("more than one policy violation exists with labels %v", labelMap) - return nil, fmt.Errorf("more than one policy violation exists with labels %v", labelMap) - } +// pvs, err := pvLister.List(policyViolationSelector) +// if err != nil { +// glog.Errorf("unable to list policy violations with label selector %v: %v", policyViolationSelector, err) +// return nil, err +// } +// //TODO: ideally there should be only one policy violation returned +// if len(pvs) > 1 { +// glog.Errorf("more than one policy violation exists with labels %v", labelMap) +// return nil, fmt.Errorf("more than one policy violation exists with labels %v", labelMap) +// } - if len(pvs) == 0 { - glog.Infof("policy violation does not exist with labels %v", labelMap) - return nil, nil - } - return pvs[0], nil -} +// if len(pvs) == 0 { +// glog.Infof("policy violation does not exist with labels %v", labelMap) +// return nil, nil +// } +// return pvs[0], nil +// } diff --git a/pkg/webhooks/validation.go b/pkg/webhooks/validation.go index 51514c8740..e5915e4eaf 100644 --- a/pkg/webhooks/validation.go +++ b/pkg/webhooks/validation.go @@ -4,6 +4,7 @@ import ( "github.com/golang/glog" engine "github.com/nirmata/kyverno/pkg/engine" "github.com/nirmata/kyverno/pkg/info" + "github.com/nirmata/kyverno/pkg/policyviolation" v1beta1 "k8s.io/api/admission/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -95,7 +96,7 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1 } // ADD POLICY VIOLATIONS - generatePolicyViolations(ws.pvListerSynced, ws.pvLister, ws.kyvernoClient, policyInfos) + policyviolation.GeneratePolicyViolations(ws.pvListerSynced, ws.pvLister, ws.kyvernoClient, policyInfos) return &v1beta1.AdmissionResponse{ Allowed: true,