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:
parent
36c549ea22
commit
0aae6a5276
3 changed files with 196 additions and 76 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue