From 0aae6a5276807cb0de910afa1b26a3473f1aefff Mon Sep 17 00:00:00 2001 From: Shuting Zhao Date: Fri, 23 Oct 2020 18:27:55 -0700 Subject: [PATCH] generate report request --- pkg/policyreport/builder.go | 135 ++++++++++++++---- pkg/policyreport/common.go | 47 ------ .../{generator.go => reportrequest.go} | 90 +++++++++++- 3 files changed, 196 insertions(+), 76 deletions(-) rename pkg/policyreport/{generator.go => reportrequest.go} (65%) diff --git a/pkg/policyreport/builder.go b/pkg/policyreport/builder.go index 82156d864c..7d09dcbb01 100755 --- a/pkg/policyreport/builder.go +++ b/pkg/policyreport/builder.go @@ -9,6 +9,14 @@ import ( report "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha1" "github.com/kyverno/kyverno/pkg/common" "github.com/kyverno/kyverno/pkg/engine/response" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + +const ( + clusterreportrequest = "clusterreportrequest" ) //GeneratePRsFromEngineResponse generate Violations from engine responses @@ -32,10 +40,10 @@ func GeneratePRsFromEngineResponse(ers []response.EngineResponse, log logr.Logge return pvInfos } -// Builder builds Policy Violation struct -// this is base type of namespaced and cluster policy violation +// Builder builds report request struct +// this is base type of namespaced and cluster policy report type Builder interface { - build(info Info) + build(info Info) (*unstructured.Unstructured, error) } type requestBuilder struct{} @@ -43,35 +51,110 @@ type requestBuilder struct{} func NewBuilder() *requestBuilder { return &requestBuilder{} } +func (pvb *requestBuilder) build(info Info) (*unstructured.Unstructured, error) { + results := []*report.PolicyReportResult{} + for _, rule := range info.Rules { + result := &report.PolicyReportResult{ + Policy: info.PolicyName, + Resources: []*v1.ObjectReference{ + { + Kind: info.Resource.GetKind(), + Namespace: info.Resource.GetNamespace(), + APIVersion: info.Resource.GetAPIVersion(), + Name: info.Resource.GetName(), + UID: info.Resource.GetUID(), + }, + }, + Scored: true, + } -func (pvb *requestBuilder) Generate(info Info) kyverno.PolicyViolationTemplate { - pv := pvb.build(info.PolicyName, info.Resource.GetKind(), info.Resource.GetNamespace(), info.Resource.GetName(), info.Rules) - return *pv + result.Rule = rule.Name + result.Message = rule.Message + result.Status = report.PolicyStatus(rule.Check) + results = append(results, result) + } + + ns := info.Resource.GetNamespace() + if ns != "" { + rr := &report.ReportRequest{ + Summary: calculateSummary(results), + Results: results, + } + + obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(rr) + if err != nil { + return nil, err + } + + req := &unstructured.Unstructured{Object: obj} + kind, apiversion := rr.GetObjectKind().GroupVersionKind().ToAPIVersionAndKind() + set(req, kind, apiversion, fmt.Sprintf("reportrequest-%s-%s", info.PolicyName, info.Resource.GetName()), info) + return req, nil + } + + rr := &report.ClusterPolicyReport{ + Summary: calculateSummary(results), + Results: results, + } + + obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(rr) + if err != nil { + return nil, err + } + req := &unstructured.Unstructured{Object: obj} + kind, apiversion := rr.GetObjectKind().GroupVersionKind().ToAPIVersionAndKind() + set(req, kind, apiversion, fmt.Sprintf("%s-%s", clusterreportrequest, info.Resource.GetName()), info) + return req, nil } -func (pvb *requestBuilder) build(policy, kind, namespace, name string, rules []kyverno.ViolatedRule) *kyverno.PolicyViolationTemplate { +func set(obj *unstructured.Unstructured, kind, apiversion, name string, info Info) { + resource := info.Resource + obj.SetName(name) + obj.SetNamespace(resource.GetNamespace()) + obj.SetKind(kind) + obj.SetAPIVersion(apiversion) - pv := &kyverno.PolicyViolationTemplate{ - Spec: kyverno.PolicyViolationSpec{ - Policy: policy, - ResourceSpec: kyverno.ResourceSpec{ - Kind: kind, - Name: name, - Namespace: namespace, - }, - ViolatedRules: rules, + obj.SetLabels(map[string]string{ + "policy": info.PolicyName, + "resource": resource.GetKind() + "-" + resource.GetName(), + }) + + if info.FromSync { + obj.SetAnnotations(map[string]string{ + "fromSync": "true", + }) + } + + controllerFlag := true + blockOwnerDeletionFlag := true + obj.SetOwnerReferences([]metav1.OwnerReference{ + { + APIVersion: resource.GetAPIVersion(), + Kind: resource.GetKind(), + Name: resource.GetName(), + UID: resource.GetUID(), + Controller: &controllerFlag, + BlockOwnerDeletion: &blockOwnerDeletionFlag, }, + }) +} + +func calculateSummary(results []*report.PolicyReportResult) (summary report.PolicyReportSummary) { + for _, res := range results { + switch string(res.Status) { + case report.StatusPass: + summary.Pass++ + case report.StatusFail: + summary.Fail++ + case "warn": + summary.Warn++ + case "error": + summary.Error++ + case "skip": + summary.Skip++ + } } - labelMap := map[string]string{ - "policy": pv.Spec.Policy, - "resource": pv.Spec.ToKey(), - } - pv.SetLabels(labelMap) - if namespace != "" { - pv.SetNamespace(namespace) - } - pv.SetGenerateName(fmt.Sprintf("%s-", policy)) - return pv + return } func buildPVInfo(er response.EngineResponse) Info { diff --git a/pkg/policyreport/common.go b/pkg/policyreport/common.go index 828af7aa14..4980a3eaf2 100755 --- a/pkg/policyreport/common.go +++ b/pkg/policyreport/common.go @@ -2,7 +2,6 @@ package policyreport import ( "fmt" - "reflect" "time" backoff "github.com/cenkalti/backoff" @@ -15,30 +14,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" ) -func createOwnerReference(resource *unstructured.Unstructured) (metav1.OwnerReference, bool) { - controllerFlag := true - blockOwnerDeletionFlag := true - - apiversion := resource.GetAPIVersion() - kind := resource.GetKind() - name := resource.GetName() - uid := resource.GetUID() - - if apiversion == "" || kind == "" || name == "" || uid == "" { - return metav1.OwnerReference{}, false - } - - ownerRef := metav1.OwnerReference{ - APIVersion: resource.GetAPIVersion(), - Kind: resource.GetKind(), - Name: resource.GetName(), - UID: resource.GetUID(), - Controller: &controllerFlag, - BlockOwnerDeletion: &blockOwnerDeletionFlag, - } - return ownerRef, true -} - func retryGetResource(client *client.Client, rspec kyverno.ResourceSpec) (*unstructured.Unstructured, error) { var i int var obj *unstructured.Unstructured @@ -106,25 +81,3 @@ func (vc violationCount) UpdateStatus(status kyverno.PolicyStatus) kyverno.Polic return status } - -// hasViolationSpecChanged returns true if oldSpec & newSpec -// are identical, exclude message in violated rules -func hasViolationSpecChanged(new, old *kyverno.PolicyViolationSpec) bool { - if new.Policy != old.Policy { - return true - } - - if new.ResourceSpec.ToKey() != old.ResourceSpec.ToKey() { - return true - } - - for i := range new.ViolatedRules { - new.ViolatedRules[i].Message = "" - } - - for i := range old.ViolatedRules { - old.ViolatedRules[i].Message = "" - } - - return !reflect.DeepEqual(*new, *old) -} diff --git a/pkg/policyreport/generator.go b/pkg/policyreport/reportrequest.go similarity index 65% rename from pkg/policyreport/generator.go rename to pkg/policyreport/reportrequest.go index 6171d44e69..18104df825 100755 --- a/pkg/policyreport/generator.go +++ b/pkg/policyreport/reportrequest.go @@ -1,6 +1,7 @@ package policyreport import ( + "fmt" "reflect" "strconv" "strings" @@ -8,14 +9,18 @@ import ( "github.com/go-logr/logr" kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1" + report "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha1" policyreportclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned" reportrequest "github.com/kyverno/kyverno/pkg/client/clientset/versioned/typed/policyreport/v1alpha1" policyreportinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/policyreport/v1alpha1" policyreport "github.com/kyverno/kyverno/pkg/client/listers/policyreport/v1alpha1" "github.com/kyverno/kyverno/pkg/constant" + client "github.com/kyverno/kyverno/pkg/dclient" dclient "github.com/kyverno/kyverno/pkg/dclient" "github.com/kyverno/kyverno/pkg/policystatus" + apierrors "k8s.io/apimachinery/pkg/api/errors" unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/tools/cache" @@ -41,6 +46,9 @@ type Generator struct { queue workqueue.RateLimitingInterface dataStore *dataStore + // update policy status with violationCount + policyStatusListener policystatus.Listener + log logr.Logger } @@ -60,6 +68,7 @@ func NewReportRequestGenerator(client *policyreportclient.Clientset, reportReqSynced: reportReqInformer.Informer().HasSynced, queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), workQueueName), dataStore: newDataStore(), + policyStatusListener: policyStatus, log: log, } @@ -207,9 +216,8 @@ func (gen *Generator) processNextWorkItem() bool { // lookup data store info := gen.dataStore.lookup(keyHash) if reflect.DeepEqual(info, Info{}) { - // empty key gen.queue.Forget(obj) - logger.Info("empty key") + logger.V(3).Info("empty key") return nil } @@ -226,6 +234,82 @@ func (gen *Generator) processNextWorkItem() bool { } func (gen *Generator) syncHandler(info Info) error { + reportRequestUnstructured, err := NewBuilder().build(info) + if err != nil { + return fmt.Errorf("unable to build reportRequest: %v", err) + } - return nil + return gen.sync(reportRequestUnstructured, info) +} + +func (gen *Generator) sync(reportReq *unstructured.Unstructured, info Info) error { + defer func() { + if val := reportReq.GetAnnotations()["fromSync"]; val == "true" { + gen.policyStatusListener.Send(violationCount{ + policyName: info.PolicyName, + violatedRules: info.Rules, + }) + } + }() + + if reportReq.GetNamespace() == "" { + old, err := gen.clusterReportRequestLister.Get(reportReq.GetName()) + if err != nil { + if apierrors.IsNotFound(err) { + return updateReportRequest(gen.dclient, old, reportReq) + } + return fmt.Errorf("unable to get clusterReportRequest: %v", err) + } + + _, err = gen.dclient.CreateResource(reportReq.GetAPIVersion(), reportReq.GetKind(), reportReq.GetNamespace(), reportReq, false) + return fmt.Errorf("failed to create clusterReportRequest: %v", err) + } + + old, err := gen.reportRequestLister.ReportRequests(reportReq.GetNamespace()).Get(reportReq.GetName()) + if err != nil { + if apierrors.IsNotFound(err) { + return updateReportRequest(gen.dclient, old, reportReq) + } + return fmt.Errorf("unable to get existing reportRequest %v", err) + } + + _, err = gen.dclient.CreateResource(reportReq.GetAPIVersion(), reportReq.GetKind(), reportReq.GetNamespace(), reportReq, false) + return fmt.Errorf("failed to create reportRequest: %v", err) +} + +func updateReportRequest(dClient *client.Client, old interface{}, new *unstructured.Unstructured) (err error) { + oldUnstructed := make(map[string]interface{}) + if oldTyped, ok := old.(*report.ReportRequest); ok { + if oldUnstructed, err = runtime.DefaultUnstructuredConverter.ToUnstructured(oldTyped); err != nil { + return fmt.Errorf("unable to convert reportRequest: %v", err) + } + new.SetResourceVersion(oldTyped.GetResourceVersion()) + } else { + oldTyped := old.(*report.ClusterReportRequest) + if oldUnstructed, err = runtime.DefaultUnstructuredConverter.ToUnstructured(oldTyped); err != nil { + return fmt.Errorf("unable to convert clusterReportRequest: %v", err) + } + new.SetResourceVersion(oldTyped.GetResourceVersion()) + } + + if !hasResultsChanged(oldUnstructed, new.UnstructuredContent()) { + return nil + } + // TODO(shuting): set annotation / label + _, err = dClient.UpdateResource(new.GetAPIVersion(), new.GetKind(), new.GetNamespace(), new, false) + return fmt.Errorf("failed to update report request: %v", err) +} + +func hasResultsChanged(old, new map[string]interface{}) bool { + oldRes, ok := old["results"] + if !ok { + return false + } + + newRes, ok := new["results"] + if !ok { + return false + } + + return !reflect.DeepEqual(oldRes, newRes) }