diff --git a/enhancements/cert-manager/trust-manager-controller.md b/enhancements/cert-manager/trust-manager-controller.md
new file mode 100644
index 0000000000..504454f86b
--- /dev/null
+++ b/enhancements/cert-manager/trust-manager-controller.md
@@ -0,0 +1,1061 @@
+---
+title: trust-manager-controller
+authors:
+ - "@chiragkyal"
+reviewers:
+ - "@tgeer" ## reviewer for cert-manager component
+ - "@bharath-b-rh"
+ - "@mytreya-rh"
+approvers:
+ - "@tgeer" ## approver for cert-manager component
+api-approvers:
+ - "@tgeer" ## approver for cert-manager component
+creation-date: 2025-12-23
+last-updated: 2026-01-12
+tracking-link:
+ - https://issues.redhat.com/browse/CM-830
+see-also:
+ - NA
+replaces:
+ - NA
+superseded-by:
+ - NA
+---
+
+# Extend cert-manager-operator to manage trust-manager
+
+## Summary
+
+This enhancement describes the proposal to extend `cert-manager-operator` to deploy and manage the [trust-manager](https://github.com/cert-manager/trust-manager)
+operand with a dedicated controller. trust-manager provides a way to manage trust bundles in Kubernetes and OpenShift
+clusters. It takes a list of trusted certificate sources and combines them into a bundle which applications can trust directly.
+
+trust-manager will be managed as an operand by an additional controller in cert-manager-operator. The operand will be
+installed in the `cert-manager` namespace and allows to maintain a consistent set of trusted
+CA certificates that can be consumed by workloads running on the cluster.
+
+**Note:**
+Throughout the document, the following terminology means:
+- `trust-manager` is the operand managed by the cert-manager operator.
+- `trust-manager-controller` is the dedicated controller in cert-manager operator managing the `trust-manager` operand deployment.
+- `trustmanagers.operator.openshift.io` is the custom resource for interacting with `trust-manager-controller` to install,
+ configure, and uninstall the `trust-manager` operand deployment.
+- `Bundle` is the CRD that defines trust bundle sources and distribution targets.
+
+## Motivation
+
+Customers require a consistent and reliable way to distribute CA certificates across their OpenShift clusters.
+Without trust-manager, administrators need to manually manage CA certificate distribution, which is error-prone and
+difficult to maintain at scale.
+
+trust-manager uses a cluster-scoped `Bundle` CRD where administrators define sources (ConfigMaps, Secrets, inline
+certificates, or default CAs) and targets (ConfigMaps or Secrets in namespaces matching a label selector).
+trust-manager automatically creates and updates targets when sources change or new matching namespaces are created.
+
+trust-manager solves this by providing:
+
+1. **Centralized Trust Management**: Define trust bundles once and distribute them cluster-wide.
+2. **Multiple Source Support**: Aggregate CA certificates from ConfigMaps, Secrets, and public trust bundles.
+3. **Automatic Updates**: When source certificates change, trust-manager automatically updates all targets.
+4. **Namespace Targeting**: Distribute bundles to specific namespaces based on labels.
+
+The `cert-manager-operator` already manages `cert-manager` and `istio-csr`. Extending it to manage `trust-manager`
+provides a unified solution for certificate lifecycle management and trust distribution on OpenShift.
+
+### User Stories
+
+- As an OpenShift administrator, I want to have an option to deploy trust-manager as a day-2 operation, so that I can
+ distribute CA certificates across my cluster.
+- As an OpenShift administrator, I want to be able to configure trust-manager, so that only required features can be enabled.
+- As an OpenShift administrator, I want to enable trust-manager to write trust bundles to Secrets in addition to ConfigMaps
+ for applications that require secrets.
+- As an OpenShift administrator, I want to use OpenShift's trusted CA bundle as the default CA package, so that trust-manager uses certificates appropriate for my cluster.
+- As an OpenShift administrator, I want to filter expired certificates from trust bundles to ensure only valid certificates
+ are distributed.
+- As an OpenShift administrator, I want to configure a custom trust namespace where trust sources (ConfigMaps/Secrets)
+ are stored, separate from the operand namespace (`cert-manager`).
+- As an OpenShift security engineer, I want to be able to identify all artifacts created by cert-manager-operator for better auditability.
+- As an OpenShift SRE, I should be able to get detailed information as part of different status conditions and messages
+ to identify the reasons for failures.
+- As an OpenShift SRE, I should be able to collect metrics for trust-manager for monitoring.
+- As a MicroShift administrator, I want to use trust-manager to manage CA bundles on my MicroShift cluster.
+
+### Goals
+
+- `cert-manager-operator` to be extended to manage `trust-manager` along with currently managed `cert-manager` and `istio-csr`.
+- New custom resource (CR) `trustmanagers.operator.openshift.io` to be made available to install and configure
+ the trust-manager deployment.
+- Provide OpenShift-native integration for the default CA package using CNO's trusted CA bundle injection instead of the
+ upstream Debian-based approach.
+- trust-manager operand will always be deployed in the `cert-manager` namespace.
+- Support configurable trust namespace (where trust sources are read from). The namespace must exist before creating the TrustManager CR.
+- Dynamic RBAC configuration based on `secretTargets` settings to control secret write permissions.
+- Support both OpenShift and MicroShift platforms.
+- Release as TechPreview with dual feature gate mechanism (operator feature gate + OpenShift FeatureSet).
+
+### Non-Goals
+
+- Removing the `trustmanagers.operator.openshift.io` CR object will not
+ remove the `trust-manager` deployment or its associated resources (ServiceAccount, RBAC, Services, etc.). Deleting
+ the CR will only stop the reconciliation of the resources created for the operand installation.
+ This limitation will be re-evaluated in future releases.
+
+- Automatic cleanup of `Bundle` resources created by users when the TrustManager CR is deleted.
+
+- Automatic deletion of the trust namespace when the TrustManager CR is deleted or updated with a new namespace.
+
+- Automatic cleanup of Configmap created to support `DefaultCAPackage` option, when this field is toggled or TrustManager CR is deleted.
+
+- For TechPreview, the `targetNamespaces` option will not be configurable. This option controls which namespaces
+ trust-manager has RBAC permissions to write Bundle targets to. By default, trust-manager can write to all namespaces.
+
+ Users can still use `namespaceSelector` in Bundle CRs to filter target namespaces, but cannot restrict the
+ RBAC-level permissions. This option will be evaluated for GA release.
+
+
+
+## Proposal
+
+`trust-manager` will be installed and managed by `cert-manager-operator`. A new custom resource is defined to configure
+the `trust-manager` operand. The `trustmanagers.operator.openshift.io` CR can be added day-2 to install `trust-manager`
+post the installation or upgrade of cert-manager operator on OpenShift clusters.
+
+Starting from cert-manager-operator v1.19.0, trust-manager will be available as
+Tech Preview. The feature requires both the operator's `TrustManager` feature gate to be enabled AND the OpenShift
+cluster to be configured with a TechPreview-compatible feature set (`TechPreviewNoUpgrade`, `DevPreviewNoUpgrade`,
+or `CustomNoUpgrade`). On clusters with the default feature set, the trust-manager feature will not be available.
+
+The feature is supported on both OpenShift and MicroShift. Since MicroShift does not support
+OpenShift FeatureSets, the FeatureSet gating is not enforced on MicroShift, and users can enable
+trust-manager using only the operator feature gate.
+
+A new controller will be added to `cert-manager-operator` to manage and maintain the `trust-manager` deployment in the
+desired state. `trust-manager-controller` will make use of static manifest templates for creating the resources required for successfully
+deploying `trust-manager`. Please refer to the [Implementation Details/Notes/Constraints](#implementation-detailsnotesconstraints) section for more details:
+
+Each of the resources created for `trust-manager` deployment will have the below set of labels added:
+* `app: cert-manager-trust-manager`
+* `app.kubernetes.io/name: cert-manager-trust-manager`
+* `app.kubernetes.io/instance: cert-manager-trust-manager`
+* `app.kubernetes.io/version: "v0.20.3"`
+* `app.kubernetes.io/managed-by: cert-manager-operator`
+* `app.kubernetes.io/part-of: cert-manager-operator`
+
+These labels aids in identifying and managing the
+`trust-manager` components within the cluster, thereby facilitating operations like monitoring and resource discovery.
+
+`trustmanagers.operator.openshift.io` CR object is a cluster-scoped singleton object. The singleton pattern is enforced
+through two mechanisms:
+
+ - An XValidation rule in the CRD rejects any TrustManager CR that is not named `cluster`
+ - The controller only reconciles TrustManager CRs with the name `cluster`, ignoring any others
+
+
+`trust-manager` will always be deployed in the `cert-manager` namespace.
+
+Configurations made available in the spec of `trustmanagers.operator.openshift.io` CR are passed as command line arguments
+to `trust-manager` and updating these configurations would cause a new rollout of the `trust-manager` deployment.
+
+
+A fork of [upstream trust-manager](https://github.com/cert-manager/trust-manager) will be created
+[downstream](https://github.com/openshift/cert-manager-trust-manager) for downstream management.
+
+### Workflow Description
+
+The following diagram illustrates the end-to-end workflow for trust-manager deployment and trust bundle distribution:
+
+```mermaid
+flowchart TB
+ subgraph User["Cluster Administrator"]
+ A[Create TrustManager CR
name: cluster]
+ end
+
+ subgraph Operator["cert-manager-operator"]
+ B[trust-manager-controller]
+ B --> C{Check Feature Gates}
+ C -->|Gates Enabled| D[Reconcile TrustManager CR]
+ C -->|Gates Disabled| X[Skip Reconciliation]
+ end
+
+ subgraph Resources["Created Resources in cert-manager namespace"]
+ D --> E[ServiceAccount]
+ D --> F[ClusterRole/ClusterRoleBinding]
+ D --> G[Role/RoleBinding
in trust namespace]
+ D --> H[Deployment: trust-manager]
+ D --> I[Services]
+ D --> J[Certificate + Issuer
for webhook TLS]
+ D --> K[ValidatingWebhookConfiguration]
+ end
+
+ subgraph SecretTargets["SecretTargets Configuration"]
+ D --> ST{secretTargets.policy?}
+ ST -->|Disabled| ST1[No secret write access]
+ ST -->|Custom| ST3[Add secret write rules
with resourceNames]
+ ST3 --> ST4[Add --secret-targets-enabled
to Deployment args]
+ end
+
+ subgraph DefaultCA["DefaultCAPackage Configuration"]
+ D --> L{defaultCAPackage.policy
Enabled?}
+ L -->|Yes| M[Read cert-manager-operator-trusted-ca-bundle
from operator namespace]
+ M --> O[Controller formats
CA bundle to JSON]
+ O --> P[Create trust-manager-default-ca-package
in operand namespace]
+ P --> Q[Mount to Deployment
at /packages]
+ Q --> Q1[Add --default-package-location
to Deployment args]
+ L -->|No| R[Skip DefaultCA setup]
+ end
+
+ subgraph TrustManager["trust-manager Operand"]
+ H --> S[trust-manager Pod
Running]
+ end
+
+ subgraph BundleWorkflow["Bundle Distribution Workflow"]
+ T[User creates Bundle CR] --> S
+ S --> U[Watch Bundle sources
ConfigMaps/Secrets]
+ U --> V[Combine CA certificates]
+ V --> W[Create/Update target
ConfigMaps or Secrets]
+ W --> Y[Target namespaces
matching selector]
+ end
+
+ A --> B
+```
+
+
+- Installation of `trust-manager`
+ - An OpenShift user creates the `trustmanagers.operator.openshift.io` CR with name `cluster`.
+ - `trust-manager-controller` based on the configuration in `trustmanagers.operator.openshift.io` CR, installs `trust-manager`
+ in the `cert-manager` namespace.
+ - If `defaultCAPackage.policy` is `Enabled`:
+ 1. Controller reads the CA bundle from `cert-manager-operator-trusted-ca-bundle` ConfigMap in the operator namespace
+ (created during operator installation via OLM bundle, with CNO injecting the trusted CA bundle)
+ 2. Controller formats the CA bundle into trust-manager's expected JSON format
+ 3. Controller creates `trust-manager-default-ca-package` ConfigMap in the operand namespace and mounts it
+ to the trust-manager deployment
+ 4. trust-manager starts with `--default-package-location` pointing to the package
+
+- Uninstallation of `trust-manager`
+ - An OpenShift user deletes the `trustmanagers.operator.openshift.io` CR.
+ - `trust-manager-controller` will not uninstall `trust-manager`, but will only stop reconciling the Kubernetes resources
+ created for installing the operand. Please refer to the `Non-Goals` section for more details.
+
+### API Extensions
+
+Below new API `trustmanagers.operator.openshift.io` is introduced for managing trust-manager.
+
+```golang
+package v1alpha1
+
+import (
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+// +kubebuilder:object:root=true
+
+// TrustManagerList is a list of TrustManager objects.
+type TrustManagerList struct {
+ metav1.TypeMeta `json:",inline"`
+
+ // metadata is the standard list's metadata.
+ // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
+ metav1.ListMeta `json:"metadata"`
+ Items []TrustManager `json:"items"`
+}
+
+// +genclient
+// +genclient:nonNamespaced
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+// +kubebuilder:resource:path=trustmanagers,scope=Cluster,categories={cert-manager-operator}
+// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status"
+// +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].message"
+// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
+// +kubebuilder:metadata:labels={"app.kubernetes.io/name=trustmanager", "app.kubernetes.io/part-of=cert-manager-operator"}
+
+// TrustManager describes the configuration and information about the managed trust-manager deployment.
+// The name must be `cluster` to make TrustManager a singleton, allowing only one instance per cluster.
+//
+// When a TrustManager is created, trust-manager is deployed in the cert-manager namespace.
+//
+// +kubebuilder:validation:XValidation:rule="self.metadata.name == 'cluster'",message="TrustManager is a singleton, .metadata.name must be 'cluster'"
+// +operator-sdk:csv:customresourcedefinitions:displayName="TrustManager"
+type TrustManager struct {
+ metav1.TypeMeta `json:",inline"`
+
+ // metadata is the standard object's metadata.
+ // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ // spec is the specification of the desired behavior of the TrustManager.
+ // +kubebuilder:validation:Required
+ // +required
+ Spec TrustManagerSpec `json:"spec"`
+
+ // status is the most recently observed status of the TrustManager.
+ // +kubebuilder:validation:Optional
+ // +optional
+ Status TrustManagerStatus `json:"status,omitempty"`
+}
+
+// TrustManagerSpec defines the desired state of TrustManager.
+// Note: trust-manager operand is always deployed in the cert-manager namespace.
+type TrustManagerSpec struct {
+ // trustManagerConfig configures the trust-manager operand's behavior.
+ // +kubebuilder:validation:Required
+ // +required
+ TrustManagerConfig TrustManagerConfig `json:"trustManagerConfig"`
+
+ // controllerConfig configures the operator's behavior for resource creation.
+ // +kubebuilder:validation:Optional
+ // +optional
+ ControllerConfig TrustManagerControllerConfig `json:"controllerConfig,omitempty"`
+}
+
+// TrustManagerConfig configures the trust-manager operand's behavior.
+type TrustManagerConfig struct {
+ // logLevel configures the verbosity of trust-manager logging.
+ // Follows [Kubernetes logging guidelines](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md#what-method-to-use).
+ // +kubebuilder:default:=1
+ // +kubebuilder:validation:Minimum:=1
+ // +kubebuilder:validation:Maximum:=5
+ // +kubebuilder:validation:Optional
+ // +optional
+ LogLevel int32 `json:"logLevel,omitempty"`
+
+ // logFormat specifies the output format for trust-manager logging.
+ // Supported formats are "text" and "json".
+ // +kubebuilder:validation:Enum:="text";"json"
+ // +kubebuilder:default:="text"
+ // +kubebuilder:validation:Optional
+ // +optional
+ LogFormat string `json:"logFormat,omitempty"`
+
+ // trustNamespace is the namespace where trust-manager looks for trust sources
+ // (ConfigMaps and Secrets containing CA certificates).
+ // Defaults to "cert-manager" if not specified.
+ // This field is immutable once set.
+ // This field can have a maximum of 63 characters.
+ // +kubebuilder:default:="cert-manager"
+ // +kubebuilder:validation:MinLength:=1
+ // +kubebuilder:validation:MaxLength:=63
+ // +kubebuilder:validation:XValidation:rule="oldSelf == '' || self == oldSelf",message="trustNamespace is immutable once set"
+ // +kubebuilder:validation:Optional
+ // +optional
+ TrustNamespace string `json:"trustNamespace,omitempty"`
+
+ // secretTargets configures whether trust-manager can write trust bundles to Secrets.
+ // +kubebuilder:validation:Optional
+ // +optional
+ SecretTargets SecretTargetsConfig `json:"secretTargets,omitempty"`
+
+ // filterExpiredCertificates controls whether trust-manager filters out
+ // expired certificates from trust bundles before distributing them.
+ // When set to "Enabled", expired certificates are removed from bundles.
+ // When set to "Disabled", expired certificates are included (default behavior).
+ // +kubebuilder:default:="Disabled"
+ // +kubebuilder:validation:Optional
+ // +optional
+ FilterExpiredCertificates FilterExpiredCertificatesPolicy `json:"filterExpiredCertificates,omitempty"`
+
+ // defaultCAPackage configures the default CA package for trust-manager.
+ // When enabled, the operator will use OpenShift's trusted CA bundle injection mechanism.
+ // +kubebuilder:validation:Optional
+ // +optional
+ DefaultCAPackage DefaultCAPackageConfig `json:"defaultCAPackage,omitempty"`
+
+ // resources defines the compute resource requirements for the trust-manager pod.
+ // ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
+ // +kubebuilder:validation:Optional
+ // +optional
+ Resources corev1.ResourceRequirements `json:"resources,omitempty"`
+
+ // affinity defines scheduling constraints for the trust-manager pod.
+ // ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/
+ // +kubebuilder:validation:Optional
+ // +optional
+ Affinity *corev1.Affinity `json:"affinity,omitempty"`
+
+ // tolerations allows the trust-manager pod to be scheduled on tainted nodes.
+ // ref: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/
+ // +listType=atomic
+ // +kubebuilder:validation:MinItems:=0
+ // +kubebuilder:validation:MaxItems:=50
+ // +kubebuilder:validation:Optional
+ // +optional
+ Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
+
+ // nodeSelector restricts which nodes the trust-manager pod can be scheduled on.
+ // ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/
+ // +mapType=atomic
+ // +kubebuilder:validation:MinProperties:=0
+ // +kubebuilder:validation:MaxProperties:=50
+ // +kubebuilder:validation:Optional
+ // +optional
+ NodeSelector map[string]string `json:"nodeSelector,omitempty"`
+}
+
+// SecretTargetsConfig configures whether and how trust-manager can write
+// trust bundles to Secrets.
+//
+// +kubebuilder:validation:XValidation:rule="self.policy != 'Custom' || (has(self.authorizedSecrets) && size(self.authorizedSecrets) > 0)",message="authorizedSecrets must not be empty when policy is Custom"
+// +kubebuilder:validation:XValidation:rule="self.policy == 'Custom' || !has(self.authorizedSecrets) || size(self.authorizedSecrets) == 0",message="authorizedSecrets must be empty when policy is not Custom"
+type SecretTargetsConfig struct {
+ // policy controls whether and how trust-manager can write trust bundles to Secrets.
+ // Allowed values are "Disabled" or "Custom".
+ // "Disabled" means trust-manager cannot write trust bundles to Secrets (default behavior).
+ // "Custom" grants trust-manager permission to create and update only the secrets listed in authorizedSecrets.
+ // +kubebuilder:default:="Disabled"
+ // +kubebuilder:validation:Optional
+ // +optional
+ Policy SecretTargetsPolicy `json:"policy,omitempty"`
+
+ // authorizedSecrets is a list of specific secret names that trust-manager
+ // is authorized to create and update. This field is only valid when policy is "Custom".
+ // +listType=set
+ // +kubebuilder:validation:MinItems:=0
+ // +kubebuilder:validation:items:MinLength:=1
+ // +kubebuilder:validation:Optional
+ // +optional
+ AuthorizedSecrets []string `json:"authorizedSecrets,omitempty"`
+}
+
+// DefaultCAPackageConfig configures the default CA package feature for trust-manager.
+type DefaultCAPackageConfig struct {
+ // policy controls whether the default CA package feature is enabled.
+ // When set to "Enabled", the operator will inject OpenShift's trusted CA bundle
+ // into trust-manager, enabling the "useDefaultCAs: true" source in Bundle resources.
+ // When set to "Disabled", no default CA package is configured and Bundles cannot use useDefaultCAs (default behavior).
+ // +kubebuilder:default:="Disabled"
+ // +kubebuilder:validation:Optional
+ // +optional
+ Policy DefaultCAPackagePolicy `json:"policy,omitempty"`
+}
+
+// TrustManagerControllerConfig configures the operator's behavior for
+// creating trust-manager resources.
+type TrustManagerControllerConfig struct {
+ // labels to apply to all resources created for the trust-manager deployment.
+ // +mapType=granular
+ // +kubebuilder:validation:MinProperties:=0
+ // +kubebuilder:validation:Optional
+ // +optional
+ Labels map[string]string `json:"labels,omitempty"`
+
+ // annotations to apply to all resources created for the trust-manager deployment.
+ // +mapType=granular
+ // +kubebuilder:validation:MinProperties:=0
+ // +kubebuilder:validation:Optional
+ // +optional
+ Annotations map[string]string `json:"annotations,omitempty"`
+}
+
+// FilterExpiredCertificatesPolicy defines the policy for filtering expired certificates.
+// +kubebuilder:validation:Enum:=Enabled;Disabled
+type FilterExpiredCertificatesPolicy string
+
+const (
+ // FilterExpiredCertificatesPolicyEnabled filters out expired certificates from bundles.
+ FilterExpiredCertificatesPolicyEnabled FilterExpiredCertificatesPolicy = "Enabled"
+ // FilterExpiredCertificatesPolicyDisabled includes expired certificates in bundles.
+ FilterExpiredCertificatesPolicyDisabled FilterExpiredCertificatesPolicy = "Disabled"
+)
+
+// SecretTargetsPolicy defines the policy for writing trust bundles to Secrets.
+// +kubebuilder:validation:Enum:=Disabled;Custom
+type SecretTargetsPolicy string
+
+const (
+ // SecretTargetsPolicyDisabled means trust-manager cannot write trust bundles to Secrets.
+ SecretTargetsPolicyDisabled SecretTargetsPolicy = "Disabled"
+ // SecretTargetsPolicyCustom grants trust-manager permission to write to specific secrets only.
+ SecretTargetsPolicyCustom SecretTargetsPolicy = "Custom"
+)
+
+// DefaultCAPackagePolicy defines the policy for the default CA package feature.
+// +kubebuilder:validation:Enum:=Enabled;Disabled
+type DefaultCAPackagePolicy string
+
+const (
+ // DefaultCAPackagePolicyEnabled enables the default CA package feature.
+ DefaultCAPackagePolicyEnabled DefaultCAPackagePolicy = "Enabled"
+ // DefaultCAPackagePolicyDisabled disables the default CA package feature.
+ DefaultCAPackagePolicyDisabled DefaultCAPackagePolicy = "Disabled"
+)
+
+// TrustManagerStatus defines the observed state of TrustManager.
+type TrustManagerStatus struct {
+ // conditions holds information about the current state of the trust-manager deployment.
+ ConditionalStatus `json:",inline,omitempty"`
+
+ // trustManagerImage is the container image (name:tag) used for trust-manager.
+ TrustManagerImage string `json:"trustManagerImage,omitempty"`
+
+ // trustNamespace is the namespace where trust-manager looks for trust sources.
+ TrustNamespace string `json:"trustNamespace,omitempty"`
+
+ // secretTargetsPolicy indicates the current secret targets policy.
+ SecretTargetsPolicy SecretTargetsPolicy `json:"secretTargetsPolicy,omitempty"`
+
+ // defaultCAPackagePolicy indicates the current default CA package policy.
+ DefaultCAPackagePolicy DefaultCAPackagePolicy `json:"defaultCAPackagePolicy,omitempty"`
+
+ // filterExpiredCertificatesPolicy indicates the current policy for filtering expired certificates.
+ FilterExpiredCertificatesPolicy FilterExpiredCertificatesPolicy `json:"filterExpiredCertificatesPolicy,omitempty"`
+}
+```
+
+### Topology Considerations
+
+#### Hypershift / Hosted Control Planes
+
+None
+
+#### Standalone Clusters
+
+None
+
+#### OpenShift Kubernetes Engine
+
+None
+
+#### Single-node Deployments or MicroShift
+
+None
+
+### Implementation Details/Notes/Constraints
+
+#### Deployment Model
+
+cert-manager-operator uses Helm charts provided by the trust-manager project to derive static manifests for deploying
+the operand. The deployment model follows this approach:
+
+1. **Manifest Generation**: The operator maintains a script (`hack/update-trust-manager-manifests.sh`) that:
+ - Downloads the trust-manager Helm chart from the upstream repository
+ - Renders the chart with specific configuration options using `helm template`
+ - Patches the manifests to update labels (e.g., `app.kubernetes.io/managed-by: cert-manager-operator`)
+ - Removes Helm-specific metadata (e.g., `helm.sh/chart` labels)
+ - Splits the combined manifest into individual resource files
+ - Stores the manifests as bindata embedded in the operator binary
+
+ ```bash
+ # Example: Generating manifests from Helm chart
+ helm template trust-manager cert-manager/trust-manager -n trust-manager \
+ --version "${TRUST_MANAGER_VERSION}" \
+ --set defaultPackage.enabled=false \
+ > manifests.yaml
+ ```
+
+ **Note**: The `app.targetNamespaces` option is not set, allowing trust-manager to write Bundle targets to all
+ namespaces by default. This may be evaluated for configurability in future releases.
+
+The generated manifests are stored in `bindata/trust-manager/resources/`
+
+2. **Runtime Customization**: When reconciling a TrustManager CR, the controller:
+ - Loads the static manifests from bindata
+ - Modifies resources based on user-provided configuration in the TrustManager CR (e.g. log level,
+ trust namespace, secret targets etc.)
+ - Applies the customized resources to the cluster
+
+#### Feature Gate Implementation
+
+The trust-manager controller is gated behind two mechanisms for the Tech Preview release:
+
+1. **Operator Feature Gate**: The cert-manager-operator defines a `TrustManager` feature gate in its feature gate
+ configuration. This gate must be explicitly enabled.
+
+2. **OpenShift FeatureSet Check**: Before starting the trust-manager controller, the operator reads the cluster's
+ feature set from `featuregates.config.openshift.io/cluster`. The controller will only start if the cluster
+ is configured with one of the following feature sets:
+ - `TechPreviewNoUpgrade`
+ - `DevPreviewNoUpgrade`
+ - `CustomNoUpgrade`
+
+ **Note**: This OpenShift FeatureSet gating is only enforced on OpenShift clusters. On MicroShift, the
+ FeatureSet restriction is not enforced, and the feature can be enabled using only the operator feature gate.
+
+#### DefaultCAPackage Implementation
+
+The DefaultCAPackage configuration replaces the upstream Debian-based init container
+approach with an OpenShift-native solution using CNO's trusted CA bundle injection.
+
+1. **Injection ConfigMap (OLM Bundle)**: A ConfigMap named `cert-manager-operator-trusted-ca-bundle` with the
+ label `config.openshift.io/inject-trusted-cabundle: true` is included in the OLM bundle and applied during
+ operator installation in the **operator namespace** (`cert-manager-operator`). CNO monitors for this label
+ and injects the cluster's trusted CA bundle into the `ca-bundle.crt` key. This ConfigMap is created early
+ during operator installation, giving CNO time to inject the CA bundle before any operand needs it. This
+ ConfigMap can also be consumed by other operands as needed.
+
+2. **Package Formatting**: When `defaultCAPackage.policy` is `Enabled`, the controller reads the injected CA
+ bundle and formats it into trust-manager's expected JSON format:
+ ```json
+ {
+ "name": "cert-manager-package-openshift",
+ "bundle": "-----BEGIN CERTIFICATE-----\n...",
+ "version": ""
+ }
+ ```
+
+3. **Package ConfigMap**: The controller creates a ConfigMap named `trust-manager-default-ca-package` in the
+ **operand namespace** (`cert-manager`) containing the formatted JSON package.
+
+4. **Volume Mount**: The package ConfigMap is mounted to the trust-manager deployment at `/packages`, and the
+ `--default-package-location=/packages/cert-manager-package-openshift.json` argument is added.
+
+5. **Controller Watch**: The trust-manager controller watches both ConfigMaps for changes:
+ - `cert-manager-operator-trusted-ca-bundle` in the operator namespace (injection ConfigMap)
+ - `trust-manager-default-ca-package` in the operand namespace (formatted package ConfigMap)
+
+ When CNO updates the injected CA bundle, the controller detects the change, reformats the package, and
+ updates the package ConfigMap in the operand namespace.
+
+6. **Pod Restart on Package Update**: To ensure trust-manager pods use the updated CA package, the controller
+ maintains a hash of the package content as an annotation on the Deployment (e.g.,
+ `operator.openshift.io/default-ca-package-hash`). When the package changes, the controller computes a new
+ hash and updates the Deployment annotation. This change triggers a rolling restart of the trust-manager pods,
+ ensuring they pick up the new CA bundle.
+
+#### RBAC Configuration
+
+The controller creates the following RBAC resources for trust-manager:
+
+**Cluster-scoped resources:**
+- `ClusterRole` (trust-manager): Permissions to manage Bundles, ConfigMaps across the cluster
+- `ClusterRoleBinding`: Binds ClusterRole to the ServiceAccount
+
+**Namespace-scoped resources:**
+- `Role` (trust-manager): Read access to secrets in the **trust namespace**
+- `RoleBinding` (trust-manager): Binds Role to ServiceAccount in the trust namespace
+- `Role` (trust-manager:leaderelection): Leader election permissions in the **operand namespace** (cert-manager)
+- `RoleBinding` (trust-manager:leaderelection): Binds Role to ServiceAccount in operand namespace
+
+**Trust Namespace Handling:**
+
+The trust namespace (`spec.trustManagerConfig.trustNamespace`) can be different from the operand namespace
+(cert-manager). The trust namespace **must exist** before creating the TrustManager CR. When a different
+trust namespace is configured:
+1. The operator validates that the trust namespace exists; if not, it sets a Degraded condition with a
+ message indicating the namespace does not exist
+2. The `trust-manager` Role and RoleBinding are created in the trust namespace (for secret access)
+3. The leader election Role and RoleBinding remain in the operand namespace (cert-manager)
+4. The ServiceAccount subject in all RoleBindings always references the operand namespace
+
+**Dynamic ClusterRole Rules:**
+
+The ClusterRole for trust-manager is dynamically configured based on the `secretTargets` configuration:
+
+- **Default (secretTargets.policy: Disabled)**: Read-only access to secrets, read-write access to configmaps
+- **secretTargets.policy: Custom**: Additional rules to create/update specific secret names listed in authorizedSecrets
+
+#### OLM Bundle Manifest
+
+The following ConfigMap is included in the OLM bundle and applied during operator installation. This ConfigMap
+triggers CNO to inject the cluster's trusted CA bundle, making it available for trust-manager and other operands.
+
+```yaml
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: cert-manager-operator-trusted-ca-bundle
+ namespace: cert-manager-operator
+ labels:
+ config.openshift.io/inject-trusted-cabundle: "true"
+ app.kubernetes.io/name: cert-manager-operator
+```
+
+#### Manifests for installing trust-manager
+
+Below are example static manifests used for creating required resources for installing trust-manager:
+
+1. ServiceAccount
+ ```yaml
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+ name: trust-manager
+ namespace: cert-manager
+ labels:
+ app: trust-manager
+ app.kubernetes.io/name: trust-manager
+ app.kubernetes.io/managed-by: cert-manager-operator
+ ```
+
+2. ClusterRole
+ ```yaml
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRole
+ metadata:
+ name: trust-manager
+ labels:
+ app: trust-manager
+ app.kubernetes.io/name: trust-manager
+ app.kubernetes.io/managed-by: cert-manager-operator
+ rules:
+ - apiGroups: ["trust.cert-manager.io"]
+ resources: ["bundles"]
+ verbs: ["get", "list", "watch"]
+ - apiGroups: ["trust.cert-manager.io"]
+ resources: ["bundles/finalizers"]
+ verbs: ["update"]
+ - apiGroups: ["trust.cert-manager.io"]
+ resources: ["bundles/status"]
+ verbs: ["patch"]
+ - apiGroups: [""]
+ resources: ["configmaps"]
+ verbs: ["get", "list", "create", "update", "patch", "watch", "delete"]
+ - apiGroups: [""]
+ resources: ["namespaces"]
+ verbs: ["get", "list", "watch"]
+ - apiGroups: [""]
+ resources: ["events"]
+ verbs: ["create", "patch"]
+ # Secret read access (when secretTargets.policy is Custom)
+ - apiGroups: [""]
+ resources: ["secrets"]
+ verbs: ["get", "list", "watch"]
+ # Secret write access (when secretTargets.policy is Custom)
+ # - apiGroups: [""]
+ # resources: ["secrets"]
+ # resourceNames: ["specific-secret-names"] # secrets listed in authorizedSecrets
+ # verbs: ["create", "update", "patch", "delete"]
+ ```
+
+3. Role (trust-manager) - Created in trust namespace for reading secrets
+ ```yaml
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: Role
+ metadata:
+ name: trust-manager
+ namespace: cert-manager # Or configured trustNamespace
+ labels:
+ app.kubernetes.io/name: trust-manager
+ app.kubernetes.io/managed-by: cert-manager-operator
+ rules:
+ - apiGroups: [""]
+ resources: ["secrets"]
+ verbs: ["get", "list", "watch"]
+ ```
+
+4. Role (trust-manager:leaderelection) - Created in operand namespace (cert-manager)
+ ```yaml
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: Role
+ metadata:
+ name: trust-manager:leaderelection
+ namespace: cert-manager
+ labels:
+ app.kubernetes.io/name: trust-manager
+ app.kubernetes.io/managed-by: cert-manager-operator
+ rules:
+ - apiGroups: ["coordination.k8s.io"]
+ resources: ["leases"]
+ verbs: ["get", "create", "update", "watch", "list"]
+ ```
+
+5. Deployment
+ ```yaml
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: trust-manager
+ namespace: cert-manager
+ labels:
+ app: trust-manager
+ app.kubernetes.io/name: trust-manager
+ app.kubernetes.io/managed-by: cert-manager-operator
+ spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: trust-manager
+ template:
+ metadata:
+ labels:
+ app: trust-manager
+ spec:
+ serviceAccountName: trust-manager
+ containers:
+ - name: trust-manager
+ image: quay.io/jetstack/trust-manager:v0.20.3
+ args:
+ - --log-format=text
+ - --log-level=1
+ - --metrics-port=9402
+ - --readiness-probe-port=6060
+ - --readiness-probe-path=/readyz
+ - --trust-namespace=cert-manager
+ - --webhook-host=0.0.0.0
+ - --webhook-port=6443
+ - --webhook-certificate-dir=/tls
+ # Added when secretTargets.policy is Custom
+ # - --secret-targets-enabled=true
+ # Added when defaultCAPackage.policy is Enabled
+ # - --default-package-location=/packages/cert-manager-package-openshift.json
+ ports:
+ - containerPort: 6443
+ - containerPort: 9402
+ readinessProbe:
+ httpGet:
+ port: 6060
+ path: /readyz
+ volumeMounts:
+ - name: tls
+ mountPath: /tls
+ readOnly: true
+ # Added when defaultCAPackage.policy is Enabled
+ # - name: default-ca-package
+ # mountPath: /packages
+ # readOnly: true
+ volumes:
+ - name: tls
+ secret:
+ secretName: trust-manager-tls
+ # Added when defaultCAPackage.policy is Enabled
+ # - name: default-ca-package
+ # configMap:
+ # name: trust-manager-default-ca-package
+ ```
+
+6. Certificate (for webhook TLS)
+ ```yaml
+ apiVersion: cert-manager.io/v1
+ kind: Certificate
+ metadata:
+ name: trust-manager
+ namespace: cert-manager
+ labels:
+ app: trust-manager
+ app.kubernetes.io/name: trust-manager
+ app.kubernetes.io/managed-by: cert-manager-operator
+ spec:
+ commonName: "trust-manager.cert-manager.svc"
+ dnsNames:
+ - "trust-manager.cert-manager.svc"
+ secretName: trust-manager-tls
+ revisionHistoryLimit: 1
+ issuerRef:
+ name: trust-manager
+ kind: Issuer
+ group: cert-manager.io
+ ```
+
+7. ValidatingWebhookConfiguration
+ ```yaml
+ apiVersion: admissionregistration.k8s.io/v1
+ kind: ValidatingWebhookConfiguration
+ metadata:
+ name: trust-manager
+ annotations:
+ cert-manager.io/inject-ca-from: cert-manager/trust-manager
+ webhooks:
+ - name: trust.cert-manager.io
+ clientConfig:
+ service:
+ name: trust-manager
+ namespace: cert-manager
+ path: /validate-trust-cert-manager-io-v1alpha1-bundle
+ rules:
+ - apiGroups: ["trust.cert-manager.io"]
+ apiVersions: ["v1alpha1"]
+ operations: ["CREATE", "UPDATE"]
+ resources: ["bundles"]
+ admissionReviewVersions: ["v1"]
+ sideEffects: None
+ ```
+
+### Risks and Mitigations
+
+
+- **DefaultCAPackage Dependency on CNO**: The DefaultCAPackage option depends on CNO's CA bundle
+ injection. The `cert-manager-operator-trusted-ca-bundle` ConfigMap is created during operator installation via the
+ OLM bundle, and CNO must inject the CA bundle into it. If CNO is not functioning correctly, the CA bundle
+ won't be injected, and trust-manager will fail to start when `defaultCAPackage.policy` is `Enabled`.
+ - Mitigation: The controller implements a requeue mechanism that waits for CNO to populate the
+ `cert-manager-operator-trusted-ca-bundle` ConfigMap before proceeding. Status conditions clearly indicate
+ when waiting for the CA bundle injection.
+
+
+
+- **Upcoming Bundle API Deprecation**: The upstream trust-manager project is planning to deprecate the current
+ cluster-scoped `Bundle` custom resource (`v1alpha1`) and introduce a new `ClusterBundle` CRD in `v1alpha2`.
+ Additionally, `Bundle` may be re-introduced in `v1` as a **namespace-scoped** resource (as opposed to the current
+ cluster-scoped type). This restructuring is intended to properly support both cluster and namespace scoped
+ resources with appropriate access controls.
+
+ **Impact**:
+ - Users who create `Bundle` resources may need to migrate to `ClusterBundle` or the new namespace-scoped `Bundle`
+ - Helm chart values may change, leading to changes in downstream CRD values.
+
+ **References**:
+ - [Announcement: trust-manager ClusterBundle Future](https://cert-manager.io/announcements/2025/09/05/trust-manager-clusterbundle-future/)
+ - [Design: Rename Bundle to ClusterBundle](https://github.com/cert-manager/trust-manager/blob/main/design/20241124-rename-bunde-to-clusterbundle.md)
+
+ - Mitigation:
+ - Monitor upstream changes and plan for API migration in future operator releases when both `Bundle` and `ClusterBundle` CRDs become available.
+
+### Drawbacks
+
+None
+
+## Design Details
+
+### Open Questions [optional]
+
+None
+
+## Test Plan
+
+- Verify trust-manager controller starts only when both operator feature gate is enabled AND cluster has
+ TechPreview-compatible feature set (TechPreviewNoUpgrade, DevPreviewNoUpgrade, or CustomNoUpgrade).
+- **MicroShift**: Verify trust-manager controller starts on MicroShift when only the operator feature gate is
+ enabled (FeatureSet gating is not enforced on MicroShift).
+- Enable `trust-manager-controller` by creating `trustmanagers.operator.openshift.io` CR and check the behavior with
+ default trust-manager configuration.
+- Enable `trust-manager-controller` by creating the `trustmanagers.operator.openshift.io` CR with permutations of
+ configurations and validate the behavior:
+ - Custom trust namespace
+ - Secret targets policy (Disabled/Custom)
+ - DefaultCAPackage policy (Enabled/Disabled)
+ - FilterExpiredCertificates policy (Enabled/Disabled)
+ - Common configurations: log levels and formats, resources, node selector, tolerations and affinity
+- Test Bundle CRD functionality:
+ - Create Bundle resources with various sources (ConfigMap, Secret, InLine)
+ - Test `useDefaultCAs` source when `defaultCAPackage.policy` is `Enabled`
+ - Verify target ConfigMaps/Secrets are created and updated correctly
+
+## Graduation Criteria
+
+### Dev Preview -> Tech Preview
+
+N/A. The feature will be released directly as Tech Preview.
+
+### Tech Preview
+
+Trust-manager will be available as Tech Preview starting from cert-manager-operator v1.19.0 release.
+
+### Tech Preview -> GA
+
+- Feature is enabled by default. OpenShift FeatureSet dependency removed; feature available on all clusters.
+- Complete end-user documentation.
+- Complete UTs and e2e tests are present.
+- Evaluate adding `targetNamespaces` configuration to restrict which namespaces trust-manager can write Bundle
+ targets to (currently defaults to all namespaces).
+- Address upstream `Bundle` to `ClusterBundle` API migration if applicable (see Risks section for details).
+- Direct upgrade from Tech Preview to GA is not supported; users should perform a fresh operator installation.
+
+### Removing a deprecated feature
+
+None.
+
+## Upgrade / Downgrade Strategy
+
+On upgrade:
+- cert-manager-operator will have functionality to enable trust-manager and based on the administrator configuration,
+ trust-manager will be deployed and available for usage.
+
+On downgrade:
+- Operator downgrade is not supported.
+
+## Version Skew Strategy
+
+trust-manager will be supported for:
+- cert-manager Operator v1.19.0+
+
+
+## Operational Aspects of API Extensions
+
+### Failure Modes
+
+- **Trust Namespace Does Not Exist**: If the configured `trustNamespace` does not exist, the TrustManager status
+ will show a Degraded condition with a message indicating the namespace does not exist. The operator will not
+ deploy trust-manager until the namespace is created by the user.
+
+- **cert-manager Not Available**: If cert-manager is not installed or the issuer is not available, the webhook
+ certificate will fail to be issued, and trust-manager will not start. The status condition will indicate the failure.
+
+
+## Support Procedures
+
+- Listing all the resources created for installing the `trust-manager`
+ ```bash
+ # Resources in cert-manager namespace (operand namespace)
+ oc get Certificates,Issuers,ClusterRoles,ClusterRoleBindings,Deployments,Roles,RoleBindings,Services,ServiceAccounts,ValidatingWebhookConfigurations,ConfigMaps -l "app.kubernetes.io/name=cert-manager-trust-manager" -n cert-manager
+
+ # If trust namespace is different from cert-manager, also check for Role/RoleBinding there
+ # oc get Roles,RoleBindings -l "app.kubernetes.io/name=cert-manager-trust-manager" -n
+ ```
+
+- Checking TrustManager status
+ ```bash
+ oc get trustmanager cluster -o yaml
+ ```
+
+- Listing all Bundle resources
+ ```bash
+ oc get Bundle -A
+ ```
+
+- Example TrustManager CR with `defaultCAPackage.policy: Enabled`
+ ```yaml
+ apiVersion: operator.openshift.io/v1alpha1
+ kind: TrustManager
+ metadata:
+ name: cluster
+ spec:
+ trustManagerConfig:
+ logLevel: 1
+ logFormat: text
+ trustNamespace: cert-manager
+ defaultCAPackage:
+ policy: Enabled
+ filterExpiredCertificates: Enabled
+ ```
+
+
+- Example Bundle with multiple sources and namespace selector
+ ```yaml
+ apiVersion: trust.cert-manager.io/v1alpha1
+ kind: Bundle
+ metadata:
+ name: my-org-trust-bundle
+ spec:
+ sources:
+ # Include default OpenShift CA certificates
+ - useDefaultCAs: true
+ # Include CA from a ConfigMap in trust namespace (cert-manager)
+ - configMap:
+ name: "my-org-ca"
+ key: "ca.crt"
+ # Include CA from a Secret in trust namespace
+ - secret:
+ name: "internal-ca"
+ key: "tls.crt"
+ # Include inline CA certificate
+ - inLine: |
+ -----BEGIN CERTIFICATE-----
+ MIIFazCCA1OgAwIBAgIUe...
+ -----END CERTIFICATE-----
+ target:
+ configMap:
+ key: "trust-bundle.pem"
+ # Only create in namespaces with this label
+ namespaceSelector:
+ matchLabels:
+ trust-bundle-injection: enabled
+ ```
+ This Bundle will create a ConfigMap named `my-org-trust-bundle` containing the combined CA certificates
+ in every namespace that has the label `trust-bundle-injection: enabled`.
+
+## Implementation History
+
+N/A
+
+## Alternatives (Not Implemented)
+
+
+- Kubernetes ClusterTrustBundle : https://github.com/kubernetes/enhancements/tree/master/keps/sig-auth/3257-cluster-trust-bundles
+
+## Infrastructure Needed [optional]
+
+- [openshift/cert-manager-trust-manager](https://github.com/openshift/cert-manager-trust-manager) GitHub repository (forked from upstream).
+- trust-manager container image published to the OpenShift registry.