Repository: deislabs/ratify
Pull request #798 (2023-04-26 15:19:08)

feat: add opa engine and support Rego policy
# Description

## What this PR does / why we need it:
More details could be checked in the design doc: https://hackmd.io/@H7a8_rG4SuaKwzu4NLT-9Q/SkJvCYHeh

1. Embed OPA engine in Ratify so that Ratify could make decisions using the engine based on provided Rego policy.
2. Add back `passthrough` mode.
3. Support `nestedReports` decoupled from verifiers. Executor has the control on the nested reference by walking through the artifact graph instead of offloading control to each verifier.
4. Support multiple verifiers(same type or different types) verify against the same artifact.
5. Added new VerificationResponse version (1.0.0) to support Rego Policy.
6. Note: Ratify would keep supporting original configPolicy which is the default option. In this PR, users still need to specify the Rego Policy in the config, which cannot be updated in runtime. I'll have following PRs to support it as CRD.

## Which issue(s) this PR fixes *(optional, using `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when the PR gets merged)*:
Related issues:
https://github.com/deislabs/ratify/issues/351
https://github.com/deislabs/ratify/issues/528
https://github.com/deislabs/ratify/issues/448
https://github.com/deislabs/ratify/issues/35

## Type of change

Please delete options that are not relevant.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Helm Chart Change (any edit/addition/update that is necessary for changes merged to the `main` branch)
- [ ] This change requires a documentation update

# How Has This Been Tested?

Added unit tests and e2e tests.

# Checklist:

- [ ] Does the affected code have corresponding tests?
- [ ] Are the changes documented, not just with inline documentation, but also with conceptual documentation such as an overview of a new feature, or task-based documentation like a tutorial? Consider if this change should be announced on your project blog.
- [ ] Does this introduce breaking changes that would require an announcement or bumping the major version?
- [ ] Do all new files have appropriate license header?

# Post Merge Requirements
- [ ] MAINTAINERS: manually trigger the "Publish Package" workflow after merging any PR that indicates `Helm Chart Change`



Patch:
diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml
index 1c52429..bb53957 100644
--- a/.github/workflows/build-pr.yml
+++ b/.github/workflows/build-pr.yml
@@ -64,7 +64,7 @@ jobs:
   build_test_e2e:
     name: "Build and run e2e Test"
     runs-on: ubuntu-latest
-    timeout-minutes: 25
+    timeout-minutes: 35
     continue-on-error: true
     permissions:
       contents: read
@@ -86,18 +86,28 @@ jobs:
           echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH
           make e2e-bootstrap KUBERNETES_VERSION=${{ matrix.KUBERNETES_VERSION }}
           make generate-certs
-      - name: Run e2e
+      - name: Run e2e with config policy
         run: |
           make e2e-deploy-gatekeeper GATEKEEPER_VERSION=${{ matrix.GATEKEEPER_VERSION }}
           make e2e-deploy-ratify GATEKEEPER_VERSION=${{ matrix.GATEKEEPER_VERSION }}
           make test-e2e GATEKEEPER_VERSION=${{ matrix.GATEKEEPER_VERSION }}
+      - name: Save logs
+        if: ${{ always() }}
+        run: |
+          kubectl logs -n gatekeeper-system -l app=ratify --tail=-1 > logs-ratify-preinstall-${{ matrix.KUBERNETES_VERSION }}-${{ matrix.GATEKEEPER_VERSION }}-config-policy.json
+          kubectl logs -n gatekeeper-system -l app.kubernetes.io/name=ratify --tail=-1 > logs-ratify-${{ matrix.KUBERNETES_VERSION }}-${{ matrix.GATEKEEPER_VERSION }}-config-policy.json
+      - name: Run e2e with Rego policy
+        run: |
+          make e2e-uninstall-ratify
+          make e2e-helm-deploy-ratify GATEKEEPER_VERSION=${{ matrix.GATEKEEPER_VERSION }} REGO_POLICY_ENABLED=true
+          make test-e2e
       - name: Save logs
         if: ${{ always() }}
         run: |
           kubectl logs -n gatekeeper-system -l control-plane=controller-manager --tail=-1 > logs-externaldata-controller-${{ matrix.KUBERNETES_VERSION }}-${{ matrix.GATEKEEPER_VERSION }}.json
           kubectl logs -n gatekeeper-system -l control-plane=audit-controller --tail=-1 > logs-externaldata-audit-${{ matrix.KUBERNETES_VERSION }}-${{ matrix.GATEKEEPER_VERSION }}.json
-          kubectl logs -n gatekeeper-system -l app=ratify --tail=-1 > logs-ratify-preinstall-${{ matrix.KUBERNETES_VERSION }}-${{ matrix.GATEKEEPER_VERSION }}.json
-          kubectl logs -n gatekeeper-system -l app.kubernetes.io/name=ratify --tail=-1 > logs-ratify-${{ matrix.KUBERNETES_VERSION }}-${{ matrix.GATEKEEPER_VERSION }}.json
+          kubectl logs -n gatekeeper-system -l app=ratify --tail=-1 > logs-ratify-preinstall-${{ matrix.KUBERNETES_VERSION }}-${{ matrix.GATEKEEPER_VERSION }}-rego-policy.json
+          kubectl logs -n gatekeeper-system -l app.kubernetes.io/name=ratify --tail=-1 > logs-ratify-${{ matrix.KUBERNETES_VERSION }}-${{ matrix.GATEKEEPER_VERSION }}-rego-policy.json
       - name: Upload artifacts
         uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
         if: ${{ always() }}
diff --git a/Makefile b/Makefile
index 4058db5..d3d8c7c 100644
--- a/Makefile
+++ b/Makefile
@@ -27,6 +27,7 @@ BATS_VERSION ?= 1.7.0
 SYFT_VERSION ?= v0.76.0
 ALPINE_IMAGE ?= alpine@sha256:93d5a28ff72d288d69b5997b8ba47396d2cbb62a72b5d87cd3351094b5d578a0
 CERT_ROTATION_ENABLED ?= false
+REGO_POLICY_ENABLED ?= false
 
 # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
 ENVTEST_K8S_VERSION = 1.24.2
@@ -441,6 +442,9 @@ e2e-build-local-ratify-image:
 	-t localbuild:test .
 	kind load docker-image --name kind localbuild:test
 
+e2e-uninstall-ratify:
+	./.staging/helm/linux-amd64/helm uninstall ${RATIFY_NAME} --namespace ${GATEKEEPER_NAMESPACE}
+
 e2e-helm-deploy-ratify:
 	printf "{\n\t\"auths\": {\n\t\t\"registry:5000\": {\n\t\t\t\"auth\": \"`echo "${TEST_REGISTRY_USERNAME}:${TEST_REGISTRY_PASSWORD}" | tr -d '\n' | base64 -i -w 0`\"\n\t\t}\n\t}\n}" > mount_config.json
 
@@ -451,6 +455,7 @@ e2e-helm-deploy-ratify:
 	--set image.tag=test \
 	--set gatekeeper.version=${GATEKEEPER_VERSION} \
 	--set featureFlags.RATIFY_CERT_ROTATION=${CERT_ROTATION_ENABLED} \
+	--set featureFlags.RATIFY_USE_REGO_POLICY=${REGO_POLICY_ENABLED} \
 	--set-file provider.tls.crt=${CERT_DIR}/server.crt \
 	--set-file provider.tls.key=${CERT_DIR}/server.key \
 	--set-file provider.tls.caCert=${CERT_DIR}/ca.crt \
diff --git a/charts/ratify/templates/configmap.yaml b/charts/ratify/templates/configmap.yaml
index d582d08..1b16d79 100644
--- a/charts/ratify/templates/configmap.yaml
+++ b/charts/ratify/templates/configmap.yaml
@@ -52,7 +52,12 @@ data:
       "policy": {
         "version": "1.0.0",
         "plugin": {
-            "name": "configPolicy"
+        {{- if .Values.featureFlags.RATIFY_USE_REGO_POLICY }}
+          "name": "regoPolicy",
+          "policyPath": "/usr/local/ratify/policy/policy.rego"
+        {{- else }}
+          "name": "configPolicy"
+        {{- end }}
         }
       },
       "verifier": {
@@ -94,4 +99,4 @@ data:
             }
           ]
       }
-    }
+    }
\ No newline at end of file
diff --git a/charts/ratify/templates/deployment.yaml b/charts/ratify/templates/deployment.yaml
index 4bc9257..d34a8f7 100644
--- a/charts/ratify/templates/deployment.yaml
+++ b/charts/ratify/templates/deployment.yaml
@@ -74,6 +74,11 @@ spec:
             - mountPath: "/usr/local/ratify"
               name: config
               readOnly: true
+            {{- if .Values.featureFlags.RATIFY_USE_REGO_POLICY }}
+            - mountPath: "/usr/local/ratify/policy"
+              name: policy
+              readOnly: true
+            {{- end }}
               {{- if $dockerAuthMode }}
             - mountPath: "/usr/local/docker"
               name: dockerconfig
@@ -143,6 +148,9 @@ spec:
         - name: config
           configMap:
             name: {{ include "ratify.fullname" . }}-configuration
+        - name: policy
+          configMap:
+            name: {{ include "ratify.fullname" . }}-rego-policy
         - name: tls
           secret:
             secretName: {{ include "ratify.fullname" . }}-tls
diff --git a/charts/ratify/templates/regopolicy.yaml b/charts/ratify/templates/regopolicy.yaml
new file mode 100644
index 0000000..4b25063
--- /dev/null
+++ b/charts/ratify/templates/regopolicy.yaml
@@ -0,0 +1,23 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: {{ include "ratify.fullname" . }}-rego-policy
+data:
+  policy.rego: |
+    package ratify.policy
+    default valid := false
+    valid {
+      not failed_verify(input)
+    }
+    # The verification requires all reports to have isSuccess as true.
+    failed_verify(reports) {
+      [path, value] := walk(reports)
+      value == false
+      path[count(path) - 1] == "isSuccess"
+    }
+    # The verification requires at least one report for each artifact.
+    failed_verify(reports) {
+      [path, value] := walk(reports)
+      path[count(path) - 1] == "verifierReports"
+      count(value) == 0
+    }
\ No newline at end of file
diff --git a/charts/ratify/values.yaml b/charts/ratify/values.yaml
index 76b6f8b..f7f9f75 100644
--- a/charts/ratify/values.yaml
+++ b/charts/ratify/values.yaml
@@ -111,6 +111,8 @@ crds:
 # See https://github.com/deislabs/ratify/blob/main/docs/reference/usage.md for a list of available feature flags
 featureFlags:
   # RATIFY_FEATURE_NAME: true
+  RATIFY_USE_REGO_POLICY: false # RATIFY_USE_REGO_POLICY is used when you want the verification results to be evaluated by an OPA engine with Rego policy intead of the default config policy. The OPA engine can be either Ratify embedded or within Gatekeeper.
+  RATIFY_PASSTHROUGH_MODE: false # RATIFY_PASSTHROUGH_MODE is used when you do NOT want the verification results to be evaluated by Ratify, and it only works when RATIFY_USE_REGO_POLICY is enabled.
 
   # RATIFY_CERT_ROTATION enables rotation for TLS certificates.
   RATIFY_CERT_ROTATION: false
diff --git a/cmd/ratify/main.go b/cmd/ratify/main.go
index 0c1f716..08f1280 100644
--- a/cmd/ratify/main.go
+++ b/cmd/ratify/main.go
@@ -20,6 +20,7 @@ import (
 
 	"github.com/deislabs/ratify/cmd/ratify/cmd"
 	_ "github.com/deislabs/ratify/pkg/policyprovider/configpolicy"
+	_ "github.com/deislabs/ratify/pkg/policyprovider/regopolicy"
 	_ "github.com/deislabs/ratify/pkg/referrerstore/oras"
 	_ "github.com/deislabs/ratify/pkg/verifier/notaryv2"
 )
diff --git a/docs/reference/providers.md b/docs/reference/providers.md
index 7f71f13..6e07d34 100644
--- a/docs/reference/providers.md
+++ b/docs/reference/providers.md
@@ -15,6 +15,177 @@ Currently, Ratify supports a Configuration based Policy Provider named `configPo
 
 The executor is the "glue" that links all Ratify plugin-based components such as the verifiers, referrer stores, and policy providers. The policy provider is primarily invoked for each reference artifact discovered AFTER a configured verifier is found that can be used to verify that particular artifacy type. If a reference artifact is found that does not have a corresponding verifier configured that can perform verification, the executor will ignore this artifact. As a result, this unverifiable artifact will NOT influence the final overall verification success determined by the policy provider.
 
+## Rego Policy Provider
+
+The Rego Policy Provider is a built-in policy provider that uses [Open Policy Agent](https://www.openpolicyagent.org/) (OPA) and [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) to implement a policy provider. The Rego Policy Provider is a plugin that is registered with Ratify and is invoked by the executor to determine the overall verification success if required. The Rego Policy Provider is configured using the `policy` section of the configuration.
+
+```json
+"policy": {
+    "version": "1.0.0",
+    "plugin": {
+        "name": "regoPolicy",
+        "policyPath": "",
+        "policy": "package ratify.policy\ndefault valid := false\nvalid {\n  not failed_verify(input)\n}\nfailed_verify(reports) {\n  [path, value] := walk(reports)\n  value == false\n  path[count(path) - 1] == \"isSuccess\"\n}"
+    }
+},
+```
+- The `name` field is REQUIRED and MUST match the name of the registered policy provider.
+- One of `policyPath` and `policy` fields MUST be specified.
+    - `policyPath`: path to the Rego policy file. The file MUST be a valid Rego policy file.
+    - `policy`: Rego policy as a string. The string MUST be a valid Rego policy.
+
+NOTE: When Ratify runs as K8s Add-on, any updates to the Policy configuration requires a restart of Ratify pod.
+
+### Rego Policy Usage
+Ratify embeds OPA engine inside the executor to provide a built-in policy provider. This feature is behind the `RATIFY_USE_REGO_POLICY` feature flag. And if users want to offload policy decison-making to Gatekeeper, they can enable `RATIFY_PASSTHROUGH_MODE` which will bypass OPA engine embedded in Ratify. In this mode, Ratify will NOT make the decision but only pass the verification results to Gatekeeper for making the decision. Note that verification results returned while switching Rego policy/config policy are different. 
+
+When `RATIFY_USE_REGO_POLICY` is enabled, the Verification Response follows `1.0.0` version. [1.0.0](../reference/verification-result-version.md#1.0.0) provides definition and example usage of the response.
+
+To enable Rego policy provider for a Ratify server, the value of `RATIFY_USE_REGO_POLICY` MUST be set to true in the helm chart.
+
+If Ratify is used as command line, `RATIFY_USE_REGO_POLICY=1` MUST be passed to the corresponding command.
+
+### Rego Policy Requirements
+
+There are some special requirements on Rego policy used by Ratify. The package declaration MUST be `package ratify.policy`, and there MUST be a boolean variable `valid` declared. The variable `valid` MUST be set to `true` if the overall verification is successful, and `false` otherwise. The `input` is a Json object that contains the verification results of all reference artifacts.
+
+### Rego Policy Examples
+Here is a sample of the `input`:
+```json
+{
+  "verifierReports": [
+    {
+      "artifactType": "org.example.sbom.v0",
+      "subject": "test.azurecr.io/test/hello-world:v1",
+      "referenceDigest": "test.azurecr.io/test/hello-world@sha256:22222",
+      "verifierReports": [
+        {
+          "verifierName": "sbom",
+          "verifierType": "sbom-verifier",
+          "isSuccess": false,
+          "message": "error msg",
+          "extensions": {}
+        },
+        {
+          "verifierName": "schemavalidator",
+          "verifierType": "verifier-schemavalidator",
+          "isSuccess": true,
+          "message": "",
+          "extensions": {}
+        }
+      ],
+      "nestedReports": [
+        {
+          "artifactType": "application/vnd.cncf.notary.signature",
+          "subject": "test.azurecr.io/test/hello-world@sha256:123",
+          "referenceDigest": "test.azurecr.io/test/hello-world@sha256:33333",
+          "verifierReports": [
+            {
+              "verifierName": "notaryv2",
+              "verifierType": "verifier-notary",
+              "isSuccess": true,
+              "message": "",
+              "extensions": {
+                "Issuer": "testIssuer",
+                "SN": "testSN"
+              }
+            }
+          ],
+          "nestedReports": []
+        }
+      ]
+    }  
+  ]
+}
+```
+
+#### Example 1
+Require all reference artifacts associated with subject image to be verify successfully.
+Check equivalent [Config Policy](#config-policy-examples)
+
+```rego
+package ratify.policy
+
+default valid := false
+
+valid {
+  not failed_verify(input)
+}
+
+failed_verify(reports) {
+  [path, value] := walk(reports)
+  value == false
+  path[count(path) - 1] == "isSuccess"
+}
+
+failed_verify(reports) {
+  [path, value] := walk(reports)
+  path[count(path) - 1] == "verifierReports"
+  count(value) == 0
+}
+```
+
+#### Example 2
+Require at least one reference artifact of the same type to verify succesfully. (relaxes the default policy to 'any').
+Check equivalent [Config Policy](#config-policy-examples)
+
+```rego
+package ratify.policy
+
+default valid := false
+
+valid if {
+	not failed_verify(input)
+}
+
+failed_verify(reports) if {
+	newReports := {"nestedReports": reports.verifierReports}
+	has_subject_failed_verify(newReports)
+}
+
+has_subject_failed_verify(nestedReports) if {
+	[path, value] := walk(nestedReports)
+	not artifact_type_pass_verify(value)
+	path[count(path) - 1] == "nestedReports"
+}
+
+# at least one artifact of the same type passed verification
+artifact_type_pass_verify(nestedReports) if {
+	count_artifact_type(nestedReports) == count_successful_artifact_type(nestedReports)
+}
+
+count_artifact_type(nestedReports) := number if {
+	artifact_types := {x |
+		some i
+		x := nestedReports[i].artifactType
+	}
+
+	number := count(artifact_types)
+}
+
+count_successful_artifact_type(nestedReports) := number if {
+	artifact_types := {x |
+		some i
+		x := nestedReports[i].artifactType
+		artifact_pass_verify(nestedReports[i].verifierReports)
+	}
+
+	number := count(artifact_types)
+}
+
+# an artifact has at least one successful report
+artifact_pass_verify(verifierReports) if {
+	verifierReports[_].isSuccess == true
+}
+
+# should be at least one verifier report that is successful
+failed_verify(reports) if {
+	[path, value] := walk(reports)
+	path[count(path) - 1] == "verifierReports"
+	count(value) == 0
+}
+```
+
 ## Config Policy Provider
 
 ```
@@ -52,7 +223,7 @@ The executor is the "glue" that links all Ratify plugin-based components such as
         },
         ...
         ```
-### Examples:
+### Config Policy Examples:
 
 - Require all reference artifacts associated with subject image to be verify successfully:
     ```
@@ -94,6 +265,7 @@ The executor is the "glue" that links all Ratify plugin-based components such as
     ...
     ```
 
+The Verification response follows `0.1.0` version. Check definition and examples in [0.1.0](../reference/verification-result-version.md#0.1.0).
 ## Notational Conventions
 
 The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" are to be interpreted as described in [RFC 2119](http://tools.ietf.org/html/rfc2119).
diff --git a/docs/reference/usage.md b/docs/reference/usage.md
index f1a3adf..0ae5f04 100644
--- a/docs/reference/usage.md
+++ b/docs/reference/usage.md
@@ -21,6 +21,9 @@ A value of `1` indicates the feature is active; any other value disables the fla
 
 - `RATIFY_DYNAMIC_PLUGINS`: (disabled) Enables Ratify to download plugins at runtime from an OCI registry by setting `source` on the plugin config
 
+- `RATIFY_USE_REGO_POLICY`: (disabled) Enables Ratify to generate verification reports in the format designed for evaluation by OPA engine with Rego language. Users can either use the OPA engine embedded in Ratify or the one within Gatekeeper.
+- `RATIFY_PASSTHROUGH_MODE`: (disabled) Enables Ratify to offload the policy decision-making to Gatekeeper. Ratify verify API will just return verification reports without the decision. `RATIFY_USE_REGO_POLICY` must be enabled to use this feature.
+
 - `RATIFY_CERT_ROTATION`: (disabled) Enables Ratify to rotate TLS certificates automatically when they are about to expire. See [cert-controller](https://github.com/open-policy-agent/cert-controller) for more details on the implementation. The cert-controller checks the validation of certificates every 12 hours, if the certificate is expiring in 90 days, cert-controller will generate a new certificate that is valid for 10 years. Notes: as this [post](https://ahmet.im/blog/kubernetes-secret-volumes-delay/) pointed out, it may take Kubernetes 60-90 seconds to progagate changes to Secrets on the mounted volumes. If you provided invalid/expired certificates/keys during the service startup, it may take up to 90 seconds for the service to rotate the certificates and get to actual working state with mounted certs.
 
 Notes: the root CA certificate generated by cert-controller will have the Subject field like:
@@ -37,4 +40,4 @@ Notes: the root CA certificate generated by cert-controller will have the Subjec
     X509v3 Key Usage: critical
       Digital Signature, Key Encipherment, Certificate Sign
   ```
-So if you want to generate your own root CA certificate, make sure it has the same Subject and x509v3 extensions fields.
\ No newline at end of file
+So if you want to generate your own root CA certificate, make sure it has the same Subject and x509v3 extensions fields.
diff --git a/docs/reference/verification-result-version.md b/docs/reference/verification-result-version.md
new file mode 100644
index 0000000..f2023ab
--- /dev/null
+++ b/docs/reference/verification-result-version.md
@@ -0,0 +1,189 @@
+# Verification Response
+
+Verification Response is the data returned for external data request calls. As we add new features, the Verification Response may change its format. To make it easy for users and other developers to work with different formats, we have implemented versioning support. This document lists all the versions of the Verification Response that have ever existed and will be updated whenever we make a new change to the format.
+
+# Versions
+
+## 0.1.0
+### Definition
+```yaml
+definitions:
+  VerificationResponse:
+    type: object
+    properties:
+      isSuccess:
+        type: boolean
+      version:
+        type: string
+        description: The version of the verification report
+      verifierReports:
+        type: array
+        items:
+          $ref: '#/definitions/VerifierReport'
+  VerifierReport:
+    type: object
+    properties:
+      subject:
+        type: string
+        description: Digested reference of the subject that was verified
+      isSuccess:
+        type: boolean
+        description: The result of the verification
+      name:
+        type: string
+        description: The name of the verifier that performed the verification
+      message:
+        type: string
+        description: The message describing the verification result
+      extensions:
+        type: object
+        description: Any extended information about the verification result
+      artifactType:
+        type: string
+        description: The media type of the artifact being verified
+      nestedResults:
+        type: array
+        items:
+          $ref: '#/definitions/VerifierReport'
+        description: The nested verification results
+```
+### Example
+```json
+{
+  "isSuccess": true,
+  "verifierReports": [
+    {
+      "subject": "registry:5000/sbom@sha256:4139357ed163984fe8ea49eaa0b82325dfc4feda98d0f6691b24f24cd6f0591e",
+      "isSuccess": true,
+      "name": "sbom",
+      "message": "SBOM verification success. The schema is good.",
+      "extensions": {
+        "created": "2023-05-08T17:11:15Z",
+        "creators": [
+          "Organization: acme",
+          "Tool: Microsoft.SBOMTool-1.0.2"
+        ],
+        "licenseListVersion": ""
+      },
+      "artifactType": "org.example.sbom.v0",
+      "nestedResults": [
+        {
+          "subject": "registry:5000/sbom@sha256:a59b9a5ee8ce41fed4be7f6b8d8619bd9e619bbda6b7b1feb591c3c85f6ab7af",
+          "isSuccess": true,
+          "name": "notaryv2",
+          "message": "signature verification success",
+          "extensions": {
+            "Issuer": "CN=ratify-bats-test,O=Notary,L=Seattle,ST=WA,C=US",
+            "SN": "CN=ratify-bats-test,O=Notary,L=Seattle,ST=WA,C=US"
+          },
+          "artifactType": "application/vnd.cncf.notary.signature"
+        }
+      ]
+    }
+  ]
+}
+```
+
+## 1.0.0
+### Definition
+```yaml
+definitions:
+  VerificationResponse:
+    type: object
+    properties:
+      version:
+        type: string
+        description: The version of the verification report
+      isSuccess:
+        type: boolean
+      verifierReports:
+        type: array
+        items:
+          $ref: '#/definitions/VerifierReport'
+  VerifierReport:
+    type: object
+    properties:
+      subject:
+        type: string
+        description: Digested reference of the subject that was verified
+      referenceDigest:
+        type: string
+        description: The digest of the artifact that was verified
+      artifactType:
+        type: string
+        description: The media type of the artifact being verified
+      verifierReports:
+        type: array
+        items:
+          $ref: '#/definitions/InnerVerifierReport'
+        description: The verification reports related to the artifact
+      nestedReports:
+        type: array
+        items:
+          $ref: '#/definitions/VerifierReport'
+        description: The nested verification reports
+  InnerVerifierReport:
+    type: object
+    properties:
+      isSuccess:
+        type: boolean
+        description: The result of the verification
+      message:
+        type: string
+        description: The message describing the verification result
+      name:
+        type: string
+        description: The name of the verifier that performed the verification
+      extensions:
+        type: object
+        description: Any extended information about the verification result
+```
+
+###
+```json
+{
+  "version": "1.0.0",
+  "isSuccess": true,
+  "verifierReports": [
+    {
+      "subject": "registry:5000/sbom@sha256:f291201143149e4006894b2d64202a8b90416b7dcde1c8ad997b1099312af3ce",
+      "referenceDigest": "sha256:932dde71a9f26ddafa61e8a7df2b296b1787bcb6e75c515584a53776e81a8a00",
+      "artifactType": "org.example.sbom.v0",
+      "verifierReports": [
+        {
+          "isSuccess": true,
+          "message": "SBOM verification success. The schema is good.",
+          "name": "sbom",
+          "extensions": {
+            "created": "2023-05-11T05:20:43Z",
+            "creators": [
+              "Organization: acme",
+              "Tool: Microsoft.SBOMTool-1.1.0"
+            ],
+            "licenseListVersion": ""
+          }
+        }
+      ],
+      "nestedReports": [
+        {
+          "subject": "registry:5000/sbom@sha256:932dde71a9f26ddafa61e8a7df2b296b1787bcb6e75c515584a53776e81a8a00",
+          "referenceDigest": "sha256:bf67213f8e048c2262b1dd007a4380f03431e1aa2ab58c7afdce7c2f763f7684",
+          "artifactType": "application/vnd.cncf.notary.signature",
+          "verifierReports": [
+            {
+              "isSuccess": true,
+              "message": "signature verification success",
+              "name": "notaryv2",
+              "extensions": {
+                "Issuer": "CN=ratify-bats-test,O=Notary,L=Seattle,ST=WA,C=US",
+                "SN": "CN=ratify-bats-test,O=Notary,L=Seattle,ST=WA,C=US"
+              }
+            }
+          ],
+          "nestedReports": []
+        }
+      ]
+    }
+  ]
+}
+```
\ No newline at end of file
diff --git a/go.mod b/go.mod
index b9430b2..978cd18 100644
--- a/go.mod
+++ b/go.mod
@@ -24,7 +24,9 @@ require (
 	github.com/gorilla/mux v1.8.0
 	github.com/notaryproject/notation-core-go v1.0.0-rc.4
 	github.com/notaryproject/notation-go v1.0.0-rc.6
+	github.com/open-policy-agent/cert-controller v0.7.0
 	github.com/open-policy-agent/frameworks/constraint v0.0.0-20230201235642-777dc99a6669
+	github.com/open-policy-agent/opa v0.51.0
 	github.com/opencontainers/go-digest v1.0.0
 	github.com/opencontainers/image-spec v1.1.0-rc2
 	github.com/pkg/errors v0.9.1
@@ -57,6 +59,7 @@ require (
 	github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
 	github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
 	github.com/ThalesIgnite/crypto11 v1.2.5 // indirect
+	github.com/agnivade/levenshtein v1.1.1 // indirect
 	github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect
 	github.com/alibabacloud-go/cr-20160607 v1.0.1 // indirect
 	github.com/alibabacloud-go/cr-20181201 v1.0.10 // indirect
@@ -86,6 +89,7 @@ require (
 	github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
 	github.com/go-jose/go-jose/v3 v3.0.0 // indirect
 	github.com/go-ldap/ldap/v3 v3.4.4 // indirect
+	github.com/gobwas/glob v0.2.3 // indirect
 	github.com/google/go-github/v50 v50.2.0 // indirect
 	github.com/google/go-querystring v1.1.0 // indirect
 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
@@ -98,9 +102,11 @@ require (
 	github.com/sigstore/protobuf-specs v0.1.0 // indirect
 	github.com/sigstore/timestamp-authority v1.0.0 // indirect
 	github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
+	github.com/tchap/go-patricia/v2 v2.3.1 // indirect
 	github.com/thales-e-security/pool v0.0.2 // indirect
 	github.com/tjfoc/gmsm v1.3.2 // indirect
 	github.com/xanzy/go-gitlab v0.83.0 // indirect
+	github.com/yashtewari/glob-intersection v0.1.0 // indirect
 	go.step.sm/crypto v0.30.0 // indirect
 	sigs.k8s.io/release-utils v0.7.4 // indirect
 )
@@ -183,8 +189,6 @@ require (
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/oklog/ulid v1.3.1 // indirect
-	github.com/open-policy-agent/cert-controller v0.7.0
-	github.com/open-policy-agent/opa v0.51.0 // indirect
 	github.com/opentracing/opentracing-go v1.2.0 // indirect
 	github.com/pelletier/go-toml/v2 v2.0.6 // indirect
 	github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
diff --git a/go.sum b/go.sum
index ebbbbc8..6956a6e 100644
--- a/go.sum
+++ b/go.sum
@@ -107,6 +107,8 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY+9ef8E=
 github.com/ThalesIgnite/crypto11 v1.2.5/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE=
+github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
+github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
 github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.2/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
 github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo=
 github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
@@ -147,6 +149,8 @@ github.com/aliyun/credentials-go v1.2.3 h1:Vmodnr52Rz1mcbwn0kzMhLRKb6soizewuKXdf
 github.com/aliyun/credentials-go v1.2.3/go.mod h1:/KowD1cfGSLrLsH28Jr8W+xwoId0ywIy5lNzDz6O1vw=
 github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc=
 github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
+github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
+github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
@@ -209,8 +213,10 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm
 github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
 github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
+github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA=
 github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
@@ -254,11 +260,14 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/depcheck-test/depcheck-test v0.0.0-20220607135614-199033aaa936 h1:foGzavPWwtoyBvjWyKJYDYsyzy+23iBV7NKTwdk+LRY=
+github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg=
 github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
 github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
 github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
+github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
+github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
 github.com/digitorus/pkcs7 v0.0.0-20221019075359-21b8b40e6bb4/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc=
 github.com/digitorus/pkcs7 v0.0.0-20221212123742-001c36b64ec3 h1:rjCXeRWazGsbcBlExMcAW8H1LGdgJ9r619y7+aeKgds=
 github.com/digitorus/pkcs7 v0.0.0-20221212123742-001c36b64ec3/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc=
@@ -298,6 +307,8 @@ github.com/facebookgo/limitgroup v0.0.0-20150612190941-6abd8d71ec01 h1:IeaD1VDVB
 github.com/facebookgo/muster v0.0.0-20150708232844-fd3d7953fd52 h1:a4DFiKFJiDRGFD1qIcqGLX/WlUMD9dyLSLDt+9QZgt8=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
+github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
+github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI=
 github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
 github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -401,6 +412,8 @@ github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWe
 github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
 github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
 github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
+github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
+github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
@@ -449,6 +462,7 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/certificate-transparency-go v1.1.4 h1:hCyXHDbtqlr/lMXU0D4WgbalXL0Zk4dSWWMbPV8VrqY=
 github.com/google/certificate-transparency-go v1.1.4/go.mod h1:D6lvbfwckhNrbM9WVl1EVeMOyzC19mpIjMOI4nxBHtQ=
+github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
 github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
 github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -625,6 +639,7 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
 github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
 github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
 github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
 github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
 github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
@@ -814,6 +829,8 @@ github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8
 github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
 github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
 github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
+github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes=
+github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
 github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
 github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
 github.com/theupdateframework/go-tuf v0.5.2 h1:habfDzTmpbzBLIFGWa2ZpVhYvFBoK0C1onC3a4zuPRA=
@@ -855,6 +872,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q
 github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk=
 github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+github.com/yashtewari/glob-intersection v0.1.0 h1:6gJvMYQlTDOL3dMsPF6J0+26vwX9MB8/1q3uAdhmTrg=
+github.com/yashtewari/glob-intersection v0.1.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok=
 github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
 github.com/ysmood/fetchup v0.2.2 h1:Qn8/q5uDW7szclt4sVXCFJ1TXup3hogz94OaLf6kloo=
 github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ=
diff --git a/httpserver/types.go b/httpserver/types.go
index a29640a..38e7665 100644
--- a/httpserver/types.go
+++ b/httpserver/types.go
@@ -17,9 +17,15 @@ package httpserver
 
 import (
 	"github.com/deislabs/ratify/pkg/executor/types"
+	"github.com/deislabs/ratify/pkg/featureflag"
 )
 
-const VerificationResultVersion = "0.1.0"
+const (
+	VerificationResultVersion = "0.1.0"
+	// Starting from this version, the verification result can be
+	// evaluated by Ratify embedded OPA engine.
+	ResultVersionSupportingRego = "1.0.0"
+)
 
 type VerificationResponse struct {
 	Version         string        `json:"version"`
@@ -28,8 +34,12 @@ type VerificationResponse struct {
 }
 
 func fromVerifyResult(res types.VerifyResult) VerificationResponse {
+	version := VerificationResultVersion
+	if featureflag.UseRegoPolicy.Enabled {
+		version = ResultVersionSupportingRego
+	}
 	return VerificationResponse{
-		Version:         VerificationResultVersion,
+		Version:         version,
 		IsSuccess:       res.IsSuccess,
 		VerifierReports: res.VerifierReports,
 	}
diff --git a/pkg/executor/core/executor.go b/pkg/executor/core/executor.go
index 480fbb8..98a5736 100644
--- a/pkg/executor/core/executor.go
+++ b/pkg/executor/core/executor.go
@@ -25,6 +25,7 @@ import (
 	e "github.com/deislabs/ratify/pkg/executor"
 	"github.com/deislabs/ratify/pkg/executor/config"
 	"github.com/deislabs/ratify/pkg/executor/types"
+	"github.com/deislabs/ratify/pkg/featureflag"
 	"github.com/deislabs/ratify/pkg/metrics"
 	"github.com/deislabs/ratify/pkg/ocispecs"
 	"github.com/deislabs/ratify/pkg/policyprovider"
@@ -32,6 +33,7 @@ import (
 	su "github.com/deislabs/ratify/pkg/referrerstore/utils"
 	"github.com/deislabs/ratify/pkg/utils"
 	vr "github.com/deislabs/ratify/pkg/verifier"
+	vt "github.com/deislabs/ratify/pkg/verifier/types"
 	"github.com/sirupsen/logrus"
 	"golang.org/x/sync/errgroup"
 )
@@ -51,8 +53,17 @@ type Executor struct {
 }
 
 // TODO Logging within executor
+// VerifySubject verifies the subject and returns results.
 func (executor Executor) VerifySubject(ctx context.Context, verifyParameters e.VerifyParameters) (types.VerifyResult, error) {
-	result, err := executor.verifySubjectInternal(ctx, verifyParameters)
+	if featureflag.UseRegoPolicy.Enabled {
+		return executor.verifySubjectForRegoPolicy(ctx, verifyParameters)
+	}
+	return executor.verifySubjectForJSONPolicy(ctx, verifyParameters)
+}
+
+// verifySubjectForJSONPolicy verifies the subject with the Json-based policy enforcer.
+func (executor Executor) verifySubjectForJSONPolicy(ctx context.Context, verifyParameters e.VerifyParameters) (types.VerifyResult, error) {
+	result, err := executor.verifySubjectInternalWithDecision(ctx, verifyParameters)
 	if err != nil {
 		// get the result for the error based on the policy.
 		// Do we need to consider no referrers as success or failure?
@@ -62,39 +73,60 @@ func (executor Executor) VerifySubject(ctx context.Context, verifyParameters e.V
 	return result, nil
 }
 
-func (executor Executor) GetVerifyRequestTimeout() time.Duration {
-	timeoutMilliSeconds := defaultVerifyRequestTimeoutMilliseconds
-	if executor.Config != nil && executor.Config.VerificationRequestTimeout != nil {
-		timeoutMilliSeconds = *executor.Config.VerificationRequestTimeout
+// verifySubjectForRegoPolicy verifies the subject with results.
+// And it also returns the decision based on the verifier results if required.
+func (executor Executor) verifySubjectForRegoPolicy(ctx context.Context, verifyParameters e.VerifyParameters) (types.VerifyResult, error) {
+	results, err := executor.verifySubjectInternalWithoutDecision(ctx, verifyParameters)
+	if err != nil {
+		return types.VerifyResult{}, err
 	}
-	return time.Duration(timeoutMilliSeconds) * time.Millisecond
+	// If it requires embedded Rego Policy Engine make the decision, execute
+	// OverallVerifyResult to evaluate the overall result based on the policy.
+	// NOTE: if Passthrough Mode is enabled, executor will just return the
+	// VerifierReports without evaluating the policy.
+	result := types.VerifyResult{VerifierReports: results}
+	if !featureflag.PassthroughMode.Enabled {
+		result.IsSuccess = executor.PolicyEnforcer.OverallVerifyResult(ctx, result.VerifierReports)
+	}
+
+	return result, nil
 }
 
-func (executor Executor) GetMutationRequestTimeout() time.Duration {
-	timeoutMilliSeconds := defaultMutateRequestTimeoutMilliseconds
-	if executor.Config != nil && executor.Config.MutationRequestTimeout != nil {
-		timeoutMilliSeconds = *executor.Config.MutationRequestTimeout
+// verifySubjectInternalWithDecision verifies the subject and makes the decision
+// based on the policy. It is only used internally for the Json-based policy.
+func (executor Executor) verifySubjectInternalWithDecision(ctx context.Context, verifyParameters e.VerifyParameters) (types.VerifyResult, error) {
+	verifierReports, err := executor.verifySubjectInternalWithoutDecision(ctx, verifyParameters)
+	if err != nil {
+		return types.VerifyResult{}, err
 	}
-	return time.Duration(timeoutMilliSeconds) * time.Millisecond
+	if len(verifierReports) == 0 {
+		return types.VerifyResult{}, ErrReferrersNotFound
+	}
+
+	// Making the decision based on the Json policy.
+	overallVerifySuccess := executor.PolicyEnforcer.OverallVerifyResult(ctx, verifierReports)
+	return types.VerifyResult{IsSuccess: overallVerifySuccess, VerifierReports: verifierReports}, nil
 }
 
-func (executor Executor) verifySubjectInternal(ctx context.Context, verifyParameters e.VerifyParameters) (types.VerifyResult, error) {
+// verifySubjectInternalWithoutDecision verifies the subject and returns result
+// without making decisions on the result.
+func (executor Executor) verifySubjectInternalWithoutDecision(ctx context.Context, verifyParameters e.VerifyParameters) ([]interface{}, error) {
 	subjectReference, err := utils.ParseSubjectReference(verifyParameters.Subject)
 	if err != nil {
-		return types.VerifyResult{}, err
+		return nil, err
 	}
 
 	desc, err := su.ResolveSubjectDescriptor(ctx, &executor.ReferrerStores, subjectReference)
 
 	if err != nil {
-		return types.VerifyResult{}, fmt.Errorf("resolving descriptor for the subject failed with error: %w", err)
+		return nil, fmt.Errorf("resolving descriptor for the subject failed with error: %w", err)
 	}
 
 	logrus.Infof("Resolve of the image completed successfully the digest is %s", desc.Digest)
 
 	subjectReference.Digest = desc.Digest
 
-	var verifierReports []interface{}
+	verifierReports := make([]interface{}, 0)
 	eg, errCtx := errgroup.WithContext(ctx)
 	var mu sync.Mutex
 
@@ -102,7 +134,7 @@ func (executor Executor) verifySubjectInternal(ctx context.Context, verifyParame
 		referrerStore := referrerStore
 		eg.Go(func() error {
 			var continuationToken string
-			wg := sync.WaitGroup{}
+			innerGroup, innerErrCtx := errgroup.WithContext(errCtx)
 			for {
 				referrersResult, err := referrerStore.ListReferrers(errCtx, subjectReference, verifyParameters.ReferenceTypes, continuationToken, desc)
 				if err != nil {
@@ -110,39 +142,47 @@ func (executor Executor) verifySubjectInternal(ctx context.Context, verifyParame
 				}
 				continuationToken = referrersResult.NextToken
 				for _, reference := range referrersResult.Referrers {
-					wg.Add(1)
-					go func(reference ocispecs.ReferenceDescriptor) {
-						defer wg.Done()
-						if executor.PolicyEnforcer.VerifyNeeded(ctx, subjectReference, reference) {
-							verifyResult := executor.verifyReference(errCtx, subjectReference, reference, referrerStore)
+					if !executor.PolicyEnforcer.VerifyNeeded(innerErrCtx, subjectReference, reference) {
+						continue
+					}
+					reference := reference
+					innerGroup.Go(func() error {
+						if featureflag.UseRegoPolicy.Enabled {
+							verifyResult, err := executor.verifyReferenceForRegoPolicy(innerErrCtx, subjectReference, reference, referrerStore)
+							if err != nil {
+								logrus.Errorf("error while verifying reference %+v, err: %v", reference, err)
+								return err
+							}
+							mu.Lock() // locks the verifierReports List for write safety
+							defer mu.Unlock()
+							verifierReports = append(verifierReports, verifyResult)
+						} else {
+							verifyResult := executor.verifyReferenceForJSONPolicy(innerErrCtx, subjectReference, reference, referrerStore)
 							mu.Lock() // locks the verifierReports List for write safety
 							defer mu.Unlock()
 							verifierReports = append(verifierReports, verifyResult.VerifierReports...)
 						}
-					}(reference)
+						return nil
+					})
 				}
 				if continuationToken == "" {
 					break
 				}
 			}
-			wg.Wait()
-			return nil
+			return innerGroup.Wait()
 		})
 	}
 
 	if err = eg.Wait(); err != nil {
-		return types.VerifyResult{}, err
-	}
-
-	if len(verifierReports) == 0 {
-		return types.VerifyResult{}, ErrReferrersNotFound
+		return nil, err
 	}
 
-	overallVerifySuccess := executor.PolicyEnforcer.OverallVerifyResult(ctx, verifierReports)
-	return types.VerifyResult{IsSuccess: overallVerifySuccess, VerifierReports: verifierReports}, nil
+	return verifierReports, nil
 }
 
-func (executor Executor) verifyReference(ctx context.Context, subjectRef common.Reference, referenceDesc ocispecs.ReferenceDescriptor, referrerStore referrerstore.ReferrerStore) types.VerifyResult {
+// verifyReferenceForJSONPolicy verifies the referenced artifact with results
+// used for the Json-based policy enforcer.
+func (executor Executor) verifyReferenceForJSONPolicy(ctx context.Context, subjectRef common.Reference, referenceDesc ocispecs.ReferenceDescriptor, referrerStore referrerstore.ReferrerStore) types.VerifyResult {
 	var verifyResults []interface{}
 	var isSuccess = true
 
@@ -173,13 +213,65 @@ func (executor Executor) verifyReference(ctx context.Context, subjectRef common.
 	return types.VerifyResult{IsSuccess: isSuccess, VerifierReports: verifyResults}
 }
 
+// verifyReferenceForRegoPolicy verifies the referenced artifact with results
+// used for Rego-based policy enforcer.
+func (executor Executor) verifyReferenceForRegoPolicy(ctx context.Context, subjectRef common.Reference, referenceDesc ocispecs.ReferenceDescriptor, referrerStore referrerstore.ReferrerStore) (types.NestedVerifierReport, error) {
+	nestedReport := types.NestedVerifierReport{
+		Subject:         subjectRef.String(),
+		ArtifactType:    referenceDesc.ArtifactType,
+		ReferenceDigest: referenceDesc.Digest.String(),
+		VerifierReports: make([]vt.VerifierResult, 0),
+		NestedReports:   make([]types.NestedVerifierReport, 0),
+	}
+	var mu sync.Mutex
+	eg, errCtx := errgroup.WithContext(ctx)
+
+	eg.Go(func() error {
+		return executor.addNestedReports(errCtx, referenceDesc, subjectRef, &nestedReport)
+	})
+
+	for _, verifier := range executor.Verifiers {
+		if !verifier.CanVerify(ctx, referenceDesc) {
+			continue
+		}
+		verifier := verifier
+		eg.Go(func() error {
+			var verifierReport vt.VerifierResult
+			verifierStartTime := time.Now()
+			verifierResult, err := verifier.Verify(errCtx, subjectRef, referenceDesc, referrerStore)
+			if err != nil {
+				verifierReport = vt.VerifierResult{
+					IsSuccess: false,
+					Name:      verifier.Name(),
+					Message:   fmt.Sprintf("an error thrown by the verifier: %v", err)}
+			} else {
+				verifierReport = vt.NewVerifierResult(verifierResult)
+			}
+
+			mu.Lock()
+			nestedReport.VerifierReports = append(nestedReport.VerifierReports, verifierReport)
+			mu.Unlock()
+
+			metrics.ReportVerifierDuration(errCtx, time.Since(verifierStartTime).Milliseconds(), verifier.Name(), subjectRef.String(), verifierReport.IsSuccess, err != nil)
+			return nil
+		})
+	}
+
+	if err := eg.Wait(); err != nil {
+		return types.NestedVerifierReport{}, err
+	}
+	return nestedReport, nil
+}
+
+// addNestedVerifierResult adds the nested verifier result to the parent verify
+// result used for Json-based policy enforcer.
 func (executor Executor) addNestedVerifierResult(ctx context.Context, referenceDesc ocispecs.ReferenceDescriptor, subjectRef common.Reference, verifyResult *vr.VerifierResult) {
 	verifyParameters := e.VerifyParameters{
 		Subject:        fmt.Sprintf("%s@%s", subjectRef.Path, referenceDesc.Digest),
 		ReferenceTypes: []string{"*"},
 	}
 
-	nestedVerifyResult, err := executor.VerifySubject(ctx, verifyParameters)
+	nestedVerifyResult, err := executor.verifySubjectForJSONPolicy(ctx, verifyParameters)
 	if err != nil {
 		nestedVerifyResult = executor.PolicyEnforcer.ErrorToVerifyResult(ctx, verifyParameters.Subject, err)
 	}
@@ -194,3 +286,46 @@ func (executor Executor) addNestedVerifierResult(ctx context.Context, referenceD
 		}
 	}
 }
+
+// addNestedReports adds the nested verifier reports to the parent report used
+// for Rego-based policy enforcer.
+func (executor Executor) addNestedReports(ctx context.Context, referenceDes ocispecs.ReferenceDescriptor, subjectRef common.Reference, verifierReport *types.NestedVerifierReport) error {
+	verifyParameters := e.VerifyParameters{
+		Subject:        fmt.Sprintf("%s@%s", subjectRef.Path, referenceDes.Digest),
+		ReferenceTypes: []string{"*"},
+	}
+
+	// get nested reports.
+	reports, err := executor.verifySubjectForRegoPolicy(ctx, verifyParameters)
+	if err != nil {
+		return fmt.Errorf("failed to verify nested subject, param: %+v, err: %w", verifyParameters, err)
+	}
+
+	// append nested reports to the parent report.
+	nestedReports := make([]types.NestedVerifierReport, 0, len(reports.VerifierReports))
+	for _, report := range reports.VerifierReports {
+		nestedReport, err := types.NewNestedVerifierReport(report)
+		if err != nil {
+			return err
+		}
+		nestedReports = append(nestedReports, nestedReport)
+	}
+	verifierReport.NestedReports = nestedReports
+	return nil
+}
+
+func (executor Executor) GetVerifyRequestTimeout() time.Duration {
+	timeoutMilliSeconds := defaultVerifyRequestTimeoutMilliseconds
+	if executor.Config != nil && executor.Config.VerificationRequestTimeout != nil {
+		timeoutMilliSeconds = *executor.Config.VerificationRequestTimeout
+	}
+	return time.Duration(timeoutMilliSeconds) * time.Millisecond
+}
+
+func (executor Executor) GetMutationRequestTimeout() time.Duration {
+	timeoutMilliSeconds := defaultMutateRequestTimeoutMilliseconds
+	if executor.Config != nil && executor.Config.MutationRequestTimeout != nil {
+		timeoutMilliSeconds = *executor.Config.MutationRequestTimeout
+	}
+	return time.Duration(timeoutMilliSeconds) * time.Millisecond
+}
diff --git a/pkg/executor/types/types.go b/pkg/executor/types/types.go
index b8da103..d615991 100644
--- a/pkg/executor/types/types.go
+++ b/pkg/executor/types/types.go
@@ -15,8 +15,32 @@ limitations under the License.
 
 package types
 
+import (
+	"fmt"
+
+	"github.com/deislabs/ratify/pkg/verifier/types"
+)
+
 // VerifyResult describes the results of verifying a subject
 type VerifyResult struct {
-	IsSuccess       bool          `json:"isSuccess"`
-	VerifierReports []interface{} `json:"verifierReports,omitempty"`
+	IsSuccess       bool          `json:"isSuccess,omitempty"`
+	VerifierReports []interface{} `json:"verifierReports"`
+}
+
+// NestedVerifierReport describes the results of verifying an artifact and its
+// nested artifacts by available verifiers.
+type NestedVerifierReport struct {
+	Subject         string                 `json:"subject"`
+	ReferenceDigest string                 `json:"referenceDigest"`
+	ArtifactType    string                 `json:"artifactType"`
+	VerifierReports []types.VerifierResult `json:"verifierReports"`
+	NestedReports   []NestedVerifierReport `json:"nestedReports"`
+}
+
+// NewNestedVerifierReport creates a new NestedVerifierReport from an interface.
+func NewNestedVerifierReport(report interface{}) (NestedVerifierReport, error) {
+	if nvr, ok := report.(NestedVerifierReport); ok {
+		return nvr, nil
+	}
+	return NestedVerifierReport{}, fmt.Errorf("unable to convert %v to NestedVerifierReport", report)
 }
diff --git a/pkg/featureflag/featureflag.go b/pkg/featureflag/featureflag.go
index 34145e6..00eb161 100644
--- a/pkg/featureflag/featureflag.go
+++ b/pkg/featureflag/featureflag.go
@@ -25,8 +25,10 @@ import (
 // They are activated via environment variables, starting with "RATIFY_", ex: RATIFY_DYNAMIC_PLUGINS=1
 // Remember to capture changes in the usage guide and release notes.
 var (
-	DynamicPlugins = newFeatureFlag("DYNAMIC_PLUGINS", false)
-	CertRotation   = newFeatureFlag("CERT_ROTATION", false)
+	DynamicPlugins  = newFeatureFlag("DYNAMIC_PLUGINS", false)
+	UseRegoPolicy   = newFeatureFlag("USE_REGO_POLICY", false)
+	PassthroughMode = newFeatureFlag("PASSTHROUGH_MODE", false)
+	CertRotation    = newFeatureFlag("CERT_ROTATION", false)
 )
 
 var flags = make(map[string]*FeatureFlag)
diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go
index a8b546c..fe43603 100644
--- a/pkg/manager/manager.go
+++ b/pkg/manager/manager.go
@@ -31,6 +31,7 @@ import (
 	"github.com/deislabs/ratify/httpserver"
 	"github.com/deislabs/ratify/pkg/featureflag"
 	_ "github.com/deislabs/ratify/pkg/policyprovider/configpolicy" // register config policy provider
+	_ "github.com/deislabs/ratify/pkg/policyprovider/regopolicy"   // register rego policy provider
 	_ "github.com/deislabs/ratify/pkg/referrerstore/oras"          // register ORAS referrer store
 	"github.com/deislabs/ratify/pkg/utils"
 	_ "github.com/deislabs/ratify/pkg/verifier/notaryv2" // register notaryv2 verifier
diff --git a/pkg/policyprovider/configpolicy/configpolicy.go b/pkg/policyprovider/configpolicy/configpolicy.go
index 17c0152..dc68a17 100644
--- a/pkg/policyprovider/configpolicy/configpolicy.go
+++ b/pkg/policyprovider/configpolicy/configpolicy.go
@@ -40,13 +40,15 @@ type configPolicyEnforcerConf struct {
 	ArtifactVerificationPolicies map[string]vt.ArtifactTypeVerifyPolicy `json:"artifactVerificationPolicies,omitempty"`
 }
 
-const defaultPolicyName string = "default"
+const (
+	defaultPolicyName = "default"
+)
 
 type configPolicyFactory struct{}
 
 // init calls Register for our config policy provider
 func init() {
-	pf.Register("configPolicy", &configPolicyFactory{})
+	pf.Register(vt.ConfigPolicy, &configPolicyFactory{})
 }
 
 // Create initializes a new policy provider based on the provider selected in config
diff --git a/pkg/policyprovider/factory/factory.go b/pkg/policyprovider/factory/factory.go
index 03737b1..09dfe3b 100644
--- a/pkg/policyprovider/factory/factory.go
+++ b/pkg/policyprovider/factory/factory.go
@@ -19,8 +19,10 @@ import (
 	"errors"
 	"fmt"
 
+	"github.com/deislabs/ratify/pkg/featureflag"
 	"github.com/deislabs/ratify/pkg/policyprovider"
 	"github.com/deislabs/ratify/pkg/policyprovider/config"
+	pt "github.com/deislabs/ratify/pkg/policyprovider/types"
 	"github.com/deislabs/ratify/pkg/verifier/types"
 	"github.com/sirupsen/logrus"
 )
@@ -47,6 +49,9 @@ func Register(name string, factory PolicyFactory) {
 
 // CreatePolicyProvidersFromConfig creates a policy provider from the provided configuration
 func CreatePolicyProviderFromConfig(policyConfig config.PoliciesConfig) (policyprovider.PolicyProvider, error) {
+	if !featureflag.UseRegoPolicy.Enabled && featureflag.PassthroughMode.Enabled {
+		return nil, errors.New("passthrough mode is only supported with rego policy")
+	}
 	if policyConfig.PolicyPlugin == nil {
 		return nil, errors.New("policy provider config must be specified")
 	}
@@ -57,6 +62,9 @@ func CreatePolicyProviderFromConfig(policyConfig config.PoliciesConfig) (policyp
 	}
 
 	providerNameStr := fmt.Sprintf("%s", policyProviderName)
+	if featureflag.UseRegoPolicy.Enabled && providerNameStr != pt.RegoPolicy {
+		return nil, fmt.Errorf("policy provider must be set to use Rego policy when enabled Rego policy feature")
+	}
 
 	policyFactory, ok := builtInPolicyProviders[providerNameStr]
 	if !ok {
diff --git a/pkg/policyprovider/policyengine/api.go b/pkg/policyprovider/policyengine/api.go
new file mode 100644
index 0000000..0572a1e
--- /dev/null
+++ b/pkg/policyprovider/policyengine/api.go
@@ -0,0 +1,27 @@
+/*
+Copyright The Ratify Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package policyengine
+
+import "context"
+
+// PolicyEngine is an interface that represents a policy engine.
+type PolicyEngine interface {
+	// Evaluate evaluates the policy with the given input.
+	// input is the verifier reports that engine evaluates against.
+	// result indicates whether the input satisfies the policy.
+	// err indicates an error happened during the evaluation.
+	Evaluate(ctx context.Context, input map[string]interface{}) (result bool, err error)
+}
diff --git a/pkg/policyprovider/policyengine/factory.go b/pkg/policyprovider/policyengine/factory.go
new file mode 100644
index 0000000..f1d38cf
--- /dev/null
+++ b/pkg/policyprovider/policyengine/factory.go
@@ -0,0 +1,69 @@
+/*
+Copyright The Ratify Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package policyengine
+
+import "fmt"
+
+var engineFactories = make(map[string]EngineFactory)
+
+// Config represents the configuration for an OPA policy engine.
+type Config struct {
+	// Name is the name of the policy engine.
+	Name string
+	// QueryLanguage is the language of the policy query.
+	QueryLanguage string
+	// Query is the policy used for query.
+	Policy string
+}
+
+// EngineFactory is an interface for creating OPA policy engines.
+type EngineFactory interface {
+	// Create creates a new engine.
+	Create(policy string, queryLanguage string) (PolicyEngine, error)
+}
+
+// Register adds the factory to the built-in opaEngines map.
+func Register(name string, factory EngineFactory) {
+	if factory == nil {
+		panic("opa engine factory cannot be nil")
+	}
+	_, registered := engineFactories[name]
+	if registered {
+		panic(fmt.Sprintf("opa engine factory named %s already registered", name))
+	}
+
+	engineFactories[name] = factory
+}
+
+// CreateEngineFromConfig creates an OPA policy engine from the provided configuration.
+func CreateEngineFromConfig(engineConfig Config) (PolicyEngine, error) {
+	engineName := engineConfig.Name
+	if engineName == "" {
+		return nil, fmt.Errorf("policy engine name must be specified")
+	}
+
+	factory, ok := engineFactories[engineName]
+	if !ok {
+		return nil, fmt.Errorf("policy engine factory named %s not registered", engineName)
+	}
+
+	engine, err := factory.Create(engineConfig.Policy, engineConfig.QueryLanguage)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create policy engine: %w", err)
+	}
+
+	return engine, nil
+}
diff --git a/pkg/policyprovider/policyengine/opaengine/opa_engine.go b/pkg/policyprovider/policyengine/opaengine/opa_engine.go
new file mode 100644
index 0000000..1f1174d
--- /dev/null
+++ b/pkg/policyprovider/policyengine/opaengine/opa_engine.go
@@ -0,0 +1,64 @@
+/*
+Copyright The Ratify Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package opa
+
+import (
+	"context"
+	"errors"
+	"strings"
+
+	"github.com/deislabs/ratify/pkg/policyprovider/policyengine"
+	"github.com/deislabs/ratify/pkg/policyprovider/policyquery"
+)
+
+const OPA = "opa"
+
+// Engine is an OPA engine implementing PolicyEvaluator interface.
+type Engine struct {
+	query policyquery.PolicyQuery
+}
+
+// EngineFactory is a factory for creating OPA engines.
+type EngineFactory struct{}
+
+func init() {
+	policyengine.Register(OPA, &EngineFactory{})
+}
+
+// Create creates a new OPA engine.
+func (f *EngineFactory) Create(policy string, queryLanguage string) (policyengine.PolicyEngine, error) {
+	engine := &Engine{}
+	trimmedPolicy := strings.TrimSpace(policy)
+	if trimmedPolicy == "" {
+		return nil, errors.New("policy is empty")
+	}
+
+	query, err := policyquery.CreateQueryFromConfig(policyquery.Config{
+		Name:   queryLanguage,
+		Policy: trimmedPolicy,
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	engine.query = query
+	return engine, nil
+}
+
+// Evaluate evaluates the policy with the given input.
+func (oe *Engine) Evaluate(ctx context.Context, input map[string]interface{}) (bool, error) {
+	return oe.query.Evaluate(ctx, input)
+}
diff --git a/pkg/policyprovider/policyquery/api.go b/pkg/policyprovider/policyquery/api.go
new file mode 100644
index 0000000..c5356d0
--- /dev/null
+++ b/pkg/policyprovider/policyquery/api.go
@@ -0,0 +1,27 @@
+/*
+Copyright The Ratify Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package policyquery
+
+import "context"
+
+// PolicyQuery is an interface with methods that make policy decisions.
+type PolicyQuery interface {
+	// Evaluate evaluates the policy with the given input.
+	// input is the verifier reports that query evaluates against.
+	// result indicates whether the input satisfies the policy.
+	// err indicates an error happened during the evaluation.
+	Evaluate(ctx context.Context, input map[string]interface{}) (bool, error)
+}
diff --git a/pkg/policyprovider/policyquery/factory.go b/pkg/policyprovider/policyquery/factory.go
new file mode 100644
index 0000000..471a5d4
--- /dev/null
+++ b/pkg/policyprovider/policyquery/factory.go
@@ -0,0 +1,64 @@
+/*
+Copyright The Ratify Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package policyquery
+
+import "fmt"
+
+var policyQueryFactories = make(map[string]Factory)
+
+// Config is a configuration for a policy query.
+type Config struct {
+	Name   string
+	Policy string
+}
+
+// Factory is an interface for creating policy queries.
+type Factory interface {
+	Create(policy string) (PolicyQuery, error)
+}
+
+// Register adds the factory to the built-in policyQueryies map.
+func Register(name string, factory Factory) {
+	if factory == nil {
+		panic("policy query factory cannot be nil")
+	}
+	_, registered := policyQueryFactories[name]
+	if registered {
+		panic(fmt.Sprintf("policy query factory named %s already registered", name))
+	}
+
+	policyQueryFactories[name] = factory
+}
+
+// CreateQueryFromConfig creates a policy query from the provided configuration.
+func CreateQueryFromConfig(queryConfig Config) (PolicyQuery, error) {
+	policyQueryName := queryConfig.Name
+	if policyQueryName == "" {
+		return nil, fmt.Errorf("policy query name must be specified")
+	}
+
+	factory, ok := policyQueryFactories[policyQueryName]
+	if !ok {
+		return nil, fmt.Errorf("policy query factory named %s not registered", policyQueryName)
+	}
+
+	policyQuery, err := factory.Create(queryConfig.Policy)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create policy query, err: %+w", err)
+	}
+
+	return policyQuery, nil
+}
diff --git a/pkg/policyprovider/policyquery/rego/query.go b/pkg/policyprovider/policyquery/rego/query.go
new file mode 100644
index 0000000..8d8d197
--- /dev/null
+++ b/pkg/policyprovider/policyquery/rego/query.go
@@ -0,0 +1,73 @@
+/*
+Copyright The Ratify Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package query
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/deislabs/ratify/pkg/policyprovider/policyquery"
+	"github.com/open-policy-agent/opa/rego"
+	"github.com/pkg/errors"
+)
+
+const (
+	query = "data.ratify.policy.valid"
+	// RegoName is a constant for "rego"
+	RegoName = "rego"
+)
+
+// Rego is a wrapper around the OPA rego library.
+type Rego struct {
+	query rego.PreparedEvalQuery
+}
+
+// RegoFactory is a factory for creating Rego query objects.
+type RegoFactory struct{}
+
+func init() {
+	policyquery.Register(RegoName, &RegoFactory{})
+}
+
+// Create creates a new Rego query object.
+func (f *RegoFactory) Create(policy string) (policyquery.PolicyQuery, error) {
+	query, err := rego.New(
+		rego.Query(query),
+		rego.Module("policy.rego", policy),
+	).PrepareForEval(context.Background())
+	if err != nil {
+		return nil, fmt.Errorf("failed to prepare rego query, err: %+w", err)
+	}
+
+	return &Rego{query: query}, nil
+}
+
+// Evaluate evaluates the policy against the input.
+func (r *Rego) Evaluate(ctx context.Context, input map[string]interface{}) (bool, error) {
+	results, err := r.query.Eval(ctx, rego.EvalInput(input))
+
+	if err != nil {
+		return false, err
+	} else if len(results) == 0 || len(results[0].Expressions) == 0 {
+		return false, errors.New("no results returned from query")
+	} else {
+		result, ok := results[0].Expressions[0].Value.(bool)
+		if !ok {
+			return false, fmt.Errorf("unexpected result type: %v", results[0].Expressions[0].Value)
+		}
+		return result, nil
+	}
+}
diff --git a/pkg/policyprovider/regopolicy/regopolicy.go b/pkg/policyprovider/regopolicy/regopolicy.go
new file mode 100644
index 0000000..9fa354e
--- /dev/null
+++ b/pkg/policyprovider/regopolicy/regopolicy.go
@@ -0,0 +1,120 @@
+/*
+Copyright The Ratify Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package regopolicy
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"os"
+
+	"github.com/deislabs/ratify/pkg/common"
+	"github.com/deislabs/ratify/pkg/executor/types"
+	"github.com/deislabs/ratify/pkg/ocispecs"
+	"github.com/deislabs/ratify/pkg/policyprovider"
+	"github.com/deislabs/ratify/pkg/policyprovider/config"
+	pf "github.com/deislabs/ratify/pkg/policyprovider/factory"
+	"github.com/deislabs/ratify/pkg/policyprovider/policyengine"
+	opa "github.com/deislabs/ratify/pkg/policyprovider/policyengine/opaengine"
+	query "github.com/deislabs/ratify/pkg/policyprovider/policyquery/rego"
+	policyTypes "github.com/deislabs/ratify/pkg/policyprovider/types"
+	"github.com/sirupsen/logrus"
+)
+
+type policyEnforcer struct {
+	Policy    string
+	OpaEngine policyengine.PolicyEngine
+}
+
+type policyEnforcerConf struct {
+	Name       string `json:"name"`
+	Policy     string `json:"policy"`
+	PolicyPath string `json:"policyPath"`
+}
+
+// Factory is a factory for creating rego policy enforcers.
+type Factory struct{}
+
+// init calls Register for our rego policy provider.
+func init() {
+	pf.Register(policyTypes.RegoPolicy, &Factory{})
+}
+
+// Create creates a new policy enforcer based on the policy provided in config.
+func (f *Factory) Create(policyConfig config.PolicyPluginConfig) (policyprovider.PolicyProvider, error) {
+	policyEnforcer := &policyEnforcer{}
+
+	conf := policyEnforcerConf{}
+	policyProviderConfigBytes, err := json.Marshal(policyConfig)
+	if err != nil {
+		return nil, fmt.Errorf("failed to marshal policy config: %w", err)
+	}
+
+	if err := json.Unmarshal(policyProviderConfigBytes, &conf); err != nil {
+		return nil, fmt.Errorf("failed to parse policy provider configuration: %w", err)
+	}
+	if conf.Policy == "" {
+		body, err := os.ReadFile(conf.PolicyPath)
+		if err != nil {
+			return nil, fmt.Errorf("unable to read rego policy file at path: %s, err: %w", conf.PolicyPath, err)
+		}
+		conf.Policy = string(body)
+	}
+	if conf.Policy == "" {
+		return nil, fmt.Errorf("policy is required for rego policy provider")
+	}
+
+	engine, err := policyengine.CreateEngineFromConfig(policyengine.Config{
+		Name:          opa.OPA,
+		QueryLanguage: query.RegoName,
+		Policy:        conf.Policy,
+	})
+	if err != nil {
+		return nil, fmt.Errorf("failed to create OPA engine: %w", err)
+	}
+
+	policyEnforcer.Policy = conf.Policy
+	policyEnforcer.OpaEngine = engine
+
+	return policyEnforcer, nil
+}
+
+// VerifyNeeded determines if verification should be performed for a given artifact.
+func (e *policyEnforcer) VerifyNeeded(_ context.Context, _ common.Reference, _ ocispecs.ReferenceDescriptor) bool {
+	return true
+}
+
+// ContinueVerifyOnFailure determines if verification should continue if a previous verification failed.
+func (e *policyEnforcer) ContinueVerifyOnFailure(_ context.Context, _ common.Reference, _ ocispecs.ReferenceDescriptor, _ types.VerifyResult) bool {
+	return true
+}
+
+// ErrorToVerifyResult converts an error to a VerifyResult.
+func (e *policyEnforcer) ErrorToVerifyResult(_ context.Context, _ string, _ error) types.VerifyResult {
+	return types.VerifyResult{}
+}
+
+// OverallVerifyResult determines if the overall verification result should be a success or failure.
+func (e *policyEnforcer) OverallVerifyResult(ctx context.Context, verifierReports []interface{}) bool {
+	nestedReports := map[string]interface{}{}
+	nestedReports["verifierReports"] = verifierReports
+	result, err := e.OpaEngine.Evaluate(ctx, nestedReports)
+	if err != nil {
+		logrus.Errorf("failed to evaluate policy: %v", err)
+		return false
+	}
+	return result
+}
diff --git a/pkg/policyprovider/types/types.go b/pkg/policyprovider/types/types.go
index 8749bec..865ac3b 100644
--- a/pkg/policyprovider/types/types.go
+++ b/pkg/policyprovider/types/types.go
@@ -21,4 +21,8 @@ type ArtifactTypeVerifyPolicy string
 const (
 	AnyVerifySuccess ArtifactTypeVerifyPolicy = "any"
 	AllVerifySuccess ArtifactTypeVerifyPolicy = "all"
+	// RegoPolicy is the name of the rego policy provider.
+	RegoPolicy = "regoPolicy"
+	// ConfigPolicy is the name of the config policy provider.
+	ConfigPolicy = "configPolicy"
 )
diff --git a/pkg/verifier/api.go b/pkg/verifier/api.go
index c0cd03d..5224c29 100644
--- a/pkg/verifier/api.go
+++ b/pkg/verifier/api.go
@@ -34,7 +34,8 @@ type VerifierResult struct { //nolint:revive // ignore linter to have unique typ
 	ArtifactType  string           `json:"artifactType,omitempty"`
 }
 
-// ReferenceVerifier is an interface that defines methods to verify a reference for a subject
+// ReferenceVerifier is an interface that defines methods to verify a reference
+// for a subject by a verifier.
 type ReferenceVerifier interface {
 	// Name returns the name of the verifier
 	Name() string
diff --git a/pkg/verifier/types/types.go b/pkg/verifier/types/types.go
index db98ae2..65fe199 100644
--- a/pkg/verifier/types/types.go
+++ b/pkg/verifier/types/types.go
@@ -70,3 +70,14 @@ func GetVerifierResult(result []byte) (*verifier.VerifierResult, error) {
 func WriteVerifyResultResult(result *verifier.VerifierResult, w io.Writer) error {
 	return json.NewEncoder(w).Encode(result)
 }
+
+// NewVerifierResult creates a new verifier result object from the given
+// verifier.VerifierResult.
+func NewVerifierResult(result verifier.VerifierResult) VerifierResult {
+	return VerifierResult{
+		IsSuccess:  result.IsSuccess,
+		Message:    result.Message,
+		Name:       result.Name,
+		Extensions: result.Extensions,
+	}
+}


Test patch:
diff --git a/pkg/executor/core/executor_test.go b/pkg/executor/core/executor_test.go
index f9a8485..cb13e54 100644
--- a/pkg/executor/core/executor_test.go
+++ b/pkg/executor/core/executor_test.go
@@ -18,41 +18,148 @@ package core
 import (
 	"context"
 	"errors"
+	"os"
+	"reflect"
 	"testing"
 	"time"
 
+	"github.com/deislabs/ratify/pkg/common"
 	exConfig "github.com/deislabs/ratify/pkg/executor/config"
+	"github.com/deislabs/ratify/pkg/featureflag"
+	"github.com/deislabs/ratify/pkg/policyprovider"
 
 	e "github.com/deislabs/ratify/pkg/executor"
+	"github.com/deislabs/ratify/pkg/executor/types"
 	"github.com/deislabs/ratify/pkg/ocispecs"
-	config "github.com/deislabs/ratify/pkg/policyprovider/configpolicy"
-	"github.com/deislabs/ratify/pkg/policyprovider/types"
+	policyConfig "github.com/deislabs/ratify/pkg/policyprovider/configpolicy"
+	policyTypes "github.com/deislabs/ratify/pkg/policyprovider/types"
 	"github.com/deislabs/ratify/pkg/referrerstore"
+	storeConfig "github.com/deislabs/ratify/pkg/referrerstore/config"
 	"github.com/deislabs/ratify/pkg/referrerstore/mocks"
 	"github.com/deislabs/ratify/pkg/verifier"
 	"github.com/opencontainers/go-digest"
+	oci "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
 const (
 	testArtifactType1 = "test-type1"
 	testArtifactType2 = "test-type2"
+	subject1          = "localhost:5000/net-monitor:v1"
+	subjectDigest     = "sha256:6a5a5368e0c2d3e5909184fa28ddfd56072e7ff3ee9a945876f7eee5896ef5bb"
+	signatureDigest   = "sha256:9f13e0ac480cf86a5c9ec5d173001bbb6ec455f501f1812f0b0ad1f3468e8cfa"
+	artifactType      = "testArtifactType"
 )
 
-func TestVerifySubject_ResolveSubjectDescriptor_Failed(t *testing.T) {
+type mockPolicyProvider struct {
+	result bool
+}
+
+func (p *mockPolicyProvider) VerifyNeeded(_ context.Context, _ common.Reference, _ ocispecs.ReferenceDescriptor) bool {
+	return true
+}
+
+func (p *mockPolicyProvider) ContinueVerifyOnFailure(_ context.Context, _ common.Reference, _ ocispecs.ReferenceDescriptor, _ types.VerifyResult) bool {
+	return true
+}
+
+func (p *mockPolicyProvider) ErrorToVerifyResult(_ context.Context, _ string, _ error) types.VerifyResult {
+	return types.VerifyResult{}
+}
+
+func (p *mockPolicyProvider) OverallVerifyResult(_ context.Context, _ []interface{}) bool {
+	return p.result
+}
+
+type mockStore struct {
+	referrers map[string][]ocispecs.ReferenceDescriptor
+}
+
+func (s *mockStore) Name() string {
+	return "mockStore"
+}
+
+func (s *mockStore) ListReferrers(_ context.Context, _ common.Reference, _ []string, _ string, subjectDesc *ocispecs.SubjectDescriptor) (referrerstore.ListReferrersResult, error) {
+	if s.referrers == nil {
+		return referrerstore.ListReferrersResult{}, errors.New("some error happened")
+	}
+	if _, ok := s.referrers[subjectDesc.Digest.String()]; ok {
+		return referrerstore.ListReferrersResult{
+			NextToken: "",
+			Referrers: s.referrers[subjectDesc.Digest.String()],
+		}, nil
+	}
+	return referrerstore.ListReferrersResult{}, nil
+}
+
+func (s *mockStore) GetBlobContent(_ context.Context, _ common.Reference, _ digest.Digest) ([]byte, error) {
+	return nil, nil
+}
+
+func (s *mockStore) GetReferenceManifest(_ context.Context, _ common.Reference, _ ocispecs.ReferenceDescriptor) (ocispecs.ReferenceManifest, error) {
+	return ocispecs.ReferenceManifest{}, nil
+}
+
+func (s *mockStore) GetConfig() *storeConfig.StoreConfig {
+	return nil
+}
+
+func (s *mockStore) GetSubjectDescriptor(_ context.Context, subjectReference common.Reference) (*ocispecs.SubjectDescriptor, error) {
+	if subjectReference.Tag == "v1" {
+		return &ocispecs.SubjectDescriptor{
+			Descriptor: oci.Descriptor{
+				Digest: subjectDigest,
+			},
+		}, nil
+	}
+	return &ocispecs.SubjectDescriptor{
+		Descriptor: oci.Descriptor{
+			Digest: subjectReference.Digest,
+		},
+	}, nil
+}
+
+type mockVerifier struct {
+	canVerify      bool
+	verifierResult verifier.VerifierResult
+}
+
+func (v *mockVerifier) Name() string {
+	return "mockVerifier"
+}
+
+func (v *mockVerifier) CanVerify(_ context.Context, _ ocispecs.ReferenceDescriptor) bool {
+	return v.canVerify
+}
+
+func (v *mockVerifier) Verify(_ context.Context,
+	_ common.Reference,
+	_ ocispecs.ReferenceDescriptor,
+	_ referrerstore.ReferrerStore) (verifier.VerifierResult, error) {
+	if reflect.DeepEqual(v.verifierResult, verifier.VerifierResult{}) {
+		return verifier.VerifierResult{}, errors.New("no verifier result")
+	}
+	return v.verifierResult, nil
+}
+
+func (v *mockVerifier) GetNestedReferences() []string {
+	return nil
+}
+
+func TestVerifySubjectInternalWithDecision_ResolveSubjectDescriptor_Failed(t *testing.T) {
 	executor := Executor{}
 
 	verifyParameters := e.VerifyParameters{
 		Subject: "localhost:5000/net-monitor:v1",
 	}
 
-	_, err := executor.verifySubjectInternal(context.Background(), verifyParameters)
+	_, err := executor.verifySubjectInternalWithDecision(context.Background(), verifyParameters)
 
 	if err == nil {
 		t.Fatal("expected subject parsing to fail")
 	}
 }
 
-func TestVerifySubject_ResolveSubjectDescriptor_Success(t *testing.T) {
+func TestVerifySubjectInternalWithDecision_ResolveSubjectDescriptor_Success(t *testing.T) {
 	testDigest := digest.FromString("test")
 	store := &mocks.TestStore{
 		References: []ocispecs.ReferenceDescriptor{},
@@ -69,14 +176,14 @@ func TestVerifySubject_ResolveSubjectDescriptor_Success(t *testing.T) {
 		Subject: "localhost:5000/net-monitor:v1",
 	}
 
-	if _, err := executor.verifySubjectInternal(context.Background(), verifyParameters); !errors.Is(err, ErrReferrersNotFound) {
+	if _, err := executor.verifySubjectInternalWithDecision(context.Background(), verifyParameters); !errors.Is(err, ErrReferrersNotFound) {
 		t.Fatalf("expected ErrReferrersNotFound actual %v", err)
 	}
 }
 
-func TestVerifySubject_Verify_NoReferrers(t *testing.T) {
+func TestVerifySubjectInternalWithDecision_Verify_NoReferrers(t *testing.T) {
 	testDigest := digest.FromString("test")
-	configPolicy := config.PolicyEnforcer{}
+	configPolicy := policyConfig.PolicyEnforcer{}
 	ex := &Executor{
 		PolicyEnforcer: configPolicy,
 		ReferrerStores: []referrerstore.ReferrerStore{&mocks.TestStore{
@@ -95,16 +202,16 @@ func TestVerifySubject_Verify_NoReferrers(t *testing.T) {
 		Subject: "localhost:5000/net-monitor:v1",
 	}
 
-	if _, err := ex.verifySubjectInternal(context.Background(), verifyParameters); !errors.Is(err, ErrReferrersNotFound) {
+	if _, err := ex.verifySubjectInternalWithDecision(context.Background(), verifyParameters); !errors.Is(err, ErrReferrersNotFound) {
 		t.Fatalf("expected ErrReferrersNotFound actual %v", err)
 	}
 }
 
-func TestVerifySubject_CanVerify_ExpectedResults(t *testing.T) {
+func TestVerifySubjectInternalWithDecision_CanVerify_ExpectedResults(t *testing.T) {
 	testDigest := digest.FromString("test")
-	configPolicy := config.PolicyEnforcer{
-		ArtifactTypePolicies: map[string]types.ArtifactTypeVerifyPolicy{
-			testArtifactType1: types.AnyVerifySuccess,
+	configPolicy := policyConfig.PolicyEnforcer{
+		ArtifactTypePolicies: map[string]policyTypes.ArtifactTypeVerifyPolicy{
+			testArtifactType1: policyTypes.AnyVerifySuccess,
 		}}
 	store := &mocks.TestStore{References: []ocispecs.ReferenceDescriptor{
 		{
@@ -140,7 +247,7 @@ func TestVerifySubject_CanVerify_ExpectedResults(t *testing.T) {
 		Subject: "localhost:5000/net-monitor:v1",
 	}
 
-	result, err := ex.verifySubjectInternal(context.Background(), verifyParameters)
+	result, err := ex.verifySubjectInternalWithDecision(context.Background(), verifyParameters)
 
 	if err != nil {
 		t.Fatalf("verification failed with err %v", err)
@@ -155,12 +262,12 @@ func TestVerifySubject_CanVerify_ExpectedResults(t *testing.T) {
 	}
 }
 
-func TestVerifySubject_VerifyFailures_ExpectedResults(t *testing.T) {
+func TestVerifySubjectInternalWithDecision_VerifyFailures_ExpectedResults(t *testing.T) {
 	testDigest := digest.FromString("test")
 	testArtifactType := "test-type1"
-	configPolicy := config.PolicyEnforcer{
-		ArtifactTypePolicies: map[string]types.ArtifactTypeVerifyPolicy{
-			testArtifactType: types.AnyVerifySuccess,
+	configPolicy := policyConfig.PolicyEnforcer{
+		ArtifactTypePolicies: map[string]policyTypes.ArtifactTypeVerifyPolicy{
+			testArtifactType: policyTypes.AnyVerifySuccess,
 		}}
 	store := &mocks.TestStore{References: []ocispecs.ReferenceDescriptor{
 		{
@@ -196,7 +303,7 @@ func TestVerifySubject_VerifyFailures_ExpectedResults(t *testing.T) {
 		Subject: "localhost:5000/net-monitor:v1",
 	}
 
-	result, err := ex.verifySubjectInternal(context.Background(), verifyParameters)
+	result, err := ex.verifySubjectInternalWithDecision(context.Background(), verifyParameters)
 
 	if err != nil {
 		t.Fatalf("verification failed with err %v", err)
@@ -207,12 +314,12 @@ func TestVerifySubject_VerifyFailures_ExpectedResults(t *testing.T) {
 	}
 }
 
-func TestVerifySubject_VerifySuccess_ExpectedResults(t *testing.T) {
+func TestVerifySubjectInternalWithDecision_VerifySuccess_ExpectedResults(t *testing.T) {
 	testDigest := digest.FromString("test")
-	configPolicy := config.PolicyEnforcer{
-		ArtifactTypePolicies: map[string]types.ArtifactTypeVerifyPolicy{
-			testArtifactType1: types.AnyVerifySuccess,
-			testArtifactType2: types.AnyVerifySuccess,
+	configPolicy := policyConfig.PolicyEnforcer{
+		ArtifactTypePolicies: map[string]policyTypes.ArtifactTypeVerifyPolicy{
+			testArtifactType1: policyTypes.AnyVerifySuccess,
+			testArtifactType2: policyTypes.AnyVerifySuccess,
 		}}
 	store := &mocks.TestStore{References: []ocispecs.ReferenceDescriptor{
 		{
@@ -248,7 +355,7 @@ func TestVerifySubject_VerifySuccess_ExpectedResults(t *testing.T) {
 		Subject: "localhost:5000/net-monitor:v1",
 	}
 
-	result, err := ex.verifySubjectInternal(context.Background(), verifyParameters)
+	result, err := ex.verifySubjectInternalWithDecision(context.Background(), verifyParameters)
 
 	if err != nil {
 		t.Fatalf("verification failed with err %v", err)
@@ -263,13 +370,13 @@ func TestVerifySubject_VerifySuccess_ExpectedResults(t *testing.T) {
 	}
 }
 
-// TestVerifySubject_MultipleArtifacts_ExpectedResults tests multiple artifacts are verified concurrently
-func TestVerifySubject_MultipleArtifacts_ExpectedResults(t *testing.T) {
+// TestVerifySubjectInternalWithDecision_MultipleArtifacts_ExpectedResults tests multiple artifacts are verified concurrently
+func TestVerifySubjectInternalWithDecision_MultipleArtifacts_ExpectedResults(t *testing.T) {
 	testDigest := digest.FromString("test")
-	configPolicy := config.PolicyEnforcer{
-		ArtifactTypePolicies: map[string]types.ArtifactTypeVerifyPolicy{
-			testArtifactType1: types.AnyVerifySuccess,
-			testArtifactType2: types.AnyVerifySuccess,
+	configPolicy := policyConfig.PolicyEnforcer{
+		ArtifactTypePolicies: map[string]policyTypes.ArtifactTypeVerifyPolicy{
+			testArtifactType1: policyTypes.AnyVerifySuccess,
+			testArtifactType2: policyTypes.AnyVerifySuccess,
 		}}
 	store := &mocks.TestStore{References: []ocispecs.ReferenceDescriptor{
 		{
@@ -308,7 +415,7 @@ func TestVerifySubject_MultipleArtifacts_ExpectedResults(t *testing.T) {
 		Subject: "localhost:5000/net-monitor:v1",
 	}
 
-	result, err := ex.verifySubjectInternal(context.Background(), verifyParameters)
+	result, err := ex.verifySubjectInternalWithDecision(context.Background(), verifyParameters)
 
 	if err != nil {
 		t.Fatalf("verification failed with err %v", err)
@@ -327,10 +434,10 @@ func TestVerifySubject_MultipleArtifacts_ExpectedResults(t *testing.T) {
 	}
 }
 
-// TestVerifySubject_NestedReferences_Expected tests verifier config can specify nested references
-func TestVerifySubject_NestedReferences_Expected(t *testing.T) {
-	configPolicy := config.PolicyEnforcer{
-		ArtifactTypePolicies: map[string]types.ArtifactTypeVerifyPolicy{
+// TestVerifySubjectInternalWithDecision_NestedReferences_Expected tests verifier config can specify nested references
+func TestVerifySubjectInternalWithDecision_NestedReferences_Expected(t *testing.T) {
+	configPolicy := policyConfig.PolicyEnforcer{
+		ArtifactTypePolicies: map[string]policyTypes.ArtifactTypeVerifyPolicy{
 			"default": "all",
 		}}
 
@@ -370,7 +477,7 @@ func TestVerifySubject_NestedReferences_Expected(t *testing.T) {
 		Subject: mocks.TestSubjectWithDigest,
 	}
 
-	result, err := ex.verifySubjectInternal(context.Background(), verifyParameters)
+	result, err := ex.verifySubjectInternalWithDecision(context.Background(), verifyParameters)
 
 	if err != nil {
 		t.Fatalf("verification failed with err %v", err)
@@ -406,10 +513,10 @@ func TestVerifySubject_NestedReferences_Expected(t *testing.T) {
 	}
 }
 
-// TestVerifySubject__NoNestedReferences_Expected tests verifier config can specify no nested references
-func TestVerifySubject_NoNestedReferences_Expected(t *testing.T) {
-	configPolicy := config.PolicyEnforcer{
-		ArtifactTypePolicies: map[string]types.ArtifactTypeVerifyPolicy{
+// TestVerifySubjectInternalWithDecision__NoNestedReferences_Expected tests verifier config can specify no nested references
+func TestVerifySubjectInternalWithDecision_NoNestedReferences_Expected(t *testing.T) {
+	configPolicy := policyConfig.PolicyEnforcer{
+		ArtifactTypePolicies: map[string]policyTypes.ArtifactTypeVerifyPolicy{
 			"default": "all",
 		}}
 	store := mocks.CreateNewTestStoreForNestedSbom()
@@ -447,7 +554,7 @@ func TestVerifySubject_NoNestedReferences_Expected(t *testing.T) {
 		Subject: mocks.TestSubjectWithDigest,
 	}
 
-	result, err := ex.verifySubjectInternal(context.Background(), verifyParameters)
+	result, err := ex.verifySubjectInternalWithDecision(context.Background(), verifyParameters)
 
 	if err != nil {
 		t.Fatalf("verification failed with err %v", err)
@@ -486,7 +593,7 @@ func TestGetVerifyRequestTimeout_ExpectedResults(t *testing.T) {
 		{
 			setTimeout: -1,
 			ex: Executor{
-				PolicyEnforcer: config.PolicyEnforcer{},
+				PolicyEnforcer: policyConfig.PolicyEnforcer{},
 				ReferrerStores: []referrerstore.ReferrerStore{},
 				Verifiers:      []verifier.ReferenceVerifier{},
 				Config:         nil,
@@ -496,7 +603,7 @@ func TestGetVerifyRequestTimeout_ExpectedResults(t *testing.T) {
 		{
 			setTimeout: -1,
 			ex: Executor{
-				PolicyEnforcer: config.PolicyEnforcer{},
+				PolicyEnforcer: policyConfig.PolicyEnforcer{},
 				ReferrerStores: []referrerstore.ReferrerStore{},
 				Verifiers:      []verifier.ReferenceVerifier{},
 				Config: &exConfig.ExecutorConfig{
@@ -509,7 +616,7 @@ func TestGetVerifyRequestTimeout_ExpectedResults(t *testing.T) {
 		{
 			setTimeout: 5000,
 			ex: Executor{
-				PolicyEnforcer: config.PolicyEnforcer{},
+				PolicyEnforcer: policyConfig.PolicyEnforcer{},
 				ReferrerStores: []referrerstore.ReferrerStore{},
 				Verifiers:      []verifier.ReferenceVerifier{},
 				Config: &exConfig.ExecutorConfig{
@@ -543,7 +650,7 @@ func TestGetMutationRequestTimeout_ExpectedResults(t *testing.T) {
 		{
 			setTimeout: -1,
 			ex: Executor{
-				PolicyEnforcer: config.PolicyEnforcer{},
+				PolicyEnforcer: policyConfig.PolicyEnforcer{},
 				ReferrerStores: []referrerstore.ReferrerStore{},
 				Verifiers:      []verifier.ReferenceVerifier{},
 				Config:         nil,
@@ -553,7 +660,7 @@ func TestGetMutationRequestTimeout_ExpectedResults(t *testing.T) {
 		{
 			setTimeout: -1,
 			ex: Executor{
-				PolicyEnforcer: config.PolicyEnforcer{},
+				PolicyEnforcer: policyConfig.PolicyEnforcer{},
 				ReferrerStores: []referrerstore.ReferrerStore{},
 				Verifiers:      []verifier.ReferenceVerifier{},
 				Config: &exConfig.ExecutorConfig{
@@ -566,7 +673,7 @@ func TestGetMutationRequestTimeout_ExpectedResults(t *testing.T) {
 		{
 			setTimeout: 2400,
 			ex: Executor{
-				PolicyEnforcer: config.PolicyEnforcer{},
+				PolicyEnforcer: policyConfig.PolicyEnforcer{},
 				ReferrerStores: []referrerstore.ReferrerStore{},
 				Verifiers:      []verifier.ReferenceVerifier{},
 				Config: &exConfig.ExecutorConfig{
@@ -589,3 +696,161 @@ func TestGetMutationRequestTimeout_ExpectedResults(t *testing.T) {
 		}
 	}
 }
+
+func TestVerifySubject(t *testing.T) {
+	if err := os.Setenv("RATIFY_USE_REGO_POLICY", "1"); err != nil {
+		t.Fatalf("failed to set env var: %v", err)
+	}
+	featureflag.InitFeatureFlagsFromEnv()
+
+	testCases := []struct {
+		name           string
+		params         e.VerifyParameters
+		expectedResult types.VerifyResult
+		stores         []referrerstore.ReferrerStore
+		policyEnforcer policyprovider.PolicyProvider
+		verifiers      []verifier.ReferenceVerifier
+		referrers      []ocispecs.ReferenceDescriptor
+		expectErr      bool
+	}{
+		{
+			name:      "verify subject with invalid subject",
+			params:    e.VerifyParameters{},
+			expectErr: true,
+		},
+		{
+			name: "error from ListReferrers",
+			params: e.VerifyParameters{
+				Subject: subject1,
+			},
+			stores: []referrerstore.ReferrerStore{
+				&mockStore{
+					referrers: nil,
+				},
+			},
+			expectErr:      true,
+			expectedResult: types.VerifyResult{},
+		},
+		{
+			name: "empty referrers",
+			params: e.VerifyParameters{
+				Subject: subject1,
+			},
+			stores: []referrerstore.ReferrerStore{
+				&mockStore{
+					referrers: nil,
+				},
+			},
+			expectErr:      true,
+			expectedResult: types.VerifyResult{},
+		},
+		{
+			name: "one signature without matching verifier",
+			params: e.VerifyParameters{
+				Subject: subject1,
+			},
+			stores: []referrerstore.ReferrerStore{
+				&mockStore{
+					referrers: map[string][]ocispecs.ReferenceDescriptor{
+						subjectDigest: {
+							{
+								ArtifactType: artifactType,
+								Descriptor: oci.Descriptor{
+									Digest: signatureDigest,
+								},
+							},
+						},
+					},
+				},
+			},
+			verifiers: []verifier.ReferenceVerifier{
+				&mockVerifier{
+					canVerify: false,
+				},
+			},
+			policyEnforcer: &mockPolicyProvider{result: true},
+			expectErr:      false,
+			expectedResult: types.VerifyResult{
+				IsSuccess: true,
+			},
+		},
+		{
+			name: "one signature with verifier failed",
+			params: e.VerifyParameters{
+				Subject: subject1,
+			},
+			stores: []referrerstore.ReferrerStore{
+				&mockStore{
+					referrers: map[string][]ocispecs.ReferenceDescriptor{
+						subjectDigest: {
+							{
+								ArtifactType: artifactType,
+								Descriptor: oci.Descriptor{
+									Digest: signatureDigest,
+								},
+							},
+						},
+					},
+				},
+			},
+			verifiers: []verifier.ReferenceVerifier{
+				&mockVerifier{
+					canVerify: true,
+					verifierResult: verifier.VerifierResult{
+						IsSuccess: false,
+					},
+				},
+			},
+			policyEnforcer: &mockPolicyProvider{result: false},
+			expectErr:      false,
+			expectedResult: types.VerifyResult{IsSuccess: false},
+		},
+		{
+			name: "one signature with verifier success",
+			params: e.VerifyParameters{
+				Subject: subject1,
+			},
+			stores: []referrerstore.ReferrerStore{
+				&mockStore{
+					referrers: map[string][]ocispecs.ReferenceDescriptor{
+						subjectDigest: {
+							{
+								ArtifactType: artifactType,
+								Descriptor: oci.Descriptor{
+									Digest: signatureDigest,
+								},
+							},
+						},
+					},
+				},
+			},
+			verifiers: []verifier.ReferenceVerifier{
+				&mockVerifier{
+					canVerify: true,
+					verifierResult: verifier.VerifierResult{
+						IsSuccess: true,
+					},
+				},
+			},
+			policyEnforcer: &mockPolicyProvider{result: true},
+			expectErr:      false,
+			expectedResult: types.VerifyResult{
+				IsSuccess: true,
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			ex := &Executor{tc.stores, tc.policyEnforcer, tc.verifiers, nil}
+
+			result, err := ex.VerifySubject(context.Background(), tc.params)
+			if (err != nil) != tc.expectErr {
+				t.Fatalf("expected error %v but got %v", tc.expectErr, err)
+			}
+			if result.IsSuccess != tc.expectedResult.IsSuccess {
+				t.Fatalf("expected result: %+v but got: %+v", tc.expectedResult, result)
+			}
+		})
+	}
+}
diff --git a/pkg/policyprovider/policyengine/factory_test.go b/pkg/policyprovider/policyengine/factory_test.go
new file mode 100644
index 0000000..4299871
--- /dev/null
+++ b/pkg/policyprovider/policyengine/factory_test.go
@@ -0,0 +1,153 @@
+/*
+Copyright The Ratify Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package policyengine
+
+import (
+	"context"
+	"errors"
+	"reflect"
+	"testing"
+)
+
+type mockEngine struct{}
+
+func (e *mockEngine) Evaluate(_ context.Context, _ map[string]interface{}) (bool, error) {
+	return true, nil
+}
+
+type mockFactory struct {
+	returnErr bool
+}
+
+func (f *mockFactory) Create(_ string, _ string) (PolicyEngine, error) {
+	if f.returnErr {
+		return nil, errors.New("error")
+	}
+	return &mockEngine{}, nil
+}
+
+func TestRegister(t *testing.T) {
+	testcases := []struct {
+		name        string
+		engineName  string
+		factory     EngineFactory
+		factories   map[string]EngineFactory
+		expectPanic bool
+	}{
+		{
+			name:        "nil factory",
+			engineName:  "test",
+			factory:     nil,
+			expectPanic: true,
+		},
+		{
+			name:       "engine duplicated",
+			engineName: "test",
+			factory:    &mockFactory{},
+			factories: map[string]EngineFactory{
+				"test": &mockFactory{},
+			},
+			expectPanic: true,
+		},
+		{
+			name:        "engine registered",
+			engineName:  "test",
+			factory:     &mockFactory{},
+			factories:   make(map[string]EngineFactory),
+			expectPanic: false,
+		},
+	}
+
+	for _, tc := range testcases {
+		t.Run(tc.name, func(t *testing.T) {
+			defer func() {
+				if r := recover(); r != nil {
+					if !tc.expectPanic {
+						t.Errorf("expected no panic, got %v", r)
+					}
+				}
+			}()
+			engineFactories = tc.factories
+			Register(tc.engineName, tc.factory)
+		})
+	}
+}
+
+func TestCreateEngineFromConf(t *testing.T) {
+	testcases := []struct {
+		name         string
+		config       Config
+		factories    map[string]EngineFactory
+		expectErr    bool
+		expectEngine PolicyEngine
+	}{
+		{
+			name: "empty engine name",
+			config: Config{
+				Name: "",
+			},
+			expectErr:    true,
+			expectEngine: nil,
+		},
+		{
+			name: "engine not found",
+			config: Config{
+				Name: "test",
+			},
+			factories:    map[string]EngineFactory{},
+			expectErr:    true,
+			expectEngine: nil,
+		},
+		{
+			name: "failed creating engine",
+			config: Config{
+				Name: "test",
+			},
+			factories: map[string]EngineFactory{
+				"test": &mockFactory{
+					returnErr: true,
+				},
+			},
+			expectErr:    true,
+			expectEngine: nil,
+		},
+		{
+			name: "engine created",
+			config: Config{
+				Name: "test",
+			},
+			factories: map[string]EngineFactory{
+				"test": &mockFactory{},
+			},
+			expectErr:    false,
+			expectEngine: &mockEngine{},
+		},
+	}
+
+	for _, tc := range testcases {
+		t.Run(tc.name, func(t *testing.T) {
+			engineFactories = tc.factories
+			engine, err := CreateEngineFromConfig(tc.config)
+			if tc.expectErr != (err != nil) {
+				t.Errorf("expected error %v, got %v", tc.expectErr, err)
+			}
+
+			if !reflect.DeepEqual(tc.expectEngine, engine) {
+				t.Errorf("expected engine %v, got %v", tc.expectEngine, engine)
+			}
+		})
+	}
+}
diff --git a/pkg/policyprovider/policyengine/opaengine/opa_engine_test.go b/pkg/policyprovider/policyengine/opaengine/opa_engine_test.go
new file mode 100644
index 0000000..49160c3
--- /dev/null
+++ b/pkg/policyprovider/policyengine/opaengine/opa_engine_test.go
@@ -0,0 +1,98 @@
+/*
+Copyright The Ratify Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package opa
+
+import (
+	"context"
+	"testing"
+
+	query "github.com/deislabs/ratify/pkg/policyprovider/policyquery/rego"
+)
+
+const (
+	policy1 = `
+package ratify.policy
+
+default valid := false
+ 
+valid {
+	input.method == "GET"
+}
+`
+	policy2 = "package"
+)
+
+type mockQuery struct{}
+
+func (q *mockQuery) Evaluate(_ context.Context, _ map[string]interface{}) (bool, error) {
+	return true, nil
+}
+
+func TestCreate(t *testing.T) {
+	testcases := []struct {
+		name          string
+		policy        string
+		queryLanguage string
+		expectErr     bool
+		expectEngine  bool
+	}{
+		{
+			name:          "empty policy",
+			policy:        "",
+			queryLanguage: "",
+			expectErr:     true,
+			expectEngine:  false,
+		},
+		{
+			name:          "invalid policy",
+			policy:        policy2,
+			queryLanguage: query.RegoName,
+			expectErr:     true,
+			expectEngine:  false,
+		},
+		{
+			name:          "valid policy",
+			policy:        policy1,
+			queryLanguage: query.RegoName,
+			expectErr:     false,
+			expectEngine:  true,
+		},
+	}
+
+	for _, tc := range testcases {
+		factory := &EngineFactory{}
+		engine, err := factory.Create(tc.policy, tc.queryLanguage)
+		if tc.expectErr != (err != nil) {
+			t.Fatalf("error = %v, expectErr = %v", err, tc.expectErr)
+		}
+		if tc.expectEngine != (engine != nil) {
+			t.Fatalf("engine = %v, expectEngine = %v", engine, tc.expectEngine)
+		}
+	}
+}
+
+func TestEvaluate(t *testing.T) {
+	engine := &Engine{
+		query: &mockQuery{},
+	}
+	result, err := engine.Evaluate(context.Background(), nil)
+	if err != nil {
+		t.Fatalf("expect no err, but got err: %v", err)
+	}
+	if !result {
+		t.Fatalf("expect result to be true")
+	}
+}
diff --git a/pkg/policyprovider/policyquery/factory_test.go b/pkg/policyprovider/policyquery/factory_test.go
new file mode 100644
index 0000000..6d50540
--- /dev/null
+++ b/pkg/policyprovider/policyquery/factory_test.go
@@ -0,0 +1,153 @@
+/*
+Copyright The Ratify Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package policyquery
+
+import (
+	"context"
+	"errors"
+	"reflect"
+	"testing"
+)
+
+type mockQuery struct{}
+
+func (q *mockQuery) Evaluate(_ context.Context, _ map[string]interface{}) (bool, error) {
+	return true, nil
+}
+
+type mockFactory struct {
+	returnErr bool
+}
+
+func (f *mockFactory) Create(_ string) (PolicyQuery, error) {
+	if f.returnErr {
+		return nil, errors.New("error")
+	}
+	return &mockQuery{}, nil
+}
+
+func TestRegister(t *testing.T) {
+	testcases := []struct {
+		name        string
+		queryName   string
+		factory     Factory
+		factories   map[string]Factory
+		expectPanic bool
+	}{
+		{
+			name:        "nil fatory",
+			queryName:   "test",
+			factory:     nil,
+			expectPanic: true,
+		},
+		{
+			name:      "factory duplicated",
+			queryName: "test",
+			factory:   &mockFactory{},
+			factories: map[string]Factory{
+				"test": &mockFactory{},
+			},
+			expectPanic: true,
+		},
+		{
+			name:        "factory registered",
+			queryName:   "test",
+			factory:     &mockFactory{},
+			factories:   map[string]Factory{},
+			expectPanic: false,
+		},
+	}
+
+	for _, tc := range testcases {
+		t.Run(tc.name, func(t *testing.T) {
+			defer func() {
+				if r := recover(); r != nil {
+					if !tc.expectPanic {
+						t.Errorf("expected no panic, got %v", r)
+					}
+				}
+			}()
+			policyQueryFactories = tc.factories
+			Register(tc.queryName, tc.factory)
+		})
+	}
+}
+
+func TestCreateQueryFromConfig(t *testing.T) {
+	testcases := []struct {
+		name        string
+		config      Config
+		factories   map[string]Factory
+		expectErr   bool
+		expectQuery PolicyQuery
+	}{
+		{
+			name: "empty query name",
+			config: Config{
+				Name: "",
+			},
+			expectErr:   true,
+			expectQuery: nil,
+		},
+		{
+			name: "query not found",
+			config: Config{
+				Name: "test",
+			},
+			factories:   map[string]Factory{},
+			expectErr:   true,
+			expectQuery: nil,
+		},
+		{
+			name: "failed creating query",
+			config: Config{
+				Name: "test",
+			},
+			factories: map[string]Factory{
+				"test": &mockFactory{
+					returnErr: true,
+				},
+			},
+			expectErr:   true,
+			expectQuery: nil,
+		},
+		{
+			name: "query created",
+			config: Config{
+				Name: "test",
+			},
+			factories: map[string]Factory{
+				"test": &mockFactory{},
+			},
+			expectErr:   false,
+			expectQuery: &mockQuery{},
+		},
+	}
+
+	for _, tc := range testcases {
+		t.Run(tc.name, func(t *testing.T) {
+			policyQueryFactories = tc.factories
+			query, err := CreateQueryFromConfig(tc.config)
+			if tc.expectErr != (err != nil) {
+				t.Errorf("expected error %v, got %v", tc.expectErr, err)
+			}
+
+			if !reflect.DeepEqual(tc.expectQuery, query) {
+				t.Errorf("expected query %v, got %v", tc.expectQuery, query)
+			}
+		})
+	}
+}
diff --git a/pkg/policyprovider/policyquery/rego/query_test.go b/pkg/policyprovider/policyquery/rego/query_test.go
new file mode 100644
index 0000000..b8488d8
--- /dev/null
+++ b/pkg/policyprovider/policyquery/rego/query_test.go
@@ -0,0 +1,119 @@
+/*
+Copyright The Ratify Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package query
+
+import (
+	"context"
+	"testing"
+)
+
+const (
+	policy1 = `
+  package ratify.policy
+  
+  default valid := false
+  
+  valid {
+	  input.method == "GET"
+  }
+  `
+	policy2 = "package"
+)
+
+func TestCreate(t *testing.T) {
+	testcases := []struct {
+		name        string
+		policy      string
+		expectErr   bool
+		expectQuery bool
+	}{
+		{
+			name:        "valid policy",
+			policy:      policy1,
+			expectErr:   false,
+			expectQuery: true,
+		},
+		{
+			name:        "invalid policy",
+			policy:      policy2,
+			expectErr:   true,
+			expectQuery: false,
+		},
+	}
+
+	for _, tc := range testcases {
+		t.Run(tc.name, func(t *testing.T) {
+			factory := &RegoFactory{}
+			query, err := factory.Create(tc.policy)
+			if tc.expectErr != (err != nil) {
+				t.Fatalf("error = %v, expectErr = %v", err, tc.expectErr)
+			}
+			if tc.expectQuery != (query != nil) {
+				t.Fatalf("query = %v, expectQuery = %v", query, tc.expectQuery)
+			}
+		})
+	}
+}
+
+func TestEvaluate(t *testing.T) {
+	factory := &RegoFactory{}
+	query, err := factory.Create(policy1)
+	if err != nil {
+		t.Fatalf("err = %v", err)
+	}
+
+	testcases := []struct {
+		name         string
+		input        map[string]interface{}
+		expectResult bool
+		expectErr    bool
+	}{
+		{
+			name:         "empty input",
+			input:        nil,
+			expectResult: false,
+			expectErr:    false,
+		},
+		{
+			name: "input with false result",
+			input: map[string]interface{}{
+				"method": "POST",
+			},
+			expectResult: false,
+			expectErr:    false,
+		},
+		{
+			name: "valid input",
+			input: map[string]interface{}{
+				"method": "GET",
+			},
+			expectResult: true,
+			expectErr:    false,
+		},
+	}
+
+	for _, tc := range testcases {
+		t.Run(tc.name, func(t *testing.T) {
+			result, err := query.Evaluate(context.Background(), tc.input)
+			if tc.expectErr != (err != nil) {
+				t.Fatalf("error = %v, expectErr = %v", err, tc.expectErr)
+			}
+			if tc.expectResult != result {
+				t.Fatalf("result = %v, expectResult = %v", result, tc.expectResult)
+			}
+		})
+	}
+}
diff --git a/pkg/policyprovider/regopolicy/regopolicy_test.go b/pkg/policyprovider/regopolicy/regopolicy_test.go
new file mode 100644
index 0000000..cc8b599
--- /dev/null
+++ b/pkg/policyprovider/regopolicy/regopolicy_test.go
@@ -0,0 +1,164 @@
+/*
+Copyright The Ratify Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package regopolicy
+
+import (
+	"context"
+	"errors"
+	"reflect"
+	"testing"
+
+	"github.com/deislabs/ratify/pkg/common"
+	"github.com/deislabs/ratify/pkg/executor/types"
+	"github.com/deislabs/ratify/pkg/ocispecs"
+	"github.com/deislabs/ratify/pkg/policyprovider/config"
+)
+
+const (
+	policy1 = `
+package ratify.policy
+
+default valid := false
+
+valid {
+    input.method == "GET"
+}
+`
+	policy2 = "package"
+)
+
+type policyEngine struct {
+	ReturnErr bool
+}
+
+func (e policyEngine) Evaluate(_ context.Context, _ map[string]interface{}) (bool, error) {
+	if e.ReturnErr {
+		return false, errors.New("error")
+	}
+	return true, nil
+}
+
+func TestCreate(t *testing.T) {
+	factory := &Factory{}
+	testCases := []struct {
+		name      string
+		config    config.PolicyPluginConfig
+		expectErr bool
+	}{
+		{
+			name:      "empty config",
+			config:    map[string]interface{}{},
+			expectErr: true,
+		},
+		{
+			name: "config with invalid policy",
+			config: map[string]interface{}{
+				"name":   "test",
+				"policy": policy2,
+			},
+			expectErr: true,
+		},
+		{
+			name: "config with valid policy",
+			config: map[string]interface{}{
+				"name":   "test",
+				"policy": policy1,
+			},
+			expectErr: false,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := factory.Create(tc.config)
+			if tc.expectErr != (err != nil) {
+				t.Fatalf("error = %v, expectErr = %v", err, tc.expectErr)
+			}
+		})
+	}
+}
+
+func TestVerifyNeeded(t *testing.T) {
+	policyEnforcer := &policyEnforcer{
+		Policy:    "",
+		OpaEngine: policyEngine{},
+	}
+	result := policyEnforcer.VerifyNeeded(context.Background(), common.Reference{}, ocispecs.ReferenceDescriptor{})
+	if result != true {
+		t.Fatalf("result = %v, expectResult = %v", result, true)
+	}
+}
+
+func TestContinueVerifyOnFailure(t *testing.T) {
+	policyEnforcer := &policyEnforcer{
+		Policy:    "",
+		OpaEngine: policyEngine{},
+	}
+	result := policyEnforcer.ContinueVerifyOnFailure(context.Background(), common.Reference{}, ocispecs.ReferenceDescriptor{}, types.VerifyResult{})
+
+	if !result {
+		t.Fatalf("result = %v, expectResult = %v", result, true)
+	}
+}
+
+func TestErrorToVerifyResult(t *testing.T) {
+	policyEnforcer := &policyEnforcer{
+		Policy:    "",
+		OpaEngine: policyEngine{},
+	}
+	result := policyEnforcer.ErrorToVerifyResult(context.Background(), "", nil)
+
+	if !reflect.DeepEqual(result, types.VerifyResult{}) {
+		t.Fatalf("result = %v, expectResult = %v", result, types.VerifyResult{})
+	}
+}
+
+func TestOverallVerifyResult(t *testing.T) {
+	testcases := []struct {
+		name         string
+		reports      []interface{}
+		returnErr    bool
+		expectResult bool
+	}{
+		{
+			name:         "opa engine returns error",
+			reports:      []interface{}{types.VerifyResult{}},
+			expectResult: false,
+			returnErr:    true,
+		},
+		{
+			name:         "opa engine returns result",
+			reports:      []interface{}{types.VerifyResult{}},
+			expectResult: true,
+			returnErr:    false,
+		},
+	}
+
+	for _, tc := range testcases {
+		t.Run(tc.name, func(t *testing.T) {
+			policyEnforcer := &policyEnforcer{
+				Policy: "",
+				OpaEngine: policyEngine{
+					ReturnErr: tc.returnErr,
+				},
+			}
+			result := policyEnforcer.OverallVerifyResult(context.Background(), tc.reports)
+			if result != tc.expectResult {
+				t.Fatalf("result = %v, expectResult = %v", result, tc.expectResult)
+			}
+		})
+	}
+}
diff --git a/test/bats/cli-test.bats b/test/bats/cli-test.bats
index b38d43d..b073070 100644
--- a/test/bats/cli-test.bats
+++ b/test/bats/cli-test.bats
@@ -24,6 +24,14 @@ load helpers
     assert_cmd_verify_failure
 }
 
+@test "notation verifier leaf cert with rego policy" {
+    run bin/ratify verify -c $RATIFY_DIR/config_rego_policy_notation_root_cert.json -s $TEST_REGISTRY/notation:leafSigned
+    assert_cmd_verify_success
+
+    run bin/ratify verify -c $RATIFY_DIR/config_rego_policy_notation_leaf_cert.json -s $TEST_REGISTRY/notation:leafSigned
+    assert_cmd_verify_failure
+}
+
 @test "cosign verifier test" {
     run bin/ratify verify -c $RATIFY_DIR/config.json -s $TEST_REGISTRY/cosign:signed-key
     assert_cmd_verify_success
diff --git a/test/bats/tests/config/config_rego_policy_notation_leaf_cert.json b/test/bats/tests/config/config_rego_policy_notation_leaf_cert.json
new file mode 100644
index 0000000..863d2ca
--- /dev/null
+++ b/test/bats/tests/config/config_rego_policy_notation_leaf_cert.json
@@ -0,0 +1,53 @@
+{
+    "executor": {},
+    "store": {
+        "version": "1.0.0",
+        "plugins": [
+            {
+                "name": "oras",
+                "cosignEnabled": true,
+                "useHttp": true
+            }
+        ]
+    },
+    "policy": {
+        "version": "1.0.0",
+        "plugin": {
+            "name": "regoPolicy",
+            "policyPath": "",
+            "policy": "package ratify.policy\ndefault valid := false\nvalid {\n  not failed_verify(input)\n}\nfailed_verify(reports) {\n  [path, value] := walk(reports)\n  value == false\n  path[count(path) - 1] == \"isSuccess\"\n}"
+        }
+    },
+    "verifier": {
+        "version": "1.0.0",
+        "plugins": [
+            {
+                "name": "notaryv2",
+                "artifactTypes": "application/vnd.cncf.notary.signature",
+                "verificationCerts": [
+                    "~/.config/notation/truststore/x509/ca/leaf-test/leaf.crt"
+                ],
+                "trustPolicyDoc": {
+                    "version": "1.0",
+                    "trustPolicies": [
+                        {
+                            "name": "default",
+                            "registryScopes": [
+                                "*"
+                            ],
+                            "signatureVerification": {
+                                "level": "strict"
+                            },
+                            "trustStores": [
+                                "ca:certs"
+                            ],
+                            "trustedIdentities": [
+                                "*"
+                            ]
+                        }
+                    ]
+                }
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/test/bats/tests/config/config_rego_policy_notation_root_cert.json b/test/bats/tests/config/config_rego_policy_notation_root_cert.json
new file mode 100644
index 0000000..ea49517
--- /dev/null
+++ b/test/bats/tests/config/config_rego_policy_notation_root_cert.json
@@ -0,0 +1,53 @@
+{
+    "executor": {},
+    "store": {
+        "version": "1.0.0",
+        "plugins": [
+            {
+                "name": "oras",
+                "cosignEnabled": true,
+                "useHttp": true
+            }
+        ]
+    },
+    "policy": {
+        "version": "1.0.0",
+        "plugin": {
+            "name": "regoPolicy",
+            "policyPath": "",
+            "policy": "package ratify.policy\ndefault valid := false\nvalid {\n  not failed_verify(input)\n}\nfailed_verify(reports) {\n  [path, value] := walk(reports)\n  value == false\n  path[count(path) - 1] == \"isSuccess\"\n}"
+        }
+    },
+    "verifier": {
+        "version": "1.0.0",
+        "plugins": [
+            {
+                "name": "notaryv2",
+                "artifactTypes": "application/vnd.cncf.notary.signature",
+                "verificationCerts": [
+                    "~/.config/notation/truststore/x509/ca/leaf-test/root.crt"
+                ],
+                "trustPolicyDoc": {
+                    "version": "1.0",
+                    "trustPolicies": [
+                        {
+                            "name": "default",
+                            "registryScopes": [
+                                "*"
+                            ],
+                            "signatureVerification": {
+                                "level": "strict"
+                            },
+                            "trustStores": [
+                                "ca:certs"
+                            ],
+                            "trustedIdentities": [
+                                "*"
+                            ]
+                        }
+                    ]
+                }
+            }
+        ]
+    }
+}
\ No newline at end of file