Repository: kyverno/kyverno
Pull request #1719 (2021-03-17 09:21:00)

Enhancement/existence anchor - should loop all the items in the array
## Related issue
closes https://github.com/kyverno/kyverno/issues/1674

## What type of PR is this
> /kind feature

## Proposed changes
Looping all the items in the given array. Currently it process only the first element in the array.

## Checklist
- [X] I have read the [contributing guidelines](https://github.com/kyverno/kyverno/blob/main/CONTRIBUTING.md).
- [] I have added tests that prove my fix is effective or that my feature works.
- [] I have added or changed [the documentation](https://github.com/kyverno/website).
  - If not, I have raised an issue in [kyverno/website](https://github.com/kyverno/website) to track the doc update:
  <!-- Uncomment to link to the issue -->
  <!-- https://github.com/kyverno/website/issues/1 -->



Patch:
diff --git a/pkg/api/kyverno/v1/utils.go b/pkg/api/kyverno/v1/utils.go
index a84f5a20e..d8ebdb224 100755
--- a/pkg/api/kyverno/v1/utils.go
+++ b/pkg/api/kyverno/v1/utils.go
@@ -3,13 +3,18 @@ package v1
 import (
 	"encoding/json"
 	"reflect"
+	"strings"
 )
 
 // HasAutoGenAnnotation checks if a policy has auto-gen annotation
 func (p *ClusterPolicy) HasAutoGenAnnotation() bool {
 	annotations := p.GetAnnotations()
-	_, ok := annotations["pod-policies.kyverno.io/autogen-controllers"]
-	return ok
+	val, ok := annotations["pod-policies.kyverno.io/autogen-controllers"]
+	if ok && strings.ToLower(val) != "none" {
+		return true
+	}
+
+	return false
 }
 
 //HasMutateOrValidateOrGenerate checks for rule types
diff --git a/pkg/engine/anchor/anchor.go b/pkg/engine/anchor/anchor.go
index 5d62f19cf..57209efc4 100644
--- a/pkg/engine/anchor/anchor.go
+++ b/pkg/engine/anchor/anchor.go
@@ -189,13 +189,20 @@ func (eh ExistenceHandler) Handle(handler resourceElementHandler, resourceMap ma
 			if !ok {
 				return currentPath, fmt.Errorf("Invalid pattern type %T: Pattern has to be of list to compare against resource", eh.pattern)
 			}
-			// get the first item in the pattern array
-			patternMap := typedPattern[0]
-			typedPatternMap, ok := patternMap.(map[string]interface{})
-			if !ok {
-				return currentPath, fmt.Errorf("Invalid pattern type %T: Pattern has to be of type map to compare against items in resource", eh.pattern)
+			// loop all item in the pattern array
+			errorPath := ""
+			var err error
+			for _, patternMap := range typedPattern {
+				typedPatternMap, ok := patternMap.(map[string]interface{})
+				if !ok {
+					return currentPath, fmt.Errorf("Invalid pattern type %T: Pattern has to be of type map to compare against items in resource", eh.pattern)
+				}
+				errorPath, err = validateExistenceListResource(handler, typedResource, typedPatternMap, originPattern, currentPath, ac)
+				if err != nil {
+					return errorPath, err
+				}
 			}
-			return validateExistenceListResource(handler, typedResource, typedPatternMap, originPattern, currentPath, ac)
+			return errorPath, err
 		default:
 			return currentPath, fmt.Errorf("Invalid resource type %T: Existence ^ () anchor can be used only on list/array type resource", value)
 		}
@@ -204,7 +211,7 @@ func (eh ExistenceHandler) Handle(handler resourceElementHandler, resourceMap ma
 }
 
 func validateExistenceListResource(handler resourceElementHandler, resourceList []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string, ac *common.AnchorKey) (string, error) {
-	// the idea is atleast on the elements in the array should satisfy the pattern
+	// the idea is all the element in the pattern array should be present atleast once in the resource list
 	// if non satisfy then throw an error
 	for i, resourceElement := range resourceList {
 		currentPath := path + strconv.Itoa(i) + "/"
diff --git a/pkg/engine/variables/operator/numeric.go b/pkg/engine/variables/operator/numeric.go
index 687cb0146..b2f32e5db 100644
--- a/pkg/engine/variables/operator/numeric.go
+++ b/pkg/engine/variables/operator/numeric.go
@@ -45,12 +45,13 @@ func compareByCondition(key float64, value float64, op kyverno.ConditionOperator
 }
 
 func (noh NumericOperatorHandler) Evaluate(key, value interface{}) bool {
-	if key, err := noh.subHandler(noh.log, noh.ctx, key); err != nil {
+	var err error
+	if key, err = noh.subHandler(noh.log, noh.ctx, key); err != nil {
 		// Failed to resolve the variable
 		noh.log.Error(err, "Failed to resolve variable", "variable", key)
 		return false
 	}
-	if value, err := noh.subHandler(noh.log, noh.ctx, value); err != nil {
+	if value, err = noh.subHandler(noh.log, noh.ctx, value); err != nil {
 		// Failed to resolve the variable
 		noh.log.Error(err, "Failed to resolve variable", "variable", value)
 		return false
@@ -133,7 +134,7 @@ func (noh NumericOperatorHandler) validateValueWithStringPattern(key string, val
 	if err == nil {
 		return noh.validateValueWithIntPattern(int64key, value)
 	}
-	noh.log.Error(fmt.Errorf("Parse Error: "), "Failed to parse both float64 and int64 from the string keyt")
+	noh.log.Error(err, "Failed to parse both float64 and int64 from the string keyt")
 	return false
 }
 
diff --git a/pkg/engine/variables/vars.go b/pkg/engine/variables/vars.go
index 33b2dfdb6..79bab4f0f 100644
--- a/pkg/engine/variables/vars.go
+++ b/pkg/engine/variables/vars.go
@@ -94,6 +94,16 @@ func subValR(log logr.Logger, ctx context.EvalInterface, valuePattern string, pa
 			variable := strings.ReplaceAll(v, "{{", "")
 			variable = strings.ReplaceAll(variable, "}}", "")
 			variable = strings.TrimSpace(variable)
+
+			var substitutedVar interface{}
+			if operation, err := ctx.Query("request.operation"); err != nil {
+				return nil, fmt.Errorf("failed to resolve %v at path %s", variable, path)
+			} else {
+				if operation == "DELETE" {
+					variable = strings.ReplaceAll(variable, "request.object", "request.oldObject")
+				}
+			}
+
 			substitutedVar, err := ctx.Query(variable)
 			if err != nil {
 				switch err.(type) {
diff --git a/pkg/policy/common/validate_pattern.go b/pkg/policy/common/validate_pattern.go
index 212dd3ad6..d14f77e67 100644
--- a/pkg/policy/common/validate_pattern.go
+++ b/pkg/policy/common/validate_pattern.go
@@ -50,9 +50,9 @@ func validateMap(patternMap map[string]interface{}, path string, supportedAnchor
 				if !ok {
 					return path + "/" + key, fmt.Errorf("Existence anchor should have value of type list")
 				}
-				// validate there is only one entry in the list
-				if len(typedValue) == 0 || len(typedValue) > 1 {
-					return path + "/" + key, fmt.Errorf("Existence anchor: single value expected, multiple specified")
+				// validate that there is atleast one entry in the list
+				if len(typedValue) == 0 {
+					return path + "/" + key, fmt.Errorf("Existence anchor: should have atleast one value")
 				}
 			}
 		}
diff --git a/pkg/utils/util.go b/pkg/utils/util.go
index 07cfee050..3a25c3e95 100644
--- a/pkg/utils/util.go
+++ b/pkg/utils/util.go
@@ -127,7 +127,7 @@ func ConvertResource(raw []byte, group, version, kind, namespace string) (unstru
 
 	obj.SetGroupVersionKind(schema.GroupVersionKind{Group: group, Version: version, Kind: kind})
 
-	if namespace != "" {
+	if namespace != "" && kind != "Namespace" {
 		obj.SetNamespace(namespace)
 	}
 


Test patch:
diff --git a/pkg/engine/variables/vars_test.go b/pkg/engine/variables/vars_test.go
index d94802902..70ad52b02 100644
--- a/pkg/engine/variables/vars_test.go
+++ b/pkg/engine/variables/vars_test.go
@@ -2,6 +2,7 @@ package variables
 
 import (
 	"encoding/json"
+	"fmt"
 	"testing"
 
 	"github.com/kyverno/kyverno/pkg/engine/context"
@@ -131,6 +132,86 @@ func Test_subVars_failed(t *testing.T) {
 	}
 }
 
+func Test_ReplacingPathWhenDeleting(t *testing.T) {
+	patternRaw := []byte(`"{{request.object.metadata.annotations.target}}"`)
+
+	var resourceRaw = []byte(`
+	{
+		"request": {
+			"operation": "DELETE",
+			"object": {
+				"metadata": {
+					"name": "curr",
+					"namespace": "ns",
+					"annotations": {
+					  "target": "foo"
+					}
+				}
+			},
+			"oldObject": {
+				"metadata": {
+					"name": "old",
+					"annotations": {
+					  "target": "bar"
+					}
+				}
+			}
+		}
+	}
+`)
+
+	var pattern interface{}
+	var err error
+	err = json.Unmarshal(patternRaw, &pattern)
+	if err != nil {
+		t.Error(err)
+	}
+	ctx := context.NewContext()
+	err = ctx.AddJSON(resourceRaw)
+	assert.NilError(t, err)
+
+	pattern, err = SubstituteVars(log.Log, ctx, pattern)
+	assert.NilError(t, err)
+
+	assert.Equal(t, fmt.Sprintf("%v", pattern), "bar")
+}
+
+func Test_ReplacingNestedVariableWhenDeleting(t *testing.T) {
+	patternRaw := []byte(`"{{request.object.metadata.annotations.{{request.object.metadata.annotations.targetnew}}}}"`)
+
+	var resourceRaw = []byte(`
+	{
+		"request":{
+		   "operation":"DELETE",
+		   "oldObject":{
+			  "metadata":{
+				 "name":"current",
+				 "namespace":"ns",
+				 "annotations":{
+					"target":"nested_target",
+					"targetnew":"target"
+				 }
+			  }
+		   }
+		}
+	}`)
+
+	var pattern interface{}
+	var err error
+	err = json.Unmarshal(patternRaw, &pattern)
+	if err != nil {
+		t.Error(err)
+	}
+	ctx := context.NewContext()
+	err = ctx.AddJSON(resourceRaw)
+	assert.NilError(t, err)
+
+	pattern, err = SubstituteVars(log.Log, ctx, pattern)
+	assert.NilError(t, err)
+
+	assert.Equal(t, fmt.Sprintf("%v", pattern), "nested_target")
+}
+
 var resourceRaw = []byte(`
 	{
 		"metadata": {