Repository: kyverno/kyverno
Pull request #1797 (2021-04-13 14:57:30)

Add severity to pod security policies
Signed-off-by: Frank Jogeleit <frank.jogeleit@web.de>

## Related issue

<!--
Please link the GitHub issue this pull request resolves in the format of `#1234`. If you discussed this change
with a maintainer, please mention her/him using the `@` syntax (e.g. `@JimBugwadia`).

If this change neither resolves an existing issue nor has sign-off from one of the maintainers, there is a
chance substantial changes will be requested or that the changes will be rejected.

You can discuss changes with maintainers in the [Kyverno Slack Channel](https://kubernetes.slack.com/).
-->

## What type of PR is this

<!--

> Uncomment only one ` /kind <>` line, hit enter to put that in a new line, and remove leading white spaces from that line:
>
> /kind api-change
> /kind bug
> /kind cleanup
> /kind design
> /kind documentation
> /kind failing-test
/kind feature
-->

## Proposed Changes

A new feature supports a new annotation "policies.kyverno.io/severity" to define the severity used in the created policy report for this policy. This PR adds this new annotation to the provided default policies to be able to configure the severity in the helm chart.

## Checklist

<!--
Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of
them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code.
-->

- [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.
- [] My PR contains new or altered behavior to Kyverno and
  - [] I have added or changed [the documentation](https://github.com/kyverno/website) myself in an existing PR and the link is:
  <!-- Uncomment to link to the PR -->
  <!-- https://github.com/kyverno/website/pull/123 -->
  - [] I have raised an issue in [kyverno/website](https://github.com/kyverno/website) to track the doc update and the link is:
  <!-- Uncomment to link to the issue -->
  <!-- https://github.com/kyverno/website/issues/1 -->
  - [] I have read the [PR documentation guide](pr_documentation.md) and followed the process including adding proof manifests to this PR.

## Further Comments

<!--
If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution
you did and what alternatives you considered, etc...
-->


chipzoller: Please also ensure you update the [readme ](https://github.com/kyverno/kyverno/blob/main/charts/kyverno/README.md)with the new param, possible values, and an explanation of its function.

Patch:
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index f1eccf20a..16a05d607 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -36,7 +36,7 @@ Describe the big picture of your changes here to communicate to the maintainers
 ### Proof Manifests
 
 <!--
-Read and follow the [PR documentation guide](pr_documentation.md) for more details first. This section is for pasting your YAML manifests (Kubernetes resources and Kyverno policies) which allow maintainers to prove the intended functionality is achieved by your PR. Please use proper fenced code block formatting, for example:
+Read and follow the [PR documentation guide](https://github.com/kyverno/kyverno/blob/main/.github/pr_documentation.md) for more details first. This section is for pasting your YAML manifests (Kubernetes resources and Kyverno policies) which allow maintainers to prove the intended functionality is achieved by your PR. Please use proper fenced code block formatting, for example:
 
 ```yaml
 apiVersion: v1
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 66448fba3..621cfcd0e 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -173,3 +173,23 @@ jobs:
         run: |
           export PROJECT_PATH=$(pwd)
           make test-all
+
+  helm-tests:
+    runs-on: ubuntu-latest
+    needs: pre-checks
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - name: Unshallow
+        run: git fetch --prune --unshallow
+
+      - uses: actions/setup-python@v2
+        with:
+          python-version: 3.7
+
+      - name: Set up chart-testing
+        uses: helm/chart-testing-action@v2.0.1
+
+      - name: Run chart-testing (lint)
+        run: ct lint --target-branch=main --check-version-increment=false
diff --git a/charts/kyverno/Chart.yaml b/charts/kyverno/Chart.yaml
index 20a00eb4e..5cc326d9a 100644
--- a/charts/kyverno/Chart.yaml
+++ b/charts/kyverno/Chart.yaml
@@ -1,7 +1,7 @@
 apiVersion: v1
 name: kyverno
-version: v1.3.5-rc3
-appVersion: v1.3.5-rc3
+version: v1.3.5
+appVersion: v1.3.5
 icon: https://github.com/kyverno/kyverno/raw/main/img/logo.png
 description: Kubernetes Native Policy Management
 keywords:
diff --git a/charts/kyverno/README.md b/charts/kyverno/README.md
index 4d73304eb..e1e3a3990 100644
--- a/charts/kyverno/README.md
+++ b/charts/kyverno/README.md
@@ -103,6 +103,7 @@ The following table lists the configurable parameters of the kyverno chart and t
 | `tolerations`                      | list of node taints to tolerate                                                                                                                                                                                                                              | `[]`                                                                                                                                                                              |
 | `securityContext`                  | security context configuration                                                                                                                                                                                                                               | `{}`                                                                                                                                                                              |
 | `podSecurityStandard`              | set desired pod security level `privileged`, `default`, `restricted`, `custom`. Set to `restricted` for maximum security for your cluster. See: https://kyverno.io/policies/pod-security/                                                                    | `default`                                                                                                                                                                         |
+| `podSecuritySeverity`              | set desired pod security severity `low`, `medium`, `high`. Used severity level in PolicyReportResults for the selected pod security policies.                                                                    | `medium`                                                                                                                                                                         |
 | `podSecurityPolicies`              | Policies to include when `podSecurityStandard` is set to `custom`                                                                                                                                                                                            | `[]`                                                                                                                                                                              |
 | `validationFailureAction`          | set to get response in failed validation check. Supported values- `audit`, `enforce`. See: https://kyverno.io/docs/writing-policies/validate/                                                                                                                | `audit`                                                                                                                                                                           |
 
diff --git a/charts/kyverno/templates/policies/default/disallow-adding-capabilities.yaml b/charts/kyverno/templates/policies/default/disallow-adding-capabilities.yaml
index 802f1adf9..8c2218d08 100644
--- a/charts/kyverno/templates/policies/default/disallow-adding-capabilities.yaml
+++ b/charts/kyverno/templates/policies/default/disallow-adding-capabilities.yaml
@@ -6,6 +6,9 @@ metadata:
   name: {{ $name }}
   annotations:
     policies.kyverno.io/category: Pod Security Standards (Default)
+    {{- if .Values.podSecuritySeverity }}
+    policies.kyverno.io/severity: {{ .Values.podSecuritySeverity | quote }}
+    {{- end -}}
     policies.kyverno.io/description: >-
       Capabilities permit privileged actions without giving full root access.
       Adding capabilities beyond the default set must not be allowed.
diff --git a/charts/kyverno/templates/policies/default/disallow-host-namespaces.yaml b/charts/kyverno/templates/policies/default/disallow-host-namespaces.yaml
index b99076ffe..114abbb0e 100644
--- a/charts/kyverno/templates/policies/default/disallow-host-namespaces.yaml
+++ b/charts/kyverno/templates/policies/default/disallow-host-namespaces.yaml
@@ -6,6 +6,9 @@ metadata:
   name: {{ $name }}
   annotations:
     policies.kyverno.io/category: Pod Security Standards (Default)
+    {{- if .Values.podSecuritySeverity }}
+    policies.kyverno.io/severity: {{ .Values.podSecuritySeverity | quote }}
+    {{- end -}}
     policies.kyverno.io/description: >- 
       Host namespaces (Process ID namespace, Inter-Process Communication namespace, and
       network namespace) allow access to shared information and can be used to elevate
diff --git a/charts/kyverno/templates/policies/default/disallow-host-path.yaml b/charts/kyverno/templates/policies/default/disallow-host-path.yaml
index f0ae2f13a..4226495bb 100644
--- a/charts/kyverno/templates/policies/default/disallow-host-path.yaml
+++ b/charts/kyverno/templates/policies/default/disallow-host-path.yaml
@@ -6,6 +6,9 @@ metadata:
   name: {{ $name }}
   annotations:
     policies.kyverno.io/category: Pod Security Standards (Default)
+    {{- if .Values.podSecuritySeverity }}
+    policies.kyverno.io/severity: {{ .Values.podSecuritySeverity | quote }}
+    {{- end -}}
     policies.kyverno.io/description: >-
       HostPath volumes let pods use host directories and volumes in containers.
       Using host resources can be used to access shared data or escalate privileges
diff --git a/charts/kyverno/templates/policies/default/disallow-host-ports.yaml b/charts/kyverno/templates/policies/default/disallow-host-ports.yaml
index ac71ce3cc..3625f6434 100644
--- a/charts/kyverno/templates/policies/default/disallow-host-ports.yaml
+++ b/charts/kyverno/templates/policies/default/disallow-host-ports.yaml
@@ -6,6 +6,9 @@ metadata:
   name: {{ $name }}
   annotations:
     policies.kyverno.io/category: Pod Security Standards (Default)
+    {{- if .Values.podSecuritySeverity }}
+    policies.kyverno.io/severity: {{ .Values.podSecuritySeverity | quote }}
+    {{- end -}}
     policies.kyverno.io/description: >-
       Access to host ports allows potential snooping of network traffic and should not be
       allowed, or at minimum restricted to a known list.
diff --git a/charts/kyverno/templates/policies/default/disallow-privileged-containers.yaml b/charts/kyverno/templates/policies/default/disallow-privileged-containers.yaml
index 80e7c7eb3..5fb6cb4f2 100644
--- a/charts/kyverno/templates/policies/default/disallow-privileged-containers.yaml
+++ b/charts/kyverno/templates/policies/default/disallow-privileged-containers.yaml
@@ -6,6 +6,9 @@ metadata:
   name: {{ $name }}
   annotations:
     policies.kyverno.io/category: Pod Security Standards (Default)
+    {{- if .Values.podSecuritySeverity }}
+    policies.kyverno.io/severity: {{ .Values.podSecuritySeverity | quote }}
+    {{- end -}}
     policies.kyverno.io/description: >-
       Privileged mode disables most security mechanisms and must not be allowed.
 spec:
diff --git a/charts/kyverno/templates/policies/default/disallow-proc-mount.yaml b/charts/kyverno/templates/policies/default/disallow-proc-mount.yaml
index 0a0589ea3..7cdc21141 100644
--- a/charts/kyverno/templates/policies/default/disallow-proc-mount.yaml
+++ b/charts/kyverno/templates/policies/default/disallow-proc-mount.yaml
@@ -6,6 +6,9 @@ metadata:
   name: {{ $name }}
   annotations:
     policies.kyverno.io/category: Pod Security Standards (Default)
+    {{- if .Values.podSecuritySeverity }}
+    policies.kyverno.io/severity: {{ .Values.podSecuritySeverity | quote }}
+    {{- end -}}
     policies.kyverno.io/description: >-
       The default /proc masks are set up to reduce attack surface and should be required.
 spec:
diff --git a/charts/kyverno/templates/policies/default/disallow-selinux.yaml b/charts/kyverno/templates/policies/default/disallow-selinux.yaml
index ab9eab833..884040944 100644
--- a/charts/kyverno/templates/policies/default/disallow-selinux.yaml
+++ b/charts/kyverno/templates/policies/default/disallow-selinux.yaml
@@ -7,6 +7,9 @@ metadata:
   annotations:
     policies.kyverno.io/title: Disallow SELinux
     policies.kyverno.io/category: Pod Security Standards (Default)
+    {{- if .Values.podSecuritySeverity }}
+    policies.kyverno.io/severity: {{ .Values.podSecuritySeverity | quote }}
+    {{- end -}}
     policies.kyverno.io/description: >-
       SELinux options can be used to escalate privileges and should not be allowed.
 spec:
diff --git a/charts/kyverno/templates/policies/default/restrict-apparmor-profiles.yaml b/charts/kyverno/templates/policies/default/restrict-apparmor-profiles.yaml
index 792adc36e..58b403415 100644
--- a/charts/kyverno/templates/policies/default/restrict-apparmor-profiles.yaml
+++ b/charts/kyverno/templates/policies/default/restrict-apparmor-profiles.yaml
@@ -7,6 +7,9 @@ metadata:
   annotations:
     policies.kyverno.io/title: Restrict AppArmor
     policies.kyverno.io/category: Pod Security Standards (Default)
+    {{- if .Values.podSecuritySeverity }}
+    policies.kyverno.io/severity: {{ .Values.podSecuritySeverity | quote }}
+    {{- end -}}
     policies.kyverno.io/description: >-
       On supported hosts, the 'runtime/default' AppArmor profile is applied by default. 
       The default policy should prevent overriding or disabling the policy, or restrict 
diff --git a/charts/kyverno/templates/policies/default/restrict-sysctls.yaml b/charts/kyverno/templates/policies/default/restrict-sysctls.yaml
index ab3d2d7e9..12b6c8db0 100644
--- a/charts/kyverno/templates/policies/default/restrict-sysctls.yaml
+++ b/charts/kyverno/templates/policies/default/restrict-sysctls.yaml
@@ -6,6 +6,9 @@ metadata:
   name: {{ $name }}
   annotations:
     policies.kyverno.io/category: Pod Security Standards (Default)
+    {{- if .Values.podSecuritySeverity }}
+    policies.kyverno.io/severity: {{ .Values.podSecuritySeverity | quote }}
+    {{- end -}}
     policies.kyverno.io/description: >-
       Sysctls can disable security mechanisms or affect all containers on a
       host, and should be disallowed except for an allowed "safe" subset. A
diff --git a/charts/kyverno/templates/policies/restricted/deny-privilege-escalation.yaml b/charts/kyverno/templates/policies/restricted/deny-privilege-escalation.yaml
index 9177b2f1a..4dd41ceba 100644
--- a/charts/kyverno/templates/policies/restricted/deny-privilege-escalation.yaml
+++ b/charts/kyverno/templates/policies/restricted/deny-privilege-escalation.yaml
@@ -6,6 +6,9 @@ metadata:
   name: {{ $name }}
   annotations:
     policies.kyverno.io/category: Pod Security Standards (Restricted)
+    {{- if .Values.podSecuritySeverity }}
+    policies.kyverno.io/severity: {{ .Values.podSecuritySeverity | quote }}
+    {{- end -}}
     policies.kyverno.io/description: >-
       Privilege escalation, such as via set-user-ID or set-group-ID file mode, should not be allowed.
 spec:
diff --git a/charts/kyverno/templates/policies/restricted/require-non-root-groups.yaml b/charts/kyverno/templates/policies/restricted/require-non-root-groups.yaml
index 2fd0cc121..b1b43d815 100644
--- a/charts/kyverno/templates/policies/restricted/require-non-root-groups.yaml
+++ b/charts/kyverno/templates/policies/restricted/require-non-root-groups.yaml
@@ -6,6 +6,9 @@ metadata:
   name: {{ $name }}
   annotations:
     policies.kyverno.io/category: Pod Security Standards (Restricted)
+    {{- if .Values.podSecuritySeverity }}
+    policies.kyverno.io/severity: {{ .Values.podSecuritySeverity | quote }}
+    {{- end -}}
     policies.kyverno.io/description: >-
       Containers should be forbidden from running with a root primary or supplementary GID.
 spec:
diff --git a/charts/kyverno/templates/policies/restricted/require-run-as-nonroot.yaml b/charts/kyverno/templates/policies/restricted/require-run-as-nonroot.yaml
index 0d6eaba15..8d140463f 100644
--- a/charts/kyverno/templates/policies/restricted/require-run-as-nonroot.yaml
+++ b/charts/kyverno/templates/policies/restricted/require-run-as-nonroot.yaml
@@ -6,6 +6,9 @@ metadata:
   name: {{ $name }}
   annotations:
     policies.kyverno.io/category: Pod Security Standards (Restricted)
+    {{- if .Values.podSecuritySeverity }}
+    policies.kyverno.io/severity: {{ .Values.podSecuritySeverity | quote }}
+    {{- end -}}
     policies.kyverno.io/description: Containers must be required to run as non-root users.
 spec:
   background: true
diff --git a/charts/kyverno/templates/policies/restricted/restrict-seccomp.yaml b/charts/kyverno/templates/policies/restricted/restrict-seccomp.yaml
index 5b96246ab..c7e171f25 100644
--- a/charts/kyverno/templates/policies/restricted/restrict-seccomp.yaml
+++ b/charts/kyverno/templates/policies/restricted/restrict-seccomp.yaml
@@ -7,6 +7,9 @@ metadata:
   annotations:
     policies.kyverno.io/title: Restrict Seccomp
     policies.kyverno.io/category: Pod Security Standards (Restricted)
+    {{- if .Values.podSecuritySeverity }}
+    policies.kyverno.io/severity: {{ .Values.podSecuritySeverity | quote }}
+    {{- end -}}
     policies.kyverno.io/description: >-
       The runtime default seccomp profile must be required, or only specific
       additional profiles should be allowed.
diff --git a/charts/kyverno/templates/policies/restricted/restrict-volume-types.yaml b/charts/kyverno/templates/policies/restricted/restrict-volume-types.yaml
index 8ef906a5b..189081c00 100644
--- a/charts/kyverno/templates/policies/restricted/restrict-volume-types.yaml
+++ b/charts/kyverno/templates/policies/restricted/restrict-volume-types.yaml
@@ -6,6 +6,9 @@ metadata:
   name: {{ $name }}
   annotations:
     policies.kyverno.io/category: Pod Security Standards (Restricted)
+    {{- if .Values.podSecuritySeverity }}
+    policies.kyverno.io/severity: {{ .Values.podSecuritySeverity | quote }}
+    {{- end -}}
     policies.kyverno.io/description: >-
       In addition to restricting HostPath volumes, the restricted pod security profile
       limits usage of non-core volume types to those defined through PersistentVolumes.
diff --git a/charts/kyverno/values.yaml b/charts/kyverno/values.yaml
index 5c3209cdf..6add45e1a 100644
--- a/charts/kyverno/values.yaml
+++ b/charts/kyverno/values.yaml
@@ -4,6 +4,8 @@ namespace:
 # Supported- default/restricted/privileged/custom
 # For more info- https://kyverno.io/policies/pod-security
 podSecurityStandard: default
+# Supported- low/medium/high
+podSecuritySeverity: medium
 # Policies to include when podSecurityStandard is custom
 podSecurityPolicies: []
 # Supported values- `audit`, `enforce`
@@ -152,4 +154,3 @@ service:
 # 3) Let Helm generate a self signed cert, by setting createSelfSignedCert true
 # If letting Kyverno create its own CA or providing your own, make createSelfSignedCert is false
 createSelfSignedCert: false
-
diff --git a/definitions/install.yaml b/definitions/install.yaml
index 9d5c1c9f9..5dd2dea71 100644
--- a/definitions/install.yaml
+++ b/definitions/install.yaml
@@ -2441,7 +2441,7 @@ spec:
               fieldPath: metadata.namespace
         - name: KYVERNO_SVC
           value: kyverno-svc
-        image: ghcr.io/kyverno/kyverno:v1.3.5-rc3
+        image: ghcr.io/kyverno/kyverno:v1.3.5
         imagePullPolicy: IfNotPresent
         livenessProbe:
           failureThreshold: 2
@@ -2483,7 +2483,7 @@ spec:
           readOnlyRootFilesystem: true
           runAsNonRoot: true
       initContainers:
-      - image: ghcr.io/kyverno/kyvernopre:v1.3.5-rc3
+      - image: ghcr.io/kyverno/kyvernopre:v1.3.5
         imagePullPolicy: IfNotPresent
         name: kyverno-pre
         securityContext:
diff --git a/definitions/kustomization.yaml b/definitions/kustomization.yaml
index 06df2b1e1..5be95fbdd 100755
--- a/definitions/kustomization.yaml
+++ b/definitions/kustomization.yaml
@@ -8,7 +8,7 @@ resources:
 images:
 - name: ghcr.io/kyverno/kyverno
   newName: ghcr.io/kyverno/kyverno
-  newTag: v1.3.5-rc3
+  newTag: v1.3.5
 - name: ghcr.io/kyverno/kyvernopre
   newName: ghcr.io/kyverno/kyvernopre
-  newTag: v1.3.5-rc3
+  newTag: v1.3.5
diff --git a/definitions/release/install.yaml b/definitions/release/install.yaml
index f9a07e718..5dd2dea71 100755
--- a/definitions/release/install.yaml
+++ b/definitions/release/install.yaml
@@ -1170,7 +1170,7 @@ spec:
       name: Background
       type: string
     - jsonPath: .spec.validationFailureAction
-      name: Validation Failure Action
+      name: Action
       type: string
     name: v1
     schema:
@@ -2386,7 +2386,7 @@ subjects:
 apiVersion: v1
 data:
   excludeGroupRole: system:serviceaccounts:kube-system,system:nodes,system:kube-scheduler
-  resourceFilters: '[Event,*,*][*,kube-system,*][*,kube-public,*][*,kube-node-lease,*][Node,*,*][APIService,*,*][TokenReview,*,*][SubjectAccessReview,*,*][*,kyverno,*][Binding,*,*][ReplicaSet,*,*][ReportChangeRequest,*,*][ClusterReportChangeRequest,*,*][PolicyReport,*,*][ClusterPolicyReport,*,*]'
+  resourceFilters: '[Event,*,*][*,kube-system,*][*,kube-public,*][*,kube-node-lease,*][Node,*,*][APIService,*,*][TokenReview,*,*][SubjectAccessReview,*,*][SelfSubjectAccessReview,*,*][*,kyverno,*][Binding,*,*][ReplicaSet,*,*][ReportChangeRequest,*,*][ClusterReportChangeRequest,*,*][PolicyReport,*,*][ClusterPolicyReport,*,*]'
 kind: ConfigMap
 metadata:
   name: init-config
@@ -2397,6 +2397,7 @@ kind: Service
 metadata:
   labels:
     app: kyverno
+    app.kubernetes.io/name: kyverno
   name: kyverno-svc
   namespace: kyverno
 spec:
@@ -2405,12 +2406,14 @@ spec:
     targetPort: https
   selector:
     app: kyverno
+    app.kubernetes.io/name: kyverno
 ---
 apiVersion: apps/v1
 kind: Deployment
 metadata:
   labels:
     app: kyverno
+    app.kubernetes.io/name: kyverno
   name: kyverno
   namespace: kyverno
 spec:
@@ -2418,10 +2421,12 @@ spec:
   selector:
     matchLabels:
       app: kyverno
+      app.kubernetes.io/name: kyverno
   template:
     metadata:
       labels:
         app: kyverno
+        app.kubernetes.io/name: kyverno
     spec:
       containers:
       - args:
@@ -2436,7 +2441,7 @@ spec:
               fieldPath: metadata.namespace
         - name: KYVERNO_SVC
           value: kyverno-svc
-        image: ghcr.io/kyverno/kyverno:v1.3.5-rc3
+        image: ghcr.io/kyverno/kyverno:v1.3.5
         imagePullPolicy: IfNotPresent
         livenessProbe:
           failureThreshold: 2
@@ -2478,7 +2483,7 @@ spec:
           readOnlyRootFilesystem: true
           runAsNonRoot: true
       initContainers:
-      - image: ghcr.io/kyverno/kyvernopre:v1.3.5-rc3
+      - image: ghcr.io/kyverno/kyvernopre:v1.3.5
         imagePullPolicy: IfNotPresent
         name: kyverno-pre
         securityContext:
diff --git a/go.mod b/go.mod
index eea78ccce..d6407e933 100644
--- a/go.mod
+++ b/go.mod
@@ -25,7 +25,6 @@ require (
 	github.com/onsi/ginkgo v1.14.1
 	github.com/onsi/gomega v1.10.2
 	github.com/opencontainers/go-digest v1.0.0 // indirect
-	github.com/opencontainers/image-spec v1.0.1 // indirect
 	github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6
 	github.com/patrickmn/go-cache v2.1.0+incompatible
 	github.com/pkg/errors v0.9.1
@@ -51,6 +50,7 @@ require (
 // Added for go1.13 migration https://github.com/golang/go/issues/32805
 replace (
 	github.com/gorilla/rpc v1.2.0+incompatible => github.com/gorilla/rpc v1.2.0
+	github.com/jmespath/go-jmespath => github.com/kyverno/go-jmespath v0.4.1-0.20210302163943-f30eab0a3ed6
 	k8s.io/code-generator => k8s.io/code-generator v0.0.0-20200306081859-6a048a382944
 	k8s.io/component-base => k8s.io/component-base v0.0.0-20190612130303-4062e14deebe
 )
diff --git a/go.sum b/go.sum
index faf5fd3ab..4b40ff2fc 100644
--- a/go.sum
+++ b/go.sum
@@ -476,8 +476,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
 github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
-github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
-github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -523,6 +523,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/kurin/blazer v0.5.4-0.20190613185654-cf2f27cc0be3/go.mod h1:4FCXMUWo9DllR2Do4TtBd377ezyAJ51vB5uTBjt0pGU=
+github.com/kyverno/go-jmespath v0.4.1-0.20210302163943-f30eab0a3ed6 h1:9rJUAc/XxL6wV4i+3X56wcRM8tDhoo0l7DIieuXPGfk=
+github.com/kyverno/go-jmespath v0.4.1-0.20210302163943-f30eab0a3ed6/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/lensesio/tableprinter v0.0.0-20201125135848-89e81fc956e7 h1:k/1ku0yehLCPqERCHkIHMDqDg1R02AcCScRuHbamU3s=
 github.com/lensesio/tableprinter v0.0.0-20201125135848-89e81fc956e7/go.mod h1:YR/zYthNdWfO8+0IOyHDcIDBBBS2JMnYUIwSsnwmRqU=
 github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@@ -652,8 +654,6 @@ github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs=
 github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
-github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
-github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
 github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 h1:lNCW6THrCKBiJBpz8kbVGjC7MgdCGKwuvBgc7LoD6sw=
 github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
diff --git a/pkg/engine/context/evaluate.go b/pkg/engine/context/evaluate.go
index 05bfec755..fb8181974 100644
--- a/pkg/engine/context/evaluate.go
+++ b/pkg/engine/context/evaluate.go
@@ -5,7 +5,7 @@ import (
 	"fmt"
 	"strings"
 
-	jmespath "github.com/jmespath/go-jmespath"
+	jmespath "github.com/kyverno/kyverno/pkg/engine/jmespath"
 )
 
 //Query the JSON context with JMESPATH search path
@@ -25,7 +25,7 @@ func (ctx *Context) Query(query string) (interface{}, error) {
 	}
 
 	// compile the query
-	queryPath, err := jmespath.Compile(query)
+	queryPath, err := jmespath.New(query)
 	if err != nil {
 		ctx.log.Error(err, "incorrect query", "query", query)
 		return emptyResult, fmt.Errorf("incorrect query %s: %v", query, err)
diff --git a/pkg/engine/jmespath/functions.go b/pkg/engine/jmespath/functions.go
new file mode 100644
index 000000000..4bfad9f5f
--- /dev/null
+++ b/pkg/engine/jmespath/functions.go
@@ -0,0 +1,381 @@
+package jmespath
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"regexp"
+	"strconv"
+	"strings"
+
+	gojmespath "github.com/jmespath/go-jmespath"
+)
+
+var (
+	JpObject      = gojmespath.JpObject
+	JpString      = gojmespath.JpString
+	JpNumber      = gojmespath.JpNumber
+	JpArray       = gojmespath.JpArray
+	JpArrayString = gojmespath.JpArrayString
+)
+
+type (
+	JpType  = gojmespath.JpType
+	ArgSpec = gojmespath.ArgSpec
+)
+
+// function names
+var (
+	compare                = "compare"
+	contains               = "contains"
+	equalFold              = "equal_fold"
+	replace                = "replace"
+	replaceAll             = "replace_all"
+	toUpper                = "to_upper"
+	toLower                = "to_lower"
+	trim                   = "trim"
+	split                  = "split"
+	equals                 = "equals"
+	regexReplaceAll        = "regex_replace_all"
+	regexReplaceAllLiteral = "regex_replace_all_literal"
+	regexMatch             = "regex_match"
+)
+
+const errorPrefix = "JMESPath function '%s': "
+const invalidArgumentTypeError = errorPrefix + "%d argument is expected of %s type"
+const genericError = errorPrefix + "%s"
+
+func getFunctions() []*gojmespath.FunctionEntry {
+	return []*gojmespath.FunctionEntry{
+		{
+			Name: compare,
+			Arguments: []ArgSpec{
+				{Types: []JpType{JpString}},
+				{Types: []JpType{JpString}},
+			},
+			Handler: jpfCompare,
+		},
+		{
+			Name: contains,
+			Arguments: []ArgSpec{
+				{Types: []JpType{JpString}},
+				{Types: []JpType{JpString}},
+			},
+			Handler: jpfContains,
+		},
+		{
+			Name: equalFold,
+			Arguments: []ArgSpec{
+				{Types: []JpType{JpString}},
+				{Types: []JpType{JpString}},
+			},
+			Handler: jpfEqualFold,
+		},
+		{
+			Name: replace,
+			Arguments: []ArgSpec{
+				{Types: []JpType{JpString}},
+				{Types: []JpType{JpString}},
+				{Types: []JpType{JpString}},
+				{Types: []JpType{JpNumber}},
+			},
+			Handler: jpfReplace,
+		},
+		{
+			Name: replaceAll,
+			Arguments: []ArgSpec{
+				{Types: []JpType{JpString}},
+				{Types: []JpType{JpString}},
+				{Types: []JpType{JpString}},
+			},
+			Handler: jpfReplaceAll,
+		},
+		{
+			Name: toUpper,
+			Arguments: []ArgSpec{
+				{Types: []JpType{JpString}},
+			},
+			Handler: jpfToUpper,
+		},
+		{
+			Name: toLower,
+			Arguments: []ArgSpec{
+				{Types: []JpType{JpString}},
+			},
+			Handler: jpfToLower,
+		},
+		{
+			Name: trim,
+			Arguments: []ArgSpec{
+				{Types: []JpType{JpString}},
+				{Types: []JpType{JpString}},
+			},
+			Handler: jpfTrim,
+		},
+		{
+			Name: split,
+			Arguments: []ArgSpec{
+				{Types: []JpType{JpString}},
+				{Types: []JpType{JpString}},
+			},
+			Handler: jpfSplit,
+		},
+		{
+			Name: regexReplaceAll,
+			Arguments: []ArgSpec{
+				{Types: []JpType{JpString}},
+				{Types: []JpType{JpString, JpNumber}},
+				{Types: []JpType{JpString, JpNumber}},
+			},
+			Handler: jpRegexReplaceAll,
+		},
+		{
+			Name: regexReplaceAllLiteral,
+			Arguments: []ArgSpec{
+				{Types: []JpType{JpString}},
+				{Types: []JpType{JpString, JpNumber}},
+				{Types: []JpType{JpString, JpNumber}},
+			},
+			Handler: jpRegexReplaceAllLiteral,
+		},
+		{
+			Name: regexMatch,
+			Arguments: []ArgSpec{
+				{Types: []JpType{JpString}},
+				{Types: []JpType{JpString, JpNumber}},
+			},
+			Handler: jpRegexMatch,
+		},
+	}
+
+}
+
+func jpfCompare(arguments []interface{}) (interface{}, error) {
+	var err error
+	a, err := validateArg(compare, arguments, 0, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	b, err := validateArg(compare, arguments, 1, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	return strings.Compare(a.String(), b.String()), nil
+}
+
+func jpfContains(arguments []interface{}) (interface{}, error) {
+	var err error
+	str, err := validateArg(contains, arguments, 0, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	substr, err := validateArg(contains, arguments, 1, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	return strings.Contains(str.String(), substr.String()), nil
+}
+
+func jpfEqualFold(arguments []interface{}) (interface{}, error) {
+	var err error
+	a, err := validateArg(equalFold, arguments, 0, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	b, err := validateArg(equalFold, arguments, 1, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	return strings.EqualFold(a.String(), b.String()), nil
+}
+
+func jpfReplace(arguments []interface{}) (interface{}, error) {
+	var err error
+	str, err := validateArg(replace, arguments, 0, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	old, err := validateArg(replace, arguments, 1, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	new, err := validateArg(replace, arguments, 2, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	n, err := validateArg(replace, arguments, 3, reflect.Float64)
+	if err != nil {
+		return nil, err
+	}
+
+	return strings.Replace(str.String(), old.String(), new.String(), int(n.Float())), nil
+}
+
+func jpfReplaceAll(arguments []interface{}) (interface{}, error) {
+	var err error
+	str, err := validateArg(replaceAll, arguments, 0, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	old, err := validateArg(replaceAll, arguments, 1, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	new, err := validateArg(replaceAll, arguments, 2, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	return strings.ReplaceAll(str.String(), old.String(), new.String()), nil
+}
+
+func jpfToUpper(arguments []interface{}) (interface{}, error) {
+	var err error
+	str, err := validateArg(toUpper, arguments, 0, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	return strings.ToUpper(str.String()), nil
+}
+
+func jpfToLower(arguments []interface{}) (interface{}, error) {
+	var err error
+	str, err := validateArg(toLower, arguments, 0, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	return strings.ToLower(str.String()), nil
+}
+
+func jpfTrim(arguments []interface{}) (interface{}, error) {
+	var err error
+	str, err := validateArg(trim, arguments, 0, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	cutset, err := validateArg(trim, arguments, 1, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	return strings.Trim(str.String(), cutset.String()), nil
+}
+
+func jpfSplit(arguments []interface{}) (interface{}, error) {
+	var err error
+	str, err := validateArg(split, arguments, 0, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	sep, err := validateArg(split, arguments, 1, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	return strings.Split(str.String(), sep.String()), nil
+}
+
+func jpRegexReplaceAll(arguments []interface{}) (interface{}, error) {
+	var err error
+	regex, err := validateArg(regexReplaceAll, arguments, 0, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	src, err := ifaceToString(arguments[1])
+	if err != nil {
+		return nil, fmt.Errorf(invalidArgumentTypeError, regexReplaceAll, 2, "String or Real")
+	}
+
+	repl, err := ifaceToString(arguments[2])
+	if err != nil {
+		return nil, fmt.Errorf(invalidArgumentTypeError, regexReplaceAll, 3, "String or Real")
+	}
+
+	reg, err := regexp.Compile(regex.String())
+	if err != nil {
+		return nil, fmt.Errorf(genericError, regexReplaceAll, err.Error())
+	}
+	return string(reg.ReplaceAll([]byte(src), []byte(repl))), nil
+}
+
+func jpRegexReplaceAllLiteral(arguments []interface{}) (interface{}, error) {
+	var err error
+	regex, err := validateArg(regexReplaceAllLiteral, arguments, 0, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	src, err := ifaceToString(arguments[1])
+	if err != nil {
+		return nil, fmt.Errorf(invalidArgumentTypeError, regexReplaceAllLiteral, 2, "String or Real")
+	}
+
+	repl, err := ifaceToString(arguments[2])
+	if err != nil {
+		return nil, fmt.Errorf(invalidArgumentTypeError, regexReplaceAllLiteral, 3, "String or Real")
+	}
+
+	reg, err := regexp.Compile(regex.String())
+	if err != nil {
+		return nil, fmt.Errorf(genericError, regexReplaceAllLiteral, err.Error())
+	}
+	return string(reg.ReplaceAllLiteral([]byte(src), []byte(repl))), nil
+}
+
+func jpRegexMatch(arguments []interface{}) (interface{}, error) {
+	var err error
+	regex, err := validateArg(regexMatch, arguments, 0, reflect.String)
+	if err != nil {
+		return nil, err
+	}
+
+	src, err := ifaceToString(arguments[1])
+	if err != nil {
+		return nil, fmt.Errorf(invalidArgumentTypeError, regexMatch, 2, "String or Real")
+	}
+
+	return regexp.Match(regex.String(), []byte(src))
+}
+
+// InterfaceToString casts an interface to a string type
+func ifaceToString(iface interface{}) (string, error) {
+	switch iface.(type) {
+	case int:
+		return strconv.Itoa(iface.(int)), nil
+	case float64:
+		return strconv.FormatFloat(iface.(float64), 'f', -1, 32), nil
+	case float32:
+		return strconv.FormatFloat(iface.(float64), 'f', -1, 32), nil
+	case string:
+		return iface.(string), nil
+	case bool:
+		return strconv.FormatBool(iface.(bool)), nil
+	default:
+		return "", errors.New("error, undefined type cast")
+	}
+}
+
+func validateArg(f string, arguments []interface{}, index int, expectedType reflect.Kind) (reflect.Value, error) {
+	arg := reflect.ValueOf(arguments[index])
+	if arg.Type().Kind() != expectedType {
+		return reflect.Value{}, fmt.Errorf(invalidArgumentTypeError, equalFold, index+1, expectedType.String())
+	}
+
+	return arg, nil
+}
diff --git a/pkg/engine/jmespath/new.go b/pkg/engine/jmespath/new.go
new file mode 100644
index 000000000..5714de69d
--- /dev/null
+++ b/pkg/engine/jmespath/new.go
@@ -0,0 +1,18 @@
+package jmespath
+
+import (
+	gojmespath "github.com/jmespath/go-jmespath"
+)
+
+func New(query string) (*gojmespath.JMESPath, error) {
+	jp, err := gojmespath.Compile(query)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, function := range getFunctions() {
+		jp.Register(function)
+	}
+
+	return jp, nil
+}
diff --git a/pkg/engine/jsonContext.go b/pkg/engine/jsonContext.go
index 207086f14..7c5c8b08a 100644
--- a/pkg/engine/jsonContext.go
+++ b/pkg/engine/jsonContext.go
@@ -8,9 +8,9 @@ import (
 	"strings"
 
 	"github.com/go-logr/logr"
-	"github.com/jmespath/go-jmespath"
 	kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
 	"github.com/kyverno/kyverno/pkg/engine/context"
+	jmespath "github.com/kyverno/kyverno/pkg/engine/jmespath"
 	"github.com/kyverno/kyverno/pkg/engine/variables"
 	"github.com/kyverno/kyverno/pkg/resourcecache"
 	"k8s.io/client-go/dynamic/dynamiclister"
@@ -88,7 +88,7 @@ func loadAPIData(logger logr.Logger, entry kyverno.ContextEntry, ctx *PolicyCont
 }
 
 func applyJMESPath(jmesPath string, jsonData []byte) (interface{}, error) {
-	jp, err := jmespath.Compile(jmesPath)
+	jp, err := jmespath.New(jmesPath)
 	if err != nil {
 		return nil, fmt.Errorf("failed to compile JMESPath: %s, error: %v", jmesPath, err)
 	}
diff --git a/pkg/engine/mutate/mutation.go b/pkg/engine/mutate/mutation.go
index 4c54e4fd5..3dd5ab42d 100644
--- a/pkg/engine/mutate/mutation.go
+++ b/pkg/engine/mutate/mutation.go
@@ -6,7 +6,6 @@ import (
 	"github.com/kyverno/kyverno/pkg/engine/context"
 	"github.com/kyverno/kyverno/pkg/engine/response"
 	"github.com/kyverno/kyverno/pkg/engine/utils"
-	"github.com/kyverno/kyverno/pkg/engine/variables"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 )
 
@@ -56,21 +55,7 @@ func newpatchStrategicMergeHandler(ruleName string, mutate *kyverno.Mutation, pa
 }
 
 func (h patchStrategicMergeHandler) Handle() (response.RuleResponse, unstructured.Unstructured) {
-	var ruleResponse response.RuleResponse
-	PatchStrategicMerge := h.mutation.PatchStrategicMerge
-	log := h.logger
-
-	// substitute the variables
-	var err error
-	if PatchStrategicMerge, err = variables.SubstituteAll(log, h.evalCtx, PatchStrategicMerge); err != nil {
-		// variable subsitution failed
-		ruleResponse.Name = h.ruleName
-		ruleResponse.Success = false
-		ruleResponse.Message = err.Error()
-		return ruleResponse, h.patchedResource
-	}
-
-	return ProcessStrategicMergePatch(h.ruleName, PatchStrategicMerge, h.patchedResource, log)
+	return ProcessStrategicMergePatch(h.ruleName, h.mutation.PatchStrategicMerge, h.patchedResource, h.logger)
 }
 
 // overlayHandler
@@ -126,19 +111,7 @@ func (h patchesJSON6902Handler) Handle() (resp response.RuleResponse, patchedRes
 }
 
 func (h overlayHandler) Handle() (response.RuleResponse, unstructured.Unstructured) {
-	var ruleResponse response.RuleResponse
-	overlay := h.mutation.Overlay
-
-	// substitute the variables
-	var err error
-	if overlay, err = variables.SubstituteAll(h.logger, h.evalCtx, overlay); err != nil {
-		// variable substitution failed
-		ruleResponse.Success = false
-		ruleResponse.Message = err.Error()
-		return ruleResponse, h.patchedResource
-	}
-
-	return ProcessOverlay(h.logger, h.ruleName, overlay, h.patchedResource)
+	return ProcessOverlay(h.logger, h.ruleName, h.mutation.Overlay, h.patchedResource)
 }
 
 // patchesHandler
diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go
index ce6c8e910..6b44cf130 100644
--- a/pkg/engine/mutation.go
+++ b/pkg/engine/mutation.go
@@ -1,12 +1,14 @@
 package engine
 
 import (
+	"fmt"
 	"time"
 
 	"github.com/go-logr/logr"
 	kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
 	"github.com/kyverno/kyverno/pkg/engine/mutate"
 	"github.com/kyverno/kyverno/pkg/engine/response"
+	"github.com/kyverno/kyverno/pkg/engine/utils"
 	"github.com/kyverno/kyverno/pkg/engine/variables"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"sigs.k8s.io/controller-runtime/pkg/log"
@@ -56,7 +58,7 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) {
 
 		// check if the resource satisfies the filter conditions defined in the rule
 		//TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
-		// dont satisfy a policy rule resource description
+		// don't satisfy a policy rule resource description
 		excludeResource := []string{}
 		if len(policyContext.ExcludeGroupRole) > 0 {
 			excludeResource = policyContext.ExcludeGroupRole
@@ -88,6 +90,21 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) {
 			continue
 		}
 
+		if rule, err = variables.SubstituteAllInRule(logger, policyContext.JSONContext, rule); err != nil {
+			ruleResp := response.RuleResponse{
+				Name:    rule.Name,
+				Type:    utils.Validation.String(),
+				Message: fmt.Sprintf("variable substitution failed for rule %s: %s", rule.Name, err.Error()),
+				Success: true,
+			}
+
+			incrementAppliedCount(resp)
+			resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResp)
+
+			logger.Error(err, "failed to substitute variables, skip current rule", "rule name", rule.Name)
+			continue
+		}
+
 		mutation := rule.Mutation.DeepCopy()
 		mutateHandler := mutate.CreateMutateHandler(rule.Name, mutation, patchedResource, ctx, logger)
 		ruleResponse, patchedResource = mutateHandler.Handle()
diff --git a/pkg/engine/validation.go b/pkg/engine/validation.go
index 88ae9936f..353d9e4e5 100644
--- a/pkg/engine/validation.go
+++ b/pkg/engine/validation.go
@@ -60,16 +60,6 @@ func buildResponse(logger logr.Logger, ctx *PolicyContext, resp *response.Engine
 		resp.PatchedResource = resource
 	}
 
-	for i := range resp.PolicyResponse.Rules {
-		messageInterface, err := variables.SubstituteAll(logger, ctx.JSONContext, resp.PolicyResponse.Rules[i].Message)
-		if err != nil {
-			logger.V(4).Info("failed to substitute variables", "error", err.Error())
-			continue
-		}
-
-		resp.PolicyResponse.Rules[i].Message, _ = messageInterface.(string)
-	}
-
 	resp.PolicyResponse.Policy = ctx.Policy.Name
 	resp.PolicyResponse.Resource.Name = resp.PatchedResource.GetName()
 	resp.PolicyResponse.Resource.Namespace = resp.PatchedResource.GetNamespace()
@@ -94,6 +84,8 @@ func validateResource(log logr.Logger, ctx *PolicyContext) *response.EngineRespo
 	defer ctx.JSONContext.Restore()
 
 	for _, rule := range ctx.Policy.Spec.Rules {
+		var err error
+
 		if !rule.HasValidate() {
 			continue
 		}
@@ -124,6 +116,21 @@ func validateResource(log logr.Logger, ctx *PolicyContext) *response.EngineRespo
 			continue
 		}
 
+		if rule, err = variables.SubstituteAllInRule(log, ctx.JSONContext, rule); err != nil {
+			ruleResp := response.RuleResponse{
+				Name:    rule.Name,
+				Type:    utils.Validation.String(),
+				Message: fmt.Sprintf("variable substitution failed for rule %s: %s", rule.Name, err.Error()),
+				Success: true,
+			}
+
+			incrementAppliedCount(resp)
+			resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResp)
+
+			log.Error(err, "failed to substitute variables, skip current rule", "rule name", rule.Name)
+			continue
+		}
+
 		if rule.Validation.Pattern != nil || rule.Validation.AnyPattern != nil {
 			ruleResponse := validateResourceWithRule(log, ctx, rule)
 			if ruleResponse != nil {
@@ -225,13 +232,6 @@ func validatePatterns(log logr.Logger, ctx context.EvalInterface, resource unstr
 		logger.V(4).Info("finished processing rule", "processingTime", resp.RuleStats.ProcessingTime.String())
 	}()
 
-	var err error
-	if rule, err = variables.SubstituteAllInRule(logger, ctx, rule); err != nil {
-		resp.Success = false
-		resp.Message = fmt.Sprintf("variable substitution failed for rule %s: %s", rule.Name, err.Error())
-		return resp
-	}
-
 	validationRule := rule.Validation.DeepCopy()
 	if validationRule.Pattern != nil {
 		pattern := validationRule.Pattern
diff --git a/pkg/engine/variables/vars.go b/pkg/engine/variables/vars.go
index 1173d314d..2711ce984 100644
--- a/pkg/engine/variables/vars.go
+++ b/pkg/engine/variables/vars.go
@@ -104,7 +104,7 @@ func validateBackgroundModeVars(log logr.Logger, ctx context.EvalInterface) json
 				case context.InvalidVariableErr:
 					return nil, err
 				default:
-					return nil, fmt.Errorf("failed to resolve %v at path %s", variable, data.Path)
+					return nil, fmt.Errorf("failed to resolve %v at path %s: %v", variable, data.Path, err)
 				}
 			}
 		}
@@ -119,7 +119,7 @@ type NotFoundVariableErr struct {
 }
 
 func (n NotFoundVariableErr) Error() string {
-	return fmt.Sprintf("variable %s not resolved at path %s", n.variable, n.path)
+	return fmt.Sprintf("NotFoundVariableErr, variable %s not resolved at path %s", n.variable, n.path)
 }
 
 // NotResolvedReferenceErr is returned when it is impossible to resolve the variable
@@ -129,7 +129,7 @@ type NotResolvedReferenceErr struct {
 }
 
 func (n NotResolvedReferenceErr) Error() string {
-	return fmt.Sprintf("reference %s not resolved at path %s", n.reference, n.path)
+	return fmt.Sprintf("NotResolvedReferenceErr,reference %s not resolved at path %s", n.reference, n.path)
 }
 
 func substituteReferencesIfAny(log logr.Logger) jsonUtils.Action {
@@ -146,12 +146,12 @@ func substituteReferencesIfAny(log logr.Logger) jsonUtils.Action {
 				case context.InvalidVariableErr:
 					return nil, err
 				default:
-					return nil, fmt.Errorf("failed to resolve %v at path %s", v, data.Path)
+					return nil, fmt.Errorf("failed to resolve %v at path %s: %v", v, data.Path, err)
 				}
 			}
 
 			if resolvedReference == nil {
-				return data.Element, fmt.Errorf("failed to resolve %v at path %s", v, data.Path)
+				return data.Element, fmt.Errorf("failed to resolve %v at path %s: %v", v, data.Path, err)
 			}
 
 			log.V(3).Info("reference resolved", "reference", v, "value", resolvedReference, "path", data.Path)
@@ -178,12 +178,18 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface) jsonUt
 			return data.Element, nil
 		}
 
-		originalPattern := value
 		vars := regexVariables.FindAllString(value, -1)
 		for len(vars) > 0 {
+			originalPattern := value
+
 			for _, v := range vars {
 				variable := replaceBracesAndTrimSpaces(v)
 
+				if variable == "@" {
+					currentPath := getJMESPath(data.Path)
+					variable = strings.Replace(variable, "@", fmt.Sprintf("request.object%s", currentPath), -1)
+				}
+
 				operation, err := ctx.Query("request.operation")
 				if err == nil && operation == "DELETE" {
 					variable = strings.ReplaceAll(variable, "request.object", "request.oldObject")
@@ -195,23 +201,22 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface) jsonUt
 					case context.InvalidVariableErr:
 						return nil, err
 					default:
-						return nil, fmt.Errorf("failed to resolve %v at path %s", variable, data.Path)
+						return nil, fmt.Errorf("failed to resolve %v at path %s: %v", variable, data.Path, err)
 					}
 				}
 
 				log.V(3).Info("variable substituted", "variable", v, "value", substitutedVar, "path", data.Path)
 
-				if val, ok := substitutedVar.(string); ok {
-					value = strings.Replace(value, v, val, -1)
-					continue
-				}
-
 				if substitutedVar != nil {
 					if originalPattern == v {
 						return substitutedVar, nil
 					}
 
-					return nil, fmt.Errorf("failed to resolve %v at path %s", variable, data.Path)
+					if value, err = substituteVarInPattern(originalPattern, v, substitutedVar); err != nil {
+						return nil, fmt.Errorf("failed to resolve %v at path %s: %s", variable, data.Path, err.Error())
+					}
+
+					continue
 				}
 
 				return nil, NotFoundVariableErr{
@@ -228,6 +233,29 @@ func substituteVariablesIfAny(log logr.Logger, ctx context.EvalInterface) jsonUt
 	})
 }
 
+// getJMESPath converts path to JMES format
+func getJMESPath(rawPath string) string {
+	path := strings.ReplaceAll(rawPath, "/", ".")
+	regex := regexp.MustCompile(`\.([\d])\.`)
+	return string(regex.ReplaceAll([]byte(path), []byte("[$1].")))
+}
+
+func substituteVarInPattern(pattern, variable string, value interface{}) (string, error) {
+	var stringToSubstitute string
+
+	if s, ok := value.(string); ok {
+		stringToSubstitute = s
+	} else {
+		buffer, err := json.Marshal(value)
+		if err != nil {
+			return "", fmt.Errorf("failed to marshal %T: %v", value, value)
+		}
+		stringToSubstitute = string(buffer)
+	}
+
+	return strings.Replace(pattern, variable, stringToSubstitute, -1), nil
+}
+
 func replaceBracesAndTrimSpaces(v string) string {
 	variable := strings.ReplaceAll(v, "{{", "")
 	variable = strings.ReplaceAll(variable, "}}", "")
diff --git a/pkg/generate/generate.go b/pkg/generate/generate.go
index 9c9cb7a42..bf80d13f4 100644
--- a/pkg/generate/generate.go
+++ b/pkg/generate/generate.go
@@ -199,6 +199,7 @@ func (c *Controller) applyGeneratePolicy(log logr.Logger, policyContext *engine.
 
 	ruleNameToProcessingTime := make(map[string]time.Duration)
 	for _, rule := range policy.Spec.Rules {
+		var err error
 		if !rule.HasGenerate() {
 			continue
 		}
@@ -221,7 +222,12 @@ func (c *Controller) applyGeneratePolicy(log logr.Logger, policyContext *engine.
 
 		// add configmap json data to context
 		if err := engine.LoadContext(log, rule.Context, resCache, policyContext); err != nil {
-			log.Info("cannot add configmaps to context", "reason", err.Error())
+			log.Error(err, "cannot add configmaps to context")
+			return nil, err
+		}
+
+		if rule, err = variables.SubstituteAllInRule(log, policyContext.JSONContext, rule); err != nil {
+			log.Error(err, "variable substitution failed for rule %s", rule.Name)
 			return nil, err
 		}
 
@@ -316,16 +322,6 @@ func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resou
 		return noGenResource, err
 	}
 
-	// Variable substitutions
-	// format : {{<variable_name}}
-	// - if there is variables that are not defined the context -> results in error and rule is not applied
-	// - valid variables are replaced with the values
-	object, err := variables.SubstituteAll(log, ctx, genUnst.Object)
-	if err != nil {
-		return noGenResource, err
-	}
-
-	genUnst.Object, _ = object.(map[string]interface{})
 	genKind, genName, genNamespace, genAPIVersion, err := getResourceInfo(genUnst.Object)
 	if err != nil {
 		return noGenResource, err
diff --git a/pkg/policy/background.go b/pkg/policy/background.go
index c459b1a98..0b3620224 100644
--- a/pkg/policy/background.go
+++ b/pkg/policy/background.go
@@ -44,10 +44,6 @@ func ContainsVariablesOtherThanObject(policy kyverno.ClusterPolicy) error {
 			return fmt.Errorf("variable substitution failed for rule %s: %s", rule.Name, err.Error())
 		}
 
-		if rule, err = variables.SubstituteAllInRule(log.Log, ctx, rule); !checkNotFoundErr(err) {
-			return fmt.Errorf("variable substitution failed for rule %s: %s", rule.Name, err.Error())
-		}
-
 		if rule.AnyAllConditions != nil {
 			if err = validatePreConditions(idx, ctx, rule.AnyAllConditions); !checkNotFoundErr(err) {
 				return err
diff --git a/pkg/policyreport/builder.go b/pkg/policyreport/builder.go
index 8d602305a..1cb29ceab 100755
--- a/pkg/policyreport/builder.go
+++ b/pkg/policyreport/builder.go
@@ -132,7 +132,7 @@ func (builder *requestBuilder) build(info Info) (req *unstructured.Unstructured,
 }
 
 func (builder *requestBuilder) buildRCRResult(policy string, resource response.ResourceSpec, rule kyverno.ViolatedRule) *report.PolicyReportResult {
-	av := builder.fetchAnnoationValues(policy, resource.Namespace)
+	av := builder.fetchAnnotationValues(policy, resource.Namespace)
 
 	result := &report.PolicyReportResult{
 		Policy: policy,
@@ -275,9 +275,9 @@ func (av *annotationValues) setSeverityFromString(severity string) {
 	}
 }
 
-func (builder *requestBuilder) fetchAnnoationValues(policy, ns string) annotationValues {
+func (builder *requestBuilder) fetchAnnotationValues(policy, ns string) annotationValues {
 	av := annotationValues{}
-	ann := builder.fetchAnnoations(policy, ns)
+	ann := builder.fetchAnnotations(policy, ns)
 
 	if category, ok := ann[categoryLabel]; ok {
 		av.category = category
@@ -289,7 +289,7 @@ func (builder *requestBuilder) fetchAnnoationValues(policy, ns string) annotatio
 	return av
 }
 
-func (builder *requestBuilder) fetchAnnoations(policy, ns string) map[string]string {
+func (builder *requestBuilder) fetchAnnotations(policy, ns string) map[string]string {
 	cpol, err := builder.cpolLister.Get(policy)
 	if err == nil {
 		if ann := cpol.GetAnnotations(); ann != nil {
diff --git a/pkg/utils/util.go b/pkg/utils/util.go
index c4be1d4c4..51cf6456b 100644
--- a/pkg/utils/util.go
+++ b/pkg/utils/util.go
@@ -205,6 +205,26 @@ func SliceContains(slice []string, values ...string) bool {
 // and converts it into []kyverno.Condition or kyverno.AnyAllConditions according to its content.
 // it also helps in validating the condtions as it returns an error when the conditions are provided wrongfully by the user.
 func ApiextensionsJsonToKyvernoConditions(original apiextensions.JSON) (interface{}, error) {
+	path := "preconditions/validate.deny.conditions"
+
+	// checks for the existence any other field apart from 'any'/'all' under preconditions/validate.deny.conditions
+	unknownFieldChecker := func(jsonByteArr []byte, path string) error {
+		allowedKeys := map[string]bool{
+			"any": true,
+			"all": true,
+		}
+		var jsonDecoded map[string]interface{}
+		if err := json.Unmarshal(jsonByteArr, &jsonDecoded); err != nil {
+			return fmt.Errorf("error occurred while checking for unknown fields under %s: %+v", path, err)
+		}
+		for k := range jsonDecoded {
+			if !allowedKeys[k] {
+				return fmt.Errorf("unknown field '%s' found under %s", k, path)
+			}
+		}
+		return nil
+	}
+
 	// marshalling the abstract apiextensions.JSON back to JSON form
 	jsonByte, err := json.Marshal(original)
 
@@ -215,8 +235,12 @@ func ApiextensionsJsonToKyvernoConditions(original apiextensions.JSON) (interfac
 
 	var kyvernoAnyAllConditions kyverno.AnyAllConditions
 	if err = json.Unmarshal(jsonByte, &kyvernoAnyAllConditions); err == nil {
+		// checking if unknown fields exist or not
+		err = unknownFieldChecker(jsonByte, path)
+		if err != nil {
+			return nil, fmt.Errorf("error occurred while parsing %s: %+v", path, err)
+		}
 		return kyvernoAnyAllConditions, nil
 	}
-
-	return nil, fmt.Errorf("conditions filled wrongfully")
+	return nil, fmt.Errorf("error occurred while parsing %s: %+v", path, err)
 }
diff --git a/pkg/webhooks/validation.go b/pkg/webhooks/validation.go
index c0f5799a9..b0eaf48bc 100644
--- a/pkg/webhooks/validation.go
+++ b/pkg/webhooks/validation.go
@@ -136,13 +136,14 @@ func HandleValidation(
 		return false, getEnforceFailureErrorMsg(engineResponses)
 	}
 
-	prInfos := policyreport.GeneratePRsFromEngineResponse(engineResponses, logger)
-	prGenerator.Add(prInfos...)
-
 	if request.Operation == v1beta1.Delete {
 		prGenerator.Add(buildDeletionPrInfo(oldR))
+		return true, ""
 	}
 
+	prInfos := policyreport.GeneratePRsFromEngineResponse(engineResponses, logger)
+	prGenerator.Add(prInfos...)
+
 	return true, ""
 }
 


Test patch:
diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml
index 574615395..c19978874 100644
--- a/.github/workflows/e2e.yaml
+++ b/.github/workflows/e2e.yaml
@@ -30,6 +30,14 @@ jobs:
         with:
           go-version: 1.14
 
+      - name: Set up Helm
+        uses: azure/setup-helm@v1
+        with:
+          version: v3.5.0
+
+      - name: Set up chart-testing
+        uses: helm/chart-testing-action@v2.0.1
+
       - name: Cache Go modules
         uses: actions/cache@v1
         with:
@@ -76,4 +84,16 @@ jobs:
           sleep 20
           echo ">>> Run Kyverno e2e test"
           make test-e2e
+          kubectl delete -f ${GITHUB_WORKSPACE}/definitions/install.yaml
 
+      - name: Update chart values
+        run: |
+          sed -i -e "s|nameOverride:.*|nameOverride: kyverno|g" charts/kyverno/values.yaml
+          sed -i -e "s|fullnameOverride:.*|fullnameOverride: kyverno|g" charts/kyverno/values.yaml
+          sed -i -e "s|namespace:.*|namespace: kyverno|g" charts/kyverno/values.yaml
+          sed -i -e "s|tag:.*|tag: $(git describe --always --tags)|g" charts/kyverno/values.yaml
+
+      - name: Run chart-testing (install)
+        run: |
+          kubectl create namespace kyverno
+          ct install --target-branch=main --namespace=kyverno
diff --git a/charts/kyverno/templates/tests/test.yaml b/charts/kyverno/templates/tests/test.yaml
new file mode 100644
index 000000000..f176cdd47
--- /dev/null
+++ b/charts/kyverno/templates/tests/test.yaml
@@ -0,0 +1,18 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: "{{ template "kyverno.fullname" . }}-test"
+  labels:
+    {{- include "kyverno.labels" . | nindent 4 }}
+  annotations:
+    "helm.sh/hook": test
+spec:
+  containers:
+    - name: wget
+      image: busybox
+      command:
+        - /bin/sh
+        - -c
+        - |
+          sleep 20 ; wget -O- -S --no-check-certificate https://{{ template "kyverno.serviceName" . }}:{{ .Values.service.port }}/health/liveness
+  restartPolicy: Never
diff --git a/pkg/engine/jmespath/functions_test.go b/pkg/engine/jmespath/functions_test.go
new file mode 100644
index 000000000..b282c3171
--- /dev/null
+++ b/pkg/engine/jmespath/functions_test.go
@@ -0,0 +1,245 @@
+package jmespath
+
+import (
+	"encoding/json"
+	"testing"
+
+	"gotest.tools/assert"
+)
+
+func TestJMESPathFunctions_CompareEqualStrings(t *testing.T) {
+	jp, err := New("compare('a', 'a')")
+	assert.NilError(t, err)
+
+	result, err := jp.Search("")
+	assert.NilError(t, err)
+
+	equal, ok := result.(int)
+	assert.Assert(t, ok)
+	assert.Equal(t, equal, 0)
+}
+
+func TestJMESPathFunctions_CompareDifferentStrings(t *testing.T) {
+	jp, err := New("compare('a', 'b')")
+	assert.NilError(t, err)
+
+	result, err := jp.Search("")
+	assert.NilError(t, err)
+
+	equal, ok := result.(int)
+	assert.Assert(t, ok)
+	assert.Equal(t, equal, -1)
+}
+
+func TestJMESPathFunctions_Contains(t *testing.T) {
+	jp, err := New("contains('string', 'str')")
+	assert.NilError(t, err)
+
+	result, err := jp.Search("")
+	assert.NilError(t, err)
+
+	contains, ok := result.(bool)
+	assert.Assert(t, ok)
+	assert.Assert(t, contains)
+}
+
+func TestJMESPathFunctions_EqualFold(t *testing.T) {
+	jp, err := New("equal_fold('Go', 'go')")
+	assert.NilError(t, err)
+
+	result, err := jp.Search("")
+	assert.NilError(t, err)
+
+	equal, ok := result.(bool)
+	assert.Assert(t, ok)
+	assert.Assert(t, equal)
+}
+
+func TestJMESPathFunctions_Replace(t *testing.T) {
+	// Can't use integer literals due to
+	// https://github.com/jmespath/go-jmespath/issues/27
+	//
+	// TODO: fix this in https://github.com/kyverno/go-jmespath
+	//
+
+	jp, err := New("replace('Lorem ipsum dolor sit amet', 'ipsum', 'muspi', `-1`)")
+	assert.NilError(t, err)
+
+	result, err := jp.Search("")
+	assert.NilError(t, err)
+
+	replaced, ok := result.(string)
+	assert.Assert(t, ok)
+	assert.Equal(t, replaced, "Lorem muspi dolor sit amet")
+}
+
+func TestJMESPathFunctions_ReplaceAll(t *testing.T) {
+	jp, err := New("replace_all('Lorem ipsum dolor sit amet', 'ipsum', 'muspi')")
+	assert.NilError(t, err)
+
+	result, err := jp.Search("")
+	assert.NilError(t, err)
+
+	replaced, ok := result.(string)
+	assert.Assert(t, ok)
+	assert.Equal(t, replaced, "Lorem muspi dolor sit amet")
+}
+
+func TestJMESPathFunctions_ToUpper(t *testing.T) {
+	jp, err := New("to_upper('abc')")
+	assert.NilError(t, err)
+
+	result, err := jp.Search("")
+	assert.NilError(t, err)
+
+	upper, ok := result.(string)
+	assert.Assert(t, ok)
+	assert.Equal(t, upper, "ABC")
+}
+
+func TestJMESPathFunctions_ToLower(t *testing.T) {
+	jp, err := New("to_lower('AbC')")
+	assert.NilError(t, err)
+
+	result, err := jp.Search("")
+	assert.NilError(t, err)
+
+	lower, ok := result.(string)
+	assert.Assert(t, ok)
+	assert.Equal(t, lower, "abc")
+}
+
+func TestJMESPathFunctions_Trim(t *testing.T) {
+	jp, err := New("trim('¡¡¡Hello, Gophers!!!', '!¡')")
+	assert.NilError(t, err)
+
+	result, err := jp.Search("")
+	assert.NilError(t, err)
+
+	trim, ok := result.(string)
+	assert.Assert(t, ok)
+	assert.Equal(t, trim, "Hello, Gophers")
+}
+
+func TestJMESPathFunctions_Split(t *testing.T) {
+	jp, err := New("split('Hello, Gophers', ', ')")
+	assert.NilError(t, err)
+
+	result, err := jp.Search("")
+	assert.NilError(t, err)
+
+	split, ok := result.([]string)
+	assert.Assert(t, ok)
+	assert.Equal(t, split[0], "Hello")
+	assert.Equal(t, split[1], "Gophers")
+}
+
+func TestJMESPathFunctions_HasPrefix(t *testing.T) {
+	jp, err := New("starts_with('Gophers', 'Go')")
+	assert.NilError(t, err)
+
+	result, err := jp.Search("")
+	assert.NilError(t, err)
+
+	split, ok := result.(bool)
+	assert.Assert(t, ok)
+	assert.Equal(t, split, true)
+}
+
+func TestJMESPathFunctions_HasSuffix(t *testing.T) {
+	jp, err := New("ends_with('Amigo', 'go')")
+	assert.NilError(t, err)
+
+	result, err := jp.Search("")
+	assert.NilError(t, err)
+
+	split, ok := result.(bool)
+	assert.Assert(t, ok)
+	assert.Equal(t, split, true)
+}
+
+func Test_regexMatch(t *testing.T) {
+	data := make(map[string]interface{})
+	data["foo"] = "hgf'b1a2r'b12g"
+
+	query, err := New("regex_match('12.*', foo)")
+	assert.NilError(t, err)
+
+	result, err := query.Search(data)
+	assert.NilError(t, err)
+	assert.Equal(t, true, result)
+}
+
+func Test_regexMatchWithNumber(t *testing.T) {
+	data := make(map[string]interface{})
+	data["foo"] = -12.0
+
+	query, err := New("regex_match('12.*', abs(foo))")
+	assert.NilError(t, err)
+
+	result, err := query.Search(data)
+	assert.NilError(t, err)
+	assert.Equal(t, true, result)
+}
+
+func Test_regexReplaceAll(t *testing.T) {
+	resourceRaw := []byte(`
+	{
+		"metadata": {
+			"name": "temp",
+			"namespace": "ns_first"
+		},
+		"spec": {
+			"namespace": "ns_first",
+			"name": "temp_other",
+			"field" : "Hello world, helworldlo"
+		}
+	}
+	`)
+	expected := "Glo world, Gworldlo"
+
+	var resource interface{}
+	err := json.Unmarshal(resourceRaw, &resource)
+	assert.NilError(t, err)
+	query, err := New(`regex_replace_all('([Hh]e|G)l', spec.field, '${2}G')`)
+	assert.NilError(t, err)
+
+	res, err := query.Search(resource)
+	assert.NilError(t, err)
+
+	result, ok := res.(string)
+	assert.Assert(t, ok)
+	assert.Equal(t, string(result), expected)
+}
+
+func Test_regexReplaceAllLiteral(t *testing.T) {
+	resourceRaw := []byte(`
+	{
+		"metadata": {
+			"name": "temp",
+			"namespace": "ns_first"
+		},
+		"spec": {
+			"namespace": "ns_first",
+			"name": "temp_other",
+			"field" : "Hello world, helworldlo"
+		}
+	}
+	`)
+	expected := "Glo world, Gworldlo"
+
+	var resource interface{}
+	err := json.Unmarshal(resourceRaw, &resource)
+	assert.NilError(t, err)
+
+	query, err := New(`regex_replace_all_literal('[Hh]el?', spec.field, 'G')`)
+	assert.NilError(t, err)
+
+	res, err := query.Search(resource)
+	assert.NilError(t, err)
+
+	result, ok := res.(string)
+	assert.Assert(t, ok)
+
+	assert.Equal(t, string(result), expected)
+}
diff --git a/pkg/engine/mutation_test.go b/pkg/engine/mutation_test.go
index d03163d65..6d7f5bdb6 100644
--- a/pkg/engine/mutation_test.go
+++ b/pkg/engine/mutation_test.go
@@ -160,7 +160,7 @@ func Test_variableSubstitutionPathNotExist(t *testing.T) {
 		JSONContext: ctx,
 		NewResource: *resourceUnstructured}
 	er := Mutate(policyContext)
-	expectedErrorStr := "variable request.object.metadata.name1 not resolved at path /spec/name"
+	expectedErrorStr := "variable substitution failed for rule test-path-not-exist: NotFoundVariableErr, variable request.object.metadata.name1 not resolved at path /mutate/overlay/spec/name"
 	t.Log(er.PolicyResponse.Rules[0].Message)
 	assert.Equal(t, er.PolicyResponse.Rules[0].Message, expectedErrorStr)
 }
diff --git a/pkg/engine/validation_test.go b/pkg/engine/validation_test.go
index 88545fc6c..5fd4850ea 100644
--- a/pkg/engine/validation_test.go
+++ b/pkg/engine/validation_test.go
@@ -1319,9 +1319,9 @@ func Test_VariableSubstitutionPathNotExistInPattern(t *testing.T) {
 		JSONContext: ctx,
 		NewResource: *resourceUnstructured}
 	er := Validate(policyContext)
-	assert.Assert(t, !er.PolicyResponse.Rules[0].Success)
+	assert.Assert(t, er.PolicyResponse.Rules[0].Success)
 	assert.Equal(t, er.PolicyResponse.Rules[0].Message,
-		"variable substitution failed for rule test-path-not-exist: variable request.object.metadata.name1 not resolved at path /validate/pattern/spec/containers/0/name")
+		"variable substitution failed for rule test-path-not-exist: NotFoundVariableErr, variable request.object.metadata.name1 not resolved at path /validate/pattern/spec/containers/0/name")
 }
 
 func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfiesButSubstitutionFails(t *testing.T) {
@@ -1406,13 +1406,72 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_OnePatternStatisfiesButSu
 	err = ctx.AddResource(resourceRaw)
 	assert.NilError(t, err)
 
+	policyContext := &PolicyContext{
+		Policy:      policy,
+		JSONContext: ctx,
+		NewResource: *resourceUnstructured}
+	er := Validate(policyContext)
+	assert.Assert(t, er.PolicyResponse.Rules[0].Success)
+	assert.Equal(t, er.PolicyResponse.Rules[0].Message, "variable substitution failed for rule test-path-not-exist: NotFoundVariableErr, variable request.object.metadata.name1 not resolved at path /validate/anyPattern/0/spec/template/spec/containers/0/name")
+}
+
+func Test_VariableSubstitution_NotOperatorWithStringVariable(t *testing.T) {
+	resourceRaw := []byte(`{
+		"apiVersion": "v1",
+		"kind": "Deployment",
+		"metadata": {
+		  "name": "test"
+		},
+		"spec": {
+		  "content": "sample text"
+		}
+	  }`)
+
+	policyraw := []byte(`{
+		"apiVersion": "kyverno.io/v1",
+		"kind": "ClusterPolicy",
+		"metadata": {
+		  "name": "substitute-variable"
+		},
+		"spec": {
+		  "rules": [
+			{
+			  "name": "not-operator-with-variable-should-alway-fail-validation",
+			  "match": {
+				"resources": {
+				  "kinds": [
+					"Deployment"
+				  ]
+				}
+			  },
+			  "validate": {
+				"pattern": {
+			      "spec": {
+				    "content": "!{{ request.object.spec.content }}"
+				  }
+				}
+			  }
+			}
+		  ]
+		}
+	  }`)
+
+	var policy kyverno.ClusterPolicy
+	assert.NilError(t, json.Unmarshal(policyraw, &policy))
+	resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
+	assert.NilError(t, err)
+
+	ctx := context.NewContext()
+	err = ctx.AddResource(resourceRaw)
+	assert.NilError(t, err)
+
 	policyContext := &PolicyContext{
 		Policy:      policy,
 		JSONContext: ctx,
 		NewResource: *resourceUnstructured}
 	er := Validate(policyContext)
 	assert.Assert(t, !er.PolicyResponse.Rules[0].Success)
-	assert.Equal(t, er.PolicyResponse.Rules[0].Message, "variable substitution failed for rule test-path-not-exist: variable request.object.metadata.name1 not resolved at path /validate/anyPattern/0/spec/template/spec/containers/0/name")
+	assert.Equal(t, er.PolicyResponse.Rules[0].Message, "validation error: rule not-operator-with-variable-should-alway-fail-validation failed at path /spec/content/")
 }
 
 func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathNotPresent(t *testing.T) {
@@ -1502,8 +1561,8 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathNotPresent(t *test
 		JSONContext: ctx,
 		NewResource: *resourceUnstructured}
 	er := Validate(policyContext)
-	assert.Assert(t, !er.PolicyResponse.Rules[0].Success)
-	assert.Equal(t, er.PolicyResponse.Rules[0].Message, "variable substitution failed for rule test-path-not-exist: variable request.object.metadata.name1 not resolved at path /validate/anyPattern/0/spec/template/spec/containers/0/name")
+	assert.Assert(t, er.PolicyResponse.Rules[0].Success)
+	assert.Equal(t, er.PolicyResponse.Rules[0].Message, "variable substitution failed for rule test-path-not-exist: NotFoundVariableErr, variable request.object.metadata.name1 not resolved at path /validate/anyPattern/0/spec/template/spec/containers/0/name")
 }
 
 func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatternSatisfy(t *testing.T) {
@@ -1599,6 +1658,168 @@ func Test_VariableSubstitutionPathNotExistInAnyPattern_AllPathPresent_NonePatter
 		"validation error: Rule test-path-not-exist[0] failed at path /spec/template/spec/containers/0/name/. Rule test-path-not-exist[1] failed at path /spec/template/spec/containers/0/name/.")
 }
 
+func Test_VariableSubstitutionValidate_VariablesInMessageAreResolved(t *testing.T) {
+	resourceRaw := []byte(`{
+		"apiVersion": "apps/v1",
+		"kind": "Deployment",
+		"metadata": {
+		  "name": "busybox",
+		  "labels": {
+			"app": "busybox",
+			"color": "red",
+			"animal": "cow",
+			"food": "pizza",
+			"car": "jeep",
+			"env": "qa"
+		  }
+		},
+		"spec": {
+		  "replicas": 1,
+		  "selector": {
+			"matchLabels": {
+			  "app": "busybox"
+			}
+		  },
+		  "template": {
+			"metadata": {
+			  "labels": {
+				"app": "busybox"
+			  }
+			},
+			"spec": {
+			  "containers": [
+				{
+				  "image": "busybox:1.28",
+				  "name": "busybox",
+				  "command": [
+					"sleep",
+					"9999"
+				  ]
+				}
+			  ]
+			}
+		  }
+		}
+	  }`)
+
+	policyraw := []byte(`{
+		"apiVersion": "kyverno.io/v1",
+		"kind": "ClusterPolicy",
+		"metadata": {
+		  "name": "cm-array-example"
+		},
+		"spec": {
+		  "validationFailureAction": "enforce",
+		  "background": false,
+		  "rules": [
+			{
+			  "name": "validate-role-annotation",
+			  "match": {
+				"resources": {
+				  "kinds": [
+					"Deployment"
+				  ]
+				}
+			  },
+			  "validate": {
+				"message": "The animal {{ request.object.metadata.labels.animal }} is not in the allowed list of animals.",
+				"deny": {
+				  "conditions": [
+					{
+					  "key": "{{ request.object.metadata.labels.animal }}",
+					  "operator": "NotIn",
+					  "value": [
+						"snake",
+						"bear",
+						"cat",
+						"dog"
+					]
+					}
+				  ]
+				}
+			  }
+			}
+		  ]
+		}
+	  }`)
+
+	var policy kyverno.ClusterPolicy
+	assert.NilError(t, json.Unmarshal(policyraw, &policy))
+	resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
+	assert.NilError(t, err)
+
+	ctx := context.NewContext()
+	err = ctx.AddResource(resourceRaw)
+	assert.NilError(t, err)
+
+	policyContext := &PolicyContext{
+		Policy:      policy,
+		JSONContext: ctx,
+		NewResource: *resourceUnstructured}
+	er := Validate(policyContext)
+	assert.Assert(t, !er.PolicyResponse.Rules[0].Success)
+	assert.Equal(t, er.PolicyResponse.Rules[0].Message, "The animal cow is not in the allowed list of animals.")
+}
+
+func Test_Flux_Kustomization_PathNotPresent(t *testing.T) {
+	tests := []struct {
+		name            string
+		policyRaw       []byte
+		resourceRaw     []byte
+		expectedResult  bool
+		expectedMessage string
+	}{
+		{
+			name:      "path-not-present",
+			policyRaw: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"flux-multi-tenancy"},"spec":{"validationFailureAction":"enforce","rules":[{"name":"serviceAccountName","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":".spec.serviceAccountName is required","pattern":{"spec":{"serviceAccountName":"?*"}}}},{"name":"sourceRefNamespace","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":"spec.sourceRef.namespace must be the same as metadata.namespace","deny":{"conditions":[{"key":"{{request.object.spec.sourceRef.namespace}}","operator":"NotEquals","value":"{{request.object.metadata.namespace}}"}]}}}]}}`),
+			// referred variable path not present
+			resourceRaw:     []byte(`{"apiVersion":"kustomize.toolkit.fluxcd.io/v1beta1","kind":"Kustomization","metadata":{"name":"dev-team","namespace":"apps"},"spec":{"serviceAccountName":"dev-team","interval":"5m","sourceRef":{"kind":"GitRepository","name":"dev-team"},"prune":true,"validation":"client"}}`),
+			expectedResult:  true,
+			expectedMessage: "variable substitution failed for rule sourceRefNamespace: NotFoundVariableErr, variable request.object.spec.sourceRef.namespace not resolved at path /validate/deny/conditions/0/key",
+		},
+		{
+			name:      "resource-with-violation",
+			policyRaw: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"flux-multi-tenancy"},"spec":{"validationFailureAction":"enforce","rules":[{"name":"serviceAccountName","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":".spec.serviceAccountName is required","pattern":{"spec":{"serviceAccountName":"?*"}}}},{"name":"sourceRefNamespace","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":"spec.sourceRef.namespace {{request.object.spec.sourceRef.namespace}} must be the same as metadata.namespace {{request.object.metadata.namespace}}","deny":{"conditions":[{"key":"{{request.object.spec.sourceRef.namespace}}","operator":"NotEquals","value":"{{request.object.metadata.namespace}}"}]}}}]}}`),
+			// referred variable path present with different value
+			resourceRaw:     []byte(`{"apiVersion":"kustomize.toolkit.fluxcd.io/v1beta1","kind":"Kustomization","metadata":{"name":"dev-team","namespace":"apps"},"spec":{"serviceAccountName":"dev-team","interval":"5m","sourceRef":{"kind":"GitRepository","name":"dev-team","namespace":"default"},"prune":true,"validation":"client"}}`),
+			expectedResult:  false,
+			expectedMessage: "spec.sourceRef.namespace default must be the same as metadata.namespace apps",
+		},
+		{
+			name:      "resource-comply",
+			policyRaw: []byte(`{"apiVersion":"kyverno.io/v1","kind":"ClusterPolicy","metadata":{"name":"flux-multi-tenancy"},"spec":{"validationFailureAction":"enforce","rules":[{"name":"serviceAccountName","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":".spec.serviceAccountName is required","pattern":{"spec":{"serviceAccountName":"?*"}}}},{"name":"sourceRefNamespace","exclude":{"resources":{"namespaces":["flux-system"]}},"match":{"resources":{"kinds":["Kustomization","HelmRelease"]}},"validate":{"message":"spec.sourceRef.namespace must be the same as metadata.namespace","deny":{"conditions":[{"key":"{{request.object.spec.sourceRef.namespace}}","operator":"NotEquals","value":"{{request.object.metadata.namespace}}"}]}}}]}}`),
+			// referred variable path present with same value - validate passes
+			resourceRaw:     []byte(`{"apiVersion":"kustomize.toolkit.fluxcd.io/v1beta1","kind":"Kustomization","metadata":{"name":"dev-team","namespace":"apps"},"spec":{"serviceAccountName":"dev-team","interval":"5m","sourceRef":{"kind":"GitRepository","name":"dev-team","namespace":"apps"},"prune":true,"validation":"client"}}`),
+			expectedResult:  true,
+			expectedMessage: "spec.sourceRef.namespace must be the same as metadata.namespace",
+		},
+	}
+
+	for _, test := range tests {
+		var policy kyverno.ClusterPolicy
+		assert.NilError(t, json.Unmarshal(test.policyRaw, &policy))
+		resourceUnstructured, err := utils.ConvertToUnstructured(test.resourceRaw)
+		assert.NilError(t, err)
+
+		ctx := context.NewContext()
+		err = ctx.AddResource(test.resourceRaw)
+		assert.NilError(t, err)
+
+		policyContext := &PolicyContext{
+			Policy:      policy,
+			JSONContext: ctx,
+			NewResource: *resourceUnstructured}
+		er := Validate(policyContext)
+
+		for i, rule := range er.PolicyResponse.Rules {
+			if rule.Name == "sourceRefNamespace" {
+				assert.Equal(t, er.PolicyResponse.Rules[i].Success, test.expectedResult)
+				assert.Equal(t, er.PolicyResponse.Rules[i].Message, test.expectedMessage, "\ntest %s failed\nexpected: %s\nactual: %s", test.name, test.expectedMessage, rule.Message)
+			}
+		}
+	}
+}
+
 type testCase struct {
 	description   string
 	policy        []byte
diff --git a/pkg/engine/variables/variables_test.go b/pkg/engine/variables/variables_test.go
index a42a8ea06..73771bd65 100644
--- a/pkg/engine/variables/variables_test.go
+++ b/pkg/engine/variables/variables_test.go
@@ -6,6 +6,7 @@ import (
 	"testing"
 
 	kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
+	"gotest.tools/assert"
 	authenticationv1 "k8s.io/api/authentication/v1"
 	"sigs.k8s.io/controller-runtime/pkg/log"
 
@@ -562,10 +563,26 @@ func Test_variableSubstitutionObjectOperatorNotEqualFail(t *testing.T) {
 		t.Error(err)
 	}
 
-	if patternCopy, err = SubstituteAll(log.Log, ctx, patternCopy); err == nil {
-		t.Error(err)
-	}
+	patternCopy, err = SubstituteAll(log.Log, ctx, patternCopy)
+	assert.NilError(t, err)
+
+	patternMapCopy, ok := patternCopy.(map[string]interface{})
+	assert.Assert(t, ok)
+
+	specInterface, ok := patternMapCopy["spec"]
+	assert.Assert(t, ok)
+
+	specMap, ok := specInterface.(map[string]interface{})
+	assert.Assert(t, ok)
+
+	variableInterface, ok := specMap["variable"]
+	assert.Assert(t, ok)
+
+	variableString, ok := variableInterface.(string)
+	assert.Assert(t, ok)
 
+	expected := `!{"var1":"temp1","var2":"temp2","varNested":{"var1":"temp1"}}`
+	assert.Equal(t, expected, variableString)
 }
 
 func Test_variableSubstitutionMultipleObject(t *testing.T) {
diff --git a/pkg/engine/variables/vars_test.go b/pkg/engine/variables/vars_test.go
index efb5e1a26..759cdb788 100644
--- a/pkg/engine/variables/vars_test.go
+++ b/pkg/engine/variables/vars_test.go
@@ -6,6 +6,7 @@ import (
 	"strings"
 	"testing"
 
+	v1 "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
 	"github.com/kyverno/kyverno/pkg/engine/context"
 	ju "github.com/kyverno/kyverno/pkg/engine/json-utils"
 	"gotest.tools/assert"
@@ -134,6 +135,149 @@ func Test_subVars_failed(t *testing.T) {
 	}
 }
 
+func Test_subVars_with_JMESPath_At(t *testing.T) {
+	patternMap := []byte(`
+	{
+		"kind": "{{@}}",
+		"data": {
+			"rules": [
+				{
+					"apiGroups": [
+						"{{request.object.metadata.name}}"
+					],
+					"resources": [
+						"namespaces"
+					],
+					"verbs": [
+						"*"
+					],
+					"resourceNames": [
+						"{{request.object.metadata.name}}"
+					]
+				}
+			]
+		}
+	}
+	`)
+
+	resourceRaw := []byte(`
+	{
+		"kind": "foo",
+		"name": "bar",
+		"metadata": {
+			"name": "temp",
+			"namespace": "n1"
+		},
+		"spec": {
+			"namespace": "n1",
+			"name": "temp1"
+		}
+	}
+		`)
+
+	expected := []byte(`{"data":{"rules":[{"apiGroups":["temp"],"resourceNames":["temp"],"resources":["namespaces"],"verbs":["*"]}]},"kind":"foo"}`)
+
+	var pattern, resource interface{}
+	var err error
+	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), string(expected))
+}
+
+func Test_subVars_withRegexMatch(t *testing.T) {
+	patternMap := []byte(`
+	{
+		"port": "{{ regex_match('(443)', '{{@}}') }}",
+		"name": "ns-owner-{{request.object.metadata.name}}"
+	}
+	`)
+
+	resourceRaw := []byte(`
+	{
+		"port": "443",
+		"metadata": {
+			"name": "temp",
+			"namespace": "n1"
+		},
+		"spec": {
+			"namespace": "n1",
+			"name": "temp1"
+		}
+	}
+		`)
+	expected := []byte(`{"name":"ns-owner-temp","port":true}`)
+
+	var pattern, resource interface{}
+	var err error
+	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)
+	fmt.Print(string(out))
+	assert.Equal(t, string(out), string(expected))
+}
+
+func Test_subVars_withRegexReplaceAll(t *testing.T) {
+	patternMap := []byte(`
+	{
+		"port": "{{ regex_replace_all_literal('.*', '{{@}}', '1313') }}",
+		"name": "ns-owner-{{request.object.metadata.name}}"
+	}
+	`)
+
+	resourceRaw := []byte(`
+	{
+		"port": "43123",
+		"metadata": {
+			"name": "temp",
+			"namespace": "n1"
+		},
+		"spec": {
+			"namespace": "n1",
+			"name": "temp1"
+		}
+	}
+		`)
+	expected := []byte(`{"name":"ns-owner-temp","port":"1313"}`)
+
+	var pattern, resource interface{}
+	var err error
+	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), string(expected))
+}
+
 func Test_ReplacingPathWhenDeleting(t *testing.T) {
 	patternRaw := []byte(`"{{request.object.metadata.annotations.target}}"`)
 
@@ -334,6 +478,341 @@ func Test_policyContextValidation(t *testing.T) {
 	assert.Assert(t, err != nil, err)
 }
 
+func Test_variableSubstitution_array(t *testing.T) {
+	configmapRaw := []byte(`
+{
+    "animals": {
+        "apiVersion": "v1",
+        "kind": "ConfigMap",
+        "metadata": {
+            "name": "animals",
+            "namespace": "default"
+        },
+        "data": {
+            "animals": "snake\nbear\ncat\ndog"
+        }
+    }
+}`)
+
+	ruleRaw := []byte(`
+{
+    "name": "validate-role-annotation",
+    "context": [
+        {
+            "name": "animals",
+            "configMap": {
+                "name": "animals",
+                "namespace": "default"
+            }
+        }
+    ],
+    "match": {
+        "resources": {
+            "kinds": [
+                "Deployment"
+            ]
+        }
+    },
+    "validate": {
+        "message": "The animal {{ request.object.metadata.labels.animal }} is not in the allowed list of animals: {{ animals.data.animals }}.",
+        "deny": {
+            "conditions": [
+                {
+                    "key": "{{ request.object.metadata.labels.animal }}",
+                    "operator": "NotIn",
+                    "value": "{{ animals.data.animals }}"
+                }
+            ]
+        }
+    }
+}`)
+
+	resourceRaw := []byte(`
+{
+    "apiVersion": "apps/v1",
+    "kind": "Deployment",
+    "metadata": {
+        "name": "busybox",
+        "labels": {
+            "app": "busybox",
+            "color": "red",
+            "animal": "cow",
+            "food": "pizza",
+            "car": "jeep",
+            "env": "qa"
+        }
+    }
+}
+`)
+
+	var rule v1.Rule
+	err := json.Unmarshal(ruleRaw, &rule)
+	assert.NilError(t, err)
+
+	ctx := context.NewContext("request.object", "animals")
+	ctx.AddJSON(configmapRaw)
+	ctx.AddResource(resourceRaw)
+
+	vars, err := SubstituteAllInRule(log.Log, ctx, rule)
+	assert.NilError(t, err)
+
+	assert.DeepEqual(t, vars.Validation.Message, "The animal cow is not in the allowed list of animals: snake\nbear\ncat\ndog.")
+}
+
+var variableObject = []byte(`
+{
+	"complex_object_array": [
+		"value1",
+		"value2",
+		"value3"
+	],
+	"complex_object_map": {
+		"key1": "value1",
+		"key2": "value2",
+		"key3": "value3"
+	},
+	"simple_object_bool": false,
+	"simple_object_int": 5,
+	"simple_object_float": -5.5,
+	"simple_object_string": "example",
+	"simple_object_null": null
+}
+`)
+
+func Test_SubstituteArray(t *testing.T) {
+	patternRaw := []byte(`
+	{
+		"spec": {
+			"content": "{{ request.object.complex_object_array }}"
+		}
+	}
+	`)
+
+	var err error
+	var pattern, resource map[string]interface{}
+	err = json.Unmarshal(patternRaw, &pattern)
+	assert.NilError(t, err)
+
+	err = json.Unmarshal(variableObject, &resource)
+	assert.NilError(t, err)
+
+	ctx := context.NewContext()
+	ctx.AddResource(variableObject)
+
+	resolved, err := SubstituteAll(log.Log, ctx, pattern)
+	assert.NilError(t, err)
+
+	content := resolved.(map[string]interface{})["spec"].(map[string]interface{})["content"]
+	expected := resource["complex_object_array"]
+
+	assert.DeepEqual(t, expected, content)
+}
+
+func Test_SubstituteArrayInString(t *testing.T) {
+	patternRaw := []byte(`
+	{
+		"spec": {
+			"content": "content is {{ request.object.complex_object_map }}"
+		}
+	}
+	`)
+
+	var err error
+	var pattern, resource map[string]interface{}
+	err = json.Unmarshal(patternRaw, &pattern)
+	assert.NilError(t, err)
+
+	err = json.Unmarshal(variableObject, &resource)
+	assert.NilError(t, err)
+
+	ctx := context.NewContext()
+	ctx.AddResource(variableObject)
+
+	resolved, err := SubstituteAll(log.Log, ctx, pattern)
+	assert.NilError(t, err)
+
+	content := resolved.(map[string]interface{})["spec"].(map[string]interface{})["content"]
+	expected := `content is {"key1":"value1","key2":"value2","key3":"value3"}`
+
+	assert.DeepEqual(t, expected, content)
+}
+
+func Test_SubstituteInt(t *testing.T) {
+	patternRaw := []byte(`
+	{
+		"spec": {
+			"content": "{{ request.object.simple_object_int }}"
+		}
+	}
+	`)
+
+	var err error
+	var pattern, resource map[string]interface{}
+	err = json.Unmarshal(patternRaw, &pattern)
+	assert.NilError(t, err)
+
+	err = json.Unmarshal(variableObject, &resource)
+	assert.NilError(t, err)
+
+	ctx := context.NewContext()
+	ctx.AddResource(variableObject)
+
+	resolved, err := SubstituteAll(log.Log, ctx, pattern)
+	assert.NilError(t, err)
+
+	content := resolved.(map[string]interface{})["spec"].(map[string]interface{})["content"]
+	expected := resource["simple_object_int"]
+
+	assert.DeepEqual(t, expected, content)
+}
+
+func Test_SubstituteIntInString(t *testing.T) {
+	patternRaw := []byte(`
+	{
+		"spec": {
+			"content": "content = {{ request.object.simple_object_int }}"
+		}
+	}
+	`)
+
+	var err error
+	var pattern, resource map[string]interface{}
+	err = json.Unmarshal(patternRaw, &pattern)
+	assert.NilError(t, err)
+
+	err = json.Unmarshal(variableObject, &resource)
+	assert.NilError(t, err)
+
+	ctx := context.NewContext()
+	ctx.AddResource(variableObject)
+
+	resolved, err := SubstituteAll(log.Log, ctx, pattern)
+	assert.NilError(t, err)
+
+	content := resolved.(map[string]interface{})["spec"].(map[string]interface{})["content"]
+	expected := "content = 5"
+
+	assert.DeepEqual(t, expected, content)
+}
+
+func Test_SubstituteBool(t *testing.T) {
+	patternRaw := []byte(`
+	{
+		"spec": {
+			"content": "{{ request.object.simple_object_bool }}"
+		}
+	}
+	`)
+
+	var err error
+	var pattern, resource map[string]interface{}
+	err = json.Unmarshal(patternRaw, &pattern)
+	assert.NilError(t, err)
+
+	err = json.Unmarshal(variableObject, &resource)
+	assert.NilError(t, err)
+
+	ctx := context.NewContext()
+	ctx.AddResource(variableObject)
+
+	resolved, err := SubstituteAll(log.Log, ctx, pattern)
+	assert.NilError(t, err)
+
+	content := resolved.(map[string]interface{})["spec"].(map[string]interface{})["content"]
+	expected := false
+
+	assert.DeepEqual(t, expected, content)
+}
+
+func Test_SubstituteBoolInString(t *testing.T) {
+	patternRaw := []byte(`
+	{
+		"spec": {
+			"content": "content = {{ request.object.simple_object_bool }}"
+		}
+	}
+	`)
+
+	var err error
+	var pattern, resource map[string]interface{}
+	err = json.Unmarshal(patternRaw, &pattern)
+	assert.NilError(t, err)
+
+	err = json.Unmarshal(variableObject, &resource)
+	assert.NilError(t, err)
+
+	ctx := context.NewContext()
+	ctx.AddResource(variableObject)
+
+	resolved, err := SubstituteAll(log.Log, ctx, pattern)
+	assert.NilError(t, err)
+
+	content := resolved.(map[string]interface{})["spec"].(map[string]interface{})["content"]
+	expected := "content = false"
+
+	assert.DeepEqual(t, expected, content)
+}
+
+func Test_SubstituteString(t *testing.T) {
+	patternRaw := []byte(`
+	{
+		"spec": {
+			"content": "{{ request.object.simple_object_string }}"
+		}
+	}
+	`)
+
+	var err error
+	var pattern, resource map[string]interface{}
+	err = json.Unmarshal(patternRaw, &pattern)
+	assert.NilError(t, err)
+
+	err = json.Unmarshal(variableObject, &resource)
+	assert.NilError(t, err)
+
+	ctx := context.NewContext()
+	ctx.AddResource(variableObject)
+
+	resolved, err := SubstituteAll(log.Log, ctx, pattern)
+	assert.NilError(t, err)
+
+	content := resolved.(map[string]interface{})["spec"].(map[string]interface{})["content"]
+	var expected interface{}
+	expected = "example"
+
+	assert.DeepEqual(t, expected, content)
+}
+
+func Test_SubstituteStringInString(t *testing.T) {
+	patternRaw := []byte(`
+	{
+		"spec": {
+			"content": "content = {{ request.object.simple_object_string }}"
+		}
+	}
+	`)
+
+	var err error
+	var pattern, resource map[string]interface{}
+	err = json.Unmarshal(patternRaw, &pattern)
+	assert.NilError(t, err)
+
+	err = json.Unmarshal(variableObject, &resource)
+	assert.NilError(t, err)
+
+	ctx := context.NewContext()
+	ctx.AddResource(variableObject)
+
+	resolved, err := SubstituteAll(log.Log, ctx, pattern)
+	assert.NilError(t, err)
+
+	content := resolved.(map[string]interface{})["spec"].(map[string]interface{})["content"]
+	var expected interface{}
+	expected = "content = example"
+
+	assert.DeepEqual(t, expected, content)
+}
+
 func Test_ReferenceSubstitution(t *testing.T) {
 	jsonRaw := []byte(`
 	{