Repository: deislabs/ratify
Pull request #933 (2023-07-13 13:43:20)

feat: add policy crd and controller
# Description

## What this PR does / why we need it:
1. Add Policy CRD to support switching between config/rego policies in runtime.
2. Add Policy controller that manipulate the active policy in Ratify. There would be at most 1 policy existing in Ratify as Ratify cannot apply multiple policies. That means when users apply a new CR, the old CR would be removed from cluster. And if there are no CR added, config policy will be the default one.
3. Refactor executor to remove duplicate code, there is no actual logic change.
4. Add a new API(GetPolicyType) to PolicyProvider interface to distinguish between policy enforcers without featureFlag.
5. Remove `PassthroughMode` from featureFlag, add it as a field under `RegoPolicyConfig`
6. Remove `Use_Rego_Policy` from featureFlag, but add it as a regular chart value.
7. Update docs.

## 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)*:
Fixes https://github.com/deislabs/ratify/issues/936

## 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?

Please describe the tests that you ran to verify your changes. Please also list any relevant details for your test configuration

Updated e2e tests.
Manual test:
1. Deploy ratify with config policy.
2. Apply Rego Policy CR.
3. Apply Config Policy CR.
4. Apply Rego Policy CR.
5. Delete Rego Policy CR.

# 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 bb53957..0016d86 100644
--- a/.github/workflows/build-pr.yml
+++ b/.github/workflows/build-pr.yml
@@ -98,8 +98,7 @@ jobs:
           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 deploy-rego-policy
           make test-e2e
       - name: Save logs
         if: ${{ always() }}
diff --git a/Makefile b/Makefile
index d3d8c7c..2343d39 100644
--- a/Makefile
+++ b/Makefile
@@ -108,6 +108,10 @@ delete-demo-constraints:
 	kubectl delete -f ./library/default/template.yaml
 	kubectl delete -f ./library/default/samples/constraint.yaml
 
+.PHONY: deploy-rego-policy
+deploy-rego-policy:
+	kubectl apply -f ./config/samples/policy/config_v1alpha1_policy_rego.yaml
+
 .PHONY: deploy-gatekeeper
 deploy-gatekeeper:
 	helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
@@ -442,9 +446,6 @@ 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
 
@@ -455,7 +456,6 @@ 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/api/unversioned/policy_types.go b/api/unversioned/policy_types.go
new file mode 100644
index 0000000..528f9e0
--- /dev/null
+++ b/api/unversioned/policy_types.go
@@ -0,0 +1,52 @@
+/*
+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 unversioned
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	runtime "k8s.io/apimachinery/pkg/runtime"
+)
+
+// PolicySpec defines the desired state of Policy
+type PolicySpec struct {
+	// Important: Run "make" to regenerate code after modifying this file
+
+	// Parameters for this policy
+	Parameters runtime.RawExtension `json:"parameters,omitempty"`
+}
+
+// PolicyStatus defines the observed state of Policy
+type PolicyStatus struct {
+	// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
+	// Important: Run "make" to regenerate code after modifying this file
+}
+
+// Policy is the Schema for the policies API
+type Policy struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec   PolicySpec   `json:"spec,omitempty"`
+	Status PolicyStatus `json:"status,omitempty"`
+}
+
+// PolicyList contains a list of Policy
+type PolicyList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []Policy `json:"items"`
+}
diff --git a/api/unversioned/zz_generated.deepcopy.go b/api/unversioned/zz_generated.deepcopy.go
index fadbe60..6ac10b4 100644
--- a/api/unversioned/zz_generated.deepcopy.go
+++ b/api/unversioned/zz_generated.deepcopy.go
@@ -118,6 +118,80 @@ func (in *PluginSource) DeepCopy() *PluginSource {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Policy) DeepCopyInto(out *Policy) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	out.Status = in.Status
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Policy.
+func (in *Policy) DeepCopy() *Policy {
+	if in == nil {
+		return nil
+	}
+	out := new(Policy)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PolicyList) DeepCopyInto(out *PolicyList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]Policy, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyList.
+func (in *PolicyList) DeepCopy() *PolicyList {
+	if in == nil {
+		return nil
+	}
+	out := new(PolicyList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PolicySpec) DeepCopyInto(out *PolicySpec) {
+	*out = *in
+	in.Parameters.DeepCopyInto(&out.Parameters)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicySpec.
+func (in *PolicySpec) DeepCopy() *PolicySpec {
+	if in == nil {
+		return nil
+	}
+	out := new(PolicySpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PolicyStatus) DeepCopyInto(out *PolicyStatus) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyStatus.
+func (in *PolicyStatus) DeepCopy() *PolicyStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(PolicyStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Store) DeepCopyInto(out *Store) {
 	*out = *in
diff --git a/api/v1alpha1/policy_types.go b/api/v1alpha1/policy_types.go
new file mode 100644
index 0000000..7b7ade3
--- /dev/null
+++ b/api/v1alpha1/policy_types.go
@@ -0,0 +1,65 @@
+/*
+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 v1alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	runtime "k8s.io/apimachinery/pkg/runtime"
+)
+
+// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!
+// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.
+
+// PolicySpec defines the desired state of Policy
+type PolicySpec struct {
+	// Important: Run "make" to regenerate code after modifying this file
+
+	// +kubebuilder:pruning:PreserveUnknownFields
+	// Parameters for this policy
+	Parameters runtime.RawExtension `json:"parameters,omitempty"`
+}
+
+// PolicyStatus defines the observed state of Policy
+type PolicyStatus struct {
+	// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
+	// Important: Run "make" to regenerate code after modifying this file
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:resource:scope="Cluster"
+// +kubebuilder:deprecatedversion:warning="v1alpha1 of the Policy API has been deprecated. Please migrate to v1beta1."
+// Policy is the Schema for the policies API
+type Policy struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec   PolicySpec   `json:"spec,omitempty"`
+	Status PolicyStatus `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:storageversion
+// PolicyList contains a list of Policy
+type PolicyList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []Policy `json:"items"`
+}
+
+func init() {
+	SchemeBuilder.Register(&Policy{}, &PolicyList{})
+}
diff --git a/api/v1alpha1/zz_generated.conversion.go b/api/v1alpha1/zz_generated.conversion.go
index 27f4a33..18e3e83 100644
--- a/api/v1alpha1/zz_generated.conversion.go
+++ b/api/v1alpha1/zz_generated.conversion.go
@@ -80,6 +80,46 @@ func RegisterConversions(s *runtime.Scheme) error {
 	}); err != nil {
 		return err
 	}
+	if err := s.AddGeneratedConversionFunc((*Policy)(nil), (*unversioned.Policy)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha1_Policy_To_unversioned_Policy(a.(*Policy), b.(*unversioned.Policy), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*unversioned.Policy)(nil), (*Policy)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_unversioned_Policy_To_v1alpha1_Policy(a.(*unversioned.Policy), b.(*Policy), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*PolicyList)(nil), (*unversioned.PolicyList)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha1_PolicyList_To_unversioned_PolicyList(a.(*PolicyList), b.(*unversioned.PolicyList), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*unversioned.PolicyList)(nil), (*PolicyList)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_unversioned_PolicyList_To_v1alpha1_PolicyList(a.(*unversioned.PolicyList), b.(*PolicyList), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*PolicySpec)(nil), (*unversioned.PolicySpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha1_PolicySpec_To_unversioned_PolicySpec(a.(*PolicySpec), b.(*unversioned.PolicySpec), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*unversioned.PolicySpec)(nil), (*PolicySpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_unversioned_PolicySpec_To_v1alpha1_PolicySpec(a.(*unversioned.PolicySpec), b.(*PolicySpec), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*PolicyStatus)(nil), (*unversioned.PolicyStatus)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha1_PolicyStatus_To_unversioned_PolicyStatus(a.(*PolicyStatus), b.(*unversioned.PolicyStatus), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*unversioned.PolicyStatus)(nil), (*PolicyStatus)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_unversioned_PolicyStatus_To_v1alpha1_PolicyStatus(a.(*unversioned.PolicyStatus), b.(*PolicyStatus), scope)
+	}); err != nil {
+		return err
+	}
 	if err := s.AddGeneratedConversionFunc((*Store)(nil), (*unversioned.Store)(nil), func(a, b interface{}, scope conversion.Scope) error {
 		return Convert_v1alpha1_Store_To_unversioned_Store(a.(*Store), b.(*unversioned.Store), scope)
 	}); err != nil {
@@ -304,6 +344,98 @@ func Convert_unversioned_PluginSource_To_v1alpha1_PluginSource(in *unversioned.P
 	return autoConvert_unversioned_PluginSource_To_v1alpha1_PluginSource(in, out, s)
 }
 
+func autoConvert_v1alpha1_Policy_To_unversioned_Policy(in *Policy, out *unversioned.Policy, s conversion.Scope) error {
+	out.ObjectMeta = in.ObjectMeta
+	if err := Convert_v1alpha1_PolicySpec_To_unversioned_PolicySpec(&in.Spec, &out.Spec, s); err != nil {
+		return err
+	}
+	if err := Convert_v1alpha1_PolicyStatus_To_unversioned_PolicyStatus(&in.Status, &out.Status, s); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Convert_v1alpha1_Policy_To_unversioned_Policy is an autogenerated conversion function.
+func Convert_v1alpha1_Policy_To_unversioned_Policy(in *Policy, out *unversioned.Policy, s conversion.Scope) error {
+	return autoConvert_v1alpha1_Policy_To_unversioned_Policy(in, out, s)
+}
+
+func autoConvert_unversioned_Policy_To_v1alpha1_Policy(in *unversioned.Policy, out *Policy, s conversion.Scope) error {
+	out.ObjectMeta = in.ObjectMeta
+	if err := Convert_unversioned_PolicySpec_To_v1alpha1_PolicySpec(&in.Spec, &out.Spec, s); err != nil {
+		return err
+	}
+	if err := Convert_unversioned_PolicyStatus_To_v1alpha1_PolicyStatus(&in.Status, &out.Status, s); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Convert_unversioned_Policy_To_v1alpha1_Policy is an autogenerated conversion function.
+func Convert_unversioned_Policy_To_v1alpha1_Policy(in *unversioned.Policy, out *Policy, s conversion.Scope) error {
+	return autoConvert_unversioned_Policy_To_v1alpha1_Policy(in, out, s)
+}
+
+func autoConvert_v1alpha1_PolicyList_To_unversioned_PolicyList(in *PolicyList, out *unversioned.PolicyList, s conversion.Scope) error {
+	out.ListMeta = in.ListMeta
+	out.Items = *(*[]unversioned.Policy)(unsafe.Pointer(&in.Items))
+	return nil
+}
+
+// Convert_v1alpha1_PolicyList_To_unversioned_PolicyList is an autogenerated conversion function.
+func Convert_v1alpha1_PolicyList_To_unversioned_PolicyList(in *PolicyList, out *unversioned.PolicyList, s conversion.Scope) error {
+	return autoConvert_v1alpha1_PolicyList_To_unversioned_PolicyList(in, out, s)
+}
+
+func autoConvert_unversioned_PolicyList_To_v1alpha1_PolicyList(in *unversioned.PolicyList, out *PolicyList, s conversion.Scope) error {
+	out.ListMeta = in.ListMeta
+	out.Items = *(*[]Policy)(unsafe.Pointer(&in.Items))
+	return nil
+}
+
+// Convert_unversioned_PolicyList_To_v1alpha1_PolicyList is an autogenerated conversion function.
+func Convert_unversioned_PolicyList_To_v1alpha1_PolicyList(in *unversioned.PolicyList, out *PolicyList, s conversion.Scope) error {
+	return autoConvert_unversioned_PolicyList_To_v1alpha1_PolicyList(in, out, s)
+}
+
+func autoConvert_v1alpha1_PolicySpec_To_unversioned_PolicySpec(in *PolicySpec, out *unversioned.PolicySpec, s conversion.Scope) error {
+	out.Parameters = in.Parameters
+	return nil
+}
+
+// Convert_v1alpha1_PolicySpec_To_unversioned_PolicySpec is an autogenerated conversion function.
+func Convert_v1alpha1_PolicySpec_To_unversioned_PolicySpec(in *PolicySpec, out *unversioned.PolicySpec, s conversion.Scope) error {
+	return autoConvert_v1alpha1_PolicySpec_To_unversioned_PolicySpec(in, out, s)
+}
+
+func autoConvert_unversioned_PolicySpec_To_v1alpha1_PolicySpec(in *unversioned.PolicySpec, out *PolicySpec, s conversion.Scope) error {
+	out.Parameters = in.Parameters
+	return nil
+}
+
+// Convert_unversioned_PolicySpec_To_v1alpha1_PolicySpec is an autogenerated conversion function.
+func Convert_unversioned_PolicySpec_To_v1alpha1_PolicySpec(in *unversioned.PolicySpec, out *PolicySpec, s conversion.Scope) error {
+	return autoConvert_unversioned_PolicySpec_To_v1alpha1_PolicySpec(in, out, s)
+}
+
+func autoConvert_v1alpha1_PolicyStatus_To_unversioned_PolicyStatus(in *PolicyStatus, out *unversioned.PolicyStatus, s conversion.Scope) error {
+	return nil
+}
+
+// Convert_v1alpha1_PolicyStatus_To_unversioned_PolicyStatus is an autogenerated conversion function.
+func Convert_v1alpha1_PolicyStatus_To_unversioned_PolicyStatus(in *PolicyStatus, out *unversioned.PolicyStatus, s conversion.Scope) error {
+	return autoConvert_v1alpha1_PolicyStatus_To_unversioned_PolicyStatus(in, out, s)
+}
+
+func autoConvert_unversioned_PolicyStatus_To_v1alpha1_PolicyStatus(in *unversioned.PolicyStatus, out *PolicyStatus, s conversion.Scope) error {
+	return nil
+}
+
+// Convert_unversioned_PolicyStatus_To_v1alpha1_PolicyStatus is an autogenerated conversion function.
+func Convert_unversioned_PolicyStatus_To_v1alpha1_PolicyStatus(in *unversioned.PolicyStatus, out *PolicyStatus, s conversion.Scope) error {
+	return autoConvert_unversioned_PolicyStatus_To_v1alpha1_PolicyStatus(in, out, s)
+}
+
 func autoConvert_v1alpha1_Store_To_unversioned_Store(in *Store, out *unversioned.Store, s conversion.Scope) error {
 	out.ObjectMeta = in.ObjectMeta
 	if err := Convert_v1alpha1_StoreSpec_To_unversioned_StoreSpec(&in.Spec, &out.Spec, s); err != nil {
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 4952bff..c3396c0 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -131,6 +131,96 @@ func (in *PluginSource) DeepCopy() *PluginSource {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Policy) DeepCopyInto(out *Policy) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	out.Status = in.Status
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Policy.
+func (in *Policy) DeepCopy() *Policy {
+	if in == nil {
+		return nil
+	}
+	out := new(Policy)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Policy) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PolicyList) DeepCopyInto(out *PolicyList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]Policy, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyList.
+func (in *PolicyList) DeepCopy() *PolicyList {
+	if in == nil {
+		return nil
+	}
+	out := new(PolicyList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *PolicyList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PolicySpec) DeepCopyInto(out *PolicySpec) {
+	*out = *in
+	in.Parameters.DeepCopyInto(&out.Parameters)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicySpec.
+func (in *PolicySpec) DeepCopy() *PolicySpec {
+	if in == nil {
+		return nil
+	}
+	out := new(PolicySpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PolicyStatus) DeepCopyInto(out *PolicyStatus) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyStatus.
+func (in *PolicyStatus) DeepCopy() *PolicyStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(PolicyStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Store) DeepCopyInto(out *Store) {
 	*out = *in
diff --git a/charts/ratify/crds/policy-customresourcedefinition.yaml b/charts/ratify/crds/policy-customresourcedefinition.yaml
new file mode 100644
index 0000000..ef07013
--- /dev/null
+++ b/charts/ratify/crds/policy-customresourcedefinition.yaml
@@ -0,0 +1,86 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.9.2
+  creationTimestamp: null
+  name: policies.config.ratify.deislabs.io
+spec:
+  group: config.ratify.deislabs.io
+  names:
+    kind: Policy
+    listKind: PolicyList
+    plural: policies
+    singular: policy
+  scope: Cluster
+  versions:
+    - deprecated: true
+      deprecationWarning:
+        v1alpha1 of the Policy API has been deprecated. Please migrate
+        to v1beta1.
+      name: v1alpha1
+      schema:
+        openAPIV3Schema:
+          description: Policy is the Schema for the policies API
+          properties:
+            apiVersion:
+              description:
+                "APIVersion defines the versioned schema of this representation
+                of an object. Servers should convert recognized schemas to the latest
+                internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources"
+              type: string
+            kind:
+              description:
+                "Kind is a string value representing the REST resource this
+                object represents. Servers may infer this from the endpoint the client
+                submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds"
+              type: string
+            metadata:
+              type: object
+            spec:
+              description: PolicySpec defines the desired state of Policy
+              properties:
+                parameters:
+                  description: Parameters for this policy
+                  type: object
+                  x-kubernetes-preserve-unknown-fields: true
+              type: object
+            status:
+              description: PolicyStatus defines the observed state of Policy
+              type: object
+          type: object
+      served: true
+      storage: false
+    - name: v1beta1
+      schema:
+        openAPIV3Schema:
+          description: Policy is the Schema for the policies API
+          properties:
+            apiVersion:
+              description:
+                "APIVersion defines the versioned schema of this representation
+                of an object. Servers should convert recognized schemas to the latest
+                internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources"
+              type: string
+            kind:
+              description:
+                "Kind is a string value representing the REST resource this
+                object represents. Servers may infer this from the endpoint the client
+                submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds"
+              type: string
+            metadata:
+              type: object
+            spec:
+              description: PolicySpec defines the desired state of Policy
+              properties:
+                parameters:
+                  description: Parameters for this policy
+                  type: object
+                  x-kubernetes-preserve-unknown-fields: true
+              type: object
+            status:
+              description: PolicyStatus defines the observed state of Policy
+              type: object
+          type: object
+      served: true
+      storage: true
diff --git a/charts/ratify/templates/configmap.yaml b/charts/ratify/templates/configmap.yaml
index 1b16d79..dd82594 100644
--- a/charts/ratify/templates/configmap.yaml
+++ b/charts/ratify/templates/configmap.yaml
@@ -52,7 +52,7 @@ data:
       "policy": {
         "version": "1.0.0",
         "plugin": {
-        {{- if .Values.featureFlags.RATIFY_USE_REGO_POLICY }}
+        {{- if .Values.policy.useRego }}
           "name": "regoPolicy",
           "policyPath": "/usr/local/ratify/policy/policy.rego"
         {{- else }}
diff --git a/charts/ratify/templates/deployment.yaml b/charts/ratify/templates/deployment.yaml
index d34a8f7..c36bd24 100644
--- a/charts/ratify/templates/deployment.yaml
+++ b/charts/ratify/templates/deployment.yaml
@@ -74,7 +74,7 @@ spec:
             - mountPath: "/usr/local/ratify"
               name: config
               readOnly: true
-            {{- if .Values.featureFlags.RATIFY_USE_REGO_POLICY }}
+            {{- if .Values.policy.useRego }}
             - mountPath: "/usr/local/ratify/policy"
               name: policy
               readOnly: true
diff --git a/charts/ratify/templates/ratify_manager-role-clusterrole.yaml b/charts/ratify/templates/ratify_manager-role-clusterrole.yaml
index b22255f..355ed30 100644
--- a/charts/ratify/templates/ratify_manager-role-clusterrole.yaml
+++ b/charts/ratify/templates/ratify_manager-role-clusterrole.yaml
@@ -83,6 +83,32 @@ rules:
   - get
   - patch
   - update
+- apiGroups:
+  - config.ratify.deislabs.io
+  resources:
+  - policies
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - config.ratify.deislabs.io
+  resources:
+  - policies/finalizers
+  verbs:
+  - update
+- apiGroups:
+  - config.ratify.deislabs.io
+  resources:
+  - policies/status
+  verbs:
+  - get
+  - patch
+  - update
 - apiGroups:
   - externaldata.gatekeeper.sh
   resources:
diff --git a/charts/ratify/values.yaml b/charts/ratify/values.yaml
index f7f9f75..8c3f38c 100644
--- a/charts/ratify/values.yaml
+++ b/charts/ratify/values.yaml
@@ -108,11 +108,12 @@ crds:
     runAsNonRoot: true
     runAsUser: 65532
 
+policy:
+  useRego: false # Set to true if Rego Policy would be used for evaluation.
+
 # 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/config/crd/bases/config.ratify.deislabs.io_policies.yaml b/config/crd/bases/config.ratify.deislabs.io_policies.yaml
new file mode 100644
index 0000000..0624b1d
--- /dev/null
+++ b/config/crd/bases/config.ratify.deislabs.io_policies.yaml
@@ -0,0 +1,51 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.9.2
+  creationTimestamp: null
+  name: policies.config.ratify.deislabs.io
+spec:
+  group: config.ratify.deislabs.io
+  names:
+    kind: Policy
+    listKind: PolicyList
+    plural: policies
+    singular: policy
+  scope: Cluster
+  versions:
+  - deprecated: true
+    deprecationWarning: v1alpha1 of the Policy API has been deprecated. Please migrate
+      to v1beta1.
+    name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: Policy is the Schema for the policies API
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: PolicySpec defines the desired state of Policy
+            properties:
+              parameters:
+                description: Parameters for this policy
+                type: object
+                x-kubernetes-preserve-unknown-fields: true
+            type: object
+          status:
+            description: PolicyStatus defines the observed state of Policy
+            type: object
+        type: object
+    served: true
+    storage: true
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index 918ca05..5a22105 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -31,6 +31,32 @@ rules:
   - get
   - patch
   - update
+- apiGroups:
+  - config.ratify.deislabs.io
+  resources:
+  - policies
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - config.ratify.deislabs.io
+  resources:
+  - policies/finalizers
+  verbs:
+  - update
+- apiGroups:
+  - config.ratify.deislabs.io
+  resources:
+  - policies/status
+  verbs:
+  - get
+  - patch
+  - update
 - apiGroups:
   - config.ratify.deislabs.io
   resources:
diff --git a/config/samples/policy/config_v1alpha1_policy_json.yaml b/config/samples/policy/config_v1alpha1_policy_json.yaml
new file mode 100644
index 0000000..127bab7
--- /dev/null
+++ b/config/samples/policy/config_v1alpha1_policy_json.yaml
@@ -0,0 +1,8 @@
+apiVersion: config.ratify.deislabs.io/v1alpha1
+kind: Policy
+metadata:
+  name: "configpolicy"
+spec:
+  parameters:
+    artifactVerificationPolicies:
+      default: "all"
diff --git a/config/samples/policy/config_v1alpha1_policy_rego.yaml b/config/samples/policy/config_v1alpha1_policy_rego.yaml
new file mode 100644
index 0000000..f809783
--- /dev/null
+++ b/config/samples/policy/config_v1alpha1_policy_rego.yaml
@@ -0,0 +1,31 @@
+apiVersion: config.ratify.deislabs.io/v1alpha1
+kind: Policy
+metadata:
+  name: "regopolicy"
+spec:
+  parameters:
+    passthroughEnabled: false
+    policy: |
+      package ratify.policy
+
+      default valid := false
+
+      # all artifacts MUST be valid
+      valid {
+        not failed_verify(input)
+      }
+
+      # all reports MUST pass the verification
+      failed_verify(reports) {
+        [path, value] := walk(reports)
+        value == false
+        path[count(path) - 1] == "isSuccess"
+      }
+
+      # each artifact MUST have at least one report
+      failed_verify(reports) {
+        [path, value] := walk(reports)
+        path[count(path) - 1] == "verifierReports"
+        count(value) == 0
+      }
+    
diff --git a/docs/reference/crds/policies.md b/docs/reference/crds/policies.md
new file mode 100644
index 0000000..a20bfee
--- /dev/null
+++ b/docs/reference/crds/policies.md
@@ -0,0 +1,75 @@
+A `Policy` resource defines a policy evaluating the verification results for a subject.
+
+View more CRD samples [here](../../../config/samples/policy). The `metadata.name` is the policy name which would be applied. Common properties:
+```yml
+apiVersion: config.ratify.deislabs.io/v1alpha1
+kind: Policy
+metadata:
+  name: "configpolicy"
+spec:
+  parameters: required. Parameters specific to this policy
+```
+There would be exactly one CR existing in the cluster. That means if users apply a new CR, the old one would be replaced.
+
+Note: `metadata.name` MUST be `configpolicy` or `regopolicy` per the usage.
+
+## configpolicy
+Sample spec:
+```yml
+apiVersion: config.ratify.deislabs.io/v1alpha1
+kind: Policy
+metadata:
+  name: "configpolicy"
+spec:
+  parameters:
+    artifactVerificationPolicies:
+      "application/vnd.cncf.notary.signature": "any"
+      default: "any"
+```
+| Name | Required | Description | Default Value |
+| ----------- | -------- | ----------- | ------------- |
+| artifactVerificationPolicies | yes | Map of artifact type to policy; each entry in the map's policy must be satisfied for Ratify to return true | "" |
+| default | no | The `default` policy applies to unspecified artifact types. | "all" |
+| `application/vnd.cncf.notary.signature` | no | It could be any artifact type that is supported by Ratify. | There is no default value, users must specify `any` or `all` |
+
+## regopolicy
+Sample spec:
+```yml
+apiVersion: config.ratify.deislabs.io/v1alpha1
+kind: Policy
+metadata:
+  name: "regopolicy"
+spec:
+  parameters:
+    passthroughEnabled: false
+    policy: |
+      package ratify.policy
+
+      default valid := false
+
+      # all artifacts MUST be valid
+      valid {
+        not failed_verify(input)
+      }
+
+      # all reports MUST pass the verification
+      failed_verify(reports) {
+        [path, value] := walk(reports)
+        value == false
+        path[count(path) - 1] == "isSuccess"
+      }
+
+      # each artifact MUST have at least one report
+      failed_verify(reports) {
+        [path, value] := walk(reports)
+        path[count(path) - 1] == "verifierReports"
+        count(value) == 0
+      }
+```
+| Name | Required | Description | Default Value |
+| ----------- | -------- | ----------- | ------------- |
+| passthroughEnabled | no | If set to true, the policy will return true if the verification result is not available. | false |
+| policy | no | The policy language that defines the policy. | "" |
+| policyPath | no | The path to the policy file if policy is mounted as a volume | "" |
+
+Note: Users MUST provide at least one of `policy` and `policyPath`. If both are specified, `policy` will be used. 
\ No newline at end of file
diff --git a/docs/reference/providers.md b/docs/reference/providers.md
index 6e07d34..c3e6342 100644
--- a/docs/reference/providers.md
+++ b/docs/reference/providers.md
@@ -25,7 +25,8 @@ The Rego Policy Provider is a built-in policy provider that uses [Open Policy Ag
     "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}"
+        "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}",
+        "passthroughEnabled": false
     }
 },
 ```
@@ -33,17 +34,22 @@ The Rego Policy Provider is a built-in policy provider that uses [Open Policy Ag
 - 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.
+- The `passthroughEnabled` field is optional, which defaults to be `false` if not provided, in thise case, Ratify will return the verification results with the decision to Gatekeeper. If `passthroughEnabled` is set to `true`, the executor will NOT make the decision but only pass the verification results to Gatekeeper for making the decision.
 
 ### 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. 
+Ratify embeds OPA engine inside the executor to provide a built-in policy provider. There are 2 approaches to enable this feature as an add-on service.
+
+1. Set the helm chart value of `policy.useRego` to `true` while deploying Ratify.
+2. Apply a Policy Custom Resource with Rego Policy if the service is up. e.g.
+```bash
+kubectl apply -f ./config/samples/policy/config_v1alpha1_policy_rego.yaml
+```
 
-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.
+And if Ratify is used as command line tool, users MUST provide a config with Rego Policy. Check `test/bats/tests/config/config_rego_policy_notation_leaf_cert.json` as an example.
 
-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.
+Note that verification results returned are different while switching Rego policy/config policy. 
 
-If Ratify is used as command line, `RATIFY_USE_REGO_POLICY=1` MUST be passed to the corresponding command.
+When Rego Policy is selected, 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.
 
 ### Rego Policy Requirements
 
diff --git a/docs/reference/usage.md b/docs/reference/usage.md
index 0ae5f04..eee69d3 100644
--- a/docs/reference/usage.md
+++ b/docs/reference/usage.md
@@ -21,9 +21,6 @@ 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:
diff --git a/httpserver/handlers.go b/httpserver/handlers.go
index 5300678..6c7ef6a 100644
--- a/httpserver/handlers.go
+++ b/httpserver/handlers.go
@@ -105,7 +105,7 @@ func (server *Server) verify(ctx context.Context, w http.ResponseWriter, r *http
 				}
 			}
 
-			returnItem.Value = fromVerifyResult(result)
+			returnItem.Value = fromVerifyResult(result, server.GetExecutor().PolicyEnforcer.GetPolicyType(ctx))
 			logrus.Debugf("verification: execution time for image %s: %dms", resolvedSubjectReference, time.Since(routineStartTime).Milliseconds())
 		}(utils.SanitizeString(subject))
 	}
diff --git a/httpserver/types.go b/httpserver/types.go
index 38e7665..02ad6e7 100644
--- a/httpserver/types.go
+++ b/httpserver/types.go
@@ -17,7 +17,7 @@ package httpserver
 
 import (
 	"github.com/deislabs/ratify/pkg/executor/types"
-	"github.com/deislabs/ratify/pkg/featureflag"
+	pt "github.com/deislabs/ratify/pkg/policyprovider/types"
 )
 
 const (
@@ -33,9 +33,9 @@ type VerificationResponse struct {
 	VerifierReports []interface{} `json:"verifierReports,omitempty"`
 }
 
-func fromVerifyResult(res types.VerifyResult) VerificationResponse {
+func fromVerifyResult(res types.VerifyResult, policyType string) VerificationResponse {
 	version := VerificationResultVersion
-	if featureflag.UseRegoPolicy.Enabled {
+	if policyType == pt.RegoPolicy {
 		version = ResultVersionSupportingRego
 	}
 	return VerificationResponse{
diff --git a/pkg/controllers/policy_controller.go b/pkg/controllers/policy_controller.go
new file mode 100644
index 0000000..85290bb
--- /dev/null
+++ b/pkg/controllers/policy_controller.go
@@ -0,0 +1,166 @@
+/*
+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 controllers
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+
+	configv1alpha1 "github.com/deislabs/ratify/api/v1alpha1"
+	"github.com/deislabs/ratify/pkg/policyprovider"
+	"github.com/deislabs/ratify/pkg/policyprovider/config"
+	pf "github.com/deislabs/ratify/pkg/policyprovider/factory"
+	"github.com/sirupsen/logrus"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/runtime"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// PolicyReconciler reconciles a Policy object
+type PolicyReconciler struct {
+	client.Client
+	Scheme *runtime.Scheme
+}
+
+type policy struct {
+	// The name of the policy.
+	Name string
+	// The policy enforcer making a decision.
+	Enforcer policyprovider.PolicyProvider
+}
+
+// ActivePolicy is the active policy generated from CRD. There would be exactly
+// one active policy at any given time.
+var ActivePolicy policy
+
+//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=policies,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=policies/status,verbs=get;update;patch
+//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=policies/finalizers,verbs=update
+
+// Reconcile is part of the main kubernetes reconciliation loop which aims to
+// move the current state of the cluster closer to the desired state.
+//
+// For more details, check Reconcile and its Result here:
+// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile
+func (r *PolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+	policyLogger := logrus.WithContext(ctx)
+
+	var policy configv1alpha1.Policy
+	var resource = req.Name
+	policyLogger.Infof("Reconciling Policy %s", resource)
+
+	if err := r.Get(ctx, req.NamespacedName, &policy); err != nil {
+		if apierrors.IsNotFound(err) && resource == ActivePolicy.Name {
+			policyLogger.Infof("delete event detected, removing policy %s", resource)
+			ActivePolicy.deletePolicy(resource)
+		} else {
+			policyLogger.Error("failed to get Policy: ", err)
+		}
+		return ctrl.Result{}, client.IgnoreNotFound(err)
+	}
+
+	if err := policyAddOrReplace(policy.Spec, resource); err != nil {
+		policyLogger.Error("unable to create policy from policy crd: ", err)
+		return ctrl.Result{}, err
+	}
+
+	// List all policies in the same namespace.
+	policyList := &configv1alpha1.PolicyList{}
+	if err := r.List(ctx, policyList, client.InNamespace(req.Namespace)); err != nil {
+		policyLogger.Error("failed to list Policies: ", err)
+		return ctrl.Result{}, err
+	}
+
+	// Delete all policies except the current one.
+	for _, item := range policyList.Items {
+		item := item
+		if item.Name != resource {
+			policyLogger.Infof("Deleting policy %s", item.Name)
+			err := r.Delete(ctx, &item)
+			if err != nil {
+				policyLogger.Error("failed to delete Policy: ", err)
+				return ctrl.Result{}, err
+			}
+			policyLogger.Info("Deleted policy", "name", item.Name)
+		}
+	}
+
+	// returning empty result and no error to indicate we’ve successfully reconciled this object
+	return ctrl.Result{}, nil
+}
+
+// SetupWithManager sets up the controller with the Manager.
+func (r *PolicyReconciler) SetupWithManager(mgr ctrl.Manager) error {
+	return ctrl.NewControllerManagedBy(mgr).
+		For(&configv1alpha1.Policy{}).
+		Complete(r)
+}
+
+func policyAddOrReplace(spec configv1alpha1.PolicySpec, policyName string) error {
+	policyEnforcer, err := specToPolicyEnforcer(spec, policyName)
+	if err != nil {
+		return fmt.Errorf("failed to create policy enforcer: %w", err)
+	}
+
+	ActivePolicy.Name = policyName
+	ActivePolicy.Enforcer = policyEnforcer
+	return nil
+}
+
+func specToPolicyEnforcer(spec configv1alpha1.PolicySpec, policyName string) (policyprovider.PolicyProvider, error) {
+	policyConfig, err := rawToPolicyConfig(spec.Parameters.Raw, policyName)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse policy config: %w", err)
+	}
+
+	policyEnforcer, err := pf.CreatePolicyProviderFromConfig(policyConfig)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create policy provider: %w", err)
+	}
+
+	return policyEnforcer, nil
+}
+
+func rawToPolicyConfig(raw []byte, policyName string) (config.PoliciesConfig, error) {
+	pluginConfig := config.PolicyPluginConfig{}
+
+	if string(raw) == "" {
+		return config.PoliciesConfig{}, fmt.Errorf("no policy parameters provided")
+	}
+	if err := json.Unmarshal(raw, &pluginConfig); err != nil {
+		return config.PoliciesConfig{}, fmt.Errorf("unable to decode policy parameters.Raw: %s, err: %w", raw, err)
+	}
+
+	pluginConfig["name"] = policyName
+
+	return config.PoliciesConfig{
+		PolicyPlugin: pluginConfig,
+	}, nil
+}
+
+func (p *policy) deletePolicy(resource string) {
+	if p.Name == resource {
+		p.Name = ""
+		p.Enforcer = nil
+	}
+}
+
+// IsEmpty returns true if there is no policy set up.
+func (p *policy) IsEmpty() bool {
+	return p.Name == "" && p.Enforcer == nil
+}
diff --git a/pkg/executor/core/executor.go b/pkg/executor/core/executor.go
index 98a5736..be6e9ce 100644
--- a/pkg/executor/core/executor.go
+++ b/pkg/executor/core/executor.go
@@ -25,10 +25,10 @@ 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"
+	pt "github.com/deislabs/ratify/pkg/policyprovider/types"
 	"github.com/deislabs/ratify/pkg/referrerstore"
 	su "github.com/deislabs/ratify/pkg/referrerstore/utils"
 	"github.com/deislabs/ratify/pkg/utils"
@@ -55,55 +55,33 @@ 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) {
-	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)
+	result, err := executor.verifySubjectInternal(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?
 		result = executor.PolicyEnforcer.ErrorToVerifyResult(ctx, verifyParameters.Subject, err)
 	}
-
-	return result, nil
+	if executor.PolicyEnforcer.GetPolicyType(ctx) == pt.ConfigPolicy {
+		return result, nil
+	}
+	return result, err
 }
 
-// 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)
+// verifySubjectInternal verifies the subject with results.
+func (executor Executor) verifySubjectInternal(ctx context.Context, verifyParameters e.VerifyParameters) (types.VerifyResult, error) {
+	verifierReports, err := executor.verifySubjectInternalWithoutDecision(ctx, verifyParameters)
 	if err != nil {
 		return types.VerifyResult{}, err
 	}
+	if executor.PolicyEnforcer.GetPolicyType(ctx) == pt.ConfigPolicy {
+		if len(verifierReports) == 0 {
+			return types.VerifyResult{}, ErrReferrersNotFound
+		}
+	}
 	// 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
-}
-
-// 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
-	}
-	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
 }
@@ -147,7 +125,7 @@ func (executor Executor) verifySubjectInternalWithoutDecision(ctx context.Contex
 					}
 					reference := reference
 					innerGroup.Go(func() error {
-						if featureflag.UseRegoPolicy.Enabled {
+						if executor.PolicyEnforcer.GetPolicyType(ctx) == pt.RegoPolicy {
 							verifyResult, err := executor.verifyReferenceForRegoPolicy(innerErrCtx, subjectReference, reference, referrerStore)
 							if err != nil {
 								logrus.Errorf("error while verifying reference %+v, err: %v", reference, err)
@@ -271,7 +249,7 @@ func (executor Executor) addNestedVerifierResult(ctx context.Context, referenceD
 		ReferenceTypes: []string{"*"},
 	}
 
-	nestedVerifyResult, err := executor.verifySubjectForJSONPolicy(ctx, verifyParameters)
+	nestedVerifyResult, err := executor.VerifySubject(ctx, verifyParameters)
 	if err != nil {
 		nestedVerifyResult = executor.PolicyEnforcer.ErrorToVerifyResult(ctx, verifyParameters.Subject, err)
 	}
@@ -296,7 +274,7 @@ func (executor Executor) addNestedReports(ctx context.Context, referenceDes ocis
 	}
 
 	// get nested reports.
-	reports, err := executor.verifySubjectForRegoPolicy(ctx, verifyParameters)
+	reports, err := executor.verifySubjectInternal(ctx, verifyParameters)
 	if err != nil {
 		return fmt.Errorf("failed to verify nested subject, param: %+v, err: %w", verifyParameters, err)
 	}
diff --git a/pkg/featureflag/featureflag.go b/pkg/featureflag/featureflag.go
index 00eb161..34145e6 100644
--- a/pkg/featureflag/featureflag.go
+++ b/pkg/featureflag/featureflag.go
@@ -25,10 +25,8 @@ 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)
-	UseRegoPolicy   = newFeatureFlag("USE_REGO_POLICY", false)
-	PassthroughMode = newFeatureFlag("PASSTHROUGH_MODE", false)
-	CertRotation    = newFeatureFlag("CERT_ROTATION", false)
+	DynamicPlugins = newFeatureFlag("DYNAMIC_PLUGINS", 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 fe43603..2742ba8 100644
--- a/pkg/manager/manager.go
+++ b/pkg/manager/manager.go
@@ -30,6 +30,7 @@ import (
 	"github.com/deislabs/ratify/config"
 	"github.com/deislabs/ratify/httpserver"
 	"github.com/deislabs/ratify/pkg/featureflag"
+	"github.com/deislabs/ratify/pkg/policyprovider"
 	_ "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
@@ -92,6 +93,7 @@ func StartServer(httpServerAddress, configFilePath, certDirectory, caCertFile st
 	server, err := httpserver.NewServer(context.Background(), httpServerAddress, func() *ef.Executor {
 		var activeVerifiers []vr.ReferenceVerifier
 		var activeStores []referrerstore.ReferrerStore
+		var activePolicyEnforcer policyprovider.PolicyProvider
 
 		// check if there are active verifiers from crd controller
 		// else use verifiers from configuration
@@ -113,11 +115,17 @@ func StartServer(httpServerAddress, configFilePath, certDirectory, caCertFile st
 			activeStores = configStores
 		}
 
+		if !controllers.ActivePolicy.IsEmpty() {
+			activePolicyEnforcer = controllers.ActivePolicy.Enforcer
+		} else {
+			activePolicyEnforcer = policy
+		}
+
 		// return executor with latest configuration
 		executor := ef.Executor{
 			Verifiers:      activeVerifiers,
 			ReferrerStores: activeStores,
-			PolicyEnforcer: policy,
+			PolicyEnforcer: activePolicyEnforcer,
 			Config:         &cf.ExecutorConfig,
 		}
 		return &executor
@@ -236,6 +244,13 @@ func StartManager(tlsWatcherReady chan struct{}) {
 		setupLog.Error(err, "unable to create controller", "controller", "Certificate Store")
 		os.Exit(1)
 	}
+	if err = (&controllers.PolicyReconciler{
+		Client: mgr.GetClient(),
+		Scheme: mgr.GetScheme(),
+	}).SetupWithManager(mgr); err != nil {
+		setupLog.Error(err, "unable to create controller", "controller", "Policy")
+		os.Exit(1)
+	}
 	//+kubebuilder:scaffold:builder
 
 	if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
diff --git a/pkg/policyprovider/api.go b/pkg/policyprovider/api.go
index dfdc512..6492ec7 100644
--- a/pkg/policyprovider/api.go
+++ b/pkg/policyprovider/api.go
@@ -34,4 +34,6 @@ type PolicyProvider interface {
 	// OverallVerifyResult determines the final outcome of verification that is constructed using the results from
 	// individual verifications
 	OverallVerifyResult(ctx context.Context, verifierReports []interface{}) bool
+	// GetPolicyType returns the type of the policy.
+	GetPolicyType(ctx context.Context) string
 }
diff --git a/pkg/policyprovider/configpolicy/configpolicy.go b/pkg/policyprovider/configpolicy/configpolicy.go
index dc68a17..62685d7 100644
--- a/pkg/policyprovider/configpolicy/configpolicy.go
+++ b/pkg/policyprovider/configpolicy/configpolicy.go
@@ -156,3 +156,8 @@ func (enforcer PolicyEnforcer) OverallVerifyResult(_ context.Context, verifierRe
 	}
 	return true
 }
+
+// GetPolicyType returns the type of the policy.
+func (enforcer PolicyEnforcer) GetPolicyType(_ context.Context) string {
+	return vt.ConfigPolicy
+}
diff --git a/pkg/policyprovider/factory/factory.go b/pkg/policyprovider/factory/factory.go
index 09dfe3b..d1d5da8 100644
--- a/pkg/policyprovider/factory/factory.go
+++ b/pkg/policyprovider/factory/factory.go
@@ -18,11 +18,10 @@ package factory
 import (
 	"errors"
 	"fmt"
+	"strings"
 
-	"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"
 )
@@ -49,9 +48,6 @@ 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")
 	}
@@ -61,10 +57,7 @@ func CreatePolicyProviderFromConfig(policyConfig config.PoliciesConfig) (policyp
 		return nil, fmt.Errorf("failed to find policy provider name in the policy config with key %s", "name")
 	}
 
-	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")
-	}
+	providerNameStr := strings.ToLower(fmt.Sprintf("%s", policyProviderName))
 
 	policyFactory, ok := builtInPolicyProviders[providerNameStr]
 	if !ok {
diff --git a/pkg/policyprovider/mocks/types.go b/pkg/policyprovider/mocks/types.go
index e3c34c7..0903f01 100644
--- a/pkg/policyprovider/mocks/types.go
+++ b/pkg/policyprovider/mocks/types.go
@@ -48,3 +48,7 @@ func (p *TestPolicyProvider) ErrorToVerifyResult(_ context.Context, subjectRefSt
 func (p *TestPolicyProvider) OverallVerifyResult(_ context.Context, _ []interface{}) bool {
 	return true
 }
+
+func (p *TestPolicyProvider) GetPolicyType(_ context.Context) string {
+	return ""
+}
diff --git a/pkg/policyprovider/regopolicy/regopolicy.go b/pkg/policyprovider/regopolicy/regopolicy.go
index 9fa354e..5fbd770 100644
--- a/pkg/policyprovider/regopolicy/regopolicy.go
+++ b/pkg/policyprovider/regopolicy/regopolicy.go
@@ -35,14 +35,16 @@ import (
 )
 
 type policyEnforcer struct {
-	Policy    string
-	OpaEngine policyengine.PolicyEngine
+	Policy             string
+	OpaEngine          policyengine.PolicyEngine
+	passthroughEnabled bool
 }
 
 type policyEnforcerConf struct {
-	Name       string `json:"name"`
-	Policy     string `json:"policy"`
-	PolicyPath string `json:"policyPath"`
+	Name               string `json:"name"`
+	Policy             string `json:"policy"`
+	PolicyPath         string `json:"policyPath"`
+	PassthroughEnabled bool   `json:"passthroughEnabled"`
 }
 
 // Factory is a factory for creating rego policy enforcers.
@@ -55,8 +57,6 @@ func init() {
 
 // 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 {
@@ -86,8 +86,11 @@ func (f *Factory) Create(policyConfig config.PolicyPluginConfig) (policyprovider
 		return nil, fmt.Errorf("failed to create OPA engine: %w", err)
 	}
 
-	policyEnforcer.Policy = conf.Policy
-	policyEnforcer.OpaEngine = engine
+	policyEnforcer := &policyEnforcer{
+		Policy:             conf.Policy,
+		OpaEngine:          engine,
+		passthroughEnabled: conf.PassthroughEnabled,
+	}
 
 	return policyEnforcer, nil
 }
@@ -109,6 +112,10 @@ func (e *policyEnforcer) ErrorToVerifyResult(_ context.Context, _ string, _ erro
 
 // OverallVerifyResult determines if the overall verification result should be a success or failure.
 func (e *policyEnforcer) OverallVerifyResult(ctx context.Context, verifierReports []interface{}) bool {
+	if e.passthroughEnabled {
+		return false
+	}
+
 	nestedReports := map[string]interface{}{}
 	nestedReports["verifierReports"] = verifierReports
 	result, err := e.OpaEngine.Evaluate(ctx, nestedReports)
@@ -118,3 +125,8 @@ func (e *policyEnforcer) OverallVerifyResult(ctx context.Context, verifierReport
 	}
 	return result
 }
+
+// GetPolicyType returns the type of the policy.
+func (e *policyEnforcer) GetPolicyType(_ context.Context) string {
+	return policyTypes.RegoPolicy
+}
diff --git a/pkg/policyprovider/types/types.go b/pkg/policyprovider/types/types.go
index 865ac3b..3f849ae 100644
--- a/pkg/policyprovider/types/types.go
+++ b/pkg/policyprovider/types/types.go
@@ -22,7 +22,7 @@ const (
 	AnyVerifySuccess ArtifactTypeVerifyPolicy = "any"
 	AllVerifySuccess ArtifactTypeVerifyPolicy = "all"
 	// RegoPolicy is the name of the rego policy provider.
-	RegoPolicy = "regoPolicy"
+	RegoPolicy = "regopolicy"
 	// ConfigPolicy is the name of the config policy provider.
-	ConfigPolicy = "configPolicy"
+	ConfigPolicy = "configpolicy"
 )


Test patch:
diff --git a/httpserver/types_test.go b/httpserver/types_test.go
new file mode 100644
index 0000000..51be421
--- /dev/null
+++ b/httpserver/types_test.go
@@ -0,0 +1,51 @@
+/*
+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 httpserver
+
+import (
+	"testing"
+
+	"github.com/deislabs/ratify/pkg/executor/types"
+	pt "github.com/deislabs/ratify/pkg/policyprovider/types"
+)
+
+func TestFromVerifyResult(t *testing.T) {
+	result := types.VerifyResult{}
+	testCases := []struct {
+		name            string
+		policyType      string
+		expectedVersion string
+	}{
+		{
+			name:            "Rego policy",
+			policyType:      pt.RegoPolicy,
+			expectedVersion: "1.0.0",
+		},
+		{
+			name:            "Config policy",
+			policyType:      pt.ConfigPolicy,
+			expectedVersion: "0.1.0",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			if res := fromVerifyResult(result, tc.policyType); res.Version != tc.expectedVersion {
+				t.Fatalf("Expected version to be %s, got %s", tc.expectedVersion, res.Version)
+			}
+		})
+	}
+}
diff --git a/pkg/controllers/policy_controller_test.go b/pkg/controllers/policy_controller_test.go
new file mode 100644
index 0000000..45b305f
--- /dev/null
+++ b/pkg/controllers/policy_controller_test.go
@@ -0,0 +1,226 @@
+/*
+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 controllers
+
+import (
+	"reflect"
+	"testing"
+
+	configv1alpha1 "github.com/deislabs/ratify/api/v1alpha1"
+	"github.com/deislabs/ratify/pkg/policyprovider/config"
+	_ "github.com/deislabs/ratify/pkg/policyprovider/configpolicy"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+const (
+	policyName1 = "policy1"
+	policyName2 = "policy2"
+)
+
+func TestDeletePolicy(t *testing.T) {
+	testCases := []struct {
+		name             string
+		policyName       string
+		expectPolicyName string
+	}{
+		{
+			name:             "Delete same name",
+			policyName:       policyName1,
+			expectPolicyName: "",
+		},
+		{
+			name:             "Delete different name",
+			policyName:       policyName2,
+			expectPolicyName: policyName1,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			policy := &policy{
+				Name: policyName1,
+			}
+			policy.deletePolicy(tc.policyName)
+			if policy.Name != tc.expectPolicyName {
+				t.Fatalf("Expected policy name to be %s, got %s", tc.expectPolicyName, policy.Name)
+			}
+		})
+	}
+}
+
+func TestIsEmpty(t *testing.T) {
+	testCases := []struct {
+		name   string
+		policy *policy
+		expect bool
+	}{
+		{
+			name:   "Empty policy",
+			policy: &policy{},
+			expect: true,
+		},
+		{
+			name: "Non-empty policy",
+			policy: &policy{
+				Name: policyName1,
+			},
+			expect: false,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			isEmpty := tc.policy.IsEmpty()
+			if isEmpty != tc.expect {
+				t.Fatalf("Expected to be %t, got %t", tc.expect, isEmpty)
+			}
+		})
+	}
+}
+
+func TestRawToPolicyConfig(t *testing.T) {
+	testCases := []struct {
+		name         string
+		raw          []byte
+		expectErr    bool
+		expectConfig config.PoliciesConfig
+	}{
+		{
+			name:         "empty Raw",
+			raw:          []byte{},
+			expectErr:    true,
+			expectConfig: config.PoliciesConfig{},
+		},
+		{
+			name:         "unmarshal failure",
+			raw:          []byte("invalid"),
+			expectErr:    true,
+			expectConfig: config.PoliciesConfig{},
+		},
+		{
+			name:      "valid Raw",
+			raw:       []byte("{\"name\": \"policy1\"}"),
+			expectErr: false,
+			expectConfig: config.PoliciesConfig{
+				PolicyPlugin: config.PolicyPluginConfig{
+					"name": policyName1,
+				},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			config, err := rawToPolicyConfig(tc.raw, policyName1)
+
+			if tc.expectErr != (err != nil) {
+				t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil)
+			}
+			if !reflect.DeepEqual(config, tc.expectConfig) {
+				t.Fatalf("Expected config to be %v, got %v", tc.expectConfig, config)
+			}
+		})
+	}
+}
+
+func TestSpecToPolicyEnforcer(t *testing.T) {
+	testCases := []struct {
+		name           string
+		policyName     string
+		spec           configv1alpha1.PolicySpec
+		expectErr      bool
+		expectProvider bool
+	}{
+		{
+			name:           "invalid spec",
+			policyName:     policyName1,
+			spec:           configv1alpha1.PolicySpec{},
+			expectErr:      true,
+			expectProvider: false,
+		},
+		{
+			name:       "non-supported policy",
+			policyName: policyName1,
+			spec: configv1alpha1.PolicySpec{
+				Parameters: runtime.RawExtension{
+					Raw: []byte("{\"name\": \"policy1\"}"),
+				},
+			},
+			expectErr:      true,
+			expectProvider: false,
+		},
+		{
+			name:       "valid spec",
+			policyName: "configpolicy",
+			spec: configv1alpha1.PolicySpec{
+				Parameters: runtime.RawExtension{
+					Raw: []byte("{\"name\": \"configpolicy\"}"),
+				},
+			},
+			expectErr:      false,
+			expectProvider: true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			provider, err := specToPolicyEnforcer(tc.spec, tc.policyName)
+
+			if tc.expectErr != (err != nil) {
+				t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil)
+			}
+			if tc.expectProvider != (provider != nil) {
+				t.Fatalf("expected provider to be %t, got %t", tc.expectProvider, provider != nil)
+			}
+		})
+	}
+}
+
+func TestPolicyAddOrReplace(t *testing.T) {
+	testCases := []struct {
+		name       string
+		spec       configv1alpha1.PolicySpec
+		policyName string
+		expectErr  bool
+	}{
+		{
+			name:       "invalid spec",
+			spec:       configv1alpha1.PolicySpec{},
+			policyName: policyName1,
+			expectErr:  true,
+		},
+		{
+			name: "valid spec",
+			spec: configv1alpha1.PolicySpec{
+				Parameters: runtime.RawExtension{
+					Raw: []byte("{\"name\": \"configpolicy\"}"),
+				},
+			},
+			policyName: "configpolicy",
+			expectErr:  false,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			err := policyAddOrReplace(tc.spec, tc.policyName)
+
+			if tc.expectErr != (err != nil) {
+				t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil)
+			}
+		})
+	}
+}
diff --git a/pkg/executor/core/executor_test.go b/pkg/executor/core/executor_test.go
index cb13e54..f40805a 100644
--- a/pkg/executor/core/executor_test.go
+++ b/pkg/executor/core/executor_test.go
@@ -18,14 +18,12 @@ 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"
@@ -33,6 +31,7 @@ import (
 	"github.com/deislabs/ratify/pkg/ocispecs"
 	policyConfig "github.com/deislabs/ratify/pkg/policyprovider/configpolicy"
 	policyTypes "github.com/deislabs/ratify/pkg/policyprovider/types"
+	pt "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"
@@ -51,7 +50,8 @@ const (
 )
 
 type mockPolicyProvider struct {
-	result bool
+	result     bool
+	policyType string
 }
 
 func (p *mockPolicyProvider) VerifyNeeded(_ context.Context, _ common.Reference, _ ocispecs.ReferenceDescriptor) bool {
@@ -70,6 +70,13 @@ func (p *mockPolicyProvider) OverallVerifyResult(_ context.Context, _ []interfac
 	return p.result
 }
 
+func (p *mockPolicyProvider) GetPolicyType(_ context.Context) string {
+	if p.policyType == "" {
+		return pt.ConfigPolicy
+	}
+	return pt.RegoPolicy
+}
+
 type mockStore struct {
 	referrers map[string][]ocispecs.ReferenceDescriptor
 }
@@ -145,21 +152,21 @@ func (v *mockVerifier) GetNestedReferences() []string {
 	return nil
 }
 
-func TestVerifySubjectInternalWithDecision_ResolveSubjectDescriptor_Failed(t *testing.T) {
+func TestVerifySubjectInternal_ResolveSubjectDescriptor_Failed(t *testing.T) {
 	executor := Executor{}
 
 	verifyParameters := e.VerifyParameters{
 		Subject: "localhost:5000/net-monitor:v1",
 	}
 
-	_, err := executor.verifySubjectInternalWithDecision(context.Background(), verifyParameters)
+	_, err := executor.verifySubjectInternal(context.Background(), verifyParameters)
 
 	if err == nil {
 		t.Fatal("expected subject parsing to fail")
 	}
 }
 
-func TestVerifySubjectInternalWithDecision_ResolveSubjectDescriptor_Success(t *testing.T) {
+func TestVerifySubjectInternal_ResolveSubjectDescriptor_Success(t *testing.T) {
 	testDigest := digest.FromString("test")
 	store := &mocks.TestStore{
 		References: []ocispecs.ReferenceDescriptor{},
@@ -170,18 +177,19 @@ func TestVerifySubjectInternalWithDecision_ResolveSubjectDescriptor_Success(t *t
 
 	executor := Executor{
 		ReferrerStores: []referrerstore.ReferrerStore{store},
+		PolicyEnforcer: &mockPolicyProvider{},
 	}
 
 	verifyParameters := e.VerifyParameters{
 		Subject: "localhost:5000/net-monitor:v1",
 	}
 
-	if _, err := executor.verifySubjectInternalWithDecision(context.Background(), verifyParameters); !errors.Is(err, ErrReferrersNotFound) {
+	if _, err := executor.verifySubjectInternal(context.Background(), verifyParameters); !errors.Is(err, ErrReferrersNotFound) {
 		t.Fatalf("expected ErrReferrersNotFound actual %v", err)
 	}
 }
 
-func TestVerifySubjectInternalWithDecision_Verify_NoReferrers(t *testing.T) {
+func TestVerifySubjectInternal_Verify_NoReferrers(t *testing.T) {
 	testDigest := digest.FromString("test")
 	configPolicy := policyConfig.PolicyEnforcer{}
 	ex := &Executor{
@@ -202,12 +210,12 @@ func TestVerifySubjectInternalWithDecision_Verify_NoReferrers(t *testing.T) {
 		Subject: "localhost:5000/net-monitor:v1",
 	}
 
-	if _, err := ex.verifySubjectInternalWithDecision(context.Background(), verifyParameters); !errors.Is(err, ErrReferrersNotFound) {
+	if _, err := ex.verifySubjectInternal(context.Background(), verifyParameters); !errors.Is(err, ErrReferrersNotFound) {
 		t.Fatalf("expected ErrReferrersNotFound actual %v", err)
 	}
 }
 
-func TestVerifySubjectInternalWithDecision_CanVerify_ExpectedResults(t *testing.T) {
+func TestVerifySubjectInternal_CanVerify_ExpectedResults(t *testing.T) {
 	testDigest := digest.FromString("test")
 	configPolicy := policyConfig.PolicyEnforcer{
 		ArtifactTypePolicies: map[string]policyTypes.ArtifactTypeVerifyPolicy{
@@ -247,7 +255,7 @@ func TestVerifySubjectInternalWithDecision_CanVerify_ExpectedResults(t *testing.
 		Subject: "localhost:5000/net-monitor:v1",
 	}
 
-	result, err := ex.verifySubjectInternalWithDecision(context.Background(), verifyParameters)
+	result, err := ex.verifySubjectInternal(context.Background(), verifyParameters)
 
 	if err != nil {
 		t.Fatalf("verification failed with err %v", err)
@@ -262,7 +270,7 @@ func TestVerifySubjectInternalWithDecision_CanVerify_ExpectedResults(t *testing.
 	}
 }
 
-func TestVerifySubjectInternalWithDecision_VerifyFailures_ExpectedResults(t *testing.T) {
+func TestVerifySubjectInternal_VerifyFailures_ExpectedResults(t *testing.T) {
 	testDigest := digest.FromString("test")
 	testArtifactType := "test-type1"
 	configPolicy := policyConfig.PolicyEnforcer{
@@ -303,7 +311,7 @@ func TestVerifySubjectInternalWithDecision_VerifyFailures_ExpectedResults(t *tes
 		Subject: "localhost:5000/net-monitor:v1",
 	}
 
-	result, err := ex.verifySubjectInternalWithDecision(context.Background(), verifyParameters)
+	result, err := ex.verifySubjectInternal(context.Background(), verifyParameters)
 
 	if err != nil {
 		t.Fatalf("verification failed with err %v", err)
@@ -314,7 +322,7 @@ func TestVerifySubjectInternalWithDecision_VerifyFailures_ExpectedResults(t *tes
 	}
 }
 
-func TestVerifySubjectInternalWithDecision_VerifySuccess_ExpectedResults(t *testing.T) {
+func TestVerifySubjectInternal_VerifySuccess_ExpectedResults(t *testing.T) {
 	testDigest := digest.FromString("test")
 	configPolicy := policyConfig.PolicyEnforcer{
 		ArtifactTypePolicies: map[string]policyTypes.ArtifactTypeVerifyPolicy{
@@ -355,7 +363,7 @@ func TestVerifySubjectInternalWithDecision_VerifySuccess_ExpectedResults(t *test
 		Subject: "localhost:5000/net-monitor:v1",
 	}
 
-	result, err := ex.verifySubjectInternalWithDecision(context.Background(), verifyParameters)
+	result, err := ex.verifySubjectInternal(context.Background(), verifyParameters)
 
 	if err != nil {
 		t.Fatalf("verification failed with err %v", err)
@@ -415,7 +423,7 @@ func TestVerifySubjectInternalWithDecision_MultipleArtifacts_ExpectedResults(t *
 		Subject: "localhost:5000/net-monitor:v1",
 	}
 
-	result, err := ex.verifySubjectInternalWithDecision(context.Background(), verifyParameters)
+	result, err := ex.verifySubjectInternal(context.Background(), verifyParameters)
 
 	if err != nil {
 		t.Fatalf("verification failed with err %v", err)
@@ -434,8 +442,8 @@ func TestVerifySubjectInternalWithDecision_MultipleArtifacts_ExpectedResults(t *
 	}
 }
 
-// TestVerifySubjectInternalWithDecision_NestedReferences_Expected tests verifier config can specify nested references
-func TestVerifySubjectInternalWithDecision_NestedReferences_Expected(t *testing.T) {
+// TestVerifySubjectInternal_NestedReferences_Expected tests verifier config can specify nested references
+func TestVerifySubjectInternal_NestedReferences_Expected(t *testing.T) {
 	configPolicy := policyConfig.PolicyEnforcer{
 		ArtifactTypePolicies: map[string]policyTypes.ArtifactTypeVerifyPolicy{
 			"default": "all",
@@ -477,7 +485,7 @@ func TestVerifySubjectInternalWithDecision_NestedReferences_Expected(t *testing.
 		Subject: mocks.TestSubjectWithDigest,
 	}
 
-	result, err := ex.verifySubjectInternalWithDecision(context.Background(), verifyParameters)
+	result, err := ex.verifySubjectInternal(context.Background(), verifyParameters)
 
 	if err != nil {
 		t.Fatalf("verification failed with err %v", err)
@@ -513,8 +521,8 @@ func TestVerifySubjectInternalWithDecision_NestedReferences_Expected(t *testing.
 	}
 }
 
-// TestVerifySubjectInternalWithDecision__NoNestedReferences_Expected tests verifier config can specify no nested references
-func TestVerifySubjectInternalWithDecision_NoNestedReferences_Expected(t *testing.T) {
+// TestVerifySubjectInternal__NoNestedReferences_Expected tests verifier config can specify no nested references
+func TestVerifySubjectInternal_NoNestedReferences_Expected(t *testing.T) {
 	configPolicy := policyConfig.PolicyEnforcer{
 		ArtifactTypePolicies: map[string]policyTypes.ArtifactTypeVerifyPolicy{
 			"default": "all",
@@ -554,7 +562,7 @@ func TestVerifySubjectInternalWithDecision_NoNestedReferences_Expected(t *testin
 		Subject: mocks.TestSubjectWithDigest,
 	}
 
-	result, err := ex.verifySubjectInternalWithDecision(context.Background(), verifyParameters)
+	result, err := ex.verifySubjectInternal(context.Background(), verifyParameters)
 
 	if err != nil {
 		t.Fatalf("verification failed with err %v", err)
@@ -698,11 +706,6 @@ 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
@@ -714,7 +717,10 @@ func TestVerifySubject(t *testing.T) {
 		expectErr      bool
 	}{
 		{
-			name:      "verify subject with invalid subject",
+			name: "verify subject with invalid subject",
+			policyEnforcer: &mockPolicyProvider{
+				policyType: pt.RegoPolicy,
+			},
 			params:    e.VerifyParameters{},
 			expectErr: true,
 		},
@@ -728,6 +734,9 @@ func TestVerifySubject(t *testing.T) {
 					referrers: nil,
 				},
 			},
+			policyEnforcer: &mockPolicyProvider{
+				policyType: pt.RegoPolicy,
+			},
 			expectErr:      true,
 			expectedResult: types.VerifyResult{},
 		},
@@ -741,6 +750,9 @@ func TestVerifySubject(t *testing.T) {
 					referrers: nil,
 				},
 			},
+			policyEnforcer: &mockPolicyProvider{
+				policyType: pt.RegoPolicy,
+			},
 			expectErr:      true,
 			expectedResult: types.VerifyResult{},
 		},
@@ -768,8 +780,11 @@ func TestVerifySubject(t *testing.T) {
 					canVerify: false,
 				},
 			},
-			policyEnforcer: &mockPolicyProvider{result: true},
-			expectErr:      false,
+			policyEnforcer: &mockPolicyProvider{
+				result:     true,
+				policyType: pt.RegoPolicy,
+			},
+			expectErr: false,
 			expectedResult: types.VerifyResult{
 				IsSuccess: true,
 			},
@@ -801,7 +816,10 @@ func TestVerifySubject(t *testing.T) {
 					},
 				},
 			},
-			policyEnforcer: &mockPolicyProvider{result: false},
+			policyEnforcer: &mockPolicyProvider{
+				result:     false,
+				policyType: pt.RegoPolicy,
+			},
 			expectErr:      false,
 			expectedResult: types.VerifyResult{IsSuccess: false},
 		},
@@ -832,8 +850,11 @@ func TestVerifySubject(t *testing.T) {
 					},
 				},
 			},
-			policyEnforcer: &mockPolicyProvider{result: true},
-			expectErr:      false,
+			policyEnforcer: &mockPolicyProvider{
+				result:     true,
+				policyType: pt.RegoPolicy,
+			},
+			expectErr: false,
 			expectedResult: types.VerifyResult{
 				IsSuccess: true,
 			},
diff --git a/pkg/policyprovider/configpolicy/configpolicy_test.go b/pkg/policyprovider/configpolicy/configpolicy_test.go
index fc84a94..59bc37c 100644
--- a/pkg/policyprovider/configpolicy/configpolicy_test.go
+++ b/pkg/policyprovider/configpolicy/configpolicy_test.go
@@ -304,3 +304,10 @@ func TestPolicyEnforcer_OverallVerifyResult(t *testing.T) {
 		}
 	}
 }
+
+func TestGetPolicyType(t *testing.T) {
+	enforcer := PolicyEnforcer{}
+	if policyType := enforcer.GetPolicyType(context.Background()); policyType != "configpolicy" {
+		t.Fatalf("expected policy type: configpolicy, got %v", policyType)
+	}
+}
diff --git a/pkg/policyprovider/regopolicy/regopolicy_test.go b/pkg/policyprovider/regopolicy/regopolicy_test.go
index cc8b599..d832794 100644
--- a/pkg/policyprovider/regopolicy/regopolicy_test.go
+++ b/pkg/policyprovider/regopolicy/regopolicy_test.go
@@ -58,11 +58,26 @@ func TestCreate(t *testing.T) {
 		config    config.PolicyPluginConfig
 		expectErr bool
 	}{
+		{
+			name: "invalid config",
+			config: map[string]interface{}{
+				"name": make(chan int),
+			},
+			expectErr: true,
+		},
 		{
 			name:      "empty config",
 			config:    map[string]interface{}{},
 			expectErr: true,
 		},
+		{
+			name: "config with invalid field",
+			config: map[string]interface{}{
+				"name":               "test",
+				"passthroughEnabled": "test",
+			},
+			expectErr: true,
+		},
 		{
 			name: "config with invalid policy",
 			config: map[string]interface{}{
@@ -128,11 +143,19 @@ func TestErrorToVerifyResult(t *testing.T) {
 
 func TestOverallVerifyResult(t *testing.T) {
 	testcases := []struct {
-		name         string
-		reports      []interface{}
-		returnErr    bool
-		expectResult bool
+		name               string
+		reports            []interface{}
+		passthroughEnabled bool
+		returnErr          bool
+		expectResult       bool
 	}{
+		{
+			name:               "passthrough enabled",
+			reports:            []interface{}{types.VerifyResult{}},
+			passthroughEnabled: true,
+			expectResult:       false,
+			returnErr:          false,
+		},
 		{
 			name:         "opa engine returns error",
 			reports:      []interface{}{types.VerifyResult{}},
@@ -154,6 +177,7 @@ func TestOverallVerifyResult(t *testing.T) {
 				OpaEngine: policyEngine{
 					ReturnErr: tc.returnErr,
 				},
+				passthroughEnabled: tc.passthroughEnabled,
 			}
 			result := policyEnforcer.OverallVerifyResult(context.Background(), tc.reports)
 			if result != tc.expectResult {
@@ -162,3 +186,10 @@ func TestOverallVerifyResult(t *testing.T) {
 		})
 	}
 }
+
+func TestGetPolicyType(t *testing.T) {
+	enforcer := policyEnforcer{}
+	if policyType := enforcer.GetPolicyType(context.Background()); policyType != "regopolicy" {
+		t.Fatalf("expected policy type: regopolicy, got %v", policyType)
+	}
+}
diff --git a/test/bats/plugin-test.bats b/test/bats/plugin-test.bats
index d8586d8..0634004 100644
--- a/test/bats/plugin-test.bats
+++ b/test/bats/plugin-test.bats
@@ -159,11 +159,8 @@ SLEEP_TIME=1
     run kubectl apply -f ./config/samples/config_v1beta1_verifier_sbom.yaml
     sleep 5
     run kubectl apply -f ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml
-
-    # Skipping test for now until expected usage/configuration of this plugin can be verified
-    # sleep 5
-    # run kubectl apply -f ./config/samples/config_v1beta1_verifier_schemavalidator.yaml
-    # sleep 5
+    run kubectl apply -f ./config/samples/config_v1beta1_verifier_schemavalidator.yaml
+    sleep 5
 
     # wait for the httpserver cache to be invalidated
     sleep 15