Repository: kyverno/kyverno
Pull request #3140 (2022-01-31 19:25:06)

Backport recent bug fixes to 1.6
Cherry-picks the following commits/PRs for the 1.6 release - 

https://github.com/kyverno/kyverno/pull/3108
https://github.com/kyverno/kyverno/pull/3133
https://github.com/kyverno/kyverno/pull/3137
https://github.com/kyverno/kyverno/pull/3129

Note - we should not squash these commits as it would be nice to have the individual commit history.


samj1912: @JimBugwadia/@prateekpandey14 could you please merge this?
samj1912: @MarcelMue thanks for the approval! Should we be merging this with a different strategy than squash? It might be better to use the rebase or merge strategy to preserve the PR history? Noting from @realshuting's comment at https://github.com/kyverno/kyverno/pull/3123#issuecomment-1024851601

Patch:
diff --git a/pkg/engine/jmespath/arithmetic.go b/pkg/engine/jmespath/arithmetic.go
index 4eacf58c6..34b754b58 100644
--- a/pkg/engine/jmespath/arithmetic.go
+++ b/pkg/engine/jmespath/arithmetic.go
@@ -227,11 +227,17 @@ func (op1 Scalar) Multiply(op2 interface{}) (interface{}, error) {
 func (op1 Quantity) Divide(op2 interface{}) (interface{}, error) {
 	switch v := op2.(type) {
 	case Quantity:
+		if v.ToDec().AsApproximateFloat64() == 0 {
+			return nil, fmt.Errorf(zeroDivisionError, divide)
+		}
 		var quo inf.Dec
 		scale := inf.Scale(math.Max(float64(op1.AsDec().Scale()), float64(v.AsDec().Scale())))
 		quo.QuoRound(op1.AsDec(), v.AsDec(), scale, inf.RoundDown)
 		return strconv.ParseFloat(quo.String(), 64)
 	case Scalar:
+		if v.float64 == 0 {
+			return nil, fmt.Errorf(zeroDivisionError, divide)
+		}
 		q, err := resource.ParseQuantity(fmt.Sprintf("%v", v.float64))
 		if err != nil {
 			return nil, err
@@ -283,6 +289,9 @@ func (op1 Scalar) Divide(op2 interface{}) (interface{}, error) {
 
 		return op1.float64 / v.float64, nil
 	case Quantity:
+		if v.ToDec().AsApproximateFloat64() == 0 {
+			return nil, fmt.Errorf(zeroDivisionError, divide)
+		}
 		q, err := resource.ParseQuantity(fmt.Sprintf("%v", op1.float64))
 		if err != nil {
 			return nil, err
diff --git a/pkg/engine/variables/operator/numeric.go b/pkg/engine/variables/operator/numeric.go
index 194274589..67ef2b2bd 100644
--- a/pkg/engine/variables/operator/numeric.go
+++ b/pkg/engine/variables/operator/numeric.go
@@ -135,21 +135,6 @@ func (noh NumericOperatorHandler) validateValueWithFloatPattern(key float64, val
 	}
 }
 
-func (noh NumericOperatorHandler) validateValueWithResourcePattern(key resource.Quantity, value interface{}) bool {
-	switch typedValue := value.(type) {
-	case string:
-		resourceValue, err := resource.ParseQuantity(typedValue)
-		if err != nil {
-			noh.log.Error(fmt.Errorf("parse error: "), "Failed to parse value type doesn't match key type")
-			return false
-		}
-		return compareByCondition(float64(key.Cmp(resourceValue)), 0, noh.condition, &noh.log)
-	default:
-		noh.log.Info("Expected type string", "value", value, "type", fmt.Sprintf("%T", value))
-		return false
-	}
-}
-
 func (noh NumericOperatorHandler) validateValueWithVersionPattern(key semver.Version, value interface{}) bool {
 	switch typedValue := value.(type) {
 	case string:
@@ -171,6 +156,11 @@ func (noh NumericOperatorHandler) validateValueWithStringPattern(key string, val
 	if err == nil {
 		return compareByCondition(float64(durationKey.Seconds()), float64(durationValue.Seconds()), noh.condition, &noh.log)
 	}
+	// attempt to extract resource quantity from string before parsing floats/ints as resources can also be ints/floats represented as string type
+	resourceKey, resourceValue, err := parseQuantity(key, value)
+	if err == nil {
+		return compareByCondition(float64(resourceKey.Cmp(resourceValue)), 0, noh.condition, &noh.log)
+	}
 	// extracting float64 from the string key
 	float64key, err := strconv.ParseFloat(key, 64)
 	if err == nil {
@@ -181,11 +171,6 @@ func (noh NumericOperatorHandler) validateValueWithStringPattern(key string, val
 	if err == nil {
 		return noh.validateValueWithIntPattern(int64key, value)
 	}
-	// attempt to extract resource quantity from string
-	resourceKey, err := resource.ParseQuantity(key)
-	if err == nil {
-		return noh.validateValueWithResourcePattern(resourceKey, value)
-	}
 	// attempt to extract version from string
 	versionKey, err := semver.Parse(key)
 	if err == nil {
@@ -196,6 +181,30 @@ func (noh NumericOperatorHandler) validateValueWithStringPattern(key string, val
 	return false
 }
 
+func parseQuantity(key, value interface{}) (parsedKey, parsedValue resource.Quantity, err error) {
+	switch typedKey := key.(type) {
+	case string:
+		parsedKey, err = resource.ParseQuantity(typedKey)
+		if err != nil {
+			return
+		}
+	default:
+		err = fmt.Errorf("key is not a quantity")
+		return
+	}
+	switch typedValue := value.(type) {
+	case string:
+		parsedValue, err = resource.ParseQuantity(typedValue)
+		if err != nil {
+			return
+		}
+	default:
+		err = fmt.Errorf("value is not a quantity")
+		return
+	}
+	return
+}
+
 // the following functions are unreachable because the key is strictly supposed to be numeric
 // still the following functions are just created to make NumericOperatorHandler struct implement OperatorHandler interface
 func (noh NumericOperatorHandler) validateValueWithBoolPattern(key bool, value interface{}) bool {
diff --git a/pkg/engine/variables/vars.go b/pkg/engine/variables/vars.go
index fbdf2e5df..9516397dd 100644
--- a/pkg/engine/variables/vars.go
+++ b/pkg/engine/variables/vars.go
@@ -18,9 +18,9 @@ import (
 	"github.com/kyverno/kyverno/pkg/engine/operator"
 )
 
-var RegexVariables = regexp.MustCompile(`^\{\{[^{}]*\}\}|[^\\]\{\{[^{}]*\}\}`)
+var RegexVariables = regexp.MustCompile(`^\{\{(\{[^{}]*\}|[^{}])*\}\}|[^\\]\{\{(\{[^{}]*\}|[^{}])*\}\}`)
 
-var RegexEscpVariables = regexp.MustCompile(`\\\{\{[^{}]*\}\}`)
+var RegexEscpVariables = regexp.MustCompile(`\\\{\{(\{[^{}]*\}|[^{}])*\}\}`)
 
 // RegexReferences is the Regex for '$(...)' at the beginning of the string, and 'x$(...)' where 'x' is not '\'
 var RegexReferences = regexp.MustCompile(`^\$\(.[^\ ]*\)|[^\\]\$\(.[^\ ]*\)`)
@@ -28,7 +28,7 @@ var RegexReferences = regexp.MustCompile(`^\$\(.[^\ ]*\)|[^\\]\$\(.[^\ ]*\)`)
 // RegexEscpReferences is the Regex for '\$(...)'
 var RegexEscpReferences = regexp.MustCompile(`\\\$\(.[^\ ]*\)`)
 
-var regexVariableInit = regexp.MustCompile(`^\{\{[^{}]*\}\}`)
+var regexVariableInit = regexp.MustCompile(`^\{\{(\{[^{}]*\}|[^{}])*\}\}`)
 
 var regexElementIndex = regexp.MustCompile(`{{\s*elementIndex\s*}}`)
 
diff --git a/pkg/policy/validate.go b/pkg/policy/validate.go
index e4d0fb270..56cb6b965 100644
--- a/pkg/policy/validate.go
+++ b/pkg/policy/validate.go
@@ -1111,9 +1111,12 @@ func validateRuleContext(rule kyverno.Rule) error {
 	}
 
 	ruleBytes, _ := json.Marshal(rule)
-	ruleString := strings.ReplaceAll(string(ruleBytes), " ", "")
 	for _, contextName := range contextNames {
-		if !strings.Contains(ruleString, fmt.Sprintf("{{"+contextName)) && !strings.Contains(ruleString, fmt.Sprintf("{{\\\""+contextName)) {
+		contextRegex, err := regexp.Compile(fmt.Sprintf(`{{.*\b%s\b.*}}`, contextName))
+		if err != nil {
+			return fmt.Errorf("unable to validate context variable `%s`, %w", contextName, err)
+		}
+		if !contextRegex.Match(ruleBytes) {
 			return fmt.Errorf("context variable `%s` is not used in the policy", contextName)
 		}
 	}


Test patch:
diff --git a/pkg/engine/jmespath/functions_test.go b/pkg/engine/jmespath/functions_test.go
index 1684f3c14..0f745290f 100644
--- a/pkg/engine/jmespath/functions_test.go
+++ b/pkg/engine/jmespath/functions_test.go
@@ -804,6 +804,50 @@ func Test_Divide(t *testing.T) {
 			test: "divide('12s', `0`)",
 			err:  true,
 		},
+		{
+			test: "divide('12s', '0s')",
+			err:  true,
+		},
+		{
+			test: "divide(`12`, '0s')",
+			err:  true,
+		},
+		{
+			test: "divide('12M', '0Mi')",
+			err:  true,
+		},
+		{
+			test: "divide('12K', `0`)",
+			err:  true,
+		},
+		{
+			test: "divide('12K', '0m')",
+			err:  true,
+		},
+		{
+			test: "divide('12Ki', '0G')",
+			err:  true,
+		},
+		{
+			test: "divide('12Mi', '0Gi')",
+			err:  true,
+		},
+		{
+			test: "divide('12Mi', `0`)",
+			err:  true,
+		},
+		{
+			test: "divide(`12`, '0Gi')",
+			err:  true,
+		},
+		{
+			test: "divide(`12`, '0K')",
+			err:  true,
+		},
+		{
+			test: "divide(`12`, `0`)",
+			err:  true,
+		},
 		{
 			test:           "divide(`25`, `2`)",
 			expectedResult: 12.5,
@@ -890,6 +934,50 @@ func Test_Modulo(t *testing.T) {
 			test: "modulo('12s', `0`)",
 			err:  true,
 		},
+		{
+			test: "modulo('12s', '0s')",
+			err:  true,
+		},
+		{
+			test: "modulo(`12`, '0s')",
+			err:  true,
+		},
+		{
+			test: "modulo('12M', '0Mi')",
+			err:  true,
+		},
+		{
+			test: "modulo('12K', `0`)",
+			err:  true,
+		},
+		{
+			test: "modulo('12K', '0m')",
+			err:  true,
+		},
+		{
+			test: "modulo('12Ki', '0G')",
+			err:  true,
+		},
+		{
+			test: "modulo('12Mi', '0Gi')",
+			err:  true,
+		},
+		{
+			test: "modulo('12Mi', `0`)",
+			err:  true,
+		},
+		{
+			test: "modulo(`12`, '0Gi')",
+			err:  true,
+		},
+		{
+			test: "modulo(`12`, '0K')",
+			err:  true,
+		},
+		{
+			test: "modulo(`12`, `0`)",
+			err:  true,
+		},
 		{
 			test:           "modulo(`25`, `2`)",
 			expectedResult: 1.0,
diff --git a/pkg/engine/variables/evaluate_test.go b/pkg/engine/variables/evaluate_test.go
index 96aee93a8..b67faed44 100644
--- a/pkg/engine/variables/evaluate_test.go
+++ b/pkg/engine/variables/evaluate_test.go
@@ -23,6 +23,8 @@ func TestEvaluate(t *testing.T) {
 		{kyverno.Condition{Key: 1.0, Operator: kyverno.ConditionOperators["Equals"], Value: 1.0}, true},
 		{kyverno.Condition{Key: true, Operator: kyverno.ConditionOperators["Equals"], Value: true}, true},
 		{kyverno.Condition{Key: false, Operator: kyverno.ConditionOperators["Equals"], Value: false}, true},
+		{kyverno.Condition{Key: "1024", Operator: kyverno.ConditionOperators["Equals"], Value: "1Ki"}, true},
+		{kyverno.Condition{Key: "1Ki", Operator: kyverno.ConditionOperators["Equals"], Value: "1024"}, true},
 		{kyverno.Condition{Key: "1Gi", Operator: kyverno.ConditionOperators["Equals"], Value: "1Gi"}, true},
 		{kyverno.Condition{Key: "1Gi", Operator: kyverno.ConditionOperators["Equals"], Value: "1024Mi"}, true},
 		{kyverno.Condition{Key: "1h", Operator: kyverno.ConditionOperators["Equals"], Value: "1h"}, true},
@@ -63,6 +65,10 @@ func TestEvaluate(t *testing.T) {
 		{kyverno.Condition{Key: 1.0, Operator: kyverno.ConditionOperators["NotEquals"], Value: 1.0}, false},
 		{kyverno.Condition{Key: true, Operator: kyverno.ConditionOperators["NotEquals"], Value: false}, true},
 		{kyverno.Condition{Key: false, Operator: kyverno.ConditionOperators["NotEquals"], Value: false}, false},
+		{kyverno.Condition{Key: "1024", Operator: kyverno.ConditionOperators["NotEquals"], Value: "1Ki"}, false},
+		{kyverno.Condition{Key: "1Ki", Operator: kyverno.ConditionOperators["NotEquals"], Value: "1024"}, false},
+		{kyverno.Condition{Key: "1023", Operator: kyverno.ConditionOperators["NotEquals"], Value: "1Ki"}, true},
+		{kyverno.Condition{Key: "1Ki", Operator: kyverno.ConditionOperators["NotEquals"], Value: "1023"}, true},
 		{kyverno.Condition{Key: "1Gi", Operator: kyverno.ConditionOperators["NotEquals"], Value: "1Gi"}, false},
 		{kyverno.Condition{Key: "10Gi", Operator: kyverno.ConditionOperators["NotEquals"], Value: "1024Mi"}, true},
 		{kyverno.Condition{Key: "1h", Operator: kyverno.ConditionOperators["NotEquals"], Value: "1h"}, false},
@@ -104,6 +110,8 @@ func TestEvaluate(t *testing.T) {
 		{kyverno.Condition{Key: 1, Operator: kyverno.ConditionOperators["GreaterThan"], Value: 1.5}, false},
 		{kyverno.Condition{Key: 1, Operator: kyverno.ConditionOperators["GreaterThan"], Value: 1}, false},
 		{kyverno.Condition{Key: 1.0, Operator: kyverno.ConditionOperators["GreaterThan"], Value: 1.0}, false},
+		{kyverno.Condition{Key: "1025", Operator: kyverno.ConditionOperators["GreaterThan"], Value: "1Ki"}, true},
+		{kyverno.Condition{Key: "1Ki", Operator: kyverno.ConditionOperators["GreaterThan"], Value: "1023"}, true},
 		{kyverno.Condition{Key: "10Gi", Operator: kyverno.ConditionOperators["GreaterThan"], Value: "1Gi"}, true},
 		{kyverno.Condition{Key: "1Gi", Operator: kyverno.ConditionOperators["GreaterThan"], Value: "1Mi"}, true},
 		{kyverno.Condition{Key: "1Gi", Operator: kyverno.ConditionOperators["GreaterThan"], Value: "10Gi"}, false},
@@ -147,6 +155,8 @@ func TestEvaluate(t *testing.T) {
 		{kyverno.Condition{Key: 1, Operator: kyverno.ConditionOperators["LessThan"], Value: 1.5}, true},
 		{kyverno.Condition{Key: 1, Operator: kyverno.ConditionOperators["LessThan"], Value: 1}, false},
 		{kyverno.Condition{Key: 1.0, Operator: kyverno.ConditionOperators["LessThan"], Value: 1.0}, false},
+		{kyverno.Condition{Key: "1023", Operator: kyverno.ConditionOperators["LessThan"], Value: "1Ki"}, true},
+		{kyverno.Condition{Key: "1Ki", Operator: kyverno.ConditionOperators["LessThan"], Value: "1025"}, true},
 		{kyverno.Condition{Key: "10Gi", Operator: kyverno.ConditionOperators["LessThan"], Value: "1Gi"}, false},
 		{kyverno.Condition{Key: "1Gi", Operator: kyverno.ConditionOperators["LessThan"], Value: "10Gi"}, true},
 		{kyverno.Condition{Key: "1Gi", Operator: kyverno.ConditionOperators["LessThan"], Value: "1Mi"}, false},
@@ -191,6 +201,10 @@ func TestEvaluate(t *testing.T) {
 		{kyverno.Condition{Key: 1, Operator: kyverno.ConditionOperators["GreaterThanOrEquals"], Value: 1}, true},
 		{kyverno.Condition{Key: 1.0, Operator: kyverno.ConditionOperators["GreaterThanOrEquals"], Value: 1.0}, true},
 		{kyverno.Condition{Key: 1.0, Operator: kyverno.ConditionOperators["GreaterThanOrEquals"], Value: 1}, true},
+		{kyverno.Condition{Key: "1025", Operator: kyverno.ConditionOperators["GreaterThanOrEquals"], Value: "1Ki"}, true},
+		{kyverno.Condition{Key: "1024", Operator: kyverno.ConditionOperators["GreaterThanOrEquals"], Value: "1Ki"}, true},
+		{kyverno.Condition{Key: "1Ki", Operator: kyverno.ConditionOperators["GreaterThanOrEquals"], Value: "1023"}, true},
+		{kyverno.Condition{Key: "1Ki", Operator: kyverno.ConditionOperators["GreaterThanOrEquals"], Value: "1024"}, true},
 		{kyverno.Condition{Key: "10Gi", Operator: kyverno.ConditionOperators["GreaterThanOrEquals"], Value: "1Gi"}, true},
 		{kyverno.Condition{Key: "1Gi", Operator: kyverno.ConditionOperators["GreaterThanOrEquals"], Value: "1Mi"}, true},
 		{kyverno.Condition{Key: "1Gi", Operator: kyverno.ConditionOperators["GreaterThanOrEquals"], Value: "10Gi"}, false},
@@ -229,6 +243,10 @@ func TestEvaluate(t *testing.T) {
 		{kyverno.Condition{Key: 1, Operator: kyverno.ConditionOperators["LessThanOrEquals"], Value: 1.5}, true},
 		{kyverno.Condition{Key: 1, Operator: kyverno.ConditionOperators["LessThanOrEquals"], Value: 1}, true},
 		{kyverno.Condition{Key: 1.0, Operator: kyverno.ConditionOperators["LessThanOrEquals"], Value: 1.0}, true},
+		{kyverno.Condition{Key: "1Ki", Operator: kyverno.ConditionOperators["LessThanOrEquals"], Value: "1024"}, true},
+		{kyverno.Condition{Key: "1024", Operator: kyverno.ConditionOperators["LessThanOrEquals"], Value: "1Ki"}, true},
+		{kyverno.Condition{Key: "1Ki", Operator: kyverno.ConditionOperators["LessThanOrEquals"], Value: "1025"}, true},
+		{kyverno.Condition{Key: "1023", Operator: kyverno.ConditionOperators["LessThanOrEquals"], Value: "1Ki"}, true},
 		{kyverno.Condition{Key: "10Gi", Operator: kyverno.ConditionOperators["LessThanOrEquals"], Value: "1Gi"}, false},
 		{kyverno.Condition{Key: "1Gi", Operator: kyverno.ConditionOperators["LessThanOrEquals"], Value: "10Gi"}, true},
 		{kyverno.Condition{Key: "1Gi", Operator: kyverno.ConditionOperators["LessThanOrEquals"], Value: "1Mi"}, false},
diff --git a/pkg/engine/variables/vars_test.go b/pkg/engine/variables/vars_test.go
index 7d6dd2081..c92e21568 100644
--- a/pkg/engine/variables/vars_test.go
+++ b/pkg/engine/variables/vars_test.go
@@ -289,6 +289,36 @@ func Test_subVars_withRegexMatch(t *testing.T) {
 	assert.Equal(t, string(out), expected.String())
 }
 
+func Test_subVars_withMerge(t *testing.T) {
+	patternMap := []byte(`{"map": "{{ merge(` + "`{\\\"a\\\": 1}`, `{\\\"b\\\": 1}`" + `)}}"}`)
+
+	resourceRaw := []byte(`{}`)
+
+	expectedRaw := []byte(`{"map": {"a":1,"b":1}}`)
+
+	var err error
+
+	expected := new(bytes.Buffer)
+	err = json.Compact(expected, expectedRaw)
+	assert.NilError(t, err)
+
+	var pattern, resource interface{}
+	err = json.Unmarshal(patternMap, &pattern)
+	assert.NilError(t, err)
+	err = json.Unmarshal(resourceRaw, &resource)
+	assert.NilError(t, err)
+	// context
+	ctx := context.NewContext()
+	err = ctx.AddResource(resourceRaw)
+	assert.NilError(t, err)
+
+	output, err := SubstituteAll(log.Log, ctx, pattern)
+	assert.NilError(t, err)
+	out, err := json.Marshal(output)
+	assert.NilError(t, err)
+	assert.Equal(t, string(out), expected.String())
+}
+
 func Test_subVars_withRegexReplaceAll(t *testing.T) {
 	patternMap := []byte(`{
 		"mutate": {
diff --git a/test/cli/test/jmespath-brackets/kyverno-test.yaml b/test/cli/test/jmespath-brackets/kyverno-test.yaml
new file mode 100644
index 000000000..6e431d745
--- /dev/null
+++ b/test/cli/test/jmespath-brackets/kyverno-test.yaml
@@ -0,0 +1,36 @@
+name: test-preconditions
+policies:
+  - policy.yaml
+resources:
+  - resources.yaml
+results:
+  - policy: test-jmespath
+    rule: test-jmespath
+    resource: test-valid1
+    kind: Pod
+    status: pass
+  - policy: test-jmespath
+    rule: test-jmespath
+    resource: test-valid2
+    kind: Pod
+    status: pass
+  - policy: test-jmespath
+    rule: test-jmespath
+    resource: test-valid3
+    kind: Pod
+    status: pass
+  - policy: test-jmespath
+    rule: test-jmespath
+    resource: test-invalid
+    kind: Pod
+    status: fail
+  - policy: namespace-validation
+    rule: namespace-validation
+    resource: test-invalid
+    kind: Namespace
+    status: fail
+  - policy: namespace-validation
+    rule: namespace-validation
+    resource: test-valid
+    kind: Namespace
+    status: pass
diff --git a/test/cli/test/jmespath-brackets/policy.yaml b/test/cli/test/jmespath-brackets/policy.yaml
new file mode 100644
index 000000000..c10e59bf6
--- /dev/null
+++ b/test/cli/test/jmespath-brackets/policy.yaml
@@ -0,0 +1,47 @@
+apiVersion: kyverno.io/v1
+kind: Policy
+metadata:
+  name: test-jmespath
+  annotations:
+    pod-policies.kyverno.io/autogen-controllers: none
+spec:
+  validationFailureAction: enforce
+  background: false
+  rules:
+  - name: test-jmespath
+    match:
+      resources:
+        kinds:
+        - Pod
+    validate:
+      message: 'All pod labels must match except test'
+      deny:
+        conditions:
+          all:
+          - key: '{{ request.object.metadata.labels | merge(@, `{"test": ""}`)}}'
+            operator: NotEquals
+            value:
+              a: "1"
+              test: ""
+---
+apiVersion: kyverno.io/v1
+kind: Policy
+metadata:
+  name: namespace-validation
+  namespace: kyverno
+spec:
+  validationFailureAction: enforce
+  background: false
+  rules:
+  - name: namespace-validation
+    match:
+      resources:
+        kinds:
+        - Namespace
+    validate:
+      message: "For creating a namespace you need to set the objectid of the Azure AD Group that needs access to this namespace as the aadobjectid label"
+      deny:
+        conditions:
+        - key: "{{regex_match('^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$', '{{request.object.metadata.labels.aadobjectid}}')}}"
+          operator: Equals
+          value: false
diff --git a/test/cli/test/jmespath-brackets/resources.yaml b/test/cli/test/jmespath-brackets/resources.yaml
new file mode 100644
index 000000000..0b286c541
--- /dev/null
+++ b/test/cli/test/jmespath-brackets/resources.yaml
@@ -0,0 +1,58 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-valid1
+  labels:
+    a: "1"
+spec:
+  containers:
+  - image: busybox:1.28
+    name: busybox
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-valid2
+  labels:
+    a: "1"
+    test: "not-equal"
+spec:
+  containers:
+  - image: busybox:1.28
+    name: busybox
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-valid3
+  labels:
+    a: "1"
+spec:
+  containers:
+  - image: busybox:1.28
+    name: busybox
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: test-invalid
+  labels:
+    a: "2"
+spec:
+  containers:
+  - image: busybox:1.28
+    name: busybox
+---
+apiVersion: v1
+kind: Namespace
+metadata:
+  labels:
+    aadobjectid: 51e5fe10-5325-4a32-bce8-7ebe9708
+  name: test-invalid
+---
+apiVersion: v1
+kind: Namespace
+metadata:
+  labels:
+    aadobjectid: 51e5fe10-5325-4a32-9ce8-7ebe97087ebe
+  name: test-valid
diff --git a/test/cli/test/variables/cm-multiple-example.yaml b/test/cli/test/variables/cm-multiple-example.yaml
new file mode 100644
index 000000000..0087b27b2
--- /dev/null
+++ b/test/cli/test/variables/cm-multiple-example.yaml
@@ -0,0 +1,25 @@
+apiVersion: kyverno.io/v1
+kind: ClusterPolicy
+metadata:
+  name: cm-multiple-example
+spec:
+  rules:
+  - name: example-configmap-lookup
+    context:
+    - name: dictionary
+      configMap:
+        name: some-config-map
+        namespace: some-namespace
+    - name: anotherdictionary
+      configMap:
+        name: another-config-map
+        namespace: some-namespace
+    match:
+      resources:
+        kinds:
+        - Pod
+    validate:
+      pattern:
+        metadata:
+          labels:
+            my-environment-name: "{{dictionary.data.env || anotherdictionary.data.env }}"
diff --git a/test/cli/test/variables/kyverno-test.yaml b/test/cli/test/variables/kyverno-test.yaml
index 03a22ca73..56e6090d7 100644
--- a/test/cli/test/variables/kyverno-test.yaml
+++ b/test/cli/test/variables/kyverno-test.yaml
@@ -1,6 +1,7 @@
 name: test-variables
 policies:
   - cm-variable-example.yaml
+  - cm-multiple-example.yaml
   - cm-array-example.yaml
   - cm-blk-scalar-example.yaml
   - cm-globalval-example.yaml
@@ -9,6 +10,16 @@ resources:
   - resources.yaml
 variables: variables.yaml
 results:
+  - policy: cm-multiple-example
+    rule: example-configmap-lookup
+    resource: test-env-test
+    kind: Pod
+    result: pass
+  - policy: cm-multiple-example
+    rule: example-configmap-lookup
+    resource: test-env-dev
+    kind: Pod
+    result: fail
   - policy: cm-variable-example
     rule: example-configmap-lookup
     resource: test-env-test
diff --git a/test/cli/test/variables/variables.yaml b/test/cli/test/variables/variables.yaml
index 176bc7a81..ce9025782 100644
--- a/test/cli/test/variables/variables.yaml
+++ b/test/cli/test/variables/variables.yaml
@@ -1,4 +1,10 @@
 policies:
+  - name: cm-multiple-example
+    rules:
+      - name: example-configmap-lookup
+        values:
+          dictionary.data.env: ''
+          anotherdictionary.data.env: test
   - name: cm-variable-example
     rules:
       - name: example-configmap-lookup