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

namespace selector (#1532)

* updated crd with namespace selector

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* added logic for validate

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* added condition in utils for namespace labels

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* added function for extracting namespace label using lister

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* added logic for generate

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* added lister in generate

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* commented generate controller changes

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* added ns lister

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* added ns label in apply.go

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* added ns label in generation.go

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* added ns label in mutation.go

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* added ns label for validation

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* using dynaminc informer

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>
This commit is contained in:
Pooja Singh 2021-02-04 02:39:42 +05:30 committed by GitHub
parent f2478921e9
commit 32522e7827
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 642 additions and 52 deletions

View file

@ -140,6 +140,58 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
namespaceSelector:
description: 'NamespaceSelector is a label selector for namespace.
Label keys and values in `matchLabels` support the wildcard
characters `*` (matches zero or many characters) and `?`
(matches one character). Wildcards allows writing label
selectors like ["storage.k8s.io/*": "*"]. Note that using
["*" : "*"] matches any key and value but does not match
an empty label set.'
properties:
matchExpressions:
description: matchExpressions is a list of label
selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a
selector that contains values, a key, and an
operator that relates the key and values.
properties:
key:
description: key is the label key that the
selector applies to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are
In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string
values. If the operator is In or NotIn,
the values array must be non-empty. If the
operator is Exists or DoesNotExist, the
values array must be empty. This array is
replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value}
pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions,
whose key field is "key", the operator is "In",
and the values array contains only "value". The
requirements are ANDed.
type: object
type: object
type: object
roles:
description: Roles is the list of namespaced role names for the user.
@ -260,6 +312,58 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
namespaceSelector:
description: 'NamespaceSelector is a label selector for namespace.
Label keys and values in `matchLabels` support the wildcard
characters `*` (matches zero or many characters) and `?`
(matches one character). Wildcards allows writing label
selectors like ["storage.k8s.io/*": "*"]. Note that using
["*" : "*"] matches any key and value but does not match
an empty label set.'
properties:
matchExpressions:
description: matchExpressions is a list of label
selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a
selector that contains values, a key, and an
operator that relates the key and values.
properties:
key:
description: key is the label key that the
selector applies to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are
In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string
values. If the operator is In or NotIn,
the values array must be non-empty. If the
operator is Exists or DoesNotExist, the
values array must be empty. This array is
replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value}
pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions,
whose key field is "key", the operator is "In",
and the values array contains only "value". The
requirements are ANDed.
type: object
type: object
type: object
roles:
description: Roles is the list of namespaced role names for the user.
@ -1259,6 +1363,58 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
namespaceSelector:
description: 'NamespaceSelector is a label selector for namespace.
Label keys and values in `matchLabels` support the wildcard
characters `*` (matches zero or many characters) and `?`
(matches one character). Wildcards allows writing label
selectors like ["storage.k8s.io/*": "*"]. Note that using
["*" : "*"] matches any key and value but does not match
an empty label set.'
properties:
matchExpressions:
description: matchExpressions is a list of label
selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a
selector that contains values, a key, and an
operator that relates the key and values.
properties:
key:
description: key is the label key that the
selector applies to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are
In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string
values. If the operator is In or NotIn,
the values array must be non-empty. If the
operator is Exists or DoesNotExist, the
values array must be empty. This array is
replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value}
pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions,
whose key field is "key", the operator is "In",
and the values array contains only "value". The
requirements are ANDed.
type: object
type: object
type: object
roles:
description: Roles is the list of namespaced role names for the user.
@ -1379,6 +1535,58 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
namespaceSelector:
description: 'NamespaceSelector is a label selector for namespace.
Label keys and values in `matchLabels` support the wildcard
characters `*` (matches zero or many characters) and `?`
(matches one character). Wildcards allows writing label
selectors like ["storage.k8s.io/*": "*"]. Note that using
["*" : "*"] matches any key and value but does not match
an empty label set.'
properties:
matchExpressions:
description: matchExpressions is a list of label
selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a
selector that contains values, a key, and an
operator that relates the key and values.
properties:
key:
description: key is the label key that the
selector applies to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are
In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string
values. If the operator is In or NotIn,
the values array must be non-empty. If the
operator is Exists or DoesNotExist, the
values array must be empty. This array is
replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value}
pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions,
whose key field is "key", the operator is "In",
and the values array contains only "value". The
requirements are ANDed.
type: object
type: object
type: object
roles:
description: Roles is the list of namespaced role names for the user.

View file

@ -274,6 +274,7 @@ func main() {
reportReqGen,
kubeInformer.Rbac().V1().RoleBindings(),
kubeInformer.Rbac().V1().ClusterRoleBindings(),
kubeInformer.Core().V1().Namespaces(),
log.Log.WithName("ValidateAuditHandler"),
configData,
rCache,
@ -321,6 +322,7 @@ func main() {
kubeInformer.Rbac().V1().ClusterRoleBindings(),
kubeInformer.Rbac().V1().Roles(),
kubeInformer.Rbac().V1().ClusterRoles(),
kubeInformer.Core().V1().Namespaces(),
eventGenerator,
pCacheController.Cache,
webhookCfg,

View file

@ -197,6 +197,58 @@ spec:
requirements are ANDed.
type: object
type: object
namespaceSelector:
description: 'NamespaceSelector is a label selector for namespace.
Label keys and values in `matchLabels` support the wildcard
characters `*` (matches zero or many characters) and `?`
(matches one character). Wildcards allows writing label
selectors like ["storage.k8s.io/*": "*"]. Note that using
["*" : "*"] matches any key and value but does not match
an empty label set.'
properties:
matchExpressions:
description: matchExpressions is a list of label
selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a
selector that contains values, a key, and an
operator that relates the key and values.
properties:
key:
description: key is the label key that the
selector applies to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are
In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string
values. If the operator is In or NotIn,
the values array must be non-empty. If the
operator is Exists or DoesNotExist, the
values array must be empty. This array is
replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value}
pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions,
whose key field is "key", the operator is "In",
and the values array contains only "value". The
requirements are ANDed.
type: object
type: object
type: object
roles:
description: Roles is the list of namespaced role names
@ -379,6 +431,58 @@ spec:
requirements are ANDed.
type: object
type: object
namespaceSelector:
description: 'NamespaceSelector is a label selector for namespace.
Label keys and values in `matchLabels` support the wildcard
characters `*` (matches zero or many characters) and `?`
(matches one character). Wildcards allows writing label
selectors like ["storage.k8s.io/*": "*"]. Note that using
["*" : "*"] matches any key and value but does not match
an empty label set.'
properties:
matchExpressions:
description: matchExpressions is a list of label
selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a
selector that contains values, a key, and an
operator that relates the key and values.
properties:
key:
description: key is the label key that the
selector applies to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are
In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string
values. If the operator is In or NotIn,
the values array must be non-empty. If the
operator is Exists or DoesNotExist, the
values array must be empty. This array is
replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value}
pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions,
whose key field is "key", the operator is "In",
and the values array contains only "value". The
requirements are ANDed.
type: object
type: object
type: object
roles:
description: Roles is the list of namespaced role names

View file

@ -198,6 +198,58 @@ spec:
requirements are ANDed.
type: object
type: object
namespaceSelector:
description: 'NamespaceSelector is a label selector for namespace.
Label keys and values in `matchLabels` support the wildcard
characters `*` (matches zero or many characters) and `?`
(matches one character). Wildcards allows writing label
selectors like ["storage.k8s.io/*": "*"]. Note that using
["*" : "*"] matches any key and value but does not match
an empty label set.'
properties:
matchExpressions:
description: matchExpressions is a list of label
selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a
selector that contains values, a key, and an
operator that relates the key and values.
properties:
key:
description: key is the label key that the
selector applies to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are
In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string
values. If the operator is In or NotIn,
the values array must be non-empty. If the
operator is Exists or DoesNotExist, the
values array must be empty. This array is
replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value}
pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions,
whose key field is "key", the operator is "In",
and the values array contains only "value". The
requirements are ANDed.
type: object
type: object
type: object
roles:
description: Roles is the list of namespaced role names
@ -380,6 +432,58 @@ spec:
requirements are ANDed.
type: object
type: object
namespaceSelector:
description: 'NamespaceSelector is a label selector for namespace.
Label keys and values in `matchLabels` support the wildcard
characters `*` (matches zero or many characters) and `?`
(matches one character). Wildcards allows writing label
selectors like ["storage.k8s.io/*": "*"]. Note that using
["*" : "*"] matches any key and value but does not match
an empty label set.'
properties:
matchExpressions:
description: matchExpressions is a list of label
selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a
selector that contains values, a key, and an
operator that relates the key and values.
properties:
key:
description: key is the label key that the
selector applies to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are
In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string
values. If the operator is In or NotIn,
the values array must be non-empty. If the
operator is Exists or DoesNotExist, the
values array must be empty. This array is
replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value}
pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions,
whose key field is "key", the operator is "In",
and the values array contains only "value". The
requirements are ANDed.
type: object
type: object
type: object
roles:
description: Roles is the list of namespaced role names

View file

@ -238,6 +238,14 @@ type ResourceDescription struct {
// using ["*" : "*"] matches any key and value but does not match an empty label set.
// +optional
Selector *metav1.LabelSelector `json:"selector,omitempty" yaml:"selector,omitempty"`
// NamespaceSelector is a label selector for the resource namespace. Label keys and values
// in `matchLabels` support the wildcard characters `*` (matches zero or many characters)
// and `?` (matches one character).Wildcards allows writing label selectors like
// ["storage.k8s.io/*": "*"]. Note that using ["*" : "*"] matches any key and value but
// does not match an empty label set.
// +optional
NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" yaml:"namespaceSelector,omitempty"`
}
// Mutation defines how resource are modified.

View file

@ -1,5 +1,16 @@
package common
import (
"encoding/json"
"github.com/go-logr/logr"
enginutils "github.com/kyverno/kyverno/pkg/engine/utils"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/informers"
listerv1 "k8s.io/client-go/listers/core/v1"
"sigs.k8s.io/controller-runtime/pkg/log"
)
// Policy Reporting Modes
const (
Enforce = "enforce" // blocks the request on failure
@ -11,3 +22,43 @@ const (
PolicyViolation = "POLICYVIOLATION"
PolicyReport = "POLICYREPORT"
)
// GetNamespaceSelectorsFromGenericInformer - extracting the namespacelabels when generic informer is passed
func GetNamespaceSelectorsFromGenericInformer(kind, namespaceOfResource string, nsInformer informers.GenericInformer, logger logr.Logger) map[string]string {
namespaceLabels := make(map[string]string)
if kind != "Namespace" {
runtimeNamespaceObj, err := nsInformer.Lister().Get(namespaceOfResource)
namespaceObj := runtimeNamespaceObj.(*v1.Namespace)
if err != nil {
log.Log.Error(err, "failed to get the namespace", "name", namespaceOfResource)
}
return GetNamespaceLabels(namespaceObj, logger)
}
return namespaceLabels
}
// GetNamespaceSelectorsFromNamespaceLister - extract the namespacelabels when namespace lister is passed
func GetNamespaceSelectorsFromNamespaceLister(kind, namespaceOfResource string, nsLister listerv1.NamespaceLister, logger logr.Logger) map[string]string {
namespaceLabels := make(map[string]string)
if kind != "Namespace" {
namespaceObj, err := nsLister.Get(namespaceOfResource)
if err != nil {
log.Log.Error(err, "failed to get the namespace", "name", namespaceOfResource)
}
return GetNamespaceLabels(namespaceObj, logger)
}
return namespaceLabels
}
// GetNamespaceLabels - from namespace obj
func GetNamespaceLabels(namespaceObj *v1.Namespace, logger logr.Logger) map[string]string {
namespaceObj.Kind = "Namespace"
namespaceRaw, err := json.Marshal(namespaceObj)
namespaceUnstructured, err := enginutils.ConvertToUnstructured(namespaceRaw)
if err != nil {
logger.Error(err, "failed to convert object resource to unstructured format")
}
return namespaceUnstructured.GetLabels()
}

View file

@ -1,11 +1,12 @@
package engine
import (
"time"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/engine/response"
"github.com/kyverno/kyverno/pkg/engine/variables"
"sigs.k8s.io/controller-runtime/pkg/log"
"time"
)
// Generate checks for validity of generate rule on the resource
@ -60,14 +61,15 @@ func filterRule(rule kyverno.Rule, policyContext *PolicyContext) *response.RuleR
ctx := policyContext.JSONContext
resCache := policyContext.ResourceCache
excludeGroupRole := policyContext.ExcludeGroupRole
namespaceLabels := policyContext.NamespaceLabels
logger := log.Log.WithName("Generate").WithValues("policy", policy.Name,
"kind", newResource.GetKind(), "namespace", newResource.GetNamespace(), "name", newResource.GetName())
if err := MatchesResourceDescription(newResource, rule, admissionInfo, excludeGroupRole); err != nil {
if err := MatchesResourceDescription(newResource, rule, admissionInfo, excludeGroupRole, namespaceLabels); err != nil {
// if the oldResource matched, return "false" to delete GR for it
if err := MatchesResourceDescription(oldResource, rule, admissionInfo, excludeGroupRole); err == nil {
if err := MatchesResourceDescription(oldResource, rule, admissionInfo, excludeGroupRole, namespaceLabels); err == nil {
return &response.RuleResponse{
Name: rule.Name,
Type: "Generation",

View file

@ -62,7 +62,7 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) {
excludeResource = policyContext.ExcludeGroupRole
}
if err := MatchesResourceDescription(patchedResource, rule, policyContext.AdmissionInfo, excludeResource); err != nil {
if err := MatchesResourceDescription(patchedResource, rule, policyContext.AdmissionInfo, excludeResource, policyContext.NamespaceLabels); err != nil {
logger.V(4).Info("rule not matched", "reason", err.Error())
continue
}

View file

@ -36,4 +36,7 @@ type PolicyContext struct {
// JSONContext is the variable context
JSONContext *context.Context
// NamespaceLabels stores the label of namespace to be processed by namespace selector
NamespaceLabels map[string]string
}

View file

@ -108,7 +108,7 @@ func checkSelector(labelSelector *metav1.LabelSelector, resourceLabels map[strin
// should be: AND across attributes but an OR inside attributes that of type list
// To filter out the targeted resources with UserInfo, the check
// should be: OR (across & inside) attributes
func doesResourceMatchConditionBlock(conditionBlock kyverno.ResourceDescription, userInfo kyverno.UserInfo, admissionInfo kyverno.RequestInfo, resource unstructured.Unstructured, dynamicConfig []string) []error {
func doesResourceMatchConditionBlock(conditionBlock kyverno.ResourceDescription, userInfo kyverno.UserInfo, admissionInfo kyverno.RequestInfo, resource unstructured.Unstructured, dynamicConfig []string, namespaceLabels map[string]string) []error {
var errs []error
if len(conditionBlock.Kinds) > 0 {
@ -146,6 +146,17 @@ func doesResourceMatchConditionBlock(conditionBlock kyverno.ResourceDescription,
}
}
if conditionBlock.NamespaceSelector != nil && namespaceLabels != nil && resource.GetKind() != "Namespace" && resource.GetKind() != "" {
hasPassed, err := checkSelector(conditionBlock.NamespaceSelector, namespaceLabels)
if err != nil {
errs = append(errs, fmt.Errorf("failed to parse namespace selector: %v", err))
} else {
if !hasPassed {
errs = append(errs, fmt.Errorf("namespace selector does not match"))
}
}
}
keys := append(admissionInfo.AdmissionUserInfo.Groups, admissionInfo.AdmissionUserInfo.Username)
var userInfoErrors []error
var checkedItem int
@ -220,7 +231,7 @@ func matchSubjects(ruleSubjects []rbacv1.Subject, userInfo authenticationv1.User
}
//MatchesResourceDescription checks if the resource matches resource description of the rule or not
func MatchesResourceDescription(resourceRef unstructured.Unstructured, ruleRef kyverno.Rule, admissionInfoRef kyverno.RequestInfo, dynamicConfig []string) error {
func MatchesResourceDescription(resourceRef unstructured.Unstructured, ruleRef kyverno.Rule, admissionInfoRef kyverno.RequestInfo, dynamicConfig []string, namespaceLabels map[string]string) error {
rule := *ruleRef.DeepCopy()
resource := *resourceRef.DeepCopy()
@ -235,7 +246,7 @@ func MatchesResourceDescription(resourceRef unstructured.Unstructured, ruleRef k
// checking if resource matches the rule
if !reflect.DeepEqual(rule.MatchResources.ResourceDescription, kyverno.ResourceDescription{}) ||
!reflect.DeepEqual(rule.MatchResources.UserInfo, kyverno.UserInfo{}) {
matchErrs := doesResourceMatchConditionBlock(rule.MatchResources.ResourceDescription, rule.MatchResources.UserInfo, admissionInfo, resource, dynamicConfig)
matchErrs := doesResourceMatchConditionBlock(rule.MatchResources.ResourceDescription, rule.MatchResources.UserInfo, admissionInfo, resource, dynamicConfig, namespaceLabels)
reasonsForFailure = append(reasonsForFailure, matchErrs...)
} else {
reasonsForFailure = append(reasonsForFailure, fmt.Errorf("match cannot be empty"))
@ -244,7 +255,7 @@ func MatchesResourceDescription(resourceRef unstructured.Unstructured, ruleRef k
// checking if resource has been excluded
if !reflect.DeepEqual(rule.ExcludeResources.ResourceDescription, kyverno.ResourceDescription{}) ||
!reflect.DeepEqual(rule.ExcludeResources.UserInfo, kyverno.UserInfo{}) {
excludeErrs := doesResourceMatchConditionBlock(rule.ExcludeResources.ResourceDescription, rule.ExcludeResources.UserInfo, admissionInfo, resource, dynamicConfig)
excludeErrs := doesResourceMatchConditionBlock(rule.ExcludeResources.ResourceDescription, rule.ExcludeResources.UserInfo, admissionInfo, resource, dynamicConfig, namespaceLabels)
if excludeErrs == nil {
reasonsForFailure = append(reasonsForFailure, fmt.Errorf("resource excluded"))
}

View file

@ -70,7 +70,7 @@ func TestMatchesResourceDescription(t *testing.T) {
resource, _ := utils.ConvertToUnstructured(tc.Resource)
for _, rule := range policy.Spec.Rules {
err := MatchesResourceDescription(*resource, rule, tc.AdmissionInfo, []string{})
err := MatchesResourceDescription(*resource, rule, tc.AdmissionInfo, []string{}, nil)
if err != nil {
if !tc.areErrorsExpected {
t.Errorf("Testcase %d Unexpected error: %v", i+1, err)
@ -138,7 +138,7 @@ func TestResourceDescriptionMatch_MultipleKind(t *testing.T) {
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}); err != nil {
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
@ -199,7 +199,7 @@ func TestResourceDescriptionMatch_Name(t *testing.T) {
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}); err != nil {
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
@ -259,7 +259,7 @@ func TestResourceDescriptionMatch_Name_Regex(t *testing.T) {
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}); err != nil {
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
@ -327,7 +327,7 @@ func TestResourceDescriptionMatch_Label_Expression_NotMatch(t *testing.T) {
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}); err != nil {
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
@ -396,7 +396,7 @@ func TestResourceDescriptionMatch_Label_Expression_Match(t *testing.T) {
}
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}); err != nil {
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil); err != nil {
t.Errorf("Testcase has failed due to the following:%v", err)
}
}
@ -476,7 +476,7 @@ func TestResourceDescriptionExclude_Label_Expression_Match(t *testing.T) {
rule := kyverno.Rule{MatchResources: kyverno.MatchResources{ResourceDescription: resourceDescription},
ExcludeResources: kyverno.ExcludeResources{ResourceDescription: resourceDescriptionExclude}}
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}); err == nil {
if err := MatchesResourceDescription(*resource, rule, kyverno.RequestInfo{}, []string{}, nil); err == nil {
t.Errorf("Testcase has failed due to the following:\n Function has returned no error, even though it was supposed to fail")
}
}

View file

@ -171,13 +171,13 @@ func validateResourceWithRule(log logr.Logger, ctx *PolicyContext, rule kyverno.
// matches checks if either the new or old resource satisfies the filter conditions defined in the rule
func matches(logger logr.Logger, rule kyverno.Rule, ctx *PolicyContext) bool {
err := MatchesResourceDescription(ctx.NewResource, rule, ctx.AdmissionInfo, ctx.ExcludeGroupRole)
err := MatchesResourceDescription(ctx.NewResource, rule, ctx.AdmissionInfo, ctx.ExcludeGroupRole, ctx.NamespaceLabels)
if err == nil {
return true
}
if !reflect.DeepEqual(ctx.OldResource, unstructured.Unstructured{}) {
err := MatchesResourceDescription(ctx.OldResource, rule, ctx.AdmissionInfo, ctx.ExcludeGroupRole)
err := MatchesResourceDescription(ctx.OldResource, rule, ctx.AdmissionInfo, ctx.ExcludeGroupRole, ctx.NamespaceLabels)
if err == nil {
return true
}

View file

@ -11,6 +11,7 @@ import (
"github.com/go-logr/logr"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
pkgcommon "github.com/kyverno/kyverno/pkg/common"
"github.com/kyverno/kyverno/pkg/config"
dclient "github.com/kyverno/kyverno/pkg/dclient"
"github.com/kyverno/kyverno/pkg/engine"
@ -44,7 +45,8 @@ func (c *Controller) processGR(gr *kyverno.GenerateRequest) error {
}
// 2 - Apply the generate policy on the resource
genResources, err = c.applyGenerate(*resource, *gr)
namespaceLabels := pkgcommon.GetNamespaceSelectorsFromGenericInformer(resource.GetKind(), resource.GetNamespace(), c.nsInformer, logger)
genResources, err = c.applyGenerate(*resource, *gr, namespaceLabels)
if err != nil {
// Need not update the stauts when policy doesn't apply on resource, because all the generate requests are removed by the cleanup controller
@ -64,7 +66,7 @@ func (c *Controller) processGR(gr *kyverno.GenerateRequest) error {
const doesNotApply = "policy does not apply to resource"
func (c *Controller) applyGenerate(resource unstructured.Unstructured, gr kyverno.GenerateRequest) ([]kyverno.ResourceSpec, error) {
func (c *Controller) applyGenerate(resource unstructured.Unstructured, gr kyverno.GenerateRequest, namespaceLabels map[string]string) ([]kyverno.ResourceSpec, error) {
logger := c.log.WithValues("name", gr.Name, "policy", gr.Spec.Policy, "kind", gr.Spec.Resource.Kind, "apiVersion", gr.Spec.Resource.APIVersion, "namespace", gr.Spec.Resource.Namespace, "name", gr.Spec.Resource.Name)
// Get the list of rules to be applied
// get policy
@ -129,6 +131,7 @@ func (c *Controller) applyGenerate(resource unstructured.Unstructured, gr kyvern
ExcludeResourceFunc: c.Config.ToFilter,
ResourceCache: c.resCache,
JSONContext: ctx,
NamespaceLabels: namespaceLabels,
}
// check if the policy still applies to the resource

View file

@ -60,6 +60,12 @@ type Controller struct {
// dynamic shared informer factory
dynamicInformer dynamicinformer.DynamicSharedInformerFactory
// // nsLister can list/get namespaces from the shared informer's store
// nsLister listerv1.NamespaceLister
// // nsListerSynced returns true if the namespace store has been synced at least once
// nsListerSynced cache.InformerSynced
//TODO: list of generic informers
// only support Namespaces for re-evaluation on resource updates
nsInformer informers.GenericInformer
@ -128,6 +134,9 @@ func NewController(
UpdateFunc: c.updateGenericResource,
})
// c.nsLister = namespaces.Lister()
// c.nsListerSynced = namespaces.Informer().HasSynced
return &c, nil
}

View file

@ -22,7 +22,7 @@ import (
// applyPolicy applies policy on a resource
func applyPolicy(policy kyverno.ClusterPolicy, resource unstructured.Unstructured,
logger logr.Logger, excludeGroupRole []string, resCache resourcecache.ResourceCache,
client *client.Client) (responses []*response.EngineResponse) {
client *client.Client, namespaceLabels map[string]string) (responses []*response.EngineResponse) {
startTime := time.Now()
defer func() {
@ -45,7 +45,7 @@ func applyPolicy(policy kyverno.ClusterPolicy, resource unstructured.Unstructure
logger.Error(err, "enable to add transform resource to ctx")
}
engineResponseMutation, err = mutation(policy, resource, logger, resCache, ctx)
engineResponseMutation, err = mutation(policy, resource, logger, resCache, ctx, namespaceLabels)
if err != nil {
logger.Error(err, "failed to process mutation rule")
}
@ -65,13 +65,14 @@ func applyPolicy(policy kyverno.ClusterPolicy, resource unstructured.Unstructure
return engineResponses
}
func mutation(policy kyverno.ClusterPolicy, resource unstructured.Unstructured, log logr.Logger, resCache resourcecache.ResourceCache, jsonContext *context.Context) (*response.EngineResponse, error) {
func mutation(policy kyverno.ClusterPolicy, resource unstructured.Unstructured, log logr.Logger, resCache resourcecache.ResourceCache, jsonContext *context.Context, namespaceLabels map[string]string) (*response.EngineResponse, error) {
policyContext := &engine.PolicyContext{
Policy: policy,
NewResource: resource,
ResourceCache: resCache,
JSONContext: jsonContext,
Policy: policy,
NewResource: resource,
ResourceCache: resCache,
JSONContext: jsonContext,
NamespaceLabels: namespaceLabels,
}
engineResponse := engine.Mutate(policyContext)

View file

@ -8,6 +8,7 @@ import (
"github.com/go-logr/logr"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/common"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/response"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -84,7 +85,8 @@ func (pc *PolicyController) applyPolicy(policy *kyverno.ClusterPolicy, resource
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, pc.client)
namespaceLabels := common.GetNamespaceSelectorsFromNamespaceLister(resource.GetKind(), resource.GetNamespace(), pc.nsLister, logger)
engineResponse := applyPolicy(*policy, resource, logger, pc.configHandler.GetExcludeGroupRole(), pc.resCache, pc.client, namespaceLabels)
engineResponses = append(engineResponses, engineResponse...)
// post-processing, register the resource as processed

View file

@ -4,11 +4,12 @@ import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
"github.com/jmespath/go-jmespath"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/kyverno/common"
"reflect"
"strings"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
dclient "github.com/kyverno/kyverno/pkg/dclient"
@ -200,11 +201,19 @@ func doMatchAndExcludeConflict(rule kyverno.Rule) bool {
excludeNamespaces[namespace] = true
}
excludeMatchExpressions := make(map[string]bool)
excludeSelectorMatchExpressions := make(map[string]bool)
if rule.ExcludeResources.ResourceDescription.Selector != nil {
for _, matchExpression := range rule.ExcludeResources.ResourceDescription.Selector.MatchExpressions {
matchExpressionRaw, _ := json.Marshal(matchExpression)
excludeMatchExpressions[string(matchExpressionRaw)] = true
excludeSelectorMatchExpressions[string(matchExpressionRaw)] = true
}
}
excludeNamespaceSelectorMatchExpressions := make(map[string]bool)
if rule.ExcludeResources.ResourceDescription.NamespaceSelector != nil {
for _, matchExpression := range rule.ExcludeResources.ResourceDescription.NamespaceSelector.MatchExpressions {
matchExpressionRaw, _ := json.Marshal(matchExpression)
excludeNamespaceSelectorMatchExpressions[string(matchExpressionRaw)] = true
}
}
@ -276,14 +285,14 @@ func doMatchAndExcludeConflict(rule kyverno.Rule) bool {
}
if rule.MatchResources.ResourceDescription.Selector != nil && rule.ExcludeResources.ResourceDescription.Selector != nil {
if len(excludeMatchExpressions) > 0 {
if len(excludeSelectorMatchExpressions) > 0 {
if len(rule.MatchResources.ResourceDescription.Selector.MatchExpressions) == 0 {
return false
}
for _, matchExpression := range rule.MatchResources.ResourceDescription.Selector.MatchExpressions {
matchExpressionRaw, _ := json.Marshal(matchExpression)
if !excludeMatchExpressions[string(matchExpressionRaw)] {
if !excludeSelectorMatchExpressions[string(matchExpressionRaw)] {
return false
}
}
@ -302,11 +311,43 @@ func doMatchAndExcludeConflict(rule kyverno.Rule) bool {
}
}
if rule.MatchResources.ResourceDescription.NamespaceSelector != nil && rule.ExcludeResources.ResourceDescription.NamespaceSelector != nil {
if len(excludeNamespaceSelectorMatchExpressions) > 0 {
if len(rule.MatchResources.ResourceDescription.NamespaceSelector.MatchExpressions) == 0 {
return false
}
for _, matchExpression := range rule.MatchResources.ResourceDescription.NamespaceSelector.MatchExpressions {
matchExpressionRaw, _ := json.Marshal(matchExpression)
if !excludeNamespaceSelectorMatchExpressions[string(matchExpressionRaw)] {
return false
}
}
}
if len(rule.ExcludeResources.ResourceDescription.NamespaceSelector.MatchLabels) > 0 {
if len(rule.MatchResources.ResourceDescription.NamespaceSelector.MatchLabels) == 0 {
return false
}
for label, value := range rule.MatchResources.ResourceDescription.NamespaceSelector.MatchLabels {
if rule.ExcludeResources.ResourceDescription.NamespaceSelector.MatchLabels[label] != value {
return false
}
}
}
}
if (rule.MatchResources.ResourceDescription.Selector == nil && rule.ExcludeResources.ResourceDescription.Selector != nil) ||
(rule.MatchResources.ResourceDescription.Selector != nil && rule.ExcludeResources.ResourceDescription.Selector == nil) {
return false
}
if (rule.MatchResources.ResourceDescription.NamespaceSelector == nil && rule.ExcludeResources.ResourceDescription.NamespaceSelector != nil) ||
(rule.MatchResources.ResourceDescription.NamespaceSelector != nil && rule.ExcludeResources.ResourceDescription.NamespaceSelector == nil) {
return false
}
if rule.MatchResources.Annotations != nil && rule.ExcludeResources.Annotations != nil {
if !(reflect.DeepEqual(rule.MatchResources.Annotations, rule.ExcludeResources.Annotations)) {
return false

View file

@ -12,6 +12,7 @@ import (
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/common"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/context"
@ -55,6 +56,9 @@ func (ws *WebhookServer) HandleGenerate(request *v1beta1.AdmissionRequest, polic
for _, policy := range policies {
var rules []response.RuleResponse
policyContext.Policy = *policy
if request.Kind.Kind != "Namespace" && request.Namespace != "" {
policyContext.NamespaceLabels = common.GetNamespaceSelectorsFromNamespaceLister(request.Kind.Kind, request.Namespace, ws.nsLister, logger)
}
engineResponse := engine.Generate(policyContext)
for _, rule := range engineResponse.PolicyResponse.Rules {
if !rule.Success {

View file

@ -7,6 +7,7 @@ import (
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/common"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/response"
@ -55,6 +56,9 @@ func (ws *WebhookServer) HandleMutation(
logger.V(3).Info("evaluating policy", "policy", policy.Name)
policyContext.Policy = *policy
if request.Kind.Kind != "Namespace" && request.Namespace != "" {
policyContext.NamespaceLabels = common.GetNamespaceSelectorsFromNamespaceLister(request.Kind.Kind, request.Namespace, ws.nsLister, logger)
}
engineResponse := engine.Mutate(policyContext)
policyPatches := engineResponse.GetPatches()

View file

@ -16,6 +16,7 @@ import (
kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned"
kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1"
kyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
"github.com/kyverno/kyverno/pkg/common"
"github.com/kyverno/kyverno/pkg/config"
client "github.com/kyverno/kyverno/pkg/dclient"
context2 "github.com/kyverno/kyverno/pkg/engine/context"
@ -33,7 +34,9 @@ import (
webhookgenerate "github.com/kyverno/kyverno/pkg/webhooks/generate"
v1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
informers "k8s.io/client-go/informers/core/v1"
rbacinformer "k8s.io/client-go/informers/rbac/v1"
listerv1 "k8s.io/client-go/listers/core/v1"
rbaclister "k8s.io/client-go/listers/rbac/v1"
"k8s.io/client-go/tools/cache"
)
@ -107,6 +110,11 @@ type WebhookServer struct {
// generate request generator
grGenerator *webhookgenerate.Generator
nsLister listerv1.NamespaceLister
// nsListerSynced returns true if the namespace store has been synced at least once
nsListerSynced cache.InformerSynced
auditHandler AuditHandler
log logr.Logger
@ -135,6 +143,7 @@ func NewWebhookServer(
crbInformer rbacinformer.ClusterRoleBindingInformer,
rInformer rbacinformer.RoleInformer,
crInformer rbacinformer.ClusterRoleInformer,
namespace informers.NamespaceInformer,
eventGen event.Interface,
pCache policycache.Interface,
webhookRegistrationClient *webhookconfig.Register,
@ -165,16 +174,18 @@ func NewWebhookServer(
tlsConfig.Certificates = []tls.Certificate{pair}
ws := &WebhookServer{
client: client,
kyvernoClient: kyvernoClient,
grLister: grInformer.Lister().GenerateRequests(config.KyvernoNamespace),
grSynced: grInformer.Informer().HasSynced,
pLister: pInformer.Lister(),
pSynced: pInformer.Informer().HasSynced,
rbLister: rbInformer.Lister(),
rbSynced: rbInformer.Informer().HasSynced,
rLister: rInformer.Lister(),
rSynced: rInformer.Informer().HasSynced,
client: client,
kyvernoClient: kyvernoClient,
grLister: grInformer.Lister().GenerateRequests(config.KyvernoNamespace),
grSynced: grInformer.Informer().HasSynced,
pLister: pInformer.Lister(),
pSynced: pInformer.Informer().HasSynced,
rbLister: rbInformer.Lister(),
rbSynced: rbInformer.Informer().HasSynced,
rLister: rInformer.Lister(),
rSynced: rInformer.Informer().HasSynced,
nsLister: namespace.Lister(),
nsListerSynced: namespace.Informer().HasSynced,
crbLister: crbInformer.Lister(),
crLister: crInformer.Lister(),
@ -458,7 +469,12 @@ func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *
logger.Error(err, "failed to load service account in context")
}
ok, msg := HandleValidation(request, policies, nil, ctx, userRequestInfo, ws.statusListener, ws.eventGen, ws.prGenerator, ws.log, ws.configHandler, ws.resCache, ws.client)
namespaceLabels := make(map[string]string)
if request.Kind.Kind != "Namespace" && request.Namespace != "" {
namespaceLabels = common.GetNamespaceSelectorsFromNamespaceLister(request.Kind.Kind, request.Namespace, ws.nsLister, logger)
}
ok, msg := HandleValidation(request, policies, nil, ctx, userRequestInfo, ws.statusListener, ws.eventGen, ws.prGenerator, ws.log, ws.configHandler, ws.resCache, ws.client, namespaceLabels)
if !ok {
logger.Info("admission request denied")
return &v1beta1.AdmissionResponse{

View file

@ -1,10 +1,12 @@
package webhooks
import (
client "github.com/kyverno/kyverno/pkg/dclient"
"strings"
"time"
"github.com/kyverno/kyverno/pkg/common"
client "github.com/kyverno/kyverno/pkg/dclient"
"github.com/go-logr/logr"
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/config"
@ -20,7 +22,9 @@ import (
"k8s.io/api/admission/v1beta1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
informers "k8s.io/client-go/informers/core/v1"
rbacinformer "k8s.io/client-go/informers/rbac/v1"
listerv1 "k8s.io/client-go/listers/core/v1"
rbaclister "k8s.io/client-go/listers/rbac/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
@ -48,10 +52,12 @@ type auditHandler struct {
statusListener policystatus.Listener
prGenerator policyreport.GeneratorInterface
rbLister rbaclister.RoleBindingLister
rbSynced cache.InformerSynced
crbLister rbaclister.ClusterRoleBindingLister
crbSynced cache.InformerSynced
rbLister rbaclister.RoleBindingLister
rbSynced cache.InformerSynced
crbLister rbaclister.ClusterRoleBindingLister
crbSynced cache.InformerSynced
nsLister listerv1.NamespaceLister
nsListerSynced cache.InformerSynced
log logr.Logger
configHandler config.Interface
@ -65,6 +71,7 @@ func NewValidateAuditHandler(pCache policycache.Interface,
prGenerator policyreport.GeneratorInterface,
rbInformer rbacinformer.RoleBindingInformer,
crbInformer rbacinformer.ClusterRoleBindingInformer,
namespaces informers.NamespaceInformer,
log logr.Logger,
dynamicConfig config.Interface,
resCache resourcecache.ResourceCache,
@ -79,6 +86,8 @@ func NewValidateAuditHandler(pCache policycache.Interface,
rbSynced: rbInformer.Informer().HasSynced,
crbLister: crbInformer.Lister(),
crbSynced: crbInformer.Informer().HasSynced,
nsLister: namespaces.Lister(),
nsListerSynced: namespaces.Informer().HasSynced,
log: log,
prGenerator: prGenerator,
configHandler: dynamicConfig,
@ -175,7 +184,12 @@ func (h *auditHandler) process(request *v1beta1.AdmissionRequest) error {
return errors.Wrap(err, "failed to load service account in context")
}
HandleValidation(request, policies, nil, ctx, userRequestInfo, h.statusListener, h.eventGen, h.prGenerator, logger, h.configHandler, h.resCache, h.client)
namespaceLabels := make(map[string]string)
if request.Kind.Kind != "Namespace" && request.Namespace != "" {
namespaceLabels = common.GetNamespaceSelectorsFromNamespaceLister(request.Kind.Kind, request.Namespace, h.nsLister, logger)
}
HandleValidation(request, policies, nil, ctx, userRequestInfo, h.statusListener, h.eventGen, h.prGenerator, logger, h.configHandler, h.resCache, h.client, namespaceLabels)
return nil
}

View file

@ -1,11 +1,12 @@
package webhooks
import (
client "github.com/kyverno/kyverno/pkg/dclient"
"reflect"
"sort"
"time"
client "github.com/kyverno/kyverno/pkg/dclient"
"github.com/go-logr/logr"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
@ -38,7 +39,8 @@ func HandleValidation(
log logr.Logger,
dynamicConfig config.Interface,
resCache resourcecache.ResourceCache,
client *client.Client) (bool, string) {
client *client.Client,
namespaceLabels map[string]string) (bool, string) {
if len(policies) == 0 {
return true, ""
@ -85,6 +87,7 @@ func HandleValidation(
for _, policy := range policies {
logger.V(3).Info("evaluating policy", "policy", policy.Name)
policyContext.Policy = *policy
policyContext.NamespaceLabels = namespaceLabels
engineResponse := engine.Validate(policyContext)
if reflect.DeepEqual(engineResponse, response.EngineResponse{}) {
// we get an empty response if old and new resources created the same response