1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-29 02:45:06 +00:00

1398 - Reduce RCR throttling requests (#1406)

* reduce RCR throttling requests by merging policy application (policy - namespace) results into single RCR

* - refactor policy controller; - fix RCR issue

* - refactor RCR controller; - fix cpolr on ns update; - reduce throttling when getting resources; - fix tests

* update CRD schema

* fix typo
This commit is contained in:
shuting 2020-12-21 11:04:19 -08:00 committed by GitHub
parent ee31fabcbc
commit 3c5f9f8888
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 583 additions and 364 deletions

View file

@ -224,9 +224,10 @@ spec:
description: APIVersion specifies resource apiVersion.
type: string
clone:
description: Clone specified the source resource used to
populate each generated resource. Exactly one of Data
or Clone must be specified.
description: Clone specifies the source resource used to
populate each generated resource. At most one of Data
or Clone can be specified. If neither are provided, the
generated resource will be created with default data only.
properties:
name:
description: Name specifies name of the resource.
@ -236,9 +237,10 @@ spec:
type: string
type: object
data:
description: Data provides the resource manifest to used
to populate each generated resource. Exactly one of Data
or Clone must be specified.
description: Data provides the resource declaration used
to populate each generated resource. At most one of Data
or Clone must be specified. If neither are provided, the
generated resource will be created with default data only.
x-kubernetes-preserve-unknown-fields: true
kind:
description: Kind specifies resource kind.
@ -251,7 +253,10 @@ spec:
type: string
synchronize:
description: Synchronize controls if generated resources
should be kept in-sync with their source resource. Optional.
should be kept in-sync with their source resource. If
Synchronize is set to "true" changes to generated resources
will be overwritten with resource data from Data or the
resource specified in the Clone declaration. Optional.
Defaults to "false" if not specified.
type: boolean
type: object
@ -959,6 +964,8 @@ spec:
kind: ClusterReportChangeRequest
listKind: ClusterReportChangeRequestList
plural: clusterreportchangerequests
shortNames:
- crcr
singular: clusterreportchangerequest
scope: Cluster
versions:
@ -1688,9 +1695,10 @@ spec:
description: APIVersion specifies resource apiVersion.
type: string
clone:
description: Clone specified the source resource used to
populate each generated resource. Exactly one of Data
or Clone must be specified.
description: Clone specifies the source resource used to
populate each generated resource. At most one of Data
or Clone can be specified. If neither are provided, the
generated resource will be created with default data only.
properties:
name:
description: Name specifies name of the resource.
@ -1700,9 +1708,10 @@ spec:
type: string
type: object
data:
description: Data provides the resource manifest to used
to populate each generated resource. Exactly one of Data
or Clone must be specified.
description: Data provides the resource declaration used
to populate each generated resource. At most one of Data
or Clone must be specified. If neither are provided, the
generated resource will be created with default data only.
x-kubernetes-preserve-unknown-fields: true
kind:
description: Kind specifies resource kind.
@ -1715,7 +1724,10 @@ spec:
type: string
synchronize:
description: Synchronize controls if generated resources
should be kept in-sync with their source resource. Optional.
should be kept in-sync with their source resource. If
Synchronize is set to "true" changes to generated resources
will be overwritten with resource data from Data or the
resource specified in the Clone declaration. Optional.
Defaults to "false" if not specified.
type: boolean
type: object

View file

@ -308,12 +308,10 @@ func removeReportChangeRequest(client *client.Client, kind string) error {
return nil
}
var wg sync.WaitGroup
wg.Add(len(rcrList.Items))
for _, rcr := range rcrList.Items {
go deleteResource(client, rcr.GetAPIVersion(), rcr.GetKind(), rcr.GetNamespace(), rcr.GetName(), &wg)
deleteResource(client, rcr.GetAPIVersion(), rcr.GetKind(), rcr.GetNamespace(), rcr.GetName(), nil)
}
wg.Wait()
return nil
}

View file

@ -226,9 +226,10 @@ spec:
description: APIVersion specifies resource apiVersion.
type: string
clone:
description: Clone specified the source resource used to
populate each generated resource. Exactly one of Data
or Clone must be specified.
description: Clone specifies the source resource used to
populate each generated resource. At most one of Data
or Clone can be specified. If neither are provided, the
generated resource will be created with default data only.
properties:
name:
description: Name specifies name of the resource.
@ -238,9 +239,10 @@ spec:
type: string
type: object
data:
description: Data provides the resource manifest to used
to populate each generated resource. Exactly one of Data
or Clone must be specified.
description: Data provides the resource declaration used
to populate each generated resource. At most one of Data
or Clone must be specified. If neither are provided, the
generated resource will be created with default data only.
x-kubernetes-preserve-unknown-fields: true
kind:
description: Kind specifies resource kind.
@ -253,7 +255,10 @@ spec:
type: string
synchronize:
description: Synchronize controls if generated resources
should be kept in-sync with their source resource. Optional.
should be kept in-sync with their source resource. If
Synchronize is set to "true" changes to generated resources
will be overwritten with resource data from Data or the
resource specified in the Clone declaration. Optional.
Defaults to "false" if not specified.
type: boolean
type: object

View file

@ -13,6 +13,8 @@ spec:
kind: ClusterReportChangeRequest
listKind: ClusterReportChangeRequestList
plural: clusterreportchangerequests
shortNames:
- crcr
singular: clusterreportchangerequest
scope: Cluster
versions:

View file

@ -227,9 +227,10 @@ spec:
description: APIVersion specifies resource apiVersion.
type: string
clone:
description: Clone specified the source resource used to
populate each generated resource. Exactly one of Data
or Clone must be specified.
description: Clone specifies the source resource used to
populate each generated resource. At most one of Data
or Clone can be specified. If neither are provided, the
generated resource will be created with default data only.
properties:
name:
description: Name specifies name of the resource.
@ -239,9 +240,10 @@ spec:
type: string
type: object
data:
description: Data provides the resource manifest to used
to populate each generated resource. Exactly one of Data
or Clone must be specified.
description: Data provides the resource declaration used
to populate each generated resource. At most one of Data
or Clone must be specified. If neither are provided, the
generated resource will be created with default data only.
x-kubernetes-preserve-unknown-fields: true
kind:
description: Kind specifies resource kind.
@ -254,7 +256,10 @@ spec:
type: string
synchronize:
description: Synchronize controls if generated resources
should be kept in-sync with their source resource. Optional.
should be kept in-sync with their source resource. If
Synchronize is set to "true" changes to generated resources
will be overwritten with resource data from Data or the
resource specified in the Clone declaration. Optional.
Defaults to "false" if not specified.
type: boolean
type: object

View file

@ -229,9 +229,10 @@ spec:
description: APIVersion specifies resource apiVersion.
type: string
clone:
description: Clone specified the source resource used to
populate each generated resource. Exactly one of Data
or Clone must be specified.
description: Clone specifies the source resource used to
populate each generated resource. At most one of Data
or Clone can be specified. If neither are provided, the
generated resource will be created with default data only.
properties:
name:
description: Name specifies name of the resource.
@ -241,9 +242,10 @@ spec:
type: string
type: object
data:
description: Data provides the resource manifest to used
to populate each generated resource. Exactly one of Data
or Clone must be specified.
description: Data provides the resource declaration used
to populate each generated resource. At most one of Data
or Clone must be specified. If neither are provided, the
generated resource will be created with default data only.
x-kubernetes-preserve-unknown-fields: true
kind:
description: Kind specifies resource kind.
@ -256,7 +258,10 @@ spec:
type: string
synchronize:
description: Synchronize controls if generated resources
should be kept in-sync with their source resource. Optional.
should be kept in-sync with their source resource. If
Synchronize is set to "true" changes to generated resources
will be overwritten with resource data from Data or the
resource specified in the Clone declaration. Optional.
Defaults to "false" if not specified.
type: boolean
type: object
@ -964,6 +969,8 @@ spec:
kind: ClusterReportChangeRequest
listKind: ClusterReportChangeRequestList
plural: clusterreportchangerequests
shortNames:
- crcr
singular: clusterreportchangerequest
scope: Cluster
versions:
@ -1693,9 +1700,10 @@ spec:
description: APIVersion specifies resource apiVersion.
type: string
clone:
description: Clone specified the source resource used to
populate each generated resource. Exactly one of Data
or Clone must be specified.
description: Clone specifies the source resource used to
populate each generated resource. At most one of Data
or Clone can be specified. If neither are provided, the
generated resource will be created with default data only.
properties:
name:
description: Name specifies name of the resource.
@ -1705,9 +1713,10 @@ spec:
type: string
type: object
data:
description: Data provides the resource manifest to used
to populate each generated resource. Exactly one of Data
or Clone must be specified.
description: Data provides the resource declaration used
to populate each generated resource. At most one of Data
or Clone must be specified. If neither are provided, the
generated resource will be created with default data only.
x-kubernetes-preserve-unknown-fields: true
kind:
description: Kind specifies resource kind.
@ -1720,7 +1729,10 @@ spec:
type: string
synchronize:
description: Synchronize controls if generated resources
should be kept in-sync with their source resource. Optional.
should be kept in-sync with their source resource. If
Synchronize is set to "true" changes to generated resources
will be overwritten with resource data from Data or the
resource specified in the Clone declaration. Optional.
Defaults to "false" if not specified.
type: boolean
type: object

View file

@ -229,9 +229,10 @@ spec:
description: APIVersion specifies resource apiVersion.
type: string
clone:
description: Clone specified the source resource used to
populate each generated resource. Exactly one of Data
or Clone must be specified.
description: Clone specifies the source resource used to
populate each generated resource. At most one of Data
or Clone can be specified. If neither are provided, the
generated resource will be created with default data only.
properties:
name:
description: Name specifies name of the resource.
@ -241,9 +242,10 @@ spec:
type: string
type: object
data:
description: Data provides the resource manifest to used
to populate each generated resource. Exactly one of Data
or Clone must be specified.
description: Data provides the resource declaration used
to populate each generated resource. At most one of Data
or Clone must be specified. If neither are provided, the
generated resource will be created with default data only.
x-kubernetes-preserve-unknown-fields: true
kind:
description: Kind specifies resource kind.
@ -256,7 +258,10 @@ spec:
type: string
synchronize:
description: Synchronize controls if generated resources
should be kept in-sync with their source resource. Optional.
should be kept in-sync with their source resource. If
Synchronize is set to "true" changes to generated resources
will be overwritten with resource data from Data or the
resource specified in the Clone declaration. Optional.
Defaults to "false" if not specified.
type: boolean
type: object
@ -964,6 +969,8 @@ spec:
kind: ClusterReportChangeRequest
listKind: ClusterReportChangeRequestList
plural: clusterreportchangerequests
shortNames:
- crcr
singular: clusterreportchangerequest
scope: Cluster
versions:
@ -1693,9 +1700,10 @@ spec:
description: APIVersion specifies resource apiVersion.
type: string
clone:
description: Clone specified the source resource used to
populate each generated resource. Exactly one of Data
or Clone must be specified.
description: Clone specifies the source resource used to
populate each generated resource. At most one of Data
or Clone can be specified. If neither are provided, the
generated resource will be created with default data only.
properties:
name:
description: Name specifies name of the resource.
@ -1705,9 +1713,10 @@ spec:
type: string
type: object
data:
description: Data provides the resource manifest to used
to populate each generated resource. Exactly one of Data
or Clone must be specified.
description: Data provides the resource declaration used
to populate each generated resource. At most one of Data
or Clone must be specified. If neither are provided, the
generated resource will be created with default data only.
x-kubernetes-preserve-unknown-fields: true
kind:
description: Kind specifies resource kind.
@ -1720,7 +1729,10 @@ spec:
type: string
synchronize:
description: Synchronize controls if generated resources
should be kept in-sync with their source resource. Optional.
should be kept in-sync with their source resource. If
Synchronize is set to "true" changes to generated resources
will be overwritten with resource data from Data or the
resource specified in the Clone declaration. Optional.
Defaults to "false" if not specified.
type: boolean
type: object

View file

@ -30,7 +30,7 @@ import (
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +genclient:nonNamespaced
// +kubebuilder:object:root=true
// +kubebuilder:resource:path=clusterreportchangerequests
// +kubebuilder:resource:path=clusterreportchangerequests,scope="Cluster",shortName=crcr
// +kubebuilder:printcolumn:name="Kind",type=string,JSONPath=`.scope.kind`,priority=1
// +kubebuilder:printcolumn:name="Name",type=string,JSONPath=`.scope.name`,priority=1
// +kubebuilder:printcolumn:name="Pass",type=integer,JSONPath=`.summary.pass`
@ -39,8 +39,6 @@ import (
// +kubebuilder:printcolumn:name="Error",type=integer,JSONPath=`.summary.error`
// +kubebuilder:printcolumn:name="Skip",type=integer,JSONPath=`.summary.skip`
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:resource:shortName=crcr
// +kubebuilder:resource:scope=Cluster
type ClusterReportChangeRequest struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

View file

@ -42,6 +42,13 @@ func (cd *ConfigData) ToFilter(kind, namespace, name string) bool {
if wildcard.Match(f.Kind, kind) && wildcard.Match(f.Namespace, namespace) && wildcard.Match(f.Name, name) {
return true
}
if kind == "Namespace" {
// [Namespace,kube-system,*] || [*,kube-system,*]
if (f.Kind == "Namespace" || f.Kind == "*") && wildcard.Match(f.Namespace, name) {
return true
}
}
}
return false
}

View file

@ -31,11 +31,14 @@ type PolicyResponse struct {
//ResourceSpec resource action applied on
type ResourceSpec struct {
//TODO: support ApiVersion
Kind string `json:"kind"`
APIVersion string `json:"apiVersion"`
Namespace string `json:"namespace"`
Name string `json:"name"`
// UID is not used to build the unique identifier
// optional
UID string `json:"uid"`
}
//GetKey returns the key
@ -51,7 +54,7 @@ type PolicyStats struct {
RulesAppliedCount int `json:"rulesAppliedCount"`
}
//RuleResponse details for each rule applicatino
//RuleResponse details for each rule application
type RuleResponse struct {
// rule name specified in policy
Name string `json:"name"`
@ -110,6 +113,17 @@ func (er EngineResponse) GetSuccessRules() []string {
return er.getRules(true)
}
// GetResourceSpec returns resourceSpec of er
func (er EngineResponse) GetResourceSpec() ResourceSpec {
return ResourceSpec{
Kind: er.PatchedResource.GetKind(),
APIVersion: er.PatchedResource.GetAPIVersion(),
Namespace: er.PatchedResource.GetNamespace(),
Name: er.PatchedResource.GetName(),
UID: string(er.PatchedResource.GetUID()),
}
}
func (er EngineResponse) getRules(success bool) []string {
var rules []string
for _, r := range er.PolicyResponse.Rules {

View file

@ -32,7 +32,7 @@ func (c *Controller) processGR(gr *kyverno.GenerateRequest) error {
// 1 - Check if the resource exists
resource, err = getResource(c.client, gr.Spec.Resource)
if err != nil {
// Dont update status
// Don't update status
logger.V(3).Info("resource does not exist or is pending creation, re-queueing", "details", err.Error())
return err
}

View file

@ -7,11 +7,13 @@ import (
report "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha1"
"github.com/kyverno/kyverno/pkg/engine/response"
"github.com/kyverno/kyverno/pkg/engine/utils"
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/policyreport"
corev1 "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/types"
log "sigs.k8s.io/controller-runtime/pkg/log"
)
@ -42,7 +44,7 @@ func buildPolicyReports(resps []response.EngineResponse, skippedPolicies []Skipp
}
if raw, err = json.Marshal(report); err != nil {
log.Log.V(3).Info("failed to serilize policy report", "error", err)
log.Log.V(3).Info("failed to serialize policy report", "error", err)
continue
}
@ -70,7 +72,7 @@ func buildPolicyReports(resps []response.EngineResponse, skippedPolicies []Skipp
report.SetName(scope)
if raw, err = json.Marshal(report); err != nil {
log.Log.V(3).Info("failed to serilize policy report", "name", report.Name, "scope", scope, "error", err)
log.Log.V(3).Info("failed to serialize policy report", "name", report.Name, "scope", scope, "error", err)
}
} else {
report := &report.PolicyReport{
@ -87,7 +89,7 @@ func buildPolicyReports(resps []response.EngineResponse, skippedPolicies []Skipp
report.SetNamespace(ns)
if raw, err = json.Marshal(report); err != nil {
log.Log.V(3).Info("failed to serilize policy report", "name", report.Name, "scope", scope, "error", err)
log.Log.V(3).Info("failed to serialize policy report", "name", report.Name, "scope", scope, "error", err)
}
}
@ -111,33 +113,38 @@ func buildPolicyResults(resps []response.EngineResponse) map[string][]*report.Po
for _, info := range infos {
var appname string
ns := info.Resource.GetNamespace()
ns := info.Namespace
if ns != "" {
appname = fmt.Sprintf("policyreport-ns-%s", ns)
} else {
appname = fmt.Sprintf(clusterpolicyreport)
}
for _, rule := range info.Rules {
result := report.PolicyReportResult{
Policy: info.PolicyName,
Resources: []*corev1.ObjectReference{
{
Kind: info.Resource.GetKind(),
Namespace: info.Resource.GetNamespace(),
APIVersion: info.Resource.GetAPIVersion(),
Name: info.Resource.GetName(),
UID: info.Resource.GetUID(),
},
},
Scored: true,
}
for _, infoResult := range info.Results {
for _, rule := range infoResult.Rules {
if rule.Type != utils.Validation.String() {
continue
}
result.Rule = rule.Name
result.Message = rule.Message
result.Status = report.PolicyStatus(rule.Check)
results[appname] = append(results[appname], &result)
result := report.PolicyReportResult{
Policy: info.PolicyName,
Resources: []*corev1.ObjectReference{
{
Kind: infoResult.Resource.Kind,
Namespace: infoResult.Resource.Namespace,
APIVersion: infoResult.Resource.APIVersion,
Name: infoResult.Resource.Name,
UID: types.UID(infoResult.Resource.UID),
},
},
Scored: true,
}
result.Rule = rule.Name
result.Message = rule.Message
result.Status = report.PolicyStatus(rule.Check)
results[appname] = append(results[appname], &result)
}
}
}

View file

@ -8,6 +8,7 @@ import (
report "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha1"
"github.com/kyverno/kyverno/pkg/common"
"github.com/kyverno/kyverno/pkg/engine/response"
"github.com/kyverno/kyverno/pkg/engine/utils"
"gotest.tools/assert"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -30,10 +31,12 @@ var engineResponses = []response.EngineResponse{
Rules: []response.RuleResponse{
{
Name: "policy1-rule1",
Type: utils.Validation.String(),
Success: true,
},
{
Name: "policy1-rule2",
Type: utils.Validation.String(),
Success: false,
},
},
@ -54,10 +57,12 @@ var engineResponses = []response.EngineResponse{
Rules: []response.RuleResponse{
{
Name: "clusterpolicy2-rule1",
Type: utils.Validation.String(),
Success: true,
},
{
Name: "clusterpolicy2-rule2",
Type: utils.Validation.String(),
Success: false,
},
},

View file

@ -19,7 +19,6 @@ import (
)
// applyPolicy applies policy on a resource
//TODO: generation rules
func applyPolicy(policy kyverno.ClusterPolicy, resource unstructured.Unstructured, logger logr.Logger, excludeGroupRole []string, resCache resourcecache.ResourceCacheIface) (responses []response.EngineResponse) {
startTime := time.Now()
defer func() {
@ -35,23 +34,21 @@ func applyPolicy(policy kyverno.ClusterPolicy, resource unstructured.Unstructure
var engineResponses []response.EngineResponse
var engineResponseMutation, engineResponseValidation response.EngineResponse
var err error
// build context
ctx := context.NewContext()
err = ctx.AddResource(transformResource(resource))
if err != nil {
logger.Error(err, "enable to add transform resource to ctx")
}
//MUTATION
engineResponseMutation, err = mutation(policy, resource, ctx, logger, resCache, ctx)
if err != nil {
logger.Error(err, "failed to process mutation rule")
}
//VALIDATION
engineResponseValidation = engine.Validate(engine.PolicyContext{Policy: policy, Context: ctx, NewResource: resource, ExcludeGroupRole: excludeGroupRole, ResourceCache: resCache, JSONContext: ctx})
engineResponses = append(engineResponses, mergeRuleRespose(engineResponseMutation, engineResponseValidation))
//TODO: GENERATION
return engineResponses
}
@ -62,10 +59,10 @@ func mutation(policy kyverno.ClusterPolicy, resource unstructured.Unstructured,
log.V(4).Info("failed to apply mutation rules; reporting them")
return engineResponse, nil
}
// Verify if the JSON pathes returned by the Mutate are already applied to the resource
// Verify if the JSON patches returned by the Mutate are already applied to the resource
if reflect.DeepEqual(resource, engineResponse.PatchedResource) {
// resources matches
log.V(4).Info("resource already satisfys the policy")
log.V(4).Info("resource already satisfies the policy")
return engineResponse, nil
}
return getFailedOverallRuleInfo(resource, engineResponse, log)

View file

@ -8,7 +8,6 @@ import (
"github.com/go-logr/logr"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/config"
client "github.com/kyverno/kyverno/pkg/dclient"
"github.com/kyverno/kyverno/pkg/utils"
"github.com/minio/minio/pkg/wildcard"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -92,7 +91,7 @@ func ExcludePod(resourceMap map[string]unstructured.Unstructured, log logr.Logge
return resourceMap
}
// GetNamespacesForRule gets the matched namespacse list for the given rule
// GetNamespacesForRule gets the matched namespaces list for the given rule
func GetNamespacesForRule(rule *kyverno.Rule, nslister listerv1.NamespaceLister, log logr.Logger) []string {
if len(rule.MatchResources.Namespaces) == 0 {
return GetAllNamespaces(nslister, log)
@ -160,7 +159,7 @@ func GetAllNamespaces(nslister listerv1.NamespaceLister, log logr.Logger) []stri
}
// GetResourcesPerNamespace ...
func GetResourcesPerNamespace(kind string, client *client.Client, namespace string, rule kyverno.Rule, configHandler config.Interface, log logr.Logger) map[string]unstructured.Unstructured {
func (pc *PolicyController) getResourcesPerNamespace(kind string, namespace string, rule kyverno.Rule, log logr.Logger) map[string]unstructured.Unstructured {
resourceMap := map[string]unstructured.Unstructured{}
ls := rule.MatchResources.Selector
@ -168,7 +167,7 @@ func GetResourcesPerNamespace(kind string, client *client.Client, namespace stri
namespace = ""
}
list, err := client.ListResource("", kind, namespace, ls)
list, err := pc.client.ListResource("", kind, namespace, ls)
if err != nil {
log.Error(err, "failed to list resources", "kind", kind, "namespace", namespace)
return nil
@ -192,7 +191,7 @@ func GetResourcesPerNamespace(kind string, client *client.Client, namespace stri
}
}
// Skip the filtered resources
if configHandler.ToFilter(r.GetKind(), r.GetNamespace(), r.GetName()) {
if pc.configHandler.ToFilter(r.GetKind(), r.GetNamespace(), r.GetName()) {
continue
}
@ -202,12 +201,12 @@ func GetResourcesPerNamespace(kind string, client *client.Client, namespace stri
// exclude the resources
// skip resources to be filtered
ExcludeResources(resourceMap, rule.ExcludeResources.ResourceDescription, configHandler, log)
excludeResources(resourceMap, rule.ExcludeResources.ResourceDescription, pc.configHandler, log)
return resourceMap
}
// ExcludeResources ...
func ExcludeResources(included map[string]unstructured.Unstructured, exclude kyverno.ResourceDescription, configHandler config.Interface, log logr.Logger) {
func excludeResources(included map[string]unstructured.Unstructured, exclude kyverno.ResourceDescription, configHandler config.Interface, log logr.Logger) {
if reflect.DeepEqual(exclude, (kyverno.ResourceDescription{})) {
return
}
@ -270,7 +269,7 @@ func ExcludeResources(included map[string]unstructured.Unstructured, exclude kyv
// check exclude condition for each resource
for uid, resource := range included {
// 0 -> dont check
// 0 -> don't check
// 1 -> is not to be exclude
// 2 -> to be exclude
excludeEval := []Condition{}

View file

@ -1,6 +1,7 @@
package policy
import (
"errors"
"sync"
"time"
@ -11,72 +12,85 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func (pc *PolicyController) processExistingResources(policy *kyverno.ClusterPolicy) []response.EngineResponse {
func (pc *PolicyController) processExistingResources(policy *kyverno.ClusterPolicy) {
logger := pc.log.WithValues("policy", policy.Name)
// Parse through all the resources
// drops the cache after configured rebuild time
logger.V(4).Info("applying policy to existing resources")
// Parse through all the resources drops the cache after configured rebuild time
pc.rm.Drop()
var engineResponses []response.EngineResponse
// get resource that are satisfy the resource description defined in the rules
resourceMap := pc.listResources(policy)
for _, resource := range resourceMap {
// pre-processing, check if the policy and resource version has been processed before
if !pc.rm.ProcessResource(policy.Name, policy.ResourceVersion, resource.GetKind(), resource.GetNamespace(), resource.GetName(), resource.GetResourceVersion()) {
logger.V(4).Info("policy and resource already processed", "policyResourceVersion", policy.ResourceVersion, "resourceResourceVersion", resource.GetResourceVersion(), "kind", resource.GetKind(), "namespace", resource.GetNamespace(), "name", resource.GetName())
for _, rule := range policy.Spec.Rules {
if !rule.HasValidate() {
continue
}
// apply the policy on each
engineResponse := applyPolicy(*policy, resource, logger, pc.configHandler.GetExcludeGroupRole(), pc.resCache)
// get engine response for mutation & validation independently
engineResponses = append(engineResponses, engineResponse...)
// post-processing, register the resource as processed
pc.rm.RegisterResource(policy.GetName(), policy.GetResourceVersion(), resource.GetKind(), resource.GetNamespace(), resource.GetName(), resource.GetResourceVersion())
}
return engineResponses
}
func (pc *PolicyController) listResources(policy *kyverno.ClusterPolicy) map[string]unstructured.Unstructured {
pc.log.V(4).Info("list resources to be processed")
// key uid
resourceMap := map[string]unstructured.Unstructured{}
for _, rule := range policy.Spec.Rules {
for _, k := range rule.MatchResources.Kinds {
resourceSchema, _, err := pc.client.DiscoveryClient.FindResource("", k)
logger = logger.WithValues("rule", rule.Name, "kind", k)
namespaced, err := pc.rm.GetScope(k)
if err != nil {
pc.log.Error(err, "failed to find resource", "kind", k)
resourceSchema, _, err := pc.client.DiscoveryClient.FindResource("", k)
if err != nil {
logger.Error(err, "failed to find resource", "kind", k)
continue
}
namespaced = resourceSchema.Namespaced
pc.rm.RegisterScope(k, namespaced)
}
if !namespaced {
pc.applyAndReportPerNamespace(policy, k, "", rule, logger)
continue
}
if !resourceSchema.Namespaced {
rMap := GetResourcesPerNamespace(k, pc.client, "", rule, pc.configHandler, pc.log)
MergeResources(resourceMap, rMap)
} else {
namespaces := GetNamespacesForRule(&rule, pc.nsLister, pc.log)
for _, ns := range namespaces {
rMap := GetResourcesPerNamespace(k, pc.client, ns, rule, pc.configHandler, pc.log)
MergeResources(resourceMap, rMap)
}
namespaces := GetNamespacesForRule(&rule, pc.nsLister, logger)
for _, ns := range namespaces {
pc.applyAndReportPerNamespace(policy, k, ns, rule, logger)
}
}
}
}
return excludeAutoGenResources(*policy, resourceMap, pc.log)
func (pc *PolicyController) applyAndReportPerNamespace(policy *kyverno.ClusterPolicy, kind string, ns string, rule kyverno.Rule, logger logr.Logger) {
rMap := pc.getResourcesPerNamespace(kind, ns, rule, logger)
excludeAutoGenResources(*policy, rMap, logger)
if len(rMap) == 0 {
return
}
var engineResponses []response.EngineResponse
for _, resource := range rMap {
responses := pc.applyPolicy(policy, resource, logger)
engineResponses = append(engineResponses, responses...)
}
pc.report(policy.Name, engineResponses, logger)
}
func (pc *PolicyController) applyPolicy(policy *kyverno.ClusterPolicy, resource unstructured.Unstructured, logger logr.Logger) (engineResponses []response.EngineResponse) {
// pre-processing, check if the policy and resource version has been processed before
if !pc.rm.ProcessResource(policy.Name, policy.ResourceVersion, resource.GetKind(), resource.GetNamespace(), resource.GetName(), resource.GetResourceVersion()) {
logger.V(4).Info("policy and resource already processed", "policyResourceVersion", policy.ResourceVersion, "resourceResourceVersion", resource.GetResourceVersion(), "kind", resource.GetKind(), "namespace", resource.GetNamespace(), "name", resource.GetName())
}
engineResponse := applyPolicy(*policy, resource, logger, pc.configHandler.GetExcludeGroupRole(), pc.resCache)
engineResponses = append(engineResponses, engineResponse...)
// post-processing, register the resource as processed
pc.rm.RegisterResource(policy.GetName(), policy.GetResourceVersion(), resource.GetKind(), resource.GetNamespace(), resource.GetName(), resource.GetResourceVersion())
return
}
// excludeAutoGenResources filter out the pods / jobs with ownerReference
func excludeAutoGenResources(policy kyverno.ClusterPolicy, resourceMap map[string]unstructured.Unstructured, log logr.Logger) map[string]unstructured.Unstructured {
func excludeAutoGenResources(policy kyverno.ClusterPolicy, resourceMap map[string]unstructured.Unstructured, log logr.Logger) {
for uid, r := range resourceMap {
if engine.SkipPolicyApplication(policy, r) {
log.V(4).Info("exclude resource", "namespace", r.GetNamespace(), "kind", r.GetKind(), "name", r.GetName())
delete(resourceMap, uid)
}
}
return resourceMap
}
//Condition defines condition type
@ -91,16 +105,10 @@ const (
Skip Condition = 2
)
// merge b into a map
func mergeResources(a, b map[string]unstructured.Unstructured) {
for k, v := range b {
a[k] = v
}
}
//NewResourceManager returns a new ResourceManager
func NewResourceManager(rebuildTime int64) *ResourceManager {
rm := ResourceManager{
scope: make(map[string]bool),
data: make(map[string]interface{}),
time: time.Now(),
rebuildTime: rebuildTime,
@ -113,6 +121,7 @@ func NewResourceManager(rebuildTime int64) *ResourceManager {
type ResourceManager struct {
// we drop and re-build the cache
// based on the memory consumer of by the map
scope map[string]bool
data map[string]interface{}
mux sync.RWMutex
time time.Time
@ -123,7 +132,8 @@ type resourceManager interface {
ProcessResource(policy, pv, kind, ns, name, rv string) bool
//TODO removeResource(kind, ns, name string) error
RegisterResource(policy, pv, kind, ns, name, rv string)
// reload
RegisterScope(kind string, namespaced bool)
GetScope(kind string) (bool, error)
Drop()
}
@ -160,6 +170,28 @@ func (rm *ResourceManager) ProcessResource(policy, pv, kind, ns, name, rv string
return !ok
}
// RegisterScope stores the scope of the given kind
func (rm *ResourceManager) RegisterScope(kind string, namespaced bool) {
rm.mux.Lock()
defer rm.mux.Unlock()
rm.scope[kind] = namespaced
}
// GetScope gets the scope of the given kind
// return error if kind is not registered
func (rm *ResourceManager) GetScope(kind string) (bool, error) {
rm.mux.RLock()
defer rm.mux.RUnlock()
namespaced, ok := rm.scope[kind]
if !ok {
return false, errors.New("NotFound")
}
return namespaced, nil
}
func buildKey(policy, pv, kind, ns, name, rv string) string {
return policy + "/" + pv + "/" + kind + "/" + ns + "/" + name + "/" + rv
}

View file

@ -9,21 +9,17 @@ import (
"github.com/kyverno/kyverno/pkg/policyreport"
)
// for each policy-resource response
// - has violation -> report
// - no violation -> cleanup policy violations
func (pc *PolicyController) cleanupAndReport(engineResponses []response.EngineResponse) {
logger := pc.log
// generate Events
eventInfos := generateEvents(pc.log, engineResponses)
func (pc *PolicyController) report(policy string, engineResponses []response.EngineResponse, logger logr.Logger) {
eventInfos := generateEvents(logger, engineResponses)
pc.eventGen.Add(eventInfos...)
// create policy violation
pvInfos := policyreport.GeneratePRsFromEngineResponse(engineResponses, logger)
for i := range pvInfos {
pvInfos[i].FromSync = true
}
pc.prGenerator.Add(pvInfos...)
pvInfos := policyreport.GeneratePRsFromEngineResponse(engineResponses, logger)
// as engineResponses holds the results for all matched resources in one namespace
// we can merge pvInfos into a single object to reduce update frequency (throttling request) on RCR
info := mergePvInfos(pvInfos)
pc.prGenerator.Add(info)
logger.V(4).Info("added a request to RCR generator", "key", info.ToKey())
}
func generateEvents(log logr.Logger, ers []response.EngineResponse) []event.Info {
@ -61,3 +57,22 @@ func generateEventsPerEr(log logr.Logger, er response.EngineResponse) []event.In
return eventInfos
}
func mergePvInfos(infos []policyreport.Info) policyreport.Info {
aggregatedInfo := policyreport.Info{}
if len(infos) == 0 {
return aggregatedInfo
}
var results []policyreport.EngineResponseResult
for _, info := range infos {
for _, res := range info.Results {
results = append(results, res)
}
}
aggregatedInfo.PolicyName = infos[0].PolicyName
aggregatedInfo.Namespace = infos[0].Namespace
aggregatedInfo.Results = results
return aggregatedInfo
}

View file

@ -50,7 +50,7 @@ type PolicyController struct {
eventGen event.Interface
eventRecorder record.EventRecorder
// Policys that need to be synced
// Policies that need to be synced
queue workqueue.RateLimitingInterface
// pLister can list/get policy from the shared informer's store
@ -62,7 +62,7 @@ type PolicyController struct {
// grLister can list/get generate request from the shared informer's store
grLister kyvernolister.GenerateRequestLister
// nsLister can list/get namespacecs from the shared informer's store
// nsLister can list/get namespaces from the shared informer's store
nsLister listerv1.NamespaceLister
// pListerSynced returns true if the cluster policy store has been synced at least once
@ -82,6 +82,7 @@ type PolicyController struct {
// grListerSynced returns true if the generate request store has been synced at least once
grListerSynced cache.InformerSynced
// Resource manager, manages the mapping for already processed resource
rm resourceManager
@ -203,7 +204,7 @@ func (pc *PolicyController) updatePolicy(old, cur interface{}) {
logger.V(4).Info("updating policy", "name", oldP.Name)
pc.enqueueDeletedRule(oldP, curP)
pc.enqueueRCRDeletedRule(oldP, curP)
pc.enqueuePolicy(curP)
}
@ -213,7 +214,7 @@ func (pc *PolicyController) deletePolicy(obj interface{}) {
if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
logger.Info("couldnt get object from tomstone", "obj", obj)
logger.Info("couldn't get object from tombstone", "obj", obj)
return
}
@ -226,9 +227,10 @@ func (pc *PolicyController) deletePolicy(obj interface{}) {
logger.V(4).Info("deleting policy", "name", p.Name)
// we process policies that are not set of background processing as we need to perform policy violation
// cleanup when a policy is deleted.
// we process policies that are not set of background processing
// as we need to clean up GRs when a policy is deleted
pc.enqueuePolicy(p)
pc.enqueueRCRDeletedPolicy(p.Name)
}
func (pc *PolicyController) addNsPolicy(obj interface{}) {
@ -257,7 +259,7 @@ func (pc *PolicyController) updateNsPolicy(old, cur interface{}) {
logger.V(4).Info("updating namespace policy", "namespace", oldP.Namespace, "name", oldP.Name)
pc.enqueueDeletedRule(ConvertPolicyToClusterPolicy(oldP), ncurP)
pc.enqueueRCRDeletedRule(ConvertPolicyToClusterPolicy(oldP), ncurP)
pc.enqueuePolicy(ncurP)
}
@ -267,7 +269,7 @@ func (pc *PolicyController) deleteNsPolicy(obj interface{}) {
if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
logger.Info("couldnt get object from tomstone", "obj", obj)
logger.Info("couldn't get object from tombstone", "obj", obj)
return
}
@ -280,12 +282,13 @@ func (pc *PolicyController) deleteNsPolicy(obj interface{}) {
pol := ConvertPolicyToClusterPolicy(p)
logger.V(4).Info("deleting namespace policy", "namespace", pol.Namespace, "name", pol.Name)
// we process policies that are not set of background processing as we need to perform policy violation
// cleanup when a policy is deleted.
// we process policies that are not set of background processing
// as we need to clean up GRs when a policy is deleted
pc.enqueuePolicy(pol)
pc.enqueueRCRDeletedPolicy(p.Name)
}
func (pc *PolicyController) enqueueDeletedRule(old, cur *kyverno.ClusterPolicy) {
func (pc *PolicyController) enqueueRCRDeletedRule(old, cur *kyverno.ClusterPolicy) {
curRule := make(map[string]bool)
for _, rule := range cur.Spec.Rules {
curRule[rule.Name] = true
@ -295,14 +298,24 @@ func (pc *PolicyController) enqueueDeletedRule(old, cur *kyverno.ClusterPolicy)
if !curRule[rule.Name] {
pc.prGenerator.Add(policyreport.Info{
PolicyName: cur.GetName(),
Rules: []kyverno.ViolatedRule{
{Name: rule.Name},
Results: []policyreport.EngineResponseResult{
{
Rules: []kyverno.ViolatedRule{
{Name: rule.Name},
},
},
},
})
}
}
}
func (pc *PolicyController) enqueueRCRDeletedPolicy(policyName string) {
pc.prGenerator.Add(policyreport.Info{
PolicyName: policyName,
})
}
func (pc *PolicyController) enqueuePolicy(policy *kyverno.ClusterPolicy) {
logger := pc.log
key, err := cache.MetaNamespaceKeyFunc(policy)
@ -372,7 +385,7 @@ func (pc *PolicyController) handleErr(err error, key interface{}) {
}
func (pc *PolicyController) syncPolicy(key string) error {
logger := pc.log
logger := pc.log.WithName("syncPolicy")
startTime := time.Now()
logger.V(4).Info("started syncing policy", "key", key, "startTime", startTime)
defer func() {
@ -384,55 +397,57 @@ func (pc *PolicyController) syncPolicy(key string) error {
logger.Error(err, "failed to list generate request")
}
var policy *kyverno.ClusterPolicy
namespace, key, isNamespacedPolicy := parseNamespacedPolicy(key)
if !isNamespacedPolicy {
policy, err = pc.pLister.Get(key)
} else {
var nspolicy *kyverno.Policy
nspolicy, err = pc.npLister.Policies(namespace).Get(key)
if err == nil && nspolicy != nil {
policy = ConvertPolicyToClusterPolicy(nspolicy)
}
}
policy, err := pc.getPolicy(key)
if err != nil {
if errors.IsNotFound(err) {
for _, v := range grList {
if key == v.Spec.Policy {
err := pc.kyvernoClient.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Delete(context.TODO(), v.GetName(), metav1.DeleteOptions{})
if err != nil && !errors.IsNotFound(err) {
logger.Error(err, "failed to delete gr")
}
}
}
go pc.removeResultsEntryFromPolicyReport(key)
deleteGR(pc.kyvernoClient, key, grList, logger)
return nil
}
return err
}
updateGR(pc.kyvernoClient, policy.Name, grList, logger)
pc.processExistingResources(policy)
return nil
}
func (pc *PolicyController) getPolicy(key string) (policy *kyverno.ClusterPolicy, err error) {
namespace, key, isNamespacedPolicy := parseNamespacedPolicy(key)
if !isNamespacedPolicy {
return pc.pLister.Get(key)
}
nsPolicy, err := pc.npLister.Policies(namespace).Get(key)
if err == nil && nsPolicy != nil {
policy = ConvertPolicyToClusterPolicy(nsPolicy)
}
return
}
func deleteGR(kyvernoClient *kyvernoclient.Clientset, policyKey string, grList []*kyverno.GenerateRequest, logger logr.Logger) {
for _, v := range grList {
if policy.Name == v.Spec.Policy {
if policyKey == v.Spec.Policy {
err := kyvernoClient.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Delete(context.TODO(), v.GetName(), metav1.DeleteOptions{})
if err != nil && !errors.IsNotFound(err) {
logger.Error(err, "failed to delete gr", "name", v.GetName())
}
}
}
}
func updateGR(kyvernoClient *kyvernoclient.Clientset, policyKey string, grList []*kyverno.GenerateRequest, logger logr.Logger) {
for _, v := range grList {
if policyKey == v.Spec.Policy {
v.SetLabels(map[string]string{
"policy-update": fmt.Sprintf("revision-count-%d", rand.Intn(100000)),
})
_, err := pc.kyvernoClient.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Update(context.TODO(), v, metav1.UpdateOptions{})
_, err := kyvernoClient.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Update(context.TODO(), v, metav1.UpdateOptions{})
if err != nil {
logger.Error(err, "failed to update gr", "policy", policy.GetName(), "gr", v.GetName())
logger.Error(err, "failed to update gr", "name", v.GetName())
}
}
}
engineResponses := pc.processExistingResources(policy)
pc.cleanupAndReport(engineResponses)
return nil
}
func (pc *PolicyController) removeResultsEntryFromPolicyReport(policyName string) {
pc.prGenerator.Add(policyreport.Info{
PolicyName: policyName,
})
}

View file

@ -15,15 +15,13 @@ import (
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
)
const (
clusterreportchangerequest string = "clusterreportchangerequest"
resourceLabelName string = "kyverno.io/resource.name"
resourceLabelKind string = "kyverno.io/resource.kind"
resourceLabelNamespace string = "kyverno.io/resource.namespace"
policyLabel string = "kyverno.io/policy"
deletedLabelResource string = "kyverno.io/delete.resource"
deletedLabelResource string = "kyverno.io/delete.resource.name"
deletedLabelResourceKind string = "kyverno.io/delete.resource.kind"
deletedLabelPolicy string = "kyverno.io/delete.policy"
deletedLabelRule string = "kyverno.io/delete.rule"
@ -76,33 +74,18 @@ func NewBuilder(cpolLister kyvernolister.ClusterPolicyLister, polLister kyvernol
func (builder *requestBuilder) build(info Info) (req *unstructured.Unstructured, err error) {
results := []*report.PolicyReportResult{}
for _, rule := range info.Rules {
if rule.Type != utils.Validation.String() {
continue
}
for _, infoResult := range info.Results {
for _, rule := range infoResult.Rules {
if rule.Type != utils.Validation.String() {
continue
}
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,
Category: builder.fetchCategory(info.PolicyName, info.Resource.GetNamespace()),
result := builder.buildRCRResult(info.PolicyName, infoResult.Resource, rule)
results = append(results, result)
}
result.Rule = rule.Name
result.Message = rule.Message
result.Status = report.PolicyStatus(rule.Check)
results = append(results, result)
}
if info.Resource.GetNamespace() != "" {
if info.Namespace != "" {
rr := &request.ReportChangeRequest{
Summary: calculateSummary(results),
Results: results,
@ -129,58 +112,84 @@ func (builder *requestBuilder) build(info Info) (req *unstructured.Unstructured,
set(req, info)
}
// deletion of a result entry
if len(info.Rules) == 0 && info.PolicyName == "" { // on resource deleteion
req.SetLabels(map[string]string{
resourceLabelNamespace: info.Resource.GetNamespace(),
deletedLabelResource: info.Resource.GetName(),
deletedLabelResourceKind: info.Resource.GetKind()})
} else if info.PolicyName != "" && reflect.DeepEqual(info.Resource, unstructured.Unstructured{}) { // on policy deleteion
req.SetKind("ReportChangeRequest")
if len(info.Rules) == 0 {
req.SetLabels(map[string]string{
deletedLabelPolicy: info.PolicyName})
req.SetName(fmt.Sprintf("reportchangerequest-%s", info.PolicyName))
} else {
req.SetLabels(map[string]string{
deletedLabelPolicy: info.PolicyName,
deletedLabelRule: info.Rules[0].Name})
req.SetName(fmt.Sprintf("reportchangerequest-%s-%s", info.PolicyName, info.Rules[0].Name))
if !setRequestLabels(req, info) {
if len(results) == 0 {
// return nil on empty result without a deletion
return nil, nil
}
} else if len(results) == 0 {
// return nil on empty result without a deletion
return nil, nil
}
return req, nil
}
func (builder *requestBuilder) buildRCRResult(policy string, resource response.ResourceSpec, rule kyverno.ViolatedRule) *report.PolicyReportResult {
result := &report.PolicyReportResult{
Policy: policy,
Resources: []*v1.ObjectReference{
{
Kind: resource.Kind,
Namespace: resource.Namespace,
APIVersion: resource.APIVersion,
Name: resource.Name,
UID: types.UID(resource.UID),
},
},
Scored: true,
Category: builder.fetchCategory(policy, resource.Namespace),
}
result.Rule = rule.Name
result.Message = rule.Message
result.Status = report.PolicyStatus(rule.Check)
return result
}
func set(obj *unstructured.Unstructured, info Info) {
resource := info.Resource
obj.SetNamespace(config.KyvernoNamespace)
obj.SetAPIVersion(request.SchemeGroupVersion.Group + "/" + request.SchemeGroupVersion.Version)
if resource.GetNamespace() == "" {
if info.Namespace == "" {
obj.SetGenerateName(clusterreportchangerequest + "-")
obj.SetKind("ClusterReportChangeRequest")
} else {
obj.SetGenerateName("reportchangerequest-")
obj.SetGenerateName("rcr-")
obj.SetKind("ReportChangeRequest")
obj.SetNamespace(config.KyvernoNamespace)
}
obj.SetLabels(map[string]string{
resourceLabelNamespace: resource.GetNamespace(),
resourceLabelName: resource.GetName(),
resourceLabelKind: resource.GetKind(),
policyLabel: info.PolicyName,
resourceLabelNamespace: info.Namespace,
})
}
if info.FromSync {
obj.SetAnnotations(map[string]string{
"fromSync": "true",
func setRequestLabels(req *unstructured.Unstructured, info Info) bool {
switch {
case isResourceDeletion(info):
req.SetLabels(map[string]string{
resourceLabelNamespace: info.Results[0].Resource.Namespace,
deletedLabelResource: info.Results[0].Resource.Name,
deletedLabelResourceKind: info.Results[0].Resource.Kind,
})
return true
case isPolicyDeletion(info):
req.SetKind("ReportChangeRequest")
req.SetGenerateName("rcr-")
req.SetLabels(map[string]string{
deletedLabelPolicy: info.PolicyName},
)
return true
case isRuleDeletion(info):
req.SetKind("ReportChangeRequest")
req.SetGenerateName("rcr-")
req.SetLabels(map[string]string{
deletedLabelPolicy: info.PolicyName,
deletedLabelRule: info.Results[0].Rules[0].Name},
)
return true
}
return false
}
func calculateSummary(results []*report.PolicyReportResult) (summary report.PolicyReportSummary) {
@ -204,8 +213,13 @@ func calculateSummary(results []*report.PolicyReportResult) (summary report.Poli
func buildPVInfo(er response.EngineResponse) Info {
info := Info{
PolicyName: er.PolicyResponse.Policy,
Resource: er.PatchedResource,
Rules: buildViolatedRules(er),
Namespace: er.PatchedResource.GetNamespace(),
Results: []EngineResponseResult{
{
Resource: er.GetResourceSpec(),
Rules: buildViolatedRules(er),
},
},
}
return info
}
@ -246,3 +260,21 @@ func (builder *requestBuilder) fetchCategory(policy, ns string) string {
return ""
}
func isResourceDeletion(info Info) bool {
return info.PolicyName == "" && len(info.Results) == 1 && info.GetRuleLength() == 0
}
func isPolicyDeletion(info Info) bool {
return info.PolicyName != "" && len(info.Results) == 0
}
func isRuleDeletion(info Info) bool {
if info.PolicyName != "" && len(info.Results) == 1 {
result := info.Results[0]
if len(result.Rules) == 1 && reflect.DeepEqual(result.Resource, response.ResourceSpec{}) {
return true
}
}
return false
}

View file

@ -15,27 +15,39 @@ type deletedResource struct {
kind, ns, name string
}
func buildLabelForDeletedResource(labels map[string]string) *deletedResource {
ok := true
kind, kindOk := labels[deletedLabelResourceKind]
ok = ok && kindOk
name, nameOk := labels[deletedLabelResource]
ok = ok && nameOk
if !ok {
return nil
}
return &deletedResource{
kind: kind,
name: name,
ns: labels[resourceLabelNamespace],
}
}
func getDeletedResources(aggregatedRequests interface{}) (resources []deletedResource) {
if requests, ok := aggregatedRequests.([]*changerequest.ClusterReportChangeRequest); ok {
for _, request := range requests {
labels := request.GetLabels()
dr := deletedResource{
kind: labels[deletedLabelResourceKind],
name: labels[deletedLabelResource],
ns: labels[resourceLabelNamespace],
dr := buildLabelForDeletedResource(request.GetLabels())
if dr != nil {
resources = append(resources, *dr)
}
resources = append(resources, dr)
}
} else if requests, ok := aggregatedRequests.([]*changerequest.ReportChangeRequest); ok {
for _, request := range requests {
labels := request.GetLabels()
dr := deletedResource{
kind: labels[deletedLabelResourceKind],
name: labels[deletedLabelResource],
ns: labels[resourceLabelNamespace],
dr := buildLabelForDeletedResource(request.GetLabels())
if dr != nil {
resources = append(resources, *dr)
}
resources = append(resources, dr)
}
}
return
@ -115,9 +127,16 @@ func generateHashKey(result map[string]interface{}, dr deletedResource) (string,
resource := resources[0].(map[string]interface{})
if !reflect.DeepEqual(dr, deletedResource{}) {
if resource["kind"] == dr.kind && resource["name"] == dr.name && resource["namespace"] == dr.ns {
return "", false
if resource["kind"] == dr.kind {
if resource["name"] == dr.name && resource["namespace"] == dr.ns {
return "", false
}
if dr.kind == "Namespace" && resource["name"] == dr.name {
return "", false
}
}
}
return fmt.Sprintf(

View file

@ -232,6 +232,8 @@ func (g *ReportGenerator) handleErr(err error, key interface{}) {
// syncHandler reconciles clusterPolicyReport if namespace == ""
// otherwise it updates policyReport
func (g *ReportGenerator) syncHandler(key string) error {
g.log.V(4).Info("syncing policy report", "key", key)
if policy, rule, ok := isDeletedPolicyKey(key); ok {
return g.removePolicyEntryFromReport(policy, rule)
}
@ -312,6 +314,31 @@ func (g *ReportGenerator) createReportIfNotPresent(namespace string, new *unstru
}
func (g *ReportGenerator) removePolicyEntryFromReport(policyName, ruleName string) error {
if err := g.removeFromClusterPolicyReport(policyName, ruleName); err != nil {
return err
}
if err := g.removeFromPolicyReport(policyName, ruleName); err != nil {
return err
}
labelset := labels.Set(map[string]string{deletedLabelPolicy: policyName})
if ruleName != "" {
labelset = labels.Set(map[string]string{
deletedLabelPolicy: policyName,
deletedLabelRule: ruleName,
})
}
aggregatedRequests, err := g.reportChangeRequestLister.ReportChangeRequests(config.KyvernoNamespace).List(labels.SelectorFromSet(labelset))
if err != nil {
return err
}
g.cleanupReportRequests(aggregatedRequests)
return nil
}
func (g *ReportGenerator) removeFromClusterPolicyReport(policyName, ruleName string) error {
cpolrs, err := g.clusterReportLister.List(labels.Everything())
if err != nil {
return fmt.Errorf("failed to list clusterPolicyReport %v", err)
@ -335,7 +362,10 @@ func (g *ReportGenerator) removePolicyEntryFromReport(policyName, ruleName strin
return fmt.Errorf("failed to update clusterPolicyReport %s %v", cpolr.Name, err)
}
}
return nil
}
func (g *ReportGenerator) removeFromPolicyReport(policyName, ruleName string) error {
namespaces, err := g.dclient.ListResource("", "Namespace", "", nil)
if err != nil {
return fmt.Errorf("unable to list namespace %v", err)
@ -370,23 +400,10 @@ func (g *ReportGenerator) removePolicyEntryFromReport(policyName, ruleName strin
return fmt.Errorf("failed to update PolicyReport %s %v", r.GetName(), err)
}
}
labelset := labels.Set(map[string]string{deletedLabelPolicy: policyName})
if ruleName != "" {
labelset = labels.Set(map[string]string{
deletedLabelPolicy: policyName,
deletedLabelRule: ruleName,
})
}
aggregatedRequests, err := g.reportChangeRequestLister.ReportChangeRequests(config.KyvernoNamespace).List(labels.SelectorFromSet(labelset))
if err != nil {
return err
}
g.cleanupReportRequests(aggregatedRequests)
return nil
}
// aggregateReports aggregates cluster / report change requests to a policy report
func (g *ReportGenerator) aggregateReports(namespace string) (
report *unstructured.Unstructured, aggregatedRequests interface{}, err error) {
@ -553,7 +570,7 @@ func (g *ReportGenerator) updateReport(old interface{}, new *unstructured.Unstru
new.Object = obj
if !hasResultsChanged(oldUnstructured, new.UnstructuredContent()) {
g.log.V(4).Info("unchanged policy report", "namespace", new.GetNamespace(), "name", new.GetName())
g.log.V(4).Info("unchanged policy report", "kind", new.GetKind(), "namespace", new.GetNamespace(), "name", new.GetName())
return nil
}

View file

@ -19,6 +19,7 @@ import (
"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/engine/response"
"github.com/kyverno/kyverno/pkg/policystatus"
apierrors "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -61,11 +62,7 @@ type Generator struct {
queue workqueue.RateLimitingInterface
dataStore *dataStore
// update policy status with violationCount
policyStatusListener policystatus.Listener
log logr.Logger
log logr.Logger
}
// NewReportChangeRequestGenerator returns a new instance of report request generator
@ -89,7 +86,6 @@ func NewReportChangeRequestGenerator(client *policyreportclient.Clientset,
polListerSynced: polInformer.Informer().HasSynced,
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), workQueueName),
dataStore: newDataStore(),
policyStatusListener: policyStatus,
log: log,
}
@ -128,32 +124,47 @@ func (ds *dataStore) delete(keyHash string) {
delete(ds.data, keyHash)
}
//Info is a request to create PV
// Info stores the policy application results for all matched resources
// Namespace is set to empty "" if resource is cluster wide resource
type Info struct {
PolicyName string
Resource unstructured.Unstructured
Rules []kyverno.ViolatedRule
FromSync bool
Namespace string
Results []EngineResponseResult
}
func (i Info) toKey() string {
type EngineResponseResult struct {
Resource response.ResourceSpec
Rules []kyverno.ViolatedRule
}
func (i Info) ToKey() string {
keys := []string{
i.PolicyName,
i.Resource.GetKind(),
i.Resource.GetNamespace(),
i.Resource.GetName(),
strconv.Itoa(len(i.Rules)),
i.Namespace,
strconv.Itoa(len(i.Results)),
}
for _, result := range i.Results {
keys = append(keys, result.Resource.GetKey())
}
return strings.Join(keys, "/")
}
func (i Info) GetRuleLength() int {
l := 0
for _, res := range i.Results {
l += len(res.Rules)
}
return l
}
// GeneratorInterface provides API to create PVs
type GeneratorInterface interface {
Add(infos ...Info)
}
func (gen *Generator) enqueue(info Info) {
keyHash := info.toKey()
keyHash := info.ToKey()
gen.dataStore.add(keyHash, info)
gen.queue.Add(keyHash)
}
@ -231,7 +242,7 @@ func (gen *Generator) processNextWorkItem() bool {
info := gen.dataStore.lookup(keyHash)
if reflect.DeepEqual(info, Info{}) {
gen.queue.Forget(obj)
logger.V(3).Info("empty key")
logger.V(4).Info("empty key")
return nil
}
@ -249,48 +260,48 @@ func (gen *Generator) processNextWorkItem() bool {
func (gen *Generator) syncHandler(info Info) error {
gen.log.V(3).Info("generating report change request")
builder := NewBuilder(gen.cpolLister, gen.polLister)
reportChangeRequestUnstructured, err := builder.build(info)
rcrUnstructured, err := builder.build(info)
if err != nil {
return fmt.Errorf("unable to build reportChangeRequest: %v", err)
}
if reportChangeRequestUnstructured == nil {
if rcrUnstructured == nil {
return nil
}
return gen.sync(reportChangeRequestUnstructured, info)
return gen.sync(rcrUnstructured, info)
}
func (gen *Generator) sync(reportReq *unstructured.Unstructured, info Info) error {
defer func() {
if val := reportReq.GetAnnotations()["fromSync"]; val == "true" {
gen.policyStatusListener.Update(violationCount{
policyName: info.PolicyName,
violatedRules: info.Rules,
})
}
}()
logger := gen.log.WithName("sync")
logger := gen.log.WithName("sync report change request")
reportReq.SetCreationTimestamp(v1.Now())
if reportReq.GetNamespace() == "" {
old, err := gen.clusterReportChangeRequestLister.Get(reportReq.GetName())
if err != nil {
if apierrors.IsNotFound(err) {
if _, err = gen.dclient.CreateResource(reportReq.GetAPIVersion(), reportReq.GetKind(), "", reportReq, false); err != nil {
return fmt.Errorf("failed to create clusterReportChangeRequest: %v", err)
}
logger.V(3).Info("successfully created clusterReportChangeRequest", "name", reportReq.GetName())
return nil
}
return fmt.Errorf("unable to get %s: %v", reportReq.GetKind(), err)
}
return updateReportChangeRequest(gen.dclient, old, reportReq, logger)
if reportReq.GetKind() == "ClusterReportChangeRequest" {
return gen.syncClusterReportChangeRequest(reportReq, logger)
}
return gen.syncReportChangeRequest(reportReq, logger)
}
func (gen *Generator) syncClusterReportChangeRequest(reportReq *unstructured.Unstructured, logger logr.Logger) error {
old, err := gen.clusterReportChangeRequestLister.Get(reportReq.GetName())
if err != nil {
if apierrors.IsNotFound(err) {
if _, err = gen.dclient.CreateResource(reportReq.GetAPIVersion(), reportReq.GetKind(), "", reportReq, false); err != nil {
return fmt.Errorf("failed to create clusterReportChangeRequest: %v", err)
}
logger.V(3).Info("successfully created clusterReportChangeRequest", "name", reportReq.GetName())
return nil
}
return fmt.Errorf("unable to get %s: %v", reportReq.GetKind(), err)
}
return updateReportChangeRequest(gen.dclient, old, reportReq, logger)
}
func (gen *Generator) syncReportChangeRequest(reportReq *unstructured.Unstructured, logger logr.Logger) error {
old, err := gen.reportChangeRequestLister.ReportChangeRequests(config.KyvernoNamespace).Get(reportReq.GetName())
if err != nil {
if apierrors.IsNotFound(err) {

View file

@ -130,22 +130,27 @@ func HandleValidation(
prGenerator.Add(prInfos...)
if request.Operation == v1beta1.Delete {
prGenerator.Add(policyreport.Info{
Resource: unstructured.Unstructured{
Object: map[string]interface{}{
"kind": oldR.GetKind(),
"metadata": map[string]interface{}{
"name": oldR.GetName(),
"namespace": oldR.GetNamespace(),
},
},
},
})
prGenerator.Add(buildDeletionPrInfo(oldR))
}
return true, ""
}
func buildDeletionPrInfo(oldR unstructured.Unstructured) policyreport.Info {
return policyreport.Info{
Namespace: oldR.GetNamespace(),
Results: []policyreport.EngineResponseResult{
{Resource: response.ResourceSpec{
Kind: oldR.GetKind(),
APIVersion: oldR.GetAPIVersion(),
Namespace: oldR.GetNamespace(),
Name: oldR.GetName(),
UID: string(oldR.GetUID()),
}},
},
}
}
type validateStats struct {
resp response.EngineResponse
namespace string