Skip to content

Protecting OLMv1 API Access

By default, only cluster administrators have permission to read, create, modify, or delete ClusterExtension resources and, when the experimental APIs are installed, ClusterObjectDeployment (COD) and ClusterObjectSet (COS) resources. No other users or groups have any access to these APIs unless explicitly granted by a cluster administrator.

Because operator-controller runs with cluster-admin privileges, any user granted write access to the ClusterExtension API is effectively being delegated cluster-admin trust. Cluster administrators are generally discouraged from delegating this access to non-admin users. If delegation is necessary, the controls described below can help limit the scope of what delegated users can do.

Direct access to experimental rollout APIs

The experimental object-controller also runs with cluster-admin privileges by default and can apply arbitrary Kubernetes resources from COD templates or COS revisions. Write access to either API delegates that trust directly; it is not limited to installing catalog content or namespaced objects.

Admission policies that match only ClusterExtension do not constrain direct COD or COS submissions. Catalog and package allowlists, installation namespace restrictions, and ClusterExtension preflight settings do not protect this path. Do not grant lower-level API write access to users whose installations are intended to be constrained through ClusterExtension policies.

RBAC

If you must delegate ClusterExtension access to non-admin users, start by assigning narrowly scoped roles. The installer role below grants writes only to ClusterExtensions; operator-controller and object-controller use their own service accounts to manage the underlying CODs and COSs. Users do not need write access to those APIs to request an extension installation.

The viewer role below includes read-only access to CODs and COSs for diagnosing experimental rollouts. Remove those resources from the viewer role if that visibility is unnecessary.

Avoid sensitive information in COD/COS

Shipping credentials, tokens, private keys, or other sensitive values through COD templates or COS revision content is not recommended. These APIs are rollout mechanisms, not a secret-distribution channel.

Inline manifests can include kind: Secret. Users with get, list, or watch access to a COD or COS can read those embedded Secret values even without permission to read Kubernetes Secrets. Base64 encoding is not encryption, and immutable COS revisions can retain old values after the COD template is updated.

Keep sensitive values in separately managed Kubernetes Secrets with appropriately restricted access, and have workloads reference those Secrets rather than embedding their values in rollout content. The object-storage secretRef mechanism externalizes manifests; it is not a recommendation to distribute sensitive information through COD/COS.

Grant the viewer role below only to users trusted to read all inline content stored in the CODs and COSs it exposes.

ClusterRole

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: clusterextension-viewer-role
rules:
  - apiGroups:
      - olm.operatorframework.io
    resources:
      - clusterextensions
      - clusterobjectdeployments
      - clusterobjectsets
    verbs:
      - get
      - list
      - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: clusterextension-installer-role
rules:
  - apiGroups:
      - olm.operatorframework.io
    resources:
      - clusterextensions
    verbs:
      - create
      - update
      - delete
      - get
      - list
      - watch

ClusterRoleBinding

Bind one of the roles above to a specific user or group. The following example grants the clusterextension-installer-role to user alice and all members of the team-monitoring group:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: clusterextension-installer-rolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: clusterextension-installer-role
subjects:
  - kind: User
    name: alice
    apiGroup: rbac.authorization.k8s.io
  - kind: Group
    name: team-monitoring
    apiGroup: rbac.authorization.k8s.io

Validating Admission Policy

A ValidatingAdmissionPolicy provides much more flexibility than simple RBAC when it comes to managing access to ClusterExtensions. For more detailed instructions on the fundamentals of the ValidatingAdmissionPolicy API, take a look at the Kubernetes docs here.

In this example, we'll be creating a ValidatingAdmissionPolicy and accompanying ValidatingAdmissionPolicyBinding in order to manage ClusterExtension access for members of the team-monitoring group.

To create a ValidatingAdmissionPolicy for ClusterExtensions, begin with the following skeleton. The matchConstraints field tells Kubernetes which API requests the policy applies to, and matchConditions further narrows the scope — here, the policy only activates for users who belong to the team-monitoring group. Add your constraints to the validations list as shown in the sections below.

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: ce-policy-team-monitoring
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: ["olm.operatorframework.io"]
        apiVersions: ["v1"]
        resources: ["clusterextensions"]
        operations: ["CREATE", "UPDATE"]
  matchConditions:
    - name: only-team-monitoring
      expression: >-
        request.userInfo.groups.exists(g, g == "team-monitoring")
  validations:
    # Add validation expressions here (see sections below)
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: ce-policy-team-monitoring
spec:
  policyName: ce-policy-team-monitoring
  validationActions: ["Deny"]

The following custom validations can be applied through native Kubernetes' ValidatingAdmissionPolicy API. Each snippet shows what to add to the validations: list in the policy above; combine multiple entries to enforce several constraints in a single policy.

Allow a group to manage extensions in specific namespaces

The spec.namespace field on a ClusterExtension determines which namespace the operator's resources are installed into. To restrict a group to one or more approved namespaces, add the following validation. In this example, team-monitoring may only target the monitoring or observability namespaces:

  validations:
    - expression: >-
        object.spec.namespace in ["monitoring", "observability"]
      messageExpression: >-
        "spec.namespace must be one of [\"monitoring\", \"observability\"], got: \"" + object.spec.namespace + "\""

Restrict which ClusterCatalogs a user/group can reference

As risk mitigation, users and groups can be restricted to only installing ClusterExtensions which target a particular catalog. In this instance, the catalog labeled as team=monitoring may have been vetted to contain only packages which are safe for this team to install.

  validations:
    # Must use a catalog selector that targets team=monitoring catalogs
    - expression: >-
        has(object.spec.source.catalog) &&
        has(object.spec.source.catalog.selector) &&
        has(object.spec.source.catalog.selector.matchLabels) &&
        "team" in object.spec.source.catalog.selector.matchLabels &&
        object.spec.source.catalog.selector.matchLabels["team"] == "monitoring"
      messageExpression: >-
        "spec.source.catalog.selector.matchLabels must include team=monitoring" +
        (has(object.spec.source.catalog.selector) &&
         has(object.spec.source.catalog.selector.matchLabels) &&
         "team" in object.spec.source.catalog.selector.matchLabels
         ? ", got team=" + object.spec.source.catalog.selector.matchLabels["team"]
         : ", got: <unset>")

Restrict which packages a user/group can install

If a user or group need only have access to a limited set of packages and versions, the following validation can be added to set those limits:

  validations:
    # Package allowlist with per-package pinned minor versions
    - expression: >-
        (object.spec.source.catalog.packageName == "prometheus" &&
         has(object.spec.source.catalog.version) &&
         object.spec.source.catalog.version.matches("^v?2\\.54(\\.(0|[1-9][0-9]*))?$"))
        ||
        (object.spec.source.catalog.packageName == "alertmanager" &&
         has(object.spec.source.catalog.version) &&
         object.spec.source.catalog.version.matches("^v?0\\.27(\\.(0|[1-9][0-9]*))?$"))
      messageExpression: >-
        "package \"" + object.spec.source.catalog.packageName + "\" with version \"" +
        (has(object.spec.source.catalog.version) ? object.spec.source.catalog.version : "<unset>") +
        "\" is not allowed; permitted combinations: prometheus@2.54.x, alertmanager@0.27.x"

Restrict upgrade constraint policy

The spec.source.catalog.upgradeConstraintPolicy field controls whether the operator-controller respects version upgrade constraints provided by the catalog. Setting it to SelfCertified bypasses those constraints, which can lead to unsafe upgrades. To prevent non-admin users from overriding this:

  validations:
    - expression: >-
        !has(object.spec.source.catalog.upgradeConstraintPolicy) ||
        object.spec.source.catalog.upgradeConstraintPolicy != "SelfCertified"
      message: >-
        team-monitoring may not set upgradeConstraintPolicy to "SelfCertified".
        Only cluster-admins may bypass catalog-provided upgrade constraints.

Restrict CRD upgrade safety

The spec.install.preflight.crdUpgradeSafety field controls whether the operator-controller runs pre-flight safety checks before applying CRD changes. Setting enforcement to None disables these checks, which can result in breaking changes being applied to cluster-wide CRDs. To prevent non-admin users from doing this:

  validations:
    - expression: >-
        !has(object.spec.install) ||
        !has(object.spec.install.preflight) ||
        !has(object.spec.install.preflight.crdUpgradeSafety) ||
        object.spec.install.preflight.crdUpgradeSafety.enforcement != "None"
      message: >-
        team-monitoring may not disable CRD upgrade safety checks.
        Remove spec.install.preflight.crdUpgradeSafety or set enforcement to "Strict".

Complete example

The following combines all of the above constraints into a single ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding. Members of the team-monitoring group may only install prometheus@2.54.x or alertmanager@0.27.x into the monitoring namespace from catalogs labeled team=monitoring, and may not bypass upgrade constraints or disable CRD safety checks.

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: ce-policy-team-monitoring
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: ["olm.operatorframework.io"]
        apiVersions: ["v1"]
        resources: ["clusterextensions"]
        operations: ["CREATE", "UPDATE"]
  matchConditions:
    - name: only-team-monitoring
      expression: >-
        request.userInfo.groups.exists(g, g == "team-monitoring")
  validations:
    - expression: >-
        has(object.spec.source.catalog) &&
        has(object.spec.source.catalog.selector) &&
        has(object.spec.source.catalog.selector.matchLabels) &&
        "team" in object.spec.source.catalog.selector.matchLabels &&
        object.spec.source.catalog.selector.matchLabels["team"] == "monitoring"
      messageExpression: >-
        "spec.source.catalog.selector.matchLabels must include team=monitoring" +
        (has(object.spec.source.catalog.selector) &&
         has(object.spec.source.catalog.selector.matchLabels) &&
         "team" in object.spec.source.catalog.selector.matchLabels
         ? ", got team=" + object.spec.source.catalog.selector.matchLabels["team"]
         : ", got: <unset>")
    - expression: >-
        (object.spec.source.catalog.packageName == "prometheus" &&
         has(object.spec.source.catalog.version) &&
         object.spec.source.catalog.version.matches("^v?2\\.54(\\.(0|[1-9][0-9]*))?$"))
        ||
        (object.spec.source.catalog.packageName == "alertmanager" &&
         has(object.spec.source.catalog.version) &&
         object.spec.source.catalog.version.matches("^v?0\\.27(\\.(0|[1-9][0-9]*))?$"))
      messageExpression: >-
        "package \"" + object.spec.source.catalog.packageName + "\" with version \"" +
        (has(object.spec.source.catalog.version) ? object.spec.source.catalog.version : "<unset>") +
        "\" is not allowed; permitted combinations: prometheus@2.54.x, alertmanager@0.27.x"
    - expression: >-
        object.spec.namespace == "monitoring"
      messageExpression: >-
        "spec.namespace must be \"monitoring\", got: \"" + object.spec.namespace + "\""
    - expression: >-
        !has(object.spec.source.catalog.upgradeConstraintPolicy) ||
        object.spec.source.catalog.upgradeConstraintPolicy != "SelfCertified"
      message: >-
        team-monitoring may not set upgradeConstraintPolicy to "SelfCertified".
        Only cluster-admins may bypass catalog-provided upgrade constraints.
    - expression: >-
        !has(object.spec.install) ||
        !has(object.spec.install.preflight) ||
        !has(object.spec.install.preflight.crdUpgradeSafety) ||
        object.spec.install.preflight.crdUpgradeSafety.enforcement != "None"
      message: >-
        team-monitoring may not disable CRD upgrade safety checks.
        Remove spec.install.preflight.crdUpgradeSafety or set enforcement to "Strict".
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: ce-policy-team-monitoring
spec:
  policyName: ce-policy-team-monitoring
  validationActions: ["Deny"]