Operator Policy
The OperatorPolicy defines a desired state for operators managed by the Operator Lifecycle Manager (OLM) on managed clusters. The Operator policy controller is provided by Open Cluster Management and runs on managed clusters as part of the config-policy-controller deployment.
View the Policy API concepts page to learn more about the OperatorPolicy API.
Prerequisites
You must meet the following prerequisites to use the Operator policy controller:
-
Ensure Golang is installed, if you are planning to install from the source.
-
Ensure the
open-cluster-managementpolicy framework is installed. See Policy Framework for more information. -
Ensure Operator Lifecycle Manager (OLM) is installed on the managed clusters where you plan to deploy operators. See the OLM Quick Start guide for installation instructions.
Installing the operator policy controller
The operator policy controller and the configuration policy controller run in the same config-policy-controller pod on managed clusters. The operator policy controller is disabled by default.
Steps 1 and 2 will install the configuration policy controller, matching the steps in Installing the configuration policy controller.
If configuration policy controller is already installed, complete Step 3 onwards.
Deploy via Clusteradm CLI
Ensure clusteradm CLI is installed and is newer than v0.3.0. Download and extract the clusteradm binary. For more details see the clusteradm GitHub page.
-
Deploy the configuration policy controller (which includes the operator policy controller) to the managed clusters:
$ clusteradm addon enable --names config-policy-controller --clusters <cluster_name> --context ${CTX_HUB_CLUSTER} Deploying config-policy-controller add-on to namespaces open-cluster-management-agent-addon of managed cluster: <cluster_name> -
Ensure the pod is running on the managed cluster with the following command:
$ kubectl get pods -n open-cluster-management-agent-addon --context ${CTX_MANAGED_CLUSTER} NAME READY STATUS RESTARTS AGE config-policy-controller-7f8fb64d8c-pmfx4 1/1 Running 0 44s -
On the managed cluster, check whether the operator policy controller was enabled by examining the
config-policy-controllerpod container arguments:$ kubectl get pods -n open-cluster-management-agent-addon --context ${CTX_MANAGED_CLUSTER} NAME READY STATUS RESTARTS AGE config-policy-controller-5888b6cbc5-lvwdj 1/1 Running 0 30s $ kubectl describe pod config-policy-controller-5888b6cbc5-lvwdj -n open-cluster-management-agent-addon --context ${CTX_MANAGED_CLUSTER} | grep enable-operator-policy - --enable-operator-policy=true -
If you do not see the
--enable-operator-policy=trueargument, enable operator policy controller on managed clustercluster1by annotating the ManagedClusterAddon in the hub cluster:$ kubectl annotate -n cluster1 managedclusteraddon config-policy-controller operator-policy-disabled=false --context ${CTX_HUB_CLUSTER} managedclusteraddon.addon.open-cluster-management.io/config-policy-controller annotated
Run step 3 again to verify the operator policy controller was enabled.
Sample operator policy
After a successful deployment, test the policy framework and operator policy controller with a sample policy.
For more information on how to use an OperatorPolicy, read the Policy API concept section.
Example: Deploy an external secrets operator
The following example deploys an external secrets operator to a managed cluster using an OperatorPolicy. The ESO operator was chosen arbitrarily for the example.
- Create a Policy in Inform mode to scan for an external secrets operator in managed cluster
cluster1in thedefaultnamespace. ThePolicyremediation action ofinformwill override the remediation action of theOperatorPolicy:
apiVersion: policy.open-cluster-management.io/v1
kind: Policy
metadata:
name: policy-eso
spec:
remediationAction: inform
disabled: false
policy-templates:
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1beta1
kind: OperatorPolicy
metadata:
name: policy-eso
spec:
remediationAction: inform
severity: medium
complianceType: musthave
upgradeApproval: None
operatorGroup:
namespace: default
name: external-secrets-operator-group
targetNamespaces:
- default
subscription:
namespace: default
name: external-secrets-operator
channel: alpha
source: operatorhubio-catalog
sourceNamespace: olm
startingCSV: external-secrets-operator.v0.11.0
versions:
- external-secrets-operator.v0.11.0
---
apiVersion: policy.open-cluster-management.io/v1
kind: PlacementBinding
metadata:
name: binding-policy-eso
placementRef:
name: placement-policy-eso
kind: Placement
apiGroup: cluster.open-cluster-management.io
subjects:
- name: policy-eso
kind: Policy
apiGroup: policy.open-cluster-management.io
---
apiVersion: cluster.open-cluster-management.io/v1beta1
kind: Placement
metadata:
name: placement-policy-eso
spec:
predicates:
- requiredClusterSelector:
celSelector:
celExpressions:
- managedCluster.metadata.name == "cluster1"
tolerations:
- key: cluster.open-cluster-management.io/unreachable
operator: Equal
- key: cluster.open-cluster-management.io/unavailable
operator: Equal
-
Apply the policy to the hub cluster:
$ kubectl apply -n default -f policy-eso.yaml --context ${CTX_HUB_CLUSTER} policy.policy.open-cluster-management.io/policy-eso created placementbinding.policy.open-cluster-management.io/binding-policy-eso created placement.cluster.open-cluster-management.io/placement-policy-eso created -
Ensure the
defaultnamespace has aManagedClusterSetBindingfor aManagedClusterSetwith at least one managed cluster resource. See Bind ManagedClusterSet to a namespace for more information. -
Verify the managed cluster is selected by the
Placement:$ kubectl get -n default placementdecision placement-policy-eso-decision-1 -o yaml --context ${CTX_HUB_CLUSTER} ... status: decisions: - clusterName: cluster1The output shows the managed cluster
cluster1is selected. -
Verify the Policy was propagated to the managed cluster:
$ kubectl get policy -A --context ${CTX_MANAGED_CLUSTER} NAMESPACE NAME REMEDIATION ACTION COMPLIANCE STATE AGE cluster1 default.policy-eso inform NonCompliant 11s -
The policy is
NonCompliant. Inspect the policy status:$ kubectl describe policy default.policy-eso --context ${CTX_MANAGED_CLUSTER} -n cluster1 ... Status: Compliant: NonCompliant Details: Compliant: NonCompliant History: Event Name: default.policy-eso.18ab8e1a8ff67343 Last Timestamp: 2026-05-01T21:25:22Z Message: NonCompliant; the policy spec is valid, ... the Subscription required by the policy was not found ...The policy is in
NonCompliantstate because the external secrets operator was not found on the managed cluster in thedefaultnamespace. -
To automatically install the operator on the managed cluster, patch the policy remediation action to
enforce. Note the top-level Policy remediation actionenforcewill override the OperatorPolicy remediation action.$ kubectl patch policy policy-eso -n default --context ${CTX_HUB_CLUSTER} --type=merge -p '{"spec": {"remediationAction": "enforce"}}' policy.policy.open-cluster-management.io/policy-eso patched -
Verify the external secrets operator subscription was created:
$ kubectl get subscription -n default --context ${CTX_MANAGED_CLUSTER} NAME PACKAGE SOURCE CHANNEL external-secrets-operator external-secrets-operator operatorhubio-catalog alphaThe output shows the external secrets operator subscription is active.
-
Verify the external secrets operator deployment is running:
$ kubectl get deployment -n default --context ${CTX_MANAGED_CLUSTER} NAME READY UP-TO-DATE AVAILABLE AGE external-secrets-operator-controller-manager 1/1 1 1 10mThe output shows the external secrets operator is deployed and running.
Cleanup: Remove the example operator
By default, the operator policy controller will delete most of the objects created by the policy when the OperatorPolicy spec.complianceType is changed to mustnothave AND the policy remediationAction is set to enforce.
- Optional: edit the OperatorPolicy
spec.removalBehaviorto customize the objects to keep or delete. The default settings are:
removalBehavior:
clusterServiceVersions: Delete
customResourceDefinitions: Keep
subscriptions: Delete
operatorGroups: DeleteIfUnused
- Edit the
complianceTypetomustnothavein the OperatorPolicy spec, then re-apply the policy:
apiVersion: policy.open-cluster-management.io/v1
kind: Policy
metadata:
name: policy-eso
spec:
remediationAction: enforce # Ensure remediationAction is set to `enforce`
disabled: false
policy-templates:
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1beta1
kind: OperatorPolicy
metadata:
name: policy-eso
spec:
complianceType: mustnothave # Edit this line to `mustnothave`
$ kubectl apply -n default -f policy-eso.yaml --context ${CTX_HUB_CLUSTER}
policy.policy.open-cluster-management.io/policy-eso configured
placementbinding.policy.open-cluster-management.io/binding-policy-eso unchanged
placement.cluster.open-cluster-management.io/placement-policy-eso unchanged
- Verify the external secrets operator and all other objects created by the Policy were deleted in the managed cluster:
$ kubectl get deployment -n default --context ${CTX_MANAGED_CLUSTER}
No resources found in default namespace.
$ kubectl get subscription -n default --context ${CTX_MANAGED_CLUSTER}
No resources found in default namespace.
# repeat for other objects
- Delete the Policy on the hub cluster.
$ kubectl delete -n default policy policy-eso --context ${CTX_HUB_CLUSTER}
policy.policy.open-cluster-management.io "policy-eso" deleted