1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-07 00:17:13 +00:00

generate report request

This commit is contained in:
Shuting Zhao 2020-10-23 18:27:55 -07:00
parent 36c549ea22
commit 0aae6a5276
3 changed files with 196 additions and 76 deletions

View file

@ -9,6 +9,14 @@ import (
report "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha1" report "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha1"
"github.com/kyverno/kyverno/pkg/common" "github.com/kyverno/kyverno/pkg/common"
"github.com/kyverno/kyverno/pkg/engine/response" "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 //GeneratePRsFromEngineResponse generate Violations from engine responses
@ -32,10 +40,10 @@ func GeneratePRsFromEngineResponse(ers []response.EngineResponse, log logr.Logge
return pvInfos return pvInfos
} }
// Builder builds Policy Violation struct // Builder builds report request struct
// this is base type of namespaced and cluster policy violation // this is base type of namespaced and cluster policy report
type Builder interface { type Builder interface {
build(info Info) build(info Info) (*unstructured.Unstructured, error)
} }
type requestBuilder struct{} type requestBuilder struct{}
@ -43,35 +51,110 @@ type requestBuilder struct{}
func NewBuilder() *requestBuilder { func NewBuilder() *requestBuilder {
return &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 { result.Rule = rule.Name
pv := pvb.build(info.PolicyName, info.Resource.GetKind(), info.Resource.GetNamespace(), info.Resource.GetName(), info.Rules) result.Message = rule.Message
return *pv 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{ obj.SetLabels(map[string]string{
Spec: kyverno.PolicyViolationSpec{ "policy": info.PolicyName,
Policy: policy, "resource": resource.GetKind() + "-" + resource.GetName(),
ResourceSpec: kyverno.ResourceSpec{ })
Kind: kind,
Name: name, if info.FromSync {
Namespace: namespace, obj.SetAnnotations(map[string]string{
}, "fromSync": "true",
ViolatedRules: rules, })
}
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) return
if namespace != "" {
pv.SetNamespace(namespace)
}
pv.SetGenerateName(fmt.Sprintf("%s-", policy))
return pv
} }
func buildPVInfo(er response.EngineResponse) Info { func buildPVInfo(er response.EngineResponse) Info {

View file

@ -2,7 +2,6 @@ package policyreport
import ( import (
"fmt" "fmt"
"reflect"
"time" "time"
backoff "github.com/cenkalti/backoff" backoff "github.com/cenkalti/backoff"
@ -15,30 +14,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log" "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) { func retryGetResource(client *client.Client, rspec kyverno.ResourceSpec) (*unstructured.Unstructured, error) {
var i int var i int
var obj *unstructured.Unstructured var obj *unstructured.Unstructured
@ -106,25 +81,3 @@ func (vc violationCount) UpdateStatus(status kyverno.PolicyStatus) kyverno.Polic
return status 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)
}

View file

@ -1,6 +1,7 @@
package policyreport package policyreport
import ( import (
"fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
@ -8,14 +9,18 @@ import (
"github.com/go-logr/logr" "github.com/go-logr/logr"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1" 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" policyreportclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned"
reportrequest "github.com/kyverno/kyverno/pkg/client/clientset/versioned/typed/policyreport/v1alpha1" reportrequest "github.com/kyverno/kyverno/pkg/client/clientset/versioned/typed/policyreport/v1alpha1"
policyreportinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/policyreport/v1alpha1" policyreportinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/policyreport/v1alpha1"
policyreport "github.com/kyverno/kyverno/pkg/client/listers/policyreport/v1alpha1" policyreport "github.com/kyverno/kyverno/pkg/client/listers/policyreport/v1alpha1"
"github.com/kyverno/kyverno/pkg/constant" "github.com/kyverno/kyverno/pkg/constant"
client "github.com/kyverno/kyverno/pkg/dclient"
dclient "github.com/kyverno/kyverno/pkg/dclient" dclient "github.com/kyverno/kyverno/pkg/dclient"
"github.com/kyverno/kyverno/pkg/policystatus" "github.com/kyverno/kyverno/pkg/policystatus"
apierrors "k8s.io/apimachinery/pkg/api/errors"
unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
@ -41,6 +46,9 @@ type Generator struct {
queue workqueue.RateLimitingInterface queue workqueue.RateLimitingInterface
dataStore *dataStore dataStore *dataStore
// update policy status with violationCount
policyStatusListener policystatus.Listener
log logr.Logger log logr.Logger
} }
@ -60,6 +68,7 @@ func NewReportRequestGenerator(client *policyreportclient.Clientset,
reportReqSynced: reportReqInformer.Informer().HasSynced, reportReqSynced: reportReqInformer.Informer().HasSynced,
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), workQueueName), queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), workQueueName),
dataStore: newDataStore(), dataStore: newDataStore(),
policyStatusListener: policyStatus,
log: log, log: log,
} }
@ -207,9 +216,8 @@ func (gen *Generator) processNextWorkItem() bool {
// lookup data store // lookup data store
info := gen.dataStore.lookup(keyHash) info := gen.dataStore.lookup(keyHash)
if reflect.DeepEqual(info, Info{}) { if reflect.DeepEqual(info, Info{}) {
// empty key
gen.queue.Forget(obj) gen.queue.Forget(obj)
logger.Info("empty key") logger.V(3).Info("empty key")
return nil return nil
} }
@ -226,6 +234,82 @@ func (gen *Generator) processNextWorkItem() bool {
} }
func (gen *Generator) syncHandler(info Info) error { 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)
} }