diff --git a/api/operator/v1alpha1/features.go b/api/operator/v1alpha1/features.go index d28e35132..6f74e9d08 100644 --- a/api/operator/v1alpha1/features.go +++ b/api/operator/v1alpha1/features.go @@ -13,8 +13,19 @@ var ( // For more details, // https://github.com/openshift/enhancements/blob/master/enhancements/cert-manager/istio-csr-controller.md FeatureIstioCSR featuregate.Feature = "IstioCSR" + + // TrustManager enables the controller for trustmanagers.operator.openshift.io resource, + // which extends cert-manager-operator to deploy and manage the trust-manager operand. + // 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. + // + // For more details, + // https://github.com/openshift/enhancements/blob/master/enhancements/cert-manager/trust-manager-controller.md + FeatureTrustManager featuregate.Feature = "TrustManager" ) var OperatorFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ - FeatureIstioCSR: {Default: true, PreRelease: featuregate.GA}, + FeatureIstioCSR: {Default: true, PreRelease: featuregate.GA}, + FeatureTrustManager: {Default: false, PreRelease: featuregate.Alpha}, } diff --git a/api/operator/v1alpha1/tests/trustmanagers.operator.openshift.io/TrustManager.testsuite.yaml b/api/operator/v1alpha1/tests/trustmanagers.operator.openshift.io/TrustManager.testsuite.yaml new file mode 100644 index 000000000..91e299e24 --- /dev/null +++ b/api/operator/v1alpha1/tests/trustmanagers.operator.openshift.io/TrustManager.testsuite.yaml @@ -0,0 +1,535 @@ +apiVersion: apiextensions.k8s.io/v1 +name: "TrustManager" +crdName: trustmanagers.operator.openshift.io +tests: + onCreate: + - name: Should be able to create a minimal TrustManager with required fields + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: {} + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 1 + logFormat: "text" + trustNamespace: "cert-manager" + filterExpiredCertificates: "Disabled" + secretTargets: + policy: "Disabled" + defaultCAPackage: + policy: "Disabled" + - name: Should reject TrustManager with name other than cluster + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: not-cluster + spec: + trustManagerConfig: {} + expectedError: "TrustManager is a singleton, .metadata.name must be 'cluster'" + - name: Should accept logFormat text + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logFormat: "text" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logFormat: "text" + - name: Should accept logFormat json + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logFormat: "json" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logFormat: "json" + - name: Should reject invalid logFormat + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logFormat: "yaml" + expectedError: "Unsupported value" + - name: Should accept logLevel within valid range + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 3 + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 3 + - name: Should reject logLevel below minimum + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 0 + expectedError: "spec.trustManagerConfig.logLevel" + - name: Should reject logLevel above maximum + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 6 + expectedError: "spec.trustManagerConfig.logLevel" + - name: Should accept filterExpiredCertificates Enabled + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + filterExpiredCertificates: "Enabled" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + filterExpiredCertificates: "Enabled" + - name: Should accept filterExpiredCertificates Disabled + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + filterExpiredCertificates: "Disabled" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + filterExpiredCertificates: "Disabled" + - name: Should reject invalid filterExpiredCertificates value + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + filterExpiredCertificates: "Invalid" + expectedError: "Unsupported value" + - name: Should accept secretTargets with policy Custom and authorizedSecrets + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Custom" + authorizedSecrets: + - "my-secret" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Custom" + authorizedSecrets: + - "my-secret" + - name: Should reject secretTargets Custom without authorizedSecrets + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Custom" + expectedError: "authorizedSecrets must not be empty when policy is Custom" + - name: Should reject secretTargets Disabled with authorizedSecrets + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Disabled" + authorizedSecrets: + - "my-secret" + expectedError: "authorizedSecrets must be empty when policy is not Custom" + - name: Should reject invalid secretTargets policy + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "AllEnabled" + expectedError: "Unsupported value" + - name: Should accept defaultCAPackage with Enabled policy + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + defaultCAPackage: + policy: "Enabled" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + defaultCAPackage: + policy: "Enabled" + - name: Should reject invalid defaultCAPackage policy + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + defaultCAPackage: + policy: "Invalid" + expectedError: "Unsupported value" + - name: Should accept custom trustNamespace + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + trustNamespace: "my-trust-ns" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + trustNamespace: "my-trust-ns" + - name: Should accept controllerConfig with labels and annotations + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: {} + controllerConfig: + labels: + app: trust-manager + annotations: + note: test + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: {} + controllerConfig: + labels: + app: trust-manager + annotations: + note: test + - name: Should accept nodeSelector + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + nodeSelector: + kubernetes.io/os: linux + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + nodeSelector: + kubernetes.io/os: linux + - name: Should accept tolerations + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + tolerations: + - key: "node-role.kubernetes.io/master" + operator: "Exists" + effect: "NoSchedule" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + tolerations: + - key: "node-role.kubernetes.io/master" + operator: "Exists" + effect: "NoSchedule" + - name: Should accept full configuration + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 2 + logFormat: "json" + trustNamespace: "custom-trust" + filterExpiredCertificates: "Enabled" + secretTargets: + policy: "Custom" + authorizedSecrets: + - "bundle-secret" + - "another-secret" + defaultCAPackage: + policy: "Enabled" + nodeSelector: + kubernetes.io/os: linux + controllerConfig: + labels: + environment: production + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 2 + logFormat: "json" + trustNamespace: "custom-trust" + filterExpiredCertificates: "Enabled" + secretTargets: + policy: "Custom" + authorizedSecrets: + - "another-secret" + - "bundle-secret" + defaultCAPackage: + policy: "Enabled" + nodeSelector: + kubernetes.io/os: linux + controllerConfig: + labels: + environment: production + onUpdate: + - name: Should not allow changing immutable trustNamespace + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + trustNamespace: "cert-manager" + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + trustNamespace: "new-namespace" + expectedError: "trustNamespace is immutable once set" + - name: Should allow updating logLevel + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 1 + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 3 + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logLevel: 3 + - name: Should allow updating logFormat + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logFormat: "text" + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logFormat: "json" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + logFormat: "json" + - name: Should allow updating filterExpiredCertificates + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + filterExpiredCertificates: "Disabled" + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + filterExpiredCertificates: "Enabled" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + filterExpiredCertificates: "Enabled" + - name: Should allow updating defaultCAPackage policy + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + defaultCAPackage: + policy: "Disabled" + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + defaultCAPackage: + policy: "Enabled" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + defaultCAPackage: + policy: "Enabled" + - name: Should allow changing secretTargets from Disabled to Custom with secrets + initial: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Disabled" + updated: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Custom" + authorizedSecrets: + - "my-secret" + expected: | + apiVersion: operator.openshift.io/v1alpha1 + kind: TrustManager + metadata: + name: cluster + spec: + trustManagerConfig: + secretTargets: + policy: "Custom" + authorizedSecrets: + - "my-secret" diff --git a/api/operator/v1alpha1/trustmanager_types.go b/api/operator/v1alpha1/trustmanager_types.go new file mode 100644 index 000000000..945a2707d --- /dev/null +++ b/api/operator/v1alpha1/trustmanager_types.go @@ -0,0 +1,265 @@ +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func init() { + SchemeBuilder.Register(&TrustManager{}, &TrustManagerList{}) +} + +// +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"` +} diff --git a/api/operator/v1alpha1/trustmanager_types_test.go b/api/operator/v1alpha1/trustmanager_types_test.go new file mode 100644 index 000000000..337858dd1 --- /dev/null +++ b/api/operator/v1alpha1/trustmanager_types_test.go @@ -0,0 +1,531 @@ +package v1alpha1 + +import ( + "os" + "path" + "testing" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + "sigs.k8s.io/yaml" +) + +const ( + trustmanagerCRDFile = "operator.openshift.io_trustmanagers.yaml" + trustmanagerCRDFilePath = "../../../config/crd/bases" +) + +// TestTrustManagerStatusDefault verifies that the trustmanager CR status does not have default value. +// The admission code is expecting that the trustmanager status field will not have a default value. +// It allows separating between clean installation and the roll-back to the previous version of the cluster. +func TestTrustManagerStatusDefault(t *testing.T) { + filepath := path.Join(trustmanagerCRDFilePath, trustmanagerCRDFile) + trustmanagerCRDBytes, err := os.ReadFile(filepath) + if err != nil { + t.Fatalf("failed to read trustmanager CRD file %q: %v", filepath, err) + } + + var trustmanagerCRD map[string]interface{} + if err := yaml.Unmarshal(trustmanagerCRDBytes, &trustmanagerCRD); err != nil { + t.Fatalf("failed to unmarshal trustmanager CRD: %v", err) + } + trustmanagerCRDSpec := trustmanagerCRD["spec"].(map[string]interface{}) + trustmanagerCRDVersions := trustmanagerCRDSpec["versions"].([]interface{}) + for _, v := range trustmanagerCRDVersions { + trustmanagerCRDVersion := v.(map[string]interface{}) + status, exists, err := unstructured.NestedMap(trustmanagerCRDVersion, "schema", "openAPIV3Schema", "properties", "status") + if err != nil { + t.Fatalf("failed to get nested map: %v", err) + } + + if !exists { + t.Fatalf("one of fields does not exist under the CRD") + } + + if _, ok := status["default"]; ok { + t.Fatalf("expected no default for the trustmanager CRD status") + } + } +} + +// TestTrustManagerCRDSingletonValidation verifies that the CRD has the singleton XValidation rule. +func TestTrustManagerCRDSingletonValidation(t *testing.T) { + filepath := path.Join(trustmanagerCRDFilePath, trustmanagerCRDFile) + trustmanagerCRDBytes, err := os.ReadFile(filepath) + if err != nil { + t.Fatalf("failed to read trustmanager CRD file %q: %v", filepath, err) + } + + var trustmanagerCRD map[string]interface{} + if err := yaml.Unmarshal(trustmanagerCRDBytes, &trustmanagerCRD); err != nil { + t.Fatalf("failed to unmarshal trustmanager CRD: %v", err) + } + trustmanagerCRDSpec := trustmanagerCRD["spec"].(map[string]interface{}) + trustmanagerCRDVersions := trustmanagerCRDSpec["versions"].([]interface{}) + for _, v := range trustmanagerCRDVersions { + trustmanagerCRDVersion := v.(map[string]interface{}) + schema, exists, err := unstructured.NestedMap(trustmanagerCRDVersion, "schema", "openAPIV3Schema") + if err != nil { + t.Fatalf("failed to get nested map: %v", err) + } + + if !exists { + t.Fatalf("openAPIV3Schema does not exist under the CRD") + } + + validations, exists, err := unstructured.NestedSlice(schema, "x-kubernetes-validations") + if err != nil { + t.Fatalf("failed to get x-kubernetes-validations: %v", err) + } + + if !exists || len(validations) == 0 { + t.Fatalf("expected x-kubernetes-validations for singleton constraint") + } + + found := false + for _, val := range validations { + valMap, ok := val.(map[string]interface{}) + if !ok { + continue + } + rule, _ := valMap["rule"].(string) + if rule == "self.metadata.name == 'cluster'" { + found = true + break + } + } + + if !found { + t.Fatalf("expected singleton validation rule 'self.metadata.name == cluster'") + } + } +} + +// TestTrustManagerCRDScope verifies that the CRD scope is Cluster. +func TestTrustManagerCRDScope(t *testing.T) { + filepath := path.Join(trustmanagerCRDFilePath, trustmanagerCRDFile) + trustmanagerCRDBytes, err := os.ReadFile(filepath) + if err != nil { + t.Fatalf("failed to read trustmanager CRD file %q: %v", filepath, err) + } + + var trustmanagerCRD map[string]interface{} + if err := yaml.Unmarshal(trustmanagerCRDBytes, &trustmanagerCRD); err != nil { + t.Fatalf("failed to unmarshal trustmanager CRD: %v", err) + } + trustmanagerCRDSpec := trustmanagerCRD["spec"].(map[string]interface{}) + scope, ok := trustmanagerCRDSpec["scope"].(string) + if !ok || scope != "Cluster" { + t.Fatalf("expected CRD scope to be 'Cluster', got %q", scope) + } +} + +// TestTrustManagerCRDRequiredFields verifies that the CRD has spec as required and +// trustManagerConfig as required within spec. +func TestTrustManagerCRDRequiredFields(t *testing.T) { + filepath := path.Join(trustmanagerCRDFilePath, trustmanagerCRDFile) + trustmanagerCRDBytes, err := os.ReadFile(filepath) + if err != nil { + t.Fatalf("failed to read trustmanager CRD file %q: %v", filepath, err) + } + + var trustmanagerCRD map[string]interface{} + if err := yaml.Unmarshal(trustmanagerCRDBytes, &trustmanagerCRD); err != nil { + t.Fatalf("failed to unmarshal trustmanager CRD: %v", err) + } + trustmanagerCRDSpec := trustmanagerCRD["spec"].(map[string]interface{}) + trustmanagerCRDVersions := trustmanagerCRDSpec["versions"].([]interface{}) + for _, v := range trustmanagerCRDVersions { + trustmanagerCRDVersion := v.(map[string]interface{}) + + // Check spec is required at root level + rootRequired, _, _ := unstructured.NestedStringSlice(trustmanagerCRDVersion, "schema", "openAPIV3Schema", "required") + foundSpec := false + for _, r := range rootRequired { + if r == "spec" { + foundSpec = true + break + } + } + if !foundSpec { + t.Fatalf("expected 'spec' to be a required field at root level") + } + + // Check trustManagerConfig is required within spec + specRequired, _, _ := unstructured.NestedStringSlice(trustmanagerCRDVersion, "schema", "openAPIV3Schema", "properties", "spec", "required") + foundTMConfig := false + for _, r := range specRequired { + if r == "trustManagerConfig" { + foundTMConfig = true + break + } + } + if !foundTMConfig { + t.Fatalf("expected 'trustManagerConfig' to be a required field within spec") + } + } +} + +// TestTrustManagerCRDEnumFields verifies that enum fields have the correct allowed values. +func TestTrustManagerCRDEnumFields(t *testing.T) { + filepath := path.Join(trustmanagerCRDFilePath, trustmanagerCRDFile) + trustmanagerCRDBytes, err := os.ReadFile(filepath) + if err != nil { + t.Fatalf("failed to read trustmanager CRD file %q: %v", filepath, err) + } + + var trustmanagerCRD map[string]interface{} + if err := yaml.Unmarshal(trustmanagerCRDBytes, &trustmanagerCRD); err != nil { + t.Fatalf("failed to unmarshal trustmanager CRD: %v", err) + } + trustmanagerCRDSpec := trustmanagerCRD["spec"].(map[string]interface{}) + trustmanagerCRDVersions := trustmanagerCRDSpec["versions"].([]interface{}) + + tests := []struct { + name string + path []string + expectedEnum []string + }{ + { + name: "logFormat enum", + path: []string{"schema", "openAPIV3Schema", "properties", "spec", "properties", "trustManagerConfig", "properties", "logFormat", "enum"}, + expectedEnum: []string{"text", "json"}, + }, + { + name: "filterExpiredCertificates enum", + path: []string{"schema", "openAPIV3Schema", "properties", "spec", "properties", "trustManagerConfig", "properties", "filterExpiredCertificates", "enum"}, + expectedEnum: []string{"Enabled", "Disabled"}, + }, + { + name: "secretTargets.policy enum", + path: []string{"schema", "openAPIV3Schema", "properties", "spec", "properties", "trustManagerConfig", "properties", "secretTargets", "properties", "policy", "enum"}, + expectedEnum: []string{"Disabled", "Custom"}, + }, + { + name: "defaultCAPackage.policy enum", + path: []string{"schema", "openAPIV3Schema", "properties", "spec", "properties", "trustManagerConfig", "properties", "defaultCAPackage", "properties", "policy", "enum"}, + expectedEnum: []string{"Enabled", "Disabled"}, + }, + } + + for _, v := range trustmanagerCRDVersions { + trustmanagerCRDVersion := v.(map[string]interface{}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + enumSlice, exists, err := unstructured.NestedSlice(trustmanagerCRDVersion, tt.path...) + if err != nil { + t.Fatalf("failed to get enum for %s: %v", tt.name, err) + } + if !exists { + t.Fatalf("enum not found for %s", tt.name) + } + if len(enumSlice) != len(tt.expectedEnum) { + t.Fatalf("expected %d enum values for %s, got %d", len(tt.expectedEnum), tt.name, len(enumSlice)) + } + for i, expected := range tt.expectedEnum { + actual, ok := enumSlice[i].(string) + if !ok || actual != expected { + t.Fatalf("expected enum value %q at index %d for %s, got %q", expected, i, tt.name, actual) + } + } + }) + } + } +} + +// TestTrustManagerCRDDefaultValues verifies that default values are set correctly in the CRD. +func TestTrustManagerCRDDefaultValues(t *testing.T) { + filepath := path.Join(trustmanagerCRDFilePath, trustmanagerCRDFile) + trustmanagerCRDBytes, err := os.ReadFile(filepath) + if err != nil { + t.Fatalf("failed to read trustmanager CRD file %q: %v", filepath, err) + } + + var trustmanagerCRD map[string]interface{} + if err := yaml.Unmarshal(trustmanagerCRDBytes, &trustmanagerCRD); err != nil { + t.Fatalf("failed to unmarshal trustmanager CRD: %v", err) + } + trustmanagerCRDSpec := trustmanagerCRD["spec"].(map[string]interface{}) + trustmanagerCRDVersions := trustmanagerCRDSpec["versions"].([]interface{}) + + tests := []struct { + name string + path []string + expectedValue interface{} + }{ + { + name: "logLevel default", + path: []string{"schema", "openAPIV3Schema", "properties", "spec", "properties", "trustManagerConfig", "properties", "logLevel", "default"}, + expectedValue: float64(1), + }, + { + name: "logFormat default", + path: []string{"schema", "openAPIV3Schema", "properties", "spec", "properties", "trustManagerConfig", "properties", "logFormat", "default"}, + expectedValue: "text", + }, + { + name: "trustNamespace default", + path: []string{"schema", "openAPIV3Schema", "properties", "spec", "properties", "trustManagerConfig", "properties", "trustNamespace", "default"}, + expectedValue: "cert-manager", + }, + { + name: "filterExpiredCertificates default", + path: []string{"schema", "openAPIV3Schema", "properties", "spec", "properties", "trustManagerConfig", "properties", "filterExpiredCertificates", "default"}, + expectedValue: "Disabled", + }, + { + name: "secretTargets.policy default", + path: []string{"schema", "openAPIV3Schema", "properties", "spec", "properties", "trustManagerConfig", "properties", "secretTargets", "properties", "policy", "default"}, + expectedValue: "Disabled", + }, + { + name: "defaultCAPackage.policy default", + path: []string{"schema", "openAPIV3Schema", "properties", "spec", "properties", "trustManagerConfig", "properties", "defaultCAPackage", "properties", "policy", "default"}, + expectedValue: "Disabled", + }, + } + + for _, v := range trustmanagerCRDVersions { + trustmanagerCRDVersion := v.(map[string]interface{}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + val, exists, err := unstructured.NestedFieldNoCopy(trustmanagerCRDVersion, tt.path...) + if err != nil { + t.Fatalf("failed to get default for %s: %v", tt.name, err) + } + if !exists { + t.Fatalf("default not found for %s", tt.name) + } + if val != tt.expectedValue { + t.Fatalf("expected default %v for %s, got %v", tt.expectedValue, tt.name, val) + } + }) + } + } +} + +// TestTrustManagerCRDLogLevelBounds verifies that logLevel has correct min/max bounds. +func TestTrustManagerCRDLogLevelBounds(t *testing.T) { + filepath := path.Join(trustmanagerCRDFilePath, trustmanagerCRDFile) + trustmanagerCRDBytes, err := os.ReadFile(filepath) + if err != nil { + t.Fatalf("failed to read trustmanager CRD file %q: %v", filepath, err) + } + + var trustmanagerCRD map[string]interface{} + if err := yaml.Unmarshal(trustmanagerCRDBytes, &trustmanagerCRD); err != nil { + t.Fatalf("failed to unmarshal trustmanager CRD: %v", err) + } + trustmanagerCRDSpec := trustmanagerCRD["spec"].(map[string]interface{}) + trustmanagerCRDVersions := trustmanagerCRDSpec["versions"].([]interface{}) + + for _, v := range trustmanagerCRDVersions { + trustmanagerCRDVersion := v.(map[string]interface{}) + basePath := []string{"schema", "openAPIV3Schema", "properties", "spec", "properties", "trustManagerConfig", "properties", "logLevel"} + + min, exists, _ := unstructured.NestedFieldNoCopy(trustmanagerCRDVersion, append(basePath, "minimum")...) + if !exists { + t.Fatalf("logLevel minimum not found") + } + if min != float64(1) { + t.Fatalf("expected logLevel minimum to be 1, got %v", min) + } + + max, exists, _ := unstructured.NestedFieldNoCopy(trustmanagerCRDVersion, append(basePath, "maximum")...) + if !exists { + t.Fatalf("logLevel maximum not found") + } + if max != float64(5) { + t.Fatalf("expected logLevel maximum to be 5, got %v", max) + } + } +} + +// TestTrustManagerCRDTrustNamespaceBounds verifies that trustNamespace has correct length constraints. +func TestTrustManagerCRDTrustNamespaceBounds(t *testing.T) { + filepath := path.Join(trustmanagerCRDFilePath, trustmanagerCRDFile) + trustmanagerCRDBytes, err := os.ReadFile(filepath) + if err != nil { + t.Fatalf("failed to read trustmanager CRD file %q: %v", filepath, err) + } + + var trustmanagerCRD map[string]interface{} + if err := yaml.Unmarshal(trustmanagerCRDBytes, &trustmanagerCRD); err != nil { + t.Fatalf("failed to unmarshal trustmanager CRD: %v", err) + } + trustmanagerCRDSpec := trustmanagerCRD["spec"].(map[string]interface{}) + trustmanagerCRDVersions := trustmanagerCRDSpec["versions"].([]interface{}) + + for _, v := range trustmanagerCRDVersions { + trustmanagerCRDVersion := v.(map[string]interface{}) + basePath := []string{"schema", "openAPIV3Schema", "properties", "spec", "properties", "trustManagerConfig", "properties", "trustNamespace"} + + minLen, exists, _ := unstructured.NestedFieldNoCopy(trustmanagerCRDVersion, append(basePath, "minLength")...) + if !exists { + t.Fatalf("trustNamespace minLength not found") + } + if minLen != float64(1) { + t.Fatalf("expected trustNamespace minLength to be 1, got %v", minLen) + } + + maxLen, exists, _ := unstructured.NestedFieldNoCopy(trustmanagerCRDVersion, append(basePath, "maxLength")...) + if !exists { + t.Fatalf("trustNamespace maxLength not found") + } + if maxLen != float64(63) { + t.Fatalf("expected trustNamespace maxLength to be 63, got %v", maxLen) + } + } +} + +// TestTrustManagerCRDSecretTargetsValidation verifies that the secretTargets field has proper CEL validation rules. +func TestTrustManagerCRDSecretTargetsValidation(t *testing.T) { + filepath := path.Join(trustmanagerCRDFilePath, trustmanagerCRDFile) + trustmanagerCRDBytes, err := os.ReadFile(filepath) + if err != nil { + t.Fatalf("failed to read trustmanager CRD file %q: %v", filepath, err) + } + + var trustmanagerCRD map[string]interface{} + if err := yaml.Unmarshal(trustmanagerCRDBytes, &trustmanagerCRD); err != nil { + t.Fatalf("failed to unmarshal trustmanager CRD: %v", err) + } + trustmanagerCRDSpec := trustmanagerCRD["spec"].(map[string]interface{}) + trustmanagerCRDVersions := trustmanagerCRDSpec["versions"].([]interface{}) + + expectedValidations := []string{ + "authorizedSecrets must not be empty when policy is Custom", + "authorizedSecrets must be empty when policy is not Custom", + } + + for _, v := range trustmanagerCRDVersions { + trustmanagerCRDVersion := v.(map[string]interface{}) + validationsPath := []string{"schema", "openAPIV3Schema", "properties", "spec", "properties", "trustManagerConfig", "properties", "secretTargets", "x-kubernetes-validations"} + + validations, exists, err := unstructured.NestedSlice(trustmanagerCRDVersion, validationsPath...) + if err != nil { + t.Fatalf("failed to get secretTargets validations: %v", err) + } + if !exists { + t.Fatalf("secretTargets x-kubernetes-validations not found") + } + + for _, expectedMsg := range expectedValidations { + found := false + for _, val := range validations { + valMap, ok := val.(map[string]interface{}) + if !ok { + continue + } + msg, _ := valMap["message"].(string) + if msg == expectedMsg { + found = true + break + } + } + if !found { + t.Fatalf("expected secretTargets validation with message %q", expectedMsg) + } + } + } +} + +// TestTrustManagerCRDTrustNamespaceImmutability verifies that the trustNamespace field has immutability validation. +func TestTrustManagerCRDTrustNamespaceImmutability(t *testing.T) { + filepath := path.Join(trustmanagerCRDFilePath, trustmanagerCRDFile) + trustmanagerCRDBytes, err := os.ReadFile(filepath) + if err != nil { + t.Fatalf("failed to read trustmanager CRD file %q: %v", filepath, err) + } + + var trustmanagerCRD map[string]interface{} + if err := yaml.Unmarshal(trustmanagerCRDBytes, &trustmanagerCRD); err != nil { + t.Fatalf("failed to unmarshal trustmanager CRD: %v", err) + } + trustmanagerCRDSpec := trustmanagerCRD["spec"].(map[string]interface{}) + trustmanagerCRDVersions := trustmanagerCRDSpec["versions"].([]interface{}) + + for _, v := range trustmanagerCRDVersions { + trustmanagerCRDVersion := v.(map[string]interface{}) + validationsPath := []string{"schema", "openAPIV3Schema", "properties", "spec", "properties", "trustManagerConfig", "properties", "trustNamespace", "x-kubernetes-validations"} + + validations, exists, err := unstructured.NestedSlice(trustmanagerCRDVersion, validationsPath...) + if err != nil { + t.Fatalf("failed to get trustNamespace validations: %v", err) + } + if !exists { + t.Fatalf("trustNamespace x-kubernetes-validations not found") + } + + found := false + for _, val := range validations { + valMap, ok := val.(map[string]interface{}) + if !ok { + continue + } + msg, _ := valMap["message"].(string) + if msg == "trustNamespace is immutable once set" { + found = true + break + } + } + if !found { + t.Fatalf("expected trustNamespace immutability validation rule") + } + } +} + +// TestTrustManagerCRDPrinterColumns verifies that the CRD has the correct additional printer columns. +func TestTrustManagerCRDPrinterColumns(t *testing.T) { + filepath := path.Join(trustmanagerCRDFilePath, trustmanagerCRDFile) + trustmanagerCRDBytes, err := os.ReadFile(filepath) + if err != nil { + t.Fatalf("failed to read trustmanager CRD file %q: %v", filepath, err) + } + + var trustmanagerCRD map[string]interface{} + if err := yaml.Unmarshal(trustmanagerCRDBytes, &trustmanagerCRD); err != nil { + t.Fatalf("failed to unmarshal trustmanager CRD: %v", err) + } + trustmanagerCRDSpec := trustmanagerCRD["spec"].(map[string]interface{}) + trustmanagerCRDVersions := trustmanagerCRDSpec["versions"].([]interface{}) + + expectedColumns := []struct { + name string + jsonPath string + }{ + {name: "Ready", jsonPath: ".status.conditions[?(@.type=='Ready')].status"}, + {name: "Message", jsonPath: ".status.conditions[?(@.type=='Ready')].message"}, + {name: "AGE", jsonPath: ".metadata.creationTimestamp"}, + } + + for _, v := range trustmanagerCRDVersions { + trustmanagerCRDVersion := v.(map[string]interface{}) + columns, exists := trustmanagerCRDVersion["additionalPrinterColumns"] + if !exists { + t.Fatalf("additionalPrinterColumns not found") + } + + columnList, ok := columns.([]interface{}) + if !ok { + t.Fatalf("additionalPrinterColumns is not a list") + } + + if len(columnList) != len(expectedColumns) { + t.Fatalf("expected %d printer columns, got %d", len(expectedColumns), len(columnList)) + } + + for i, expected := range expectedColumns { + col, ok := columnList[i].(map[string]interface{}) + if !ok { + t.Fatalf("printer column at index %d is not a map", i) + } + if col["name"] != expected.name { + t.Fatalf("expected printer column name %q at index %d, got %q", expected.name, i, col["name"]) + } + if col["jsonPath"] != expected.jsonPath { + t.Fatalf("expected printer column jsonPath %q at index %d, got %q", expected.jsonPath, i, col["jsonPath"]) + } + } + } +} diff --git a/api/operator/v1alpha1/zz_generated.deepcopy.go b/api/operator/v1alpha1/zz_generated.deepcopy.go index d878e25ce..eddbd4337 100644 --- a/api/operator/v1alpha1/zz_generated.deepcopy.go +++ b/api/operator/v1alpha1/zz_generated.deepcopy.go @@ -278,6 +278,21 @@ func (in *ControllerConfig) DeepCopy() *ControllerConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DefaultCAPackageConfig) DeepCopyInto(out *DefaultCAPackageConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DefaultCAPackageConfig. +func (in *DefaultCAPackageConfig) DeepCopy() *DefaultCAPackageConfig { + if in == nil { + return nil + } + out := new(DefaultCAPackageConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DeploymentConfig) DeepCopyInto(out *DeploymentConfig) { *out = *in @@ -535,6 +550,26 @@ func (in *NetworkPolicy) DeepCopy() *NetworkPolicy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretTargetsConfig) DeepCopyInto(out *SecretTargetsConfig) { + *out = *in + if in.AuthorizedSecrets != nil { + in, out := &in.AuthorizedSecrets, &out.AuthorizedSecrets + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretTargetsConfig. +func (in *SecretTargetsConfig) DeepCopy() *SecretTargetsConfig { + if in == nil { + return nil + } + out := new(SecretTargetsConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServerConfig) DeepCopyInto(out *ServerConfig) { *out = *in @@ -550,6 +585,164 @@ func (in *ServerConfig) DeepCopy() *ServerConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrustManager) DeepCopyInto(out *TrustManager) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrustManager. +func (in *TrustManager) DeepCopy() *TrustManager { + if in == nil { + return nil + } + out := new(TrustManager) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TrustManager) 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 *TrustManagerConfig) DeepCopyInto(out *TrustManagerConfig) { + *out = *in + in.SecretTargets.DeepCopyInto(&out.SecretTargets) + out.DefaultCAPackage = in.DefaultCAPackage + in.Resources.DeepCopyInto(&out.Resources) + if in.Affinity != nil { + in, out := &in.Affinity, &out.Affinity + *out = new(v1.Affinity) + (*in).DeepCopyInto(*out) + } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]v1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrustManagerConfig. +func (in *TrustManagerConfig) DeepCopy() *TrustManagerConfig { + if in == nil { + return nil + } + out := new(TrustManagerConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrustManagerControllerConfig) DeepCopyInto(out *TrustManagerControllerConfig) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrustManagerControllerConfig. +func (in *TrustManagerControllerConfig) DeepCopy() *TrustManagerControllerConfig { + if in == nil { + return nil + } + out := new(TrustManagerControllerConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrustManagerList) DeepCopyInto(out *TrustManagerList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]TrustManager, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrustManagerList. +func (in *TrustManagerList) DeepCopy() *TrustManagerList { + if in == nil { + return nil + } + out := new(TrustManagerList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TrustManagerList) 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 *TrustManagerSpec) DeepCopyInto(out *TrustManagerSpec) { + *out = *in + in.TrustManagerConfig.DeepCopyInto(&out.TrustManagerConfig) + in.ControllerConfig.DeepCopyInto(&out.ControllerConfig) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrustManagerSpec. +func (in *TrustManagerSpec) DeepCopy() *TrustManagerSpec { + if in == nil { + return nil + } + out := new(TrustManagerSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TrustManagerStatus) DeepCopyInto(out *TrustManagerStatus) { + *out = *in + in.ConditionalStatus.DeepCopyInto(&out.ConditionalStatus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrustManagerStatus. +func (in *TrustManagerStatus) DeepCopy() *TrustManagerStatus { + if in == nil { + return nil + } + out := new(TrustManagerStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UnsupportedConfigOverrides) DeepCopyInto(out *UnsupportedConfigOverrides) { *out = *in diff --git a/bindata/trust-manager/trust-manager-clusterrole.yaml b/bindata/trust-manager/trust-manager-clusterrole.yaml new file mode 100644 index 000000000..df227aa64 --- /dev/null +++ b/bindata/trust-manager/trust-manager-clusterrole.yaml @@ -0,0 +1,58 @@ +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/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +rules: + - apiGroups: + - trust.cert-manager.io + resources: + - bundles + verbs: + - get + - list + - watch + - apiGroups: + - trust.cert-manager.io + resources: + - bundles/status + verbs: + - patch + - apiGroups: + - trust.cert-manager.io + resources: + - bundles/finalizers + verbs: + - update + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - create + - update + - patch + - watch + - delete + - apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch diff --git a/bindata/trust-manager/trust-manager-clusterrolebinding.yaml b/bindata/trust-manager/trust-manager-clusterrolebinding.yaml new file mode 100644 index 000000000..604cbe446 --- /dev/null +++ b/bindata/trust-manager/trust-manager-clusterrolebinding.yaml @@ -0,0 +1,18 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: trust-manager + labels: + app: trust-manager + app.kubernetes.io/name: trust-manager + app.kubernetes.io/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: trust-manager +subjects: + - kind: ServiceAccount + name: trust-manager + namespace: cert-manager diff --git a/bindata/trust-manager/trust-manager-deployment.yaml b/bindata/trust-manager/trust-manager-deployment.yaml new file mode 100644 index 000000000..c40b0f68c --- /dev/null +++ b/bindata/trust-manager/trust-manager-deployment.yaml @@ -0,0 +1,70 @@ +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/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: 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: trust-manager:latest + imagePullPolicy: IfNotPresent + args: + - --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 + ports: + - containerPort: 6443 + protocol: TCP + - containerPort: 9402 + protocol: TCP + readinessProbe: + httpGet: + path: /readyz + port: 6060 + initialDelaySeconds: 3 + periodSeconds: 7 + resources: + requests: + cpu: 25m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + capabilities: + drop: + - ALL + volumeMounts: + - name: tls + mountPath: /tls + readOnly: true + volumes: + - name: tls + secret: + secretName: trust-manager-tls + defaultMode: 420 + nodeSelector: + kubernetes.io/os: linux diff --git a/bindata/trust-manager/trust-manager-metrics-service.yaml b/bindata/trust-manager/trust-manager-metrics-service.yaml new file mode 100644 index 000000000..ab5952835 --- /dev/null +++ b/bindata/trust-manager/trust-manager-metrics-service.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Service +metadata: + name: trust-manager-metrics + namespace: cert-manager + labels: + app: trust-manager + app.kubernetes.io/name: trust-manager + app.kubernetes.io/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +spec: + type: ClusterIP + ports: + - port: 9402 + targetPort: 9402 + protocol: TCP + name: metrics + selector: + app: trust-manager diff --git a/bindata/trust-manager/trust-manager-role.yaml b/bindata/trust-manager/trust-manager-role.yaml new file mode 100644 index 000000000..9b4c7dadc --- /dev/null +++ b/bindata/trust-manager/trust-manager-role.yaml @@ -0,0 +1,42 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: trust-manager + app.kubernetes.io/name: trust-manager + app.kubernetes.io/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - create + - update + - patch + - watch + - delete + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - create + - update + - watch + - list diff --git a/bindata/trust-manager/trust-manager-rolebinding.yaml b/bindata/trust-manager/trust-manager-rolebinding.yaml new file mode 100644 index 000000000..b05ab0d08 --- /dev/null +++ b/bindata/trust-manager/trust-manager-rolebinding.yaml @@ -0,0 +1,19 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: trust-manager + app.kubernetes.io/name: trust-manager + app.kubernetes.io/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: trust-manager +subjects: + - kind: ServiceAccount + name: trust-manager + namespace: cert-manager diff --git a/bindata/trust-manager/trust-manager-serviceaccount.yaml b/bindata/trust-manager/trust-manager-serviceaccount.yaml new file mode 100644 index 000000000..42473bfe9 --- /dev/null +++ b/bindata/trust-manager/trust-manager-serviceaccount.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: trust-manager + app.kubernetes.io/name: trust-manager + app.kubernetes.io/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator diff --git a/bindata/trust-manager/trust-manager-validatingwebhookconfiguration.yaml b/bindata/trust-manager/trust-manager-validatingwebhookconfiguration.yaml new file mode 100644 index 000000000..84fe51cc5 --- /dev/null +++ b/bindata/trust-manager/trust-manager-validatingwebhookconfiguration.yaml @@ -0,0 +1,34 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: trust-manager + labels: + app: trust-manager + app.kubernetes.io/name: trust-manager + app.kubernetes.io/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator + annotations: + cert-manager.io/inject-ca-from: cert-manager/trust-manager +webhooks: + - name: trust.cert-manager.io + rules: + - apiGroups: + - trust.cert-manager.io + apiVersions: + - "*" + operations: + - CREATE + - UPDATE + resources: + - bundles + admissionReviewVersions: + - v1 + timeoutSeconds: 5 + failurePolicy: Fail + sideEffects: None + clientConfig: + service: + name: trust-manager + namespace: cert-manager + path: /validate-trust-cert-manager-io-v1alpha1-bundle diff --git a/bindata/trust-manager/trust-manager-webhook-certificate.yaml b/bindata/trust-manager/trust-manager-webhook-certificate.yaml new file mode 100644 index 000000000..f7c6cc656 --- /dev/null +++ b/bindata/trust-manager/trust-manager-webhook-certificate.yaml @@ -0,0 +1,21 @@ +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/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +spec: + commonName: "trust-manager.cert-manager.svc" + dnsNames: + - "trust-manager.cert-manager.svc" + secretName: trust-manager-tls + revisionHistoryLimit: 1 + issuerRef: + name: cert-manager-operator-selfsigned-issuer + kind: Issuer + group: cert-manager.io diff --git a/bindata/trust-manager/trust-manager-webhook-service.yaml b/bindata/trust-manager/trust-manager-webhook-service.yaml new file mode 100644 index 000000000..f2151dcaf --- /dev/null +++ b/bindata/trust-manager/trust-manager-webhook-service.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Service +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: trust-manager + app.kubernetes.io/name: trust-manager + app.kubernetes.io/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +spec: + type: ClusterIP + ports: + - port: 443 + targetPort: 6443 + protocol: TCP + name: webhook + selector: + app: trust-manager diff --git a/config/crd/bases/operator.openshift.io_trustmanagers.yaml b/config/crd/bases/operator.openshift.io_trustmanagers.yaml new file mode 100644 index 000000000..98e585d19 --- /dev/null +++ b/config/crd/bases/operator.openshift.io_trustmanagers.yaml @@ -0,0 +1,1325 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: trustmanager + app.kubernetes.io/part-of: cert-manager-operator + name: trustmanagers.operator.openshift.io +spec: + group: operator.openshift.io + names: + categories: + - cert-manager-operator + kind: TrustManager + listKind: TrustManagerList + plural: trustmanagers + singular: trustmanager + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].message + name: Message + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + 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. + 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: spec is the specification of the desired behavior of the + TrustManager. + properties: + controllerConfig: + description: controllerConfig configures the operator's behavior for + resource creation. + properties: + annotations: + additionalProperties: + type: string + description: annotations to apply to all resources created for + the trust-manager deployment. + minProperties: 0 + type: object + x-kubernetes-map-type: granular + labels: + additionalProperties: + type: string + description: labels to apply to all resources created for the + trust-manager deployment. + minProperties: 0 + type: object + x-kubernetes-map-type: granular + type: object + trustManagerConfig: + description: trustManagerConfig configures the trust-manager operand's + behavior. + properties: + affinity: + description: |- + affinity defines scheduling constraints for the trust-manager pod. + ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/ + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + defaultCAPackage: + description: |- + defaultCAPackage configures the default CA package for trust-manager. + When enabled, the operator will use OpenShift's trusted CA bundle injection mechanism. + properties: + policy: + default: Disabled + description: |- + 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). + enum: + - Enabled + - Disabled + type: string + type: object + filterExpiredCertificates: + default: Disabled + description: |- + 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). + enum: + - Enabled + - Disabled + type: string + logFormat: + default: text + description: |- + logFormat specifies the output format for trust-manager logging. + Supported formats are "text" and "json". + enum: + - text + - json + type: string + logLevel: + default: 1 + description: |- + 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 + format: int32 + maximum: 5 + minimum: 1 + type: integer + nodeSelector: + additionalProperties: + type: string + description: |- + nodeSelector restricts which nodes the trust-manager pod can be scheduled on. + ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + maxProperties: 50 + minProperties: 0 + type: object + x-kubernetes-map-type: atomic + resources: + description: |- + resources defines the compute resource requirements for the trust-manager pod. + ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + secretTargets: + description: secretTargets configures whether trust-manager can + write trust bundles to Secrets. + properties: + authorizedSecrets: + description: |- + 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". + items: + minLength: 1 + type: string + minItems: 0 + type: array + x-kubernetes-list-type: set + policy: + default: Disabled + description: |- + 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. + enum: + - Disabled + - Custom + type: string + type: object + x-kubernetes-validations: + - message: authorizedSecrets must not be empty when policy is + Custom + rule: self.policy != 'Custom' || (has(self.authorizedSecrets) + && size(self.authorizedSecrets) > 0) + - message: authorizedSecrets must be empty when policy is not + Custom + rule: self.policy == 'Custom' || !has(self.authorizedSecrets) + || size(self.authorizedSecrets) == 0 + tolerations: + description: |- + tolerations allows the trust-manager pod to be scheduled on tainted nodes. + ref: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + maxItems: 50 + minItems: 0 + type: array + x-kubernetes-list-type: atomic + trustNamespace: + default: cert-manager + description: |- + 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. + maxLength: 63 + minLength: 1 + type: string + x-kubernetes-validations: + - message: trustNamespace is immutable once set + rule: oldSelf == '' || self == oldSelf + type: object + required: + - trustManagerConfig + type: object + status: + description: status is the most recently observed status of the TrustManager. + properties: + conditions: + description: conditions holds information about the current state + of the istio-csr agent deployment. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + defaultCAPackagePolicy: + description: defaultCAPackagePolicy indicates the current default + CA package policy. + enum: + - Enabled + - Disabled + type: string + filterExpiredCertificatesPolicy: + description: filterExpiredCertificatesPolicy indicates the current + policy for filtering expired certificates. + enum: + - Enabled + - Disabled + type: string + secretTargetsPolicy: + description: secretTargetsPolicy indicates the current secret targets + policy. + enum: + - Disabled + - Custom + type: string + trustManagerImage: + description: trustManagerImage is the container image (name:tag) used + for trust-manager. + type: string + trustNamespace: + description: trustNamespace is the namespace where trust-manager looks + for trust sources. + type: string + type: object + required: + - spec + type: object + x-kubernetes-validations: + - message: TrustManager is a singleton, .metadata.name must be 'cluster' + rule: self.metadata.name == 'cluster' + served: true + storage: true + subresources: + status: {} diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index c387f3a40..aed4a6c3c 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -226,6 +226,7 @@ rules: resources: - certmanagers/finalizers - istiocsrs/finalizers + - trustmanagers/finalizers verbs: - update - apiGroups: @@ -233,6 +234,7 @@ rules: resources: - certmanagers/status - istiocsrs/status + - trustmanagers/status verbs: - get - patch @@ -241,6 +243,7 @@ rules: - operator.openshift.io resources: - istiocsrs + - trustmanagers verbs: - get - list diff --git a/pkg/controller/trustmanager/certificates.go b/pkg/controller/trustmanager/certificates.go new file mode 100644 index 000000000..c166ab95c --- /dev/null +++ b/pkg/controller/trustmanager/certificates.go @@ -0,0 +1,53 @@ +package trustmanager + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +func (r *Reconciler) createOrApplyCertificates(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired := r.getCertificateObject(resourceLabels) + + certificateName := fmt.Sprintf("%s/%s", desired.GetNamespace(), desired.GetName()) + r.log.V(4).Info("reconciling certificate resource", "name", certificateName) + fetched := &certmanagerv1.Certificate{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return FromClientError(err, "failed to check %s certificate resource already exists", certificateName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s certificate resource already exists, maybe from previous installation", certificateName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("certificate has been modified, updating to desired state", "name", certificateName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return FromClientError(err, "failed to update %s certificate resource", certificateName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "certificate resource %s reconciled back to desired state", certificateName) + } else { + r.log.V(4).Info("certificate resource already exists and is in expected state", "name", certificateName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return FromClientError(err, "failed to create %s certificate resource", certificateName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "certificate resource %s created", certificateName) + } + + return nil +} + +func (r *Reconciler) getCertificateObject(resourceLabels map[string]string) *certmanagerv1.Certificate { + certificate := decodeCertificateObjBytes(assets.MustAsset(webhookCertificateAssetName)) + updateNamespace(certificate, trustManagerOperandNamespace) + updateResourceLabels(certificate, resourceLabels) + return certificate +} diff --git a/pkg/controller/trustmanager/client.go b/pkg/controller/trustmanager/client.go new file mode 100644 index 000000000..5428527cc --- /dev/null +++ b/pkg/controller/trustmanager/client.go @@ -0,0 +1,114 @@ +package trustmanager + +import ( + "context" + "fmt" + "reflect" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/client-go/util/retry" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +type ctrlClientImpl struct { + client.Client +} + +//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate +//counterfeiter:generate -o fakes . ctrlClient +type ctrlClient interface { + Get(context.Context, client.ObjectKey, client.Object) error + List(context.Context, client.ObjectList, ...client.ListOption) error + StatusUpdate(context.Context, client.Object, ...client.SubResourceUpdateOption) error + Update(context.Context, client.Object, ...client.UpdateOption) error + UpdateWithRetry(context.Context, client.Object, ...client.UpdateOption) error + Create(context.Context, client.Object, ...client.CreateOption) error + Delete(context.Context, client.Object, ...client.DeleteOption) error + Patch(context.Context, client.Object, client.Patch, ...client.PatchOption) error + Exists(context.Context, client.ObjectKey, client.Object) (bool, error) +} + +func NewClient(m manager.Manager) (ctrlClient, error) { + // Use the manager's client directly instead of creating a custom client. + // The manager's client uses the manager's cache, which ensures the reconciler + // reads from the same cache that the controller's watches use, preventing + // cache mismatch issues. + return &ctrlClientImpl{ + Client: m.GetClient(), + }, nil +} + +func (c *ctrlClientImpl) Get( + ctx context.Context, key client.ObjectKey, obj client.Object, +) error { + return c.Client.Get(ctx, key, obj) +} + +func (c *ctrlClientImpl) List( + ctx context.Context, list client.ObjectList, opts ...client.ListOption, +) error { + return c.Client.List(ctx, list, opts...) +} + +func (c *ctrlClientImpl) Create( + ctx context.Context, obj client.Object, opts ...client.CreateOption, +) error { + return c.Client.Create(ctx, obj, opts...) +} + +func (c *ctrlClientImpl) Delete( + ctx context.Context, obj client.Object, opts ...client.DeleteOption, +) error { + return c.Client.Delete(ctx, obj, opts...) +} + +func (c *ctrlClientImpl) Update( + ctx context.Context, obj client.Object, opts ...client.UpdateOption, +) error { + return c.Client.Update(ctx, obj, opts...) +} + +func (c *ctrlClientImpl) UpdateWithRetry( + ctx context.Context, obj client.Object, opts ...client.UpdateOption, +) error { + key := client.ObjectKeyFromObject(obj) + if err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + current := reflect.New(reflect.TypeOf(obj).Elem()).Interface().(client.Object) + if err := c.Client.Get(ctx, key, current); err != nil { + return fmt.Errorf("failed to fetch latest %q for update: %w", key, err) + } + obj.SetResourceVersion(current.GetResourceVersion()) + if err := c.Client.Update(ctx, obj, opts...); err != nil { + return fmt.Errorf("failed to update %q resource: %w", key, err) + } + return nil + }); err != nil { + return err + } + + return nil +} + +func (c *ctrlClientImpl) StatusUpdate( + ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption, +) error { + return c.Client.Status().Update(ctx, obj, opts...) +} + +func (c *ctrlClientImpl) Patch( + ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption, +) error { + return c.Client.Patch(ctx, obj, patch, opts...) +} + +func (c *ctrlClientImpl) Exists(ctx context.Context, key client.ObjectKey, obj client.Object) (bool, error) { + if err := c.Client.Get(ctx, key, obj); err != nil { + if errors.IsNotFound(err) { + return false, nil + } + return false, err + } + return true, nil +} diff --git a/pkg/controller/trustmanager/constants.go b/pkg/controller/trustmanager/constants.go new file mode 100644 index 000000000..67e1daf68 --- /dev/null +++ b/pkg/controller/trustmanager/constants.go @@ -0,0 +1,73 @@ +package trustmanager + +import ( + "os" + "time" +) + +const ( + // trustManagerCommonName is the name commonly used for naming resources. + trustManagerCommonName = "trust-manager" + + // ControllerName is the name of the controller used in logs and events. + ControllerName = trustManagerCommonName + "-controller" + + // controllerProcessedAnnotation is the annotation added to trustmanager resource once after + // successful reconciliation by the controller. + controllerProcessedAnnotation = "operator.openshift.io/trust-manager-processed" + + // finalizer name for trustmanagers.openshift.operator.io resource. + finalizer = "trustmanagers.operator.openshift.io/" + ControllerName + + // defaultRequeueTime is the default reconcile requeue time. + defaultRequeueTime = time.Second * 30 + + // trustManagerContainerName is the name of the container created for trust-manager. + trustManagerContainerName = trustManagerCommonName + + // trustManagerImageNameEnvVarName is the environment variable key name + // containing the image name of the trust-manager as value. + trustManagerImageNameEnvVarName = "RELATED_IMAGE_TRUST_MANAGER" + + // trustManagerImageVersionEnvVarName is the environment variable key name + // containing the image version of the trust-manager as value. + trustManagerImageVersionEnvVarName = "TRUST_MANAGER_OPERAND_IMAGE_VERSION" + + // trustManagerOperandNamespace is the namespace where trust-manager operand is deployed. + trustManagerOperandNamespace = "cert-manager" + + // defaultCAPackageConfigMapName is the name of the ConfigMap used for the default CA package. + defaultCAPackageConfigMapName = "trust-manager-default-ca-package" + + // defaultCAPackageKeyName is the key name for the CA bundle data in the default CA package ConfigMap. + defaultCAPackageKeyName = "ca-certificates.crt" + + // cnoTrustedCAAnnotation is the annotation used to request CNO trusted CA bundle injection. + cnoTrustedCAAnnotation = "config.openshift.io/inject-trusted-cabundle" +) + +var ( + controllerDefaultResourceLabels = map[string]string{ + "app": trustManagerCommonName, + "app.kubernetes.io/name": trustManagerCommonName, + "app.kubernetes.io/instance": trustManagerCommonName, + "app.kubernetes.io/version": os.Getenv(trustManagerImageVersionEnvVarName), + "app.kubernetes.io/managed-by": "cert-manager-operator", + "app.kubernetes.io/part-of": "cert-manager-operator", + } +) + +// asset names are the files present in the root bindata/ dir. Which are then loaded +// and made available by the pkg/operator/assets package. +const ( + clusterRoleAssetName = "trust-manager/trust-manager-clusterrole.yaml" + clusterRoleBindingAssetName = "trust-manager/trust-manager-clusterrolebinding.yaml" + deploymentAssetName = "trust-manager/trust-manager-deployment.yaml" + roleAssetName = "trust-manager/trust-manager-role.yaml" + roleBindingAssetName = "trust-manager/trust-manager-rolebinding.yaml" + metricsServiceAssetName = "trust-manager/trust-manager-metrics-service.yaml" + webhookServiceAssetName = "trust-manager/trust-manager-webhook-service.yaml" + serviceAccountAssetName = "trust-manager/trust-manager-serviceaccount.yaml" + webhookCertificateAssetName = "trust-manager/trust-manager-webhook-certificate.yaml" + validatingWebhookConfigurationAssetName = "trust-manager/trust-manager-validatingwebhookconfiguration.yaml" +) diff --git a/pkg/controller/trustmanager/controller.go b/pkg/controller/trustmanager/controller.go new file mode 100644 index 000000000..b7549f248 --- /dev/null +++ b/pkg/controller/trustmanager/controller.go @@ -0,0 +1,273 @@ +package trustmanager + +import ( + "context" + "fmt" + "reflect" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/go-logr/logr" + + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + + v1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" +) + +var ( + // requestEnqueueLabelKey is the label key name used for filtering reconcile + // events to include only the resources created by the controller. + requestEnqueueLabelKey = "app" + + // requestEnqueueLabelValue is the label value used for filtering reconcile + // events to include only the resources created by the controller. + requestEnqueueLabelValue = "trust-manager" +) + +// Reconciler reconciles a TrustManager object +type Reconciler struct { + ctrlClient + + ctx context.Context + eventRecorder record.EventRecorder + log logr.Logger + scheme *runtime.Scheme +} + +// +kubebuilder:rbac:groups=operator.openshift.io,resources=trustmanagers,verbs=get;list;watch;update;patch +// +kubebuilder:rbac:groups=operator.openshift.io,resources=trustmanagers/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=operator.openshift.io,resources=trustmanagers/finalizers,verbs=update +// +kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=validatingwebhookconfigurations,verbs=get;list;watch;create;update;patch;delete + +// NewCacheBuilder returns a cache builder function configured with label selectors +// for managed resources. This function is used by the manager to create its cache +// to ensure the reconciler reads from the same cache that the controller's watches use. +func NewCacheBuilder(config *rest.Config, opts cache.Options) (cache.Cache, error) { + managedResourceLabelReq, err := labels.NewRequirement(requestEnqueueLabelKey, selection.Equals, []string{requestEnqueueLabelValue}) + if err != nil { + return nil, fmt.Errorf("invalid cache label requirement for %q: %w", requestEnqueueLabelKey, err) + } + managedResourceLabelReqSelector := labels.NewSelector().Add(*managedResourceLabelReq) + + // Configure cache with label selectors for managed resources + opts.ByObject = map[client.Object]cache.ByObject{ + // Explicitly include TrustManager to ensure the cache properly watches and syncs all TrustManager objects + &v1alpha1.TrustManager{}: {}, + // Resources managed by controller (with label selectors) + &certmanagerv1.Certificate{}: { + Label: managedResourceLabelReqSelector, + }, + &appsv1.Deployment{}: { + Label: managedResourceLabelReqSelector, + }, + &rbacv1.ClusterRole{}: { + Label: managedResourceLabelReqSelector, + }, + &rbacv1.ClusterRoleBinding{}: { + Label: managedResourceLabelReqSelector, + }, + &rbacv1.Role{}: { + Label: managedResourceLabelReqSelector, + }, + &rbacv1.RoleBinding{}: { + Label: managedResourceLabelReqSelector, + }, + &corev1.Service{}: { + Label: managedResourceLabelReqSelector, + }, + &corev1.ServiceAccount{}: { + Label: managedResourceLabelReqSelector, + }, + &admissionregistrationv1.ValidatingWebhookConfiguration{}: { + Label: managedResourceLabelReqSelector, + }, + } + + return cache.New(config, opts) +} + +// New returns a new Reconciler instance. +func New(mgr ctrl.Manager) (*Reconciler, error) { + c, err := NewClient(mgr) + if err != nil { + return nil, err + } + return &Reconciler{ + ctrlClient: c, + ctx: context.Background(), + eventRecorder: mgr.GetEventRecorderFor(ControllerName), + log: ctrl.Log.WithName(ControllerName), + scheme: mgr.GetScheme(), + }, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { + mapFunc := func(ctx context.Context, obj client.Object) []reconcile.Request { + r.log.V(4).Info("received reconcile event", "object", fmt.Sprintf("%T", obj), "name", obj.GetName(), "namespace", obj.GetNamespace()) + + objLabels := obj.GetLabels() + if objLabels != nil && objLabels[requestEnqueueLabelKey] == requestEnqueueLabelValue { + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: "cluster", + }, + }, + } + } + + r.log.V(4).Info("object not of interest, ignoring reconcile event", "object", fmt.Sprintf("%T", obj), "name", obj.GetName(), "namespace", obj.GetNamespace()) + return []reconcile.Request{} + } + + // predicate function to ignore events for objects not managed by controller. + controllerManagedResources := predicate.NewPredicateFuncs(func(object client.Object) bool { + return object.GetLabels() != nil && object.GetLabels()[requestEnqueueLabelKey] == requestEnqueueLabelValue + }) + + withIgnoreStatusUpdatePredicates := builder.WithPredicates(predicate.GenerationChangedPredicate{}, controllerManagedResources) + controllerManagedResourcePredicates := builder.WithPredicates(controllerManagedResources) + + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.TrustManager{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). + Named(ControllerName). + Watches(&certmanagerv1.Certificate{}, handler.EnqueueRequestsFromMapFunc(mapFunc), withIgnoreStatusUpdatePredicates). + Watches(&appsv1.Deployment{}, handler.EnqueueRequestsFromMapFunc(mapFunc), withIgnoreStatusUpdatePredicates). + Watches(&rbacv1.ClusterRole{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Watches(&rbacv1.ClusterRoleBinding{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Watches(&rbacv1.Role{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Watches(&rbacv1.RoleBinding{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Watches(&corev1.Service{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Watches(&corev1.ServiceAccount{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Watches(&corev1.ConfigMap{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Watches(&admissionregistrationv1.ValidatingWebhookConfiguration{}, handler.EnqueueRequestsFromMapFunc(mapFunc), controllerManagedResourcePredicates). + Complete(r) +} + +// Reconcile function to compare the state specified by the TrustManager object against the actual cluster state, +// and to make the cluster state reflect the state specified by the user. +func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.log.V(1).Info("reconciling", "request", req) + + // Fetch the trustmanagers.openshift.operator.io CR + trustManager := &v1alpha1.TrustManager{} + if err := r.Get(ctx, req.NamespacedName, trustManager); err != nil { + if errors.IsNotFound(err) { + // NotFound errors, since they can't be fixed by an immediate + // requeue (have to wait for a new notification), and can be processed + // on deleted requests. + r.log.V(1).Info("trustmanagers.openshift.operator.io object not found, skipping reconciliation", "request", req) + return ctrl.Result{}, nil + } + return ctrl.Result{}, fmt.Errorf("failed to fetch trustmanagers.openshift.operator.io %q during reconciliation: %w", req.NamespacedName, err) + } + + if !trustManager.DeletionTimestamp.IsZero() { + r.log.V(1).Info("trustmanagers.openshift.operator.io is marked for deletion", "name", req.NamespacedName) + + if requeue, err := r.cleanUp(trustManager); err != nil { + return ctrl.Result{}, fmt.Errorf("clean up failed for %q trustmanagers.openshift.operator.io instance deletion: %w", req.NamespacedName, err) + } else if requeue { + return ctrl.Result{RequeueAfter: defaultRequeueTime}, nil + } + + if err := r.removeFinalizer(ctx, trustManager, finalizer); err != nil { + return ctrl.Result{}, err + } + + r.log.V(1).Info("removed finalizer, cleanup complete", "request", req.NamespacedName) + return ctrl.Result{}, nil + } + + // Set finalizers on the trustmanagers.openshift.operator.io resource + if err := r.addFinalizer(ctx, trustManager); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to update %q trustmanagers.openshift.operator.io with finalizers: %w", req.NamespacedName, err) + } + + return r.processReconcileRequest(trustManager, req.NamespacedName) +} + +func (r *Reconciler) processReconcileRequest(trustManager *v1alpha1.TrustManager, req types.NamespacedName) (ctrl.Result, error) { + trustManagerCreateRecon := false + if !containsProcessedAnnotation(trustManager) && reflect.DeepEqual(trustManager.Status, v1alpha1.TrustManagerStatus{}) { + r.log.V(1).Info("starting reconciliation of newly created trustmanager", "name", trustManager.GetName()) + trustManagerCreateRecon = true + } + + var errUpdate error + if err := r.reconcileTrustManagerDeployment(trustManager, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile TrustManager deployment", "request", req) + if IsIrrecoverableError(err) { + // Set both conditions atomically before updating status + degradedChanged := trustManager.Status.SetCondition(v1alpha1.Degraded, metav1.ConditionTrue, v1alpha1.ReasonFailed, fmt.Sprintf("reconciliation failed with irrecoverable error not retrying: %v", err)) + readyChanged := trustManager.Status.SetCondition(v1alpha1.Ready, metav1.ConditionFalse, v1alpha1.ReasonReady, "") + + if degradedChanged || readyChanged { + r.log.V(2).Info("updating trustmanager conditions on irrecoverable error", + "name", trustManager.GetName(), + "degradedChanged", degradedChanged, + "readyChanged", readyChanged, + "error", err) + errUpdate = r.updateCondition(trustManager, nil) + } + return ctrl.Result{}, errUpdate + } + + // Set both conditions atomically before updating status + degradedChanged := trustManager.Status.SetCondition(v1alpha1.Degraded, metav1.ConditionFalse, v1alpha1.ReasonReady, "") + readyChanged := trustManager.Status.SetCondition(v1alpha1.Ready, metav1.ConditionFalse, v1alpha1.ReasonInProgress, fmt.Sprintf("reconciliation failed, retrying: %v", err)) + + if degradedChanged || readyChanged { + r.log.V(2).Info("updating trustmanager conditions on recoverable error", + "name", trustManager.GetName(), + "degradedChanged", degradedChanged, + "readyChanged", readyChanged, + "error", err) + errUpdate = r.updateCondition(trustManager, err) + } + // For recoverable errors, either requeue manually or return error, not both + if errUpdate != nil { + return ctrl.Result{}, errUpdate + } + return ctrl.Result{RequeueAfter: defaultRequeueTime}, nil + } + + // Set both conditions atomically before updating status on success + degradedChanged := trustManager.Status.SetCondition(v1alpha1.Degraded, metav1.ConditionFalse, v1alpha1.ReasonReady, "") + readyChanged := trustManager.Status.SetCondition(v1alpha1.Ready, metav1.ConditionTrue, v1alpha1.ReasonReady, "reconciliation successful") + + if degradedChanged || readyChanged { + r.log.V(2).Info("updating trustmanager conditions on successful reconciliation", + "name", trustManager.GetName(), + "degradedChanged", degradedChanged, + "readyChanged", readyChanged) + errUpdate = r.updateCondition(trustManager, nil) + } + return ctrl.Result{}, errUpdate +} + +// cleanUp handles deletion of trustmanagers.openshift.operator.io gracefully. +func (r *Reconciler) cleanUp(trustManager *v1alpha1.TrustManager) (bool, error) { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "RemoveDeployment", "%s trustmanager marked for deletion, removing all resources created for trust-manager deployment", trustManager.GetName()) + return false, nil +} diff --git a/pkg/controller/trustmanager/core_validation_helpers_duplication.go b/pkg/controller/trustmanager/core_validation_helpers_duplication.go new file mode 100644 index 000000000..43a30b5fb --- /dev/null +++ b/pkg/controller/trustmanager/core_validation_helpers_duplication.go @@ -0,0 +1,165 @@ +package trustmanager + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/kubernetes/pkg/apis/core" + corevalidation "k8s.io/kubernetes/pkg/apis/core/validation" +) + +/* + * Methods here are the replicates of the methods defined in k8s.io/kubernetes/pkg/apis/core/validation package, which + * is done just because of the lack of better alternative to use private methods. + * TODO: Remove this source file when validateAffinity method is made public. + */ + +// validateAffinity checks if given affinities are valid +func validateAffinity(affinity *core.Affinity, opts corevalidation.PodValidationOptions, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if affinity != nil { + if affinity.NodeAffinity != nil { + allErrs = append(allErrs, validateNodeAffinity(affinity.NodeAffinity, fldPath.Child("nodeAffinity"))...) + } + if affinity.PodAffinity != nil { + allErrs = append(allErrs, validatePodAffinity(affinity.PodAffinity, opts.AllowInvalidLabelValueInSelector, fldPath.Child("podAffinity"))...) + } + if affinity.PodAntiAffinity != nil { + allErrs = append(allErrs, validatePodAntiAffinity(affinity.PodAntiAffinity, opts.AllowInvalidLabelValueInSelector, fldPath.Child("podAntiAffinity"))...) + } + } + + return allErrs +} + +// validateNodeAffinity tests that the specified nodeAffinity fields have valid data +func validateNodeAffinity(na *core.NodeAffinity, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if na.RequiredDuringSchedulingIgnoredDuringExecution != nil { + allErrs = append(allErrs, corevalidation.ValidateNodeSelector(na.RequiredDuringSchedulingIgnoredDuringExecution, false, fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...) + } + if len(na.PreferredDuringSchedulingIgnoredDuringExecution) > 0 { + allErrs = append(allErrs, corevalidation.ValidatePreferredSchedulingTerms(na.PreferredDuringSchedulingIgnoredDuringExecution, fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...) + } + return allErrs +} + +// validatePodAffinity tests that the specified podAffinity fields have valid data +func validatePodAffinity(podAffinity *core.PodAffinity, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if podAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil { + allErrs = append(allErrs, validatePodAffinityTerms(podAffinity.RequiredDuringSchedulingIgnoredDuringExecution, allowInvalidLabelValueInSelector, + fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...) + } + if podAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil { + allErrs = append(allErrs, validateWeightedPodAffinityTerms(podAffinity.PreferredDuringSchedulingIgnoredDuringExecution, allowInvalidLabelValueInSelector, + fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...) + } + return allErrs +} + +// validatePodAntiAffinity tests that the specified podAntiAffinity fields have valid data +func validatePodAntiAffinity(podAntiAffinity *core.PodAntiAffinity, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil { + allErrs = append(allErrs, validatePodAffinityTerms(podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution, allowInvalidLabelValueInSelector, + fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...) + } + if podAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil { + allErrs = append(allErrs, validateWeightedPodAffinityTerms(podAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution, allowInvalidLabelValueInSelector, + fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...) + } + return allErrs +} + +// validatePodAffinityTerms tests that the specified podAffinityTerms fields have valid data +func validatePodAffinityTerms(podAffinityTerms []core.PodAffinityTerm, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for i, podAffinityTerm := range podAffinityTerms { + allErrs = append(allErrs, validatePodAffinityTerm(podAffinityTerm, allowInvalidLabelValueInSelector, fldPath.Index(i))...) + } + return allErrs +} + +// validatePodAffinityTerm tests that the specified podAffinityTerm fields have valid data +func validatePodAffinityTerm(podAffinityTerm core.PodAffinityTerm, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + allErrs = append(allErrs, corevalidation.ValidatePodAffinityTermSelector(podAffinityTerm, allowInvalidLabelValueInSelector, fldPath)...) + for _, name := range podAffinityTerm.Namespaces { + for _, msg := range corevalidation.ValidateNamespaceName(name, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), name, msg)) + } + } + allErrs = append(allErrs, validateMatchLabelKeysAndMismatchLabelKeys(fldPath, podAffinityTerm.MatchLabelKeys, podAffinityTerm.MismatchLabelKeys, podAffinityTerm.LabelSelector)...) + if len(podAffinityTerm.TopologyKey) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("topologyKey"), "can not be empty")) + } + return append(allErrs, metav1validation.ValidateLabelName(podAffinityTerm.TopologyKey, fldPath.Child("topologyKey"))...) +} + +// validateWeightedPodAffinityTerms tests that the specified weightedPodAffinityTerms fields have valid data +func validateWeightedPodAffinityTerms(weightedPodAffinityTerms []core.WeightedPodAffinityTerm, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for j, weightedTerm := range weightedPodAffinityTerms { + if weightedTerm.Weight <= 0 || weightedTerm.Weight > 100 { + allErrs = append(allErrs, field.Invalid(fldPath.Index(j).Child("weight"), weightedTerm.Weight, "must be in the range 1-100")) + } + allErrs = append(allErrs, validatePodAffinityTerm(weightedTerm.PodAffinityTerm, allowInvalidLabelValueInSelector, fldPath.Index(j).Child("podAffinityTerm"))...) + } + return allErrs +} + +// validateMatchLabelKeysAndMismatchLabelKeys checks if both matchLabelKeys and mismatchLabelKeys are valid. +func validateMatchLabelKeysAndMismatchLabelKeys(fldPath *field.Path, matchLabelKeys, mismatchLabelKeys []string, labelSelector *metav1.LabelSelector) field.ErrorList { + var allErrs field.ErrorList + allErrs = append(allErrs, validateLabelKeys(fldPath.Child("matchLabelKeys"), matchLabelKeys, labelSelector)...) + allErrs = append(allErrs, validateLabelKeys(fldPath.Child("mismatchLabelKeys"), mismatchLabelKeys, labelSelector)...) + + if labelSelector != nil { + labelKeysMap := map[string]int{} + for i, key := range matchLabelKeys { + labelKeysMap[key] = i + } + labelSelectorKeys := sets.New[string]() + for key := range labelSelector.MatchLabels { + labelSelectorKeys.Insert(key) + } + for _, matchExpression := range labelSelector.MatchExpressions { + key := matchExpression.Key + if i, ok := labelKeysMap[key]; ok && labelSelectorKeys.Has(key) { + allErrs = append(allErrs, field.Invalid(fldPath.Index(i), key, "exists in both matchLabelKeys and labelSelector")) + } + labelSelectorKeys.Insert(key) + } + } + + mismatchLabelKeysSet := sets.New(mismatchLabelKeys...) + for i, k := range matchLabelKeys { + if mismatchLabelKeysSet.Has(k) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("matchLabelKeys").Index(i), k, "exists in both matchLabelKeys and mismatchLabelKeys")) + } + } + + return allErrs +} + +// validateLabelKeys tests that the label keys are a valid label name. +func validateLabelKeys(fldPath *field.Path, labelKeys []string, labelSelector *metav1.LabelSelector) field.ErrorList { + if len(labelKeys) == 0 { + return nil + } + + if labelSelector == nil { + return field.ErrorList{field.Forbidden(fldPath, "must not be specified when labelSelector is not set")} + } + + var allErrs field.ErrorList + for i, key := range labelKeys { + allErrs = append(allErrs, metav1validation.ValidateLabelName(key, fldPath.Index(i))...) + } + + return allErrs +} diff --git a/pkg/controller/trustmanager/default_ca_package.go b/pkg/controller/trustmanager/default_ca_package.go new file mode 100644 index 000000000..2126a4a58 --- /dev/null +++ b/pkg/controller/trustmanager/default_ca_package.go @@ -0,0 +1,72 @@ +package trustmanager + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" +) + +// createOrApplyDefaultCAPackage creates and manages the default CA package ConfigMap +// using OpenShift's CNO trusted CA bundle injection mechanism. +// +// When DefaultCAPackage policy is "Enabled", a ConfigMap is created with the +// "config.openshift.io/inject-trusted-cabundle" annotation. The Cluster Network Operator (CNO) +// automatically injects the cluster's trusted CA bundle into this ConfigMap, which trust-manager +// then uses as the default CA package for Bundles that specify "useDefaultCAs: true". +func (r *Reconciler) createOrApplyDefaultCAPackage(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired := r.getDefaultCAPackageConfigMap(resourceLabels) + + configMapName := fmt.Sprintf("%s/%s", desired.GetNamespace(), desired.GetName()) + r.log.V(4).Info("reconciling default CA package configmap resource", "name", configMapName) + fetched := &corev1.ConfigMap{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return FromClientError(err, "failed to check %s default CA package configmap resource already exists", configMapName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s default CA package configmap resource already exists, maybe from previous installation", configMapName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("default CA package configmap has been modified, updating to desired state", "name", configMapName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return FromClientError(err, "failed to update %s default CA package configmap resource", configMapName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "default CA package configmap resource %s reconciled back to desired state", configMapName) + } else { + r.log.V(4).Info("default CA package configmap resource already exists and is in expected state", "name", configMapName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return FromClientError(err, "failed to create %s default CA package configmap resource", configMapName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "default CA package configmap resource %s created", configMapName) + } + + return nil +} + +func (r *Reconciler) getDefaultCAPackageConfigMap(resourceLabels map[string]string) *corev1.ConfigMap { + // Merge resource labels with CNO trusted CA bundle injection annotation + annotations := map[string]string{ + cnoTrustedCAAnnotation: "true", + } + + configMap := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: defaultCAPackageConfigMapName, + Namespace: trustManagerOperandNamespace, + Labels: resourceLabels, + Annotations: annotations, + }, + // Data is intentionally left empty. The CNO trusted CA bundle injector + // will populate the ca-certificates.crt key with the cluster's trusted CA bundle. + Data: map[string]string{}, + } + + return configMap +} diff --git a/pkg/controller/trustmanager/deployments.go b/pkg/controller/trustmanager/deployments.go new file mode 100644 index 000000000..d98cd6752 --- /dev/null +++ b/pkg/controller/trustmanager/deployments.go @@ -0,0 +1,283 @@ +package trustmanager + +import ( + "fmt" + "os" + "reflect" + "unsafe" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/kubernetes/pkg/apis/core" + corevalidation "k8s.io/kubernetes/pkg/apis/core/validation" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +func (r *Reconciler) createOrApplyDeployments(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired, err := r.getDeploymentObject(trustManager, resourceLabels) + if err != nil { + return fmt.Errorf("failed to generate deployment resource for creation: %w", err) + } + + deploymentName := fmt.Sprintf("%s/%s", desired.GetNamespace(), desired.GetName()) + r.log.V(4).Info("reconciling deployment resource", "name", deploymentName) + fetched := &appsv1.Deployment{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return FromClientError(err, "failed to check %s deployment resource already exists", deploymentName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s deployment resource already exists, maybe from previous installation", deploymentName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("deployment has been modified, updating to desired state", "name", deploymentName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return FromClientError(err, "failed to update %s deployment resource", deploymentName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "deployment resource %s reconciled back to desired state", deploymentName) + } else { + r.log.V(4).Info("deployment resource already exists and is in expected state", "name", deploymentName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return FromClientError(err, "failed to create %s deployment resource", deploymentName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "deployment resource %s created", deploymentName) + } + + if err := r.updateImageInStatus(trustManager, desired); err != nil { + return FromClientError(err, "failed to update %s trustmanager status with image info", trustManager.GetName()) + } + return nil +} + +func (r *Reconciler) getDeploymentObject(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string) (*appsv1.Deployment, error) { + deployment := decodeDeploymentObjBytes(assets.MustAsset(deploymentAssetName)) + + updateNamespace(deployment, trustManagerOperandNamespace) + updateResourceLabels(deployment, resourceLabels) + updatePodTemplateLabels(deployment, resourceLabels) + + updateArgList(deployment, trustManager) + + if err := updateResourceRequirement(deployment, trustManager); err != nil { + return nil, fmt.Errorf("failed to update resource requirements: %w", err) + } + if err := updateAffinityRules(deployment, trustManager); err != nil { + return nil, fmt.Errorf("failed to update affinity rules: %w", err) + } + if err := updatePodTolerations(deployment, trustManager); err != nil { + return nil, fmt.Errorf("failed to update pod tolerations: %w", err) + } + if err := updateNodeSelector(deployment, trustManager); err != nil { + return nil, fmt.Errorf("failed to update node selector: %w", err) + } + if err := r.updateImage(deployment); err != nil { + return nil, NewIrrecoverableError(err, "failed to update image for %s", trustManager.GetName()) + } + + // Add default CA package volume if enabled + if trustManager.Spec.TrustManagerConfig.DefaultCAPackage.Policy == v1alpha1.DefaultCAPackagePolicyEnabled { + addDefaultCAPackageVolume(deployment) + } + + return deployment, nil +} + +func (r *Reconciler) updateImage(deployment *appsv1.Deployment) error { + image := os.Getenv(trustManagerImageNameEnvVarName) + if image == "" { + return fmt.Errorf("%s environment variable with trust-manager image not set", trustManagerImageNameEnvVarName) + } + for i, container := range deployment.Spec.Template.Spec.Containers { + if container.Name == trustManagerContainerName { + deployment.Spec.Template.Spec.Containers[i].Image = image + } + } + return nil +} + +func (r *Reconciler) updateImageInStatus(trustManager *v1alpha1.TrustManager, deployment *appsv1.Deployment) error { + for _, container := range deployment.Spec.Template.Spec.Containers { + if container.Name == trustManagerContainerName { + if trustManager.Status.TrustManagerImage == container.Image { + return nil + } + trustManager.Status.TrustManagerImage = container.Image + } + } + return r.updateStatus(r.ctx, trustManager) +} + +func updatePodTemplateLabels(deployment *appsv1.Deployment, resourceLabels map[string]string) { + deployment.Spec.Template.Labels = resourceLabels +} + +func updateArgList(deployment *appsv1.Deployment, trustManager *v1alpha1.TrustManager) { + config := trustManager.Spec.TrustManagerConfig + + trustNamespace := config.TrustNamespace + if trustNamespace == "" { + trustNamespace = trustManagerOperandNamespace + } + + args := []string{ + fmt.Sprintf("--log-level=%d", config.LogLevel), + fmt.Sprintf("--log-format=%s", config.LogFormat), + "--metrics-port=9402", + "--readiness-probe-port=6060", + "--readiness-probe-path=/readyz", + fmt.Sprintf("--trust-namespace=%s", trustNamespace), + "--webhook-host=0.0.0.0", + "--webhook-port=6443", + } + + // Add filter-expired-certificates flag if enabled + if config.FilterExpiredCertificates == v1alpha1.FilterExpiredCertificatesPolicyEnabled { + args = append(args, "--filter-expired-certificates=true") + } + + // Add secret targets flags if enabled + if config.SecretTargets.Policy == v1alpha1.SecretTargetsPolicyCustom { + args = append(args, "--secret-targets-enabled=true") + } + + // Add default CA package flag if enabled + if config.DefaultCAPackage.Policy == v1alpha1.DefaultCAPackagePolicyEnabled { + args = append(args, fmt.Sprintf("--default-package-location=/var/run/configmaps/default-ca-package/%s", defaultCAPackageKeyName)) + } + + for i, container := range deployment.Spec.Template.Spec.Containers { + if container.Name == trustManagerContainerName { + deployment.Spec.Template.Spec.Containers[i].Args = args + } + } +} + +// addDefaultCAPackageVolume adds the ConfigMap volume containing the default CA package. +func addDefaultCAPackageVolume(deployment *appsv1.Deployment) { + const ( + defaultCAVolumeName = "default-ca-package" + defaultCAVolumeMountPath = "/var/run/configmaps/default-ca-package" + ) + var defaultMode = int32(420) + + desiredVolumeMount := corev1.VolumeMount{ + Name: defaultCAVolumeName, + MountPath: defaultCAVolumeMountPath, + ReadOnly: true, + } + + desiredVolume := corev1.Volume{ + Name: defaultCAVolumeName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: defaultCAPackageConfigMapName, + }, + Items: []corev1.KeyToPath{ + { + Key: defaultCAPackageKeyName, + Path: defaultCAPackageKeyName, + Mode: &defaultMode, + }, + }, + DefaultMode: &defaultMode, + }, + }, + } + + for i, container := range deployment.Spec.Template.Spec.Containers { + if container.Name == trustManagerContainerName { + deployment.Spec.Template.Spec.Containers[i].VolumeMounts = append( + deployment.Spec.Template.Spec.Containers[i].VolumeMounts, + desiredVolumeMount, + ) + break + } + } + + deployment.Spec.Template.Spec.Volumes = append( + deployment.Spec.Template.Spec.Volumes, + desiredVolume, + ) +} + +func updateResourceRequirement(deployment *appsv1.Deployment, trustManager *v1alpha1.TrustManager) error { + if reflect.ValueOf(trustManager.Spec.TrustManagerConfig.Resources).IsZero() { + return nil + } + if err := validateResourceRequirements(trustManager.Spec.TrustManagerConfig.Resources, + field.NewPath("spec", "trustManagerConfig")); err != nil { + return err + } + for i := range deployment.Spec.Template.Spec.Containers { + deployment.Spec.Template.Spec.Containers[i].Resources = trustManager.Spec.TrustManagerConfig.Resources + } + return nil +} + +func updateAffinityRules(deployment *appsv1.Deployment, trustManager *v1alpha1.TrustManager) error { + if trustManager.Spec.TrustManagerConfig.Affinity == nil { + return nil + } + if err := validateAffinityRules(trustManager.Spec.TrustManagerConfig.Affinity, + field.NewPath("spec", "trustManagerConfig")); err != nil { + return err + } + deployment.Spec.Template.Spec.Affinity = trustManager.Spec.TrustManagerConfig.Affinity + return nil +} + +func updatePodTolerations(deployment *appsv1.Deployment, trustManager *v1alpha1.TrustManager) error { + if trustManager.Spec.TrustManagerConfig.Tolerations == nil { + return nil + } + if err := validateTolerationsConfig(trustManager.Spec.TrustManagerConfig.Tolerations, + field.NewPath("spec", "trustManagerConfig")); err != nil { + return err + } + deployment.Spec.Template.Spec.Tolerations = trustManager.Spec.TrustManagerConfig.Tolerations + return nil +} + +func updateNodeSelector(deployment *appsv1.Deployment, trustManager *v1alpha1.TrustManager) error { + if trustManager.Spec.TrustManagerConfig.NodeSelector == nil { + return nil + } + if err := validateNodeSelectorConfig(trustManager.Spec.TrustManagerConfig.NodeSelector, + field.NewPath("spec", "trustManagerConfig")); err != nil { + return err + } + deployment.Spec.Template.Spec.NodeSelector = trustManager.Spec.TrustManagerConfig.NodeSelector + return nil +} + +// validateNodeSelectorConfig validates the NodeSelector configuration. +func validateNodeSelectorConfig(nodeSelector map[string]string, fldPath *field.Path) error { + return metav1validation.ValidateLabels(nodeSelector, fldPath.Child("nodeSelector")).ToAggregate() +} + +func validateTolerationsConfig(tolerations []corev1.Toleration, fldPath *field.Path) error { + // convert corev1.Tolerations to core.Tolerations, required for validation. + convTolerations := *(*[]core.Toleration)(unsafe.Pointer(&tolerations)) + return corevalidation.ValidateTolerations(convTolerations, fldPath.Child("tolerations")).ToAggregate() +} + +func validateResourceRequirements(requirements corev1.ResourceRequirements, fldPath *field.Path) error { + // convert corev1.ResourceRequirements to core.ResourceRequirements, required for validation. + convRequirements := *(*core.ResourceRequirements)(unsafe.Pointer(&requirements)) + return corevalidation.ValidateContainerResourceRequirements(&convRequirements, nil, fldPath.Child("resources"), corevalidation.PodValidationOptions{}).ToAggregate() +} + +func validateAffinityRules(affinity *corev1.Affinity, fldPath *field.Path) error { + // convert corev1.Affinity to core.Affinity, required for validation. + convAffinity := (*core.Affinity)(unsafe.Pointer(affinity)) + return validateAffinity(convAffinity, corevalidation.PodValidationOptions{}, fldPath.Child("affinity")).ToAggregate() +} diff --git a/pkg/controller/trustmanager/errors.go b/pkg/controller/trustmanager/errors.go new file mode 100644 index 000000000..87b3801bb --- /dev/null +++ b/pkg/controller/trustmanager/errors.go @@ -0,0 +1,87 @@ +package trustmanager + +import ( + "errors" + "fmt" + + apierrors "k8s.io/apimachinery/pkg/api/errors" +) + +type ErrorReason string + +const ( + IrrecoverableError ErrorReason = "IrrecoverableError" + + RetryRequiredError ErrorReason = "RetryRequiredError" +) + +type ReconcileError struct { + Reason ErrorReason `json:"reason,omitempty"` + Message string `json:"message,omitempty"` + Err error `json:"error,omitempty"` +} + +var _ error = &ReconcileError{} + +func NewIrrecoverableError(err error, message string, args ...any) *ReconcileError { + if err == nil { + return nil + } + return &ReconcileError{ + Reason: IrrecoverableError, + Message: fmt.Sprintf(message, args...), + Err: err, + } +} + +func NewRetryRequiredError(err error, message string, args ...any) *ReconcileError { + if err == nil { + return nil + } + return &ReconcileError{ + Reason: RetryRequiredError, + Message: fmt.Sprintf(message, args...), + Err: err, + } +} + +func FromClientError(err error, message string, args ...any) *ReconcileError { + if err == nil { + return nil + } + if apierrors.IsUnauthorized(err) || apierrors.IsForbidden(err) || apierrors.IsInvalid(err) || + apierrors.IsBadRequest(err) || apierrors.IsServiceUnavailable(err) { + return NewIrrecoverableError(err, message, args...) + } + + return NewRetryRequiredError(err, message, args...) +} + +func FromError(err error, message string, args ...any) *ReconcileError { + if err == nil { + return nil + } + if IsIrrecoverableError(err) { + return NewIrrecoverableError(err, message, args...) + } + return NewRetryRequiredError(err, message, args...) +} + +func IsIrrecoverableError(err error) bool { + if rerr, ok := err.(*ReconcileError); ok || errors.As(err, &rerr) { + return rerr.Reason == IrrecoverableError + } + return false +} + +func IsRetryRequiredError(err error) bool { + if rerr, ok := err.(*ReconcileError); ok || errors.As(err, &rerr) { + return rerr.Reason == RetryRequiredError + } + return false +} + +// ReconcileError implements the error interface. +func (e *ReconcileError) Error() string { + return fmt.Sprintf("%s: %s", e.Message, e.Err) +} diff --git a/pkg/controller/trustmanager/install_trustmanager.go b/pkg/controller/trustmanager/install_trustmanager.go new file mode 100644 index 000000000..272c3f207 --- /dev/null +++ b/pkg/controller/trustmanager/install_trustmanager.go @@ -0,0 +1,74 @@ +package trustmanager + +import ( + "fmt" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" +) + +func (r *Reconciler) reconcileTrustManagerDeployment(trustManager *v1alpha1.TrustManager, trustManagerCreateRecon bool) error { + if err := validateTrustManagerConfig(trustManager); err != nil { + return NewIrrecoverableError(err, "%s configuration validation failed", trustManager.GetName()) + } + + // if user has set custom labels to be added to all resources created by the controller + // merge it with the controller's own default labels. + resourceLabels := make(map[string]string) + if len(trustManager.Spec.ControllerConfig.Labels) != 0 { + for k, v := range trustManager.Spec.ControllerConfig.Labels { + resourceLabels[k] = v + } + } + for k, v := range controllerDefaultResourceLabels { + resourceLabels[k] = v + } + + if err := r.createOrApplyServiceAccounts(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile serviceaccount resource") + return err + } + + if err := r.createOrApplyRBACResources(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile rbac resources") + return err + } + + if err := r.createOrApplyServices(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile service resources") + return err + } + + if err := r.createOrApplyCertificates(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile certificate resource") + return err + } + + if err := r.createOrApplyDeployments(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile deployment resource") + return err + } + + if err := r.createOrApplyValidatingWebhookConfigurations(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile validatingwebhookconfiguration resource") + return err + } + + if trustManager.Spec.TrustManagerConfig.DefaultCAPackage.Policy == v1alpha1.DefaultCAPackagePolicyEnabled { + if err := r.createOrApplyDefaultCAPackage(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile default CA package") + return err + } + } + + // Update status with current config policies + updateStatusPolicies(trustManager) + + if addProcessedAnnotation(trustManager) { + if err := r.UpdateWithRetry(r.ctx, trustManager); err != nil { + return fmt.Errorf("failed to update processed annotation to %s: %w", trustManager.GetName(), err) + } + } + + r.log.V(4).Info("finished reconciliation of trustmanager", "name", trustManager.GetName()) + return nil +} diff --git a/pkg/controller/trustmanager/rbacs.go b/pkg/controller/trustmanager/rbacs.go new file mode 100644 index 000000000..fae700436 --- /dev/null +++ b/pkg/controller/trustmanager/rbacs.go @@ -0,0 +1,298 @@ +package trustmanager + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +func (r *Reconciler) createOrApplyRBACResources(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + serviceAccount := decodeServiceAccountObjBytes(assets.MustAsset(serviceAccountAssetName)).GetName() + + if err := r.createOrApplyClusterRoles(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile clusterrole resource") + return err + } + + if err := r.createOrApplyClusterRoleBindings(trustManager, serviceAccount, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile clusterrolebinding resource") + return err + } + + if err := r.createOrApplyRoles(trustManager, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile role resource") + return err + } + + if err := r.createOrApplyRoleBindings(trustManager, serviceAccount, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile rolebinding resource") + return err + } + + // Handle dynamic RBAC for secret targets + if trustManager.Spec.TrustManagerConfig.SecretTargets.Policy == v1alpha1.SecretTargetsPolicyCustom { + if err := r.createOrApplySecretTargetsRBAC(trustManager, serviceAccount, resourceLabels, trustManagerCreateRecon); err != nil { + r.log.Error(err, "failed to reconcile secret targets rbac resources") + return err + } + } + + return nil +} + +func (r *Reconciler) createOrApplyClusterRoles(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired := r.getClusterRoleObject(resourceLabels) + + roleName := desired.GetName() + r.log.V(4).Info("reconciling clusterrole resource", "name", roleName) + fetched := &rbacv1.ClusterRole{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return FromClientError(err, "failed to check %s clusterrole resource already exists", roleName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s clusterrole resource already exists, maybe from previous installation", roleName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("clusterrole has been modified, updating to desired state", "name", roleName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return FromClientError(err, "failed to update %s clusterrole resource", roleName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "clusterrole resource %s reconciled back to desired state", roleName) + } else { + r.log.V(4).Info("clusterrole resource already exists and is in expected state", "name", roleName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return FromClientError(err, "failed to create %s clusterrole resource", roleName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "clusterrole resource %s created", roleName) + } + + return nil +} + +func (r *Reconciler) getClusterRoleObject(resourceLabels map[string]string) *rbacv1.ClusterRole { + clusterRole := decodeClusterRoleObjBytes(assets.MustAsset(clusterRoleAssetName)) + updateResourceLabels(clusterRole, resourceLabels) + return clusterRole +} + +func (r *Reconciler) createOrApplyClusterRoleBindings(trustManager *v1alpha1.TrustManager, serviceAccount string, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired := r.getClusterRoleBindingObject(serviceAccount, resourceLabels) + + roleBindingName := desired.GetName() + r.log.V(4).Info("reconciling clusterrolebinding resource", "name", roleBindingName) + fetched := &rbacv1.ClusterRoleBinding{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return FromClientError(err, "failed to check %s clusterrolebinding resource already exists", roleBindingName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s clusterrolebinding resource already exists, maybe from previous installation", roleBindingName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("clusterrolebinding has been modified, updating to desired state", "name", roleBindingName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return FromClientError(err, "failed to update %s clusterrolebinding resource", roleBindingName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "clusterrolebinding resource %s reconciled back to desired state", roleBindingName) + } else { + r.log.V(4).Info("clusterrolebinding resource already exists and is in expected state", "name", roleBindingName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return FromClientError(err, "failed to create %s clusterrolebinding resource", roleBindingName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "clusterrolebinding resource %s created", roleBindingName) + } + + return nil +} + +func (r *Reconciler) getClusterRoleBindingObject(serviceAccount string, resourceLabels map[string]string) *rbacv1.ClusterRoleBinding { + clusterRoleBinding := decodeClusterRoleBindingObjBytes(assets.MustAsset(clusterRoleBindingAssetName)) + updateResourceLabels(clusterRoleBinding, resourceLabels) + updateServiceAccountNamespaceInRBACBindingObject[*rbacv1.ClusterRoleBinding](clusterRoleBinding, serviceAccount, trustManagerOperandNamespace) + return clusterRoleBinding +} + +func (r *Reconciler) createOrApplyRoles(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired := r.getRoleObject(trustManager, resourceLabels) + + roleName := fmt.Sprintf("%s/%s", desired.GetNamespace(), desired.GetName()) + r.log.V(4).Info("reconciling role resource", "name", roleName) + fetched := &rbacv1.Role{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return FromClientError(err, "failed to check %s role resource already exists", roleName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s role resource already exists, maybe from previous installation", roleName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("role has been modified, updating to desired state", "name", roleName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return FromClientError(err, "failed to update %s role resource", roleName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "role resource %s reconciled back to desired state", roleName) + } else { + r.log.V(4).Info("role resource already exists and is in expected state", "name", roleName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return FromClientError(err, "failed to create %s role resource", roleName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "role resource %s created", roleName) + } + + return nil +} + +func (r *Reconciler) getRoleObject(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string) *rbacv1.Role { + role := decodeRoleObjBytes(assets.MustAsset(roleAssetName)) + // Deploy the role in the trust namespace + trustNamespace := trustManager.Spec.TrustManagerConfig.TrustNamespace + if trustNamespace == "" { + trustNamespace = trustManagerOperandNamespace + } + updateNamespace(role, trustNamespace) + updateResourceLabels(role, resourceLabels) + return role +} + +func (r *Reconciler) createOrApplyRoleBindings(trustManager *v1alpha1.TrustManager, serviceAccount string, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired := r.getRoleBindingObject(trustManager, serviceAccount, resourceLabels) + + roleBindingName := fmt.Sprintf("%s/%s", desired.GetNamespace(), desired.GetName()) + r.log.V(4).Info("reconciling rolebinding resource", "name", roleBindingName) + fetched := &rbacv1.RoleBinding{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return FromClientError(err, "failed to check %s rolebinding resource already exists", roleBindingName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s rolebinding resource already exists, maybe from previous installation", roleBindingName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("rolebinding has been modified, updating to desired state", "name", roleBindingName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return FromClientError(err, "failed to update %s rolebinding resource", roleBindingName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "rolebinding resource %s reconciled back to desired state", roleBindingName) + } else { + r.log.V(4).Info("rolebinding resource already exists and is in expected state", "name", roleBindingName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return FromClientError(err, "failed to create %s rolebinding resource", roleBindingName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "rolebinding resource %s created", roleBindingName) + } + + return nil +} + +func (r *Reconciler) getRoleBindingObject(trustManager *v1alpha1.TrustManager, serviceAccount string, resourceLabels map[string]string) *rbacv1.RoleBinding { + roleBinding := decodeRoleBindingObjBytes(assets.MustAsset(roleBindingAssetName)) + // Deploy the rolebinding in the trust namespace + trustNamespace := trustManager.Spec.TrustManagerConfig.TrustNamespace + if trustNamespace == "" { + trustNamespace = trustManagerOperandNamespace + } + updateNamespace(roleBinding, trustNamespace) + updateResourceLabels(roleBinding, resourceLabels) + updateServiceAccountNamespaceInRBACBindingObject[*rbacv1.RoleBinding](roleBinding, serviceAccount, trustManagerOperandNamespace) + return roleBinding +} + +// createOrApplySecretTargetsRBAC creates additional RBAC rules when the SecretTargets policy +// is set to Custom. This grants trust-manager permissions to create and update the specific +// secrets listed in authorizedSecrets. +func (r *Reconciler) createOrApplySecretTargetsRBAC(trustManager *v1alpha1.TrustManager, serviceAccount string, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + secretTargetsClusterRole := &rbacv1.ClusterRole{ + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"get", "list", "watch", "create", "update", "patch"}, + ResourceNames: trustManager.Spec.TrustManagerConfig.SecretTargets.AuthorizedSecrets, + }, + }, + } + secretTargetsClusterRole.SetName("trust-manager-secret-targets") + secretTargetsClusterRole.SetLabels(resourceLabels) + + roleName := secretTargetsClusterRole.GetName() + r.log.V(4).Info("reconciling secret targets clusterrole resource", "name", roleName) + fetched := &rbacv1.ClusterRole{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(secretTargetsClusterRole), fetched) + if err != nil { + return FromClientError(err, "failed to check %s secret targets clusterrole resource already exists", roleName) + } + + if exist && hasObjectChanged(secretTargetsClusterRole, fetched) { + r.log.V(1).Info("secret targets clusterrole has been modified, updating to desired state", "name", roleName) + if err := r.UpdateWithRetry(r.ctx, secretTargetsClusterRole); err != nil { + return FromClientError(err, "failed to update %s secret targets clusterrole resource", roleName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "secret targets clusterrole resource %s reconciled back to desired state", roleName) + } + if !exist { + if err := r.Create(r.ctx, secretTargetsClusterRole); err != nil { + return FromClientError(err, "failed to create %s secret targets clusterrole resource", roleName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "secret targets clusterrole resource %s created", roleName) + } + + // Create corresponding ClusterRoleBinding + secretTargetsBinding := &rbacv1.ClusterRoleBinding{ + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: roleName, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: serviceAccount, + Namespace: trustManagerOperandNamespace, + }, + }, + } + secretTargetsBinding.SetName("trust-manager-secret-targets") + secretTargetsBinding.SetLabels(resourceLabels) + + bindingName := secretTargetsBinding.GetName() + r.log.V(4).Info("reconciling secret targets clusterrolebinding resource", "name", bindingName) + fetchedBinding := &rbacv1.ClusterRoleBinding{} + exist, err = r.Exists(r.ctx, client.ObjectKeyFromObject(secretTargetsBinding), fetchedBinding) + if err != nil { + return FromClientError(err, "failed to check %s secret targets clusterrolebinding resource already exists", bindingName) + } + + if exist && hasObjectChanged(secretTargetsBinding, fetchedBinding) { + r.log.V(1).Info("secret targets clusterrolebinding has been modified, updating to desired state", "name", bindingName) + if err := r.UpdateWithRetry(r.ctx, secretTargetsBinding); err != nil { + return FromClientError(err, "failed to update %s secret targets clusterrolebinding resource", bindingName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "secret targets clusterrolebinding resource %s reconciled back to desired state", bindingName) + } + if !exist { + if err := r.Create(r.ctx, secretTargetsBinding); err != nil { + return FromClientError(err, "failed to create %s secret targets clusterrolebinding resource", bindingName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "secret targets clusterrolebinding resource %s created", bindingName) + } + + return nil +} diff --git a/pkg/controller/trustmanager/serviceaccounts.go b/pkg/controller/trustmanager/serviceaccounts.go new file mode 100644 index 000000000..e3324d6d5 --- /dev/null +++ b/pkg/controller/trustmanager/serviceaccounts.go @@ -0,0 +1,45 @@ +package trustmanager + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +func (r *Reconciler) createOrApplyServiceAccounts(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired := r.getServiceAccountObject(resourceLabels) + + serviceAccountName := fmt.Sprintf("%s/%s", desired.GetNamespace(), desired.GetName()) + r.log.V(4).Info("reconciling serviceaccount resource", "name", serviceAccountName) + fetched := &corev1.ServiceAccount{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return FromClientError(err, "failed to check %s serviceaccount resource already exists", serviceAccountName) + } + + if exist { + if trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s serviceaccount resource already exists, maybe from previous installation", serviceAccountName) + } + r.log.V(4).Info("serviceaccount resource already exists and is in expected state", "name", serviceAccountName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return FromClientError(err, "failed to create %s serviceaccount resource", serviceAccountName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "serviceaccount resource %s created", serviceAccountName) + } + + return nil +} + +func (r *Reconciler) getServiceAccountObject(resourceLabels map[string]string) *corev1.ServiceAccount { + serviceAccount := decodeServiceAccountObjBytes(assets.MustAsset(serviceAccountAssetName)) + updateNamespace(serviceAccount, trustManagerOperandNamespace) + updateResourceLabels(serviceAccount, resourceLabels) + return serviceAccount +} diff --git a/pkg/controller/trustmanager/services.go b/pkg/controller/trustmanager/services.go new file mode 100644 index 000000000..70939cd4c --- /dev/null +++ b/pkg/controller/trustmanager/services.go @@ -0,0 +1,58 @@ +package trustmanager + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +func (r *Reconciler) createOrApplyServices(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + serviceAssets := []string{ + webhookServiceAssetName, + metricsServiceAssetName, + } + + for _, assetName := range serviceAssets { + desired := r.getServiceObject(assetName, resourceLabels) + + serviceName := fmt.Sprintf("%s/%s", desired.GetNamespace(), desired.GetName()) + r.log.V(4).Info("reconciling service resource", "name", serviceName) + fetched := &corev1.Service{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return FromClientError(err, "failed to check %s service resource already exists", serviceName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s service resource already exists, maybe from previous installation", serviceName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("service has been modified, updating to desired state", "name", serviceName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return FromClientError(err, "failed to update %s service resource", serviceName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "service resource %s reconciled back to desired state", serviceName) + } else { + r.log.V(4).Info("service resource already exists and is in expected state", "name", serviceName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return FromClientError(err, "failed to create %s service resource", serviceName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "service resource %s created", serviceName) + } + } + + return nil +} + +func (r *Reconciler) getServiceObject(assetName string, resourceLabels map[string]string) *corev1.Service { + service := decodeServiceObjBytes(assets.MustAsset(assetName)) + updateNamespace(service, trustManagerOperandNamespace) + updateResourceLabels(service, resourceLabels) + return service +} diff --git a/pkg/controller/trustmanager/utils.go b/pkg/controller/trustmanager/utils.go new file mode 100644 index 000000000..59a454b63 --- /dev/null +++ b/pkg/controller/trustmanager/utils.go @@ -0,0 +1,352 @@ +package trustmanager + +import ( + "context" + "fmt" + "reflect" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/client-go/util/retry" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" +) + +var ( + localScheme = runtime.NewScheme() + codecs = serializer.NewCodecFactory(localScheme) +) + +func init() { + if err := appsv1.AddToScheme(localScheme); err != nil { + panic(err) + } + if err := corev1.AddToScheme(localScheme); err != nil { + panic(err) + } + if err := rbacv1.AddToScheme(localScheme); err != nil { + panic(err) + } + if err := certmanagerv1.AddToScheme(localScheme); err != nil { + panic(err) + } + if err := admissionregistrationv1.AddToScheme(localScheme); err != nil { + panic(err) + } +} + +// updateStatus is for updating the status subresource of trustmanagers.openshift.operator.io. +func (r *Reconciler) updateStatus(ctx context.Context, changed *v1alpha1.TrustManager) error { + namespacedName := client.ObjectKeyFromObject(changed) + if err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + r.log.V(4).Info("updating trustmanagers.openshift.operator.io status", "request", namespacedName) + current := &v1alpha1.TrustManager{} + if err := r.Get(ctx, namespacedName, current); err != nil { + return fmt.Errorf("failed to fetch trustmanagers.openshift.operator.io %q for status update: %w", namespacedName, err) + } + changed.Status.DeepCopyInto(¤t.Status) + + if err := r.StatusUpdate(ctx, current); err != nil { + return fmt.Errorf("failed to update trustmanagers.openshift.operator.io %q status: %w", namespacedName, err) + } + + return nil + }); err != nil { + return err + } + + return nil +} + +// addFinalizer adds finalizer to trustmanagers.openshift.operator.io resource. +func (r *Reconciler) addFinalizer(ctx context.Context, trustManager *v1alpha1.TrustManager) error { + namespacedName := client.ObjectKeyFromObject(trustManager) + if !controllerutil.ContainsFinalizer(trustManager, finalizer) { + if !controllerutil.AddFinalizer(trustManager, finalizer) { + return fmt.Errorf("failed to create %q trustmanagers.openshift.operator.io object with finalizers added", namespacedName) + } + + // update trustmanagers.openshift.operator.io on adding finalizer. + if err := r.UpdateWithRetry(ctx, trustManager); err != nil { + return fmt.Errorf("failed to add finalizers on %q trustmanagers.openshift.operator.io with %w", namespacedName, err) + } + + updated := &v1alpha1.TrustManager{} + if err := r.Get(ctx, namespacedName, updated); err != nil { + return fmt.Errorf("failed to fetch trustmanagers.openshift.operator.io %q after updating finalizers: %w", namespacedName, err) + } + updated.DeepCopyInto(trustManager) + return nil + } + return nil +} + +// removeFinalizer removes finalizers added to trustmanagers.openshift.operator.io resource. +func (r *Reconciler) removeFinalizer(ctx context.Context, trustManager *v1alpha1.TrustManager, finalizerName string) error { + namespacedName := client.ObjectKeyFromObject(trustManager) + if controllerutil.ContainsFinalizer(trustManager, finalizerName) { + if !controllerutil.RemoveFinalizer(trustManager, finalizerName) { + return fmt.Errorf("failed to create %q trustmanagers.openshift.operator.io object with finalizers removed", namespacedName) + } + + if err := r.UpdateWithRetry(ctx, trustManager); err != nil { + return fmt.Errorf("failed to remove finalizers on %q trustmanagers.openshift.operator.io with %w", namespacedName, err) + } + return nil + } + + return nil +} + +func containsProcessedAnnotation(trustManager *v1alpha1.TrustManager) bool { + _, exist := trustManager.GetAnnotations()[controllerProcessedAnnotation] + return exist +} + +func addProcessedAnnotation(trustManager *v1alpha1.TrustManager) bool { + annotations := trustManager.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string, 1) + } + if _, exist := annotations[controllerProcessedAnnotation]; !exist { + annotations[controllerProcessedAnnotation] = "true" + trustManager.SetAnnotations(annotations) + return true + } + return false +} + +func updateNamespace(obj client.Object, newNamespace string) { + obj.SetNamespace(newNamespace) +} + +func updateResourceLabels(obj client.Object, labels map[string]string) { + obj.SetLabels(labels) +} + +func decodeDeploymentObjBytes(objBytes []byte) *appsv1.Deployment { + obj, err := runtime.Decode(codecs.UniversalDecoder(appsv1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*appsv1.Deployment) +} + +func decodeClusterRoleObjBytes(objBytes []byte) *rbacv1.ClusterRole { + obj, err := runtime.Decode(codecs.UniversalDecoder(rbacv1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*rbacv1.ClusterRole) +} + +func decodeClusterRoleBindingObjBytes(objBytes []byte) *rbacv1.ClusterRoleBinding { + obj, err := runtime.Decode(codecs.UniversalDecoder(rbacv1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*rbacv1.ClusterRoleBinding) +} + +func decodeRoleObjBytes(objBytes []byte) *rbacv1.Role { + obj, err := runtime.Decode(codecs.UniversalDecoder(rbacv1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*rbacv1.Role) +} + +func decodeRoleBindingObjBytes(objBytes []byte) *rbacv1.RoleBinding { + obj, err := runtime.Decode(codecs.UniversalDecoder(rbacv1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*rbacv1.RoleBinding) +} + +func decodeServiceObjBytes(objBytes []byte) *corev1.Service { + obj, err := runtime.Decode(codecs.UniversalDecoder(corev1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*corev1.Service) +} + +func decodeServiceAccountObjBytes(objBytes []byte) *corev1.ServiceAccount { + obj, err := runtime.Decode(codecs.UniversalDecoder(corev1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*corev1.ServiceAccount) +} + +func decodeCertificateObjBytes(objBytes []byte) *certmanagerv1.Certificate { + obj, err := runtime.Decode(codecs.UniversalDecoder(certmanagerv1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*certmanagerv1.Certificate) +} + +func decodeValidatingWebhookConfigurationObjBytes(objBytes []byte) *admissionregistrationv1.ValidatingWebhookConfiguration { + obj, err := runtime.Decode(codecs.UniversalDecoder(admissionregistrationv1.SchemeGroupVersion), objBytes) + if err != nil { + panic(err) + } + return obj.(*admissionregistrationv1.ValidatingWebhookConfiguration) +} + +func hasObjectChanged(desired, fetched client.Object) bool { + if reflect.TypeOf(desired) != reflect.TypeOf(fetched) { + panic("both objects to be compared must be of same type") + } + + var objectModified bool + switch desired.(type) { + case *certmanagerv1.Certificate: + objectModified = !reflect.DeepEqual(desired.(*certmanagerv1.Certificate).Spec, fetched.(*certmanagerv1.Certificate).Spec) + case *rbacv1.ClusterRole: + objectModified = !reflect.DeepEqual(desired.(*rbacv1.ClusterRole).Rules, fetched.(*rbacv1.ClusterRole).Rules) + case *rbacv1.ClusterRoleBinding: + objectModified = !reflect.DeepEqual(desired.(*rbacv1.ClusterRoleBinding).RoleRef, fetched.(*rbacv1.ClusterRoleBinding).RoleRef) || + !reflect.DeepEqual(desired.(*rbacv1.ClusterRoleBinding).Subjects, fetched.(*rbacv1.ClusterRoleBinding).Subjects) + case *appsv1.Deployment: + objectModified = deploymentSpecModified(desired.(*appsv1.Deployment), fetched.(*appsv1.Deployment)) + case *rbacv1.Role: + objectModified = !reflect.DeepEqual(desired.(*rbacv1.Role).Rules, fetched.(*rbacv1.Role).Rules) + case *rbacv1.RoleBinding: + objectModified = !reflect.DeepEqual(desired.(*rbacv1.RoleBinding).RoleRef, fetched.(*rbacv1.RoleBinding).RoleRef) || + !reflect.DeepEqual(desired.(*rbacv1.RoleBinding).Subjects, fetched.(*rbacv1.RoleBinding).Subjects) + case *corev1.Service: + objectModified = serviceSpecModified(desired.(*corev1.Service), fetched.(*corev1.Service)) + case *corev1.ConfigMap: + objectModified = !reflect.DeepEqual(desired.(*corev1.ConfigMap).Data, fetched.(*corev1.ConfigMap).Data) + case *admissionregistrationv1.ValidatingWebhookConfiguration: + objectModified = !reflect.DeepEqual(desired.(*admissionregistrationv1.ValidatingWebhookConfiguration).Webhooks, fetched.(*admissionregistrationv1.ValidatingWebhookConfiguration).Webhooks) + default: + panic(fmt.Sprintf("unsupported object type: %T", desired)) + } + return objectModified || !reflect.DeepEqual(desired.GetLabels(), fetched.GetLabels()) +} + +func deploymentSpecModified(desired, fetched *appsv1.Deployment) bool { + if *desired.Spec.Replicas != *fetched.Spec.Replicas || + !reflect.DeepEqual(desired.Spec.Selector.MatchLabels, fetched.Spec.Selector.MatchLabels) { + return true + } + + if !reflect.DeepEqual(desired.Spec.Template.Labels, fetched.Spec.Template.Labels) || + len(desired.Spec.Template.Spec.Containers) != len(fetched.Spec.Template.Spec.Containers) { + return true + } + + desiredContainer := desired.Spec.Template.Spec.Containers[0] + fetchedContainer := fetched.Spec.Template.Spec.Containers[0] + if !reflect.DeepEqual(desiredContainer.Args, fetchedContainer.Args) || + desiredContainer.Name != fetchedContainer.Name || desiredContainer.Image != fetchedContainer.Image || + desiredContainer.ImagePullPolicy != fetchedContainer.ImagePullPolicy { + return true + } + + if len(desiredContainer.Ports) != len(fetchedContainer.Ports) { + return true + } + for _, fetchedPort := range fetchedContainer.Ports { + matched := false + for _, desiredPort := range desiredContainer.Ports { + if fetchedPort.ContainerPort == desiredPort.ContainerPort { + matched = true + break + } + } + if !matched { + return true + } + } + + if desiredContainer.ReadinessProbe.HTTPGet.Path != fetchedContainer.ReadinessProbe.HTTPGet.Path || + desiredContainer.ReadinessProbe.InitialDelaySeconds != fetchedContainer.ReadinessProbe.InitialDelaySeconds || + desiredContainer.ReadinessProbe.PeriodSeconds != fetchedContainer.ReadinessProbe.PeriodSeconds { + return true + } + + if !reflect.DeepEqual(desiredContainer.Resources, fetchedContainer.Resources) || + !reflect.DeepEqual(*desiredContainer.SecurityContext, *fetchedContainer.SecurityContext) || + !reflect.DeepEqual(desiredContainer.VolumeMounts, fetchedContainer.VolumeMounts) { + return true + } + + if desired.Spec.Template.Spec.ServiceAccountName != fetched.Spec.Template.Spec.ServiceAccountName || + !reflect.DeepEqual(desired.Spec.Template.Spec.NodeSelector, fetched.Spec.Template.Spec.NodeSelector) || + !reflect.DeepEqual(desired.Spec.Template.Spec.Volumes, fetched.Spec.Template.Spec.Volumes) { + return true + } + + return false +} + +func serviceSpecModified(desired, fetched *corev1.Service) bool { + if desired.Spec.Type != fetched.Spec.Type || + !reflect.DeepEqual(desired.Spec.Ports, fetched.Spec.Ports) || + !reflect.DeepEqual(desired.Spec.Selector, fetched.Spec.Selector) { + return true + } + + return false +} + +func (r *Reconciler) updateCondition(trustManager *v1alpha1.TrustManager, prependErr error) error { + if err := r.updateStatus(r.ctx, trustManager); err != nil { + errUpdate := fmt.Errorf("failed to update %s status: %w", trustManager.GetName(), err) + if prependErr != nil { + return utilerrors.NewAggregate([]error{err, errUpdate}) + } + return errUpdate + } + return prependErr +} + +func updateServiceAccountNamespaceInRBACBindingObject[Object *rbacv1.RoleBinding | *rbacv1.ClusterRoleBinding](obj Object, serviceAccount, newNamespace string) { + const roleBindingSubjectKind = "ServiceAccount" + var subjects *[]rbacv1.Subject + switch o := any(obj).(type) { + case *rbacv1.ClusterRoleBinding: + subjects = &o.Subjects + case *rbacv1.RoleBinding: + subjects = &o.Subjects + } + for i := range *subjects { + if (*subjects)[i].Kind == roleBindingSubjectKind && (*subjects)[i].Name == serviceAccount { + (*subjects)[i].Namespace = newNamespace + } + } +} + +func validateTrustManagerConfig(trustManager *v1alpha1.TrustManager) error { + if reflect.ValueOf(trustManager.Spec.TrustManagerConfig).IsZero() { + return fmt.Errorf("spec.trustManagerConfig config cannot be empty") + } + return nil +} + +// updateStatusPolicies updates the status fields that reflect the current config policies. +func updateStatusPolicies(trustManager *v1alpha1.TrustManager) { + trustManager.Status.TrustNamespace = trustManager.Spec.TrustManagerConfig.TrustNamespace + trustManager.Status.SecretTargetsPolicy = trustManager.Spec.TrustManagerConfig.SecretTargets.Policy + trustManager.Status.DefaultCAPackagePolicy = trustManager.Spec.TrustManagerConfig.DefaultCAPackage.Policy + trustManager.Status.FilterExpiredCertificatesPolicy = trustManager.Spec.TrustManagerConfig.FilterExpiredCertificates + + // Set the trust namespace from the CRD default if not set in status yet + if trustManager.Status.TrustNamespace == "" { + trustManager.Status.TrustNamespace = trustManagerOperandNamespace + } +} diff --git a/pkg/controller/trustmanager/webhooks.go b/pkg/controller/trustmanager/webhooks.go new file mode 100644 index 000000000..12e63d263 --- /dev/null +++ b/pkg/controller/trustmanager/webhooks.go @@ -0,0 +1,49 @@ +package trustmanager + +import ( + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + "github.com/openshift/cert-manager-operator/pkg/operator/assets" +) + +func (r *Reconciler) createOrApplyValidatingWebhookConfigurations(trustManager *v1alpha1.TrustManager, resourceLabels map[string]string, trustManagerCreateRecon bool) error { + desired := r.getValidatingWebhookConfigurationObject(resourceLabels) + + webhookName := desired.GetName() + r.log.V(4).Info("reconciling validatingwebhookconfiguration resource", "name", webhookName) + fetched := &admissionregistrationv1.ValidatingWebhookConfiguration{} + exist, err := r.Exists(r.ctx, client.ObjectKeyFromObject(desired), fetched) + if err != nil { + return FromClientError(err, "failed to check %s validatingwebhookconfiguration resource already exists", webhookName) + } + + if exist && trustManagerCreateRecon { + r.eventRecorder.Eventf(trustManager, corev1.EventTypeWarning, "ResourceAlreadyExists", "%s validatingwebhookconfiguration resource already exists, maybe from previous installation", webhookName) + } + if exist && hasObjectChanged(desired, fetched) { + r.log.V(1).Info("validatingwebhookconfiguration has been modified, updating to desired state", "name", webhookName) + if err := r.UpdateWithRetry(r.ctx, desired); err != nil { + return FromClientError(err, "failed to update %s validatingwebhookconfiguration resource", webhookName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "validatingwebhookconfiguration resource %s reconciled back to desired state", webhookName) + } else { + r.log.V(4).Info("validatingwebhookconfiguration resource already exists and is in expected state", "name", webhookName) + } + if !exist { + if err := r.Create(r.ctx, desired); err != nil { + return FromClientError(err, "failed to create %s validatingwebhookconfiguration resource", webhookName) + } + r.eventRecorder.Eventf(trustManager, corev1.EventTypeNormal, "Reconciled", "validatingwebhookconfiguration resource %s created", webhookName) + } + + return nil +} + +func (r *Reconciler) getValidatingWebhookConfigurationObject(resourceLabels map[string]string) *admissionregistrationv1.ValidatingWebhookConfiguration { + webhook := decodeValidatingWebhookConfigurationObjBytes(assets.MustAsset(validatingWebhookConfigurationAssetName)) + updateResourceLabels(webhook, resourceLabels) + return webhook +} diff --git a/pkg/operator/applyconfigurations/internal/internal.go b/pkg/operator/applyconfigurations/internal/internal.go index ab48e360d..cf84b3f02 100644 --- a/pkg/operator/applyconfigurations/internal/internal.go +++ b/pkg/operator/applyconfigurations/internal/internal.go @@ -43,6 +43,16 @@ var schemaYAML = typed.YAMLObject(`types: elementType: namedType: __untyped_deduced_ elementRelationship: separable +- name: com.github.openshift.cert-manager-operator.api.operator.v1alpha1.TrustManager + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_deduced_ + elementRelationship: separable - name: __untyped_atomic_ scalar: untyped list: diff --git a/pkg/operator/applyconfigurations/operator/v1alpha1/defaultcapackageconfig.go b/pkg/operator/applyconfigurations/operator/v1alpha1/defaultcapackageconfig.go new file mode 100644 index 000000000..a45123bee --- /dev/null +++ b/pkg/operator/applyconfigurations/operator/v1alpha1/defaultcapackageconfig.go @@ -0,0 +1,27 @@ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + operatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" +) + +// DefaultCAPackageConfigApplyConfiguration represents a declarative configuration of the DefaultCAPackageConfig type for use +// with apply. +type DefaultCAPackageConfigApplyConfiguration struct { + Policy *operatorv1alpha1.DefaultCAPackagePolicy `json:"policy,omitempty"` +} + +// DefaultCAPackageConfigApplyConfiguration constructs a declarative configuration of the DefaultCAPackageConfig type for use with +// apply. +func DefaultCAPackageConfig() *DefaultCAPackageConfigApplyConfiguration { + return &DefaultCAPackageConfigApplyConfiguration{} +} + +// WithPolicy sets the Policy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Policy field is set to the value of the last call. +func (b *DefaultCAPackageConfigApplyConfiguration) WithPolicy(value operatorv1alpha1.DefaultCAPackagePolicy) *DefaultCAPackageConfigApplyConfiguration { + b.Policy = &value + return b +} diff --git a/pkg/operator/applyconfigurations/operator/v1alpha1/secrettargetsconfig.go b/pkg/operator/applyconfigurations/operator/v1alpha1/secrettargetsconfig.go new file mode 100644 index 000000000..c272e6bd7 --- /dev/null +++ b/pkg/operator/applyconfigurations/operator/v1alpha1/secrettargetsconfig.go @@ -0,0 +1,38 @@ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + operatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" +) + +// SecretTargetsConfigApplyConfiguration represents a declarative configuration of the SecretTargetsConfig type for use +// with apply. +type SecretTargetsConfigApplyConfiguration struct { + Policy *operatorv1alpha1.SecretTargetsPolicy `json:"policy,omitempty"` + AuthorizedSecrets []string `json:"authorizedSecrets,omitempty"` +} + +// SecretTargetsConfigApplyConfiguration constructs a declarative configuration of the SecretTargetsConfig type for use with +// apply. +func SecretTargetsConfig() *SecretTargetsConfigApplyConfiguration { + return &SecretTargetsConfigApplyConfiguration{} +} + +// WithPolicy sets the Policy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Policy field is set to the value of the last call. +func (b *SecretTargetsConfigApplyConfiguration) WithPolicy(value operatorv1alpha1.SecretTargetsPolicy) *SecretTargetsConfigApplyConfiguration { + b.Policy = &value + return b +} + +// WithAuthorizedSecrets adds the given value to the AuthorizedSecrets field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the AuthorizedSecrets field. +func (b *SecretTargetsConfigApplyConfiguration) WithAuthorizedSecrets(values ...string) *SecretTargetsConfigApplyConfiguration { + for i := range values { + b.AuthorizedSecrets = append(b.AuthorizedSecrets, values[i]) + } + return b +} diff --git a/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanager.go b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanager.go new file mode 100644 index 000000000..2ab528196 --- /dev/null +++ b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanager.go @@ -0,0 +1,246 @@ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + operatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + internal "github.com/openshift/cert-manager-operator/pkg/operator/applyconfigurations/internal" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// TrustManagerApplyConfiguration represents a declarative configuration of the TrustManager type for use +// with apply. +type TrustManagerApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *TrustManagerSpecApplyConfiguration `json:"spec,omitempty"` + Status *TrustManagerStatusApplyConfiguration `json:"status,omitempty"` +} + +// TrustManager constructs a declarative configuration of the TrustManager type for use with +// apply. +func TrustManager(name string) *TrustManagerApplyConfiguration { + b := &TrustManagerApplyConfiguration{} + b.WithName(name) + b.WithKind("TrustManager") + b.WithAPIVersion("operator.openshift.io/v1alpha1") + return b +} + +// ExtractTrustManager extracts the applied configuration owned by fieldManager from +// trustManager. If no managedFields are found in trustManager for fieldManager, a +// TrustManagerApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// trustManager must be a unmodified TrustManager API object that was retrieved from the Kubernetes API. +// ExtractTrustManager provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +// Experimental! +func ExtractTrustManager(trustManager *operatorv1alpha1.TrustManager, fieldManager string) (*TrustManagerApplyConfiguration, error) { + return extractTrustManager(trustManager, fieldManager, "") +} + +// ExtractTrustManagerStatus is the same as ExtractTrustManager except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractTrustManagerStatus(trustManager *operatorv1alpha1.TrustManager, fieldManager string) (*TrustManagerApplyConfiguration, error) { + return extractTrustManager(trustManager, fieldManager, "status") +} + +func extractTrustManager(trustManager *operatorv1alpha1.TrustManager, fieldManager string, subresource string) (*TrustManagerApplyConfiguration, error) { + b := &TrustManagerApplyConfiguration{} + err := managedfields.ExtractInto(trustManager, internal.Parser().Type("com.github.openshift.cert-manager-operator.api.operator.v1alpha1.TrustManager"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(trustManager.Name) + + b.WithKind("TrustManager") + b.WithAPIVersion("operator.openshift.io/v1alpha1") + return b, nil +} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithKind(value string) *TrustManagerApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithAPIVersion(value string) *TrustManagerApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithName(value string) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithGenerateName(value string) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithNamespace(value string) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithUID(value types.UID) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithResourceVersion(value string) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithGeneration(value int64) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithCreationTimestamp(value metav1.Time) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *TrustManagerApplyConfiguration) WithLabels(entries map[string]string) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *TrustManagerApplyConfiguration) WithAnnotations(entries map[string]string) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *TrustManagerApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *TrustManagerApplyConfiguration) WithFinalizers(values ...string) *TrustManagerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *TrustManagerApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithSpec(value *TrustManagerSpecApplyConfiguration) *TrustManagerApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *TrustManagerApplyConfiguration) WithStatus(value *TrustManagerStatusApplyConfiguration) *TrustManagerApplyConfiguration { + b.Status = value + return b +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *TrustManagerApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} diff --git a/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerconfig.go b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerconfig.go new file mode 100644 index 000000000..9f1e59efa --- /dev/null +++ b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerconfig.go @@ -0,0 +1,117 @@ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + operatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + v1 "k8s.io/api/core/v1" +) + +// TrustManagerConfigApplyConfiguration represents a declarative configuration of the TrustManagerConfig type for use +// with apply. +type TrustManagerConfigApplyConfiguration struct { + LogLevel *int32 `json:"logLevel,omitempty"` + LogFormat *string `json:"logFormat,omitempty"` + TrustNamespace *string `json:"trustNamespace,omitempty"` + SecretTargets *SecretTargetsConfigApplyConfiguration `json:"secretTargets,omitempty"` + FilterExpiredCertificates *operatorv1alpha1.FilterExpiredCertificatesPolicy `json:"filterExpiredCertificates,omitempty"` + DefaultCAPackage *DefaultCAPackageConfigApplyConfiguration `json:"defaultCAPackage,omitempty"` + Resources *v1.ResourceRequirements `json:"resources,omitempty"` + Affinity *v1.Affinity `json:"affinity,omitempty"` + Tolerations []v1.Toleration `json:"tolerations,omitempty"` + NodeSelector map[string]string `json:"nodeSelector,omitempty"` +} + +// TrustManagerConfigApplyConfiguration constructs a declarative configuration of the TrustManagerConfig type for use with +// apply. +func TrustManagerConfig() *TrustManagerConfigApplyConfiguration { + return &TrustManagerConfigApplyConfiguration{} +} + +// WithLogLevel sets the LogLevel field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LogLevel field is set to the value of the last call. +func (b *TrustManagerConfigApplyConfiguration) WithLogLevel(value int32) *TrustManagerConfigApplyConfiguration { + b.LogLevel = &value + return b +} + +// WithLogFormat sets the LogFormat field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LogFormat field is set to the value of the last call. +func (b *TrustManagerConfigApplyConfiguration) WithLogFormat(value string) *TrustManagerConfigApplyConfiguration { + b.LogFormat = &value + return b +} + +// WithTrustNamespace sets the TrustNamespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TrustNamespace field is set to the value of the last call. +func (b *TrustManagerConfigApplyConfiguration) WithTrustNamespace(value string) *TrustManagerConfigApplyConfiguration { + b.TrustNamespace = &value + return b +} + +// WithSecretTargets sets the SecretTargets field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SecretTargets field is set to the value of the last call. +func (b *TrustManagerConfigApplyConfiguration) WithSecretTargets(value *SecretTargetsConfigApplyConfiguration) *TrustManagerConfigApplyConfiguration { + b.SecretTargets = value + return b +} + +// WithFilterExpiredCertificates sets the FilterExpiredCertificates field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the FilterExpiredCertificates field is set to the value of the last call. +func (b *TrustManagerConfigApplyConfiguration) WithFilterExpiredCertificates(value operatorv1alpha1.FilterExpiredCertificatesPolicy) *TrustManagerConfigApplyConfiguration { + b.FilterExpiredCertificates = &value + return b +} + +// WithDefaultCAPackage sets the DefaultCAPackage field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DefaultCAPackage field is set to the value of the last call. +func (b *TrustManagerConfigApplyConfiguration) WithDefaultCAPackage(value *DefaultCAPackageConfigApplyConfiguration) *TrustManagerConfigApplyConfiguration { + b.DefaultCAPackage = value + return b +} + +// WithResources sets the Resources field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Resources field is set to the value of the last call. +func (b *TrustManagerConfigApplyConfiguration) WithResources(value v1.ResourceRequirements) *TrustManagerConfigApplyConfiguration { + b.Resources = &value + return b +} + +// WithAffinity sets the Affinity field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Affinity field is set to the value of the last call. +func (b *TrustManagerConfigApplyConfiguration) WithAffinity(value v1.Affinity) *TrustManagerConfigApplyConfiguration { + b.Affinity = &value + return b +} + +// WithTolerations adds the given value to the Tolerations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Tolerations field. +func (b *TrustManagerConfigApplyConfiguration) WithTolerations(values ...v1.Toleration) *TrustManagerConfigApplyConfiguration { + for i := range values { + b.Tolerations = append(b.Tolerations, values[i]) + } + return b +} + +// WithNodeSelector puts the entries into the NodeSelector field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the NodeSelector field, +// overwriting an existing map entries in NodeSelector field with the same key. +func (b *TrustManagerConfigApplyConfiguration) WithNodeSelector(entries map[string]string) *TrustManagerConfigApplyConfiguration { + if b.NodeSelector == nil && len(entries) > 0 { + b.NodeSelector = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.NodeSelector[k] = v + } + return b +} diff --git a/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagercontrollerconfig.go b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagercontrollerconfig.go new file mode 100644 index 000000000..031986672 --- /dev/null +++ b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagercontrollerconfig.go @@ -0,0 +1,44 @@ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// TrustManagerControllerConfigApplyConfiguration represents a declarative configuration of the TrustManagerControllerConfig type for use +// with apply. +type TrustManagerControllerConfigApplyConfiguration struct { + Labels map[string]string `json:"labels,omitempty"` + Annotations map[string]string `json:"annotations,omitempty"` +} + +// TrustManagerControllerConfigApplyConfiguration constructs a declarative configuration of the TrustManagerControllerConfig type for use with +// apply. +func TrustManagerControllerConfig() *TrustManagerControllerConfigApplyConfiguration { + return &TrustManagerControllerConfigApplyConfiguration{} +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *TrustManagerControllerConfigApplyConfiguration) WithLabels(entries map[string]string) *TrustManagerControllerConfigApplyConfiguration { + if b.Labels == nil && len(entries) > 0 { + b.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *TrustManagerControllerConfigApplyConfiguration) WithAnnotations(entries map[string]string) *TrustManagerControllerConfigApplyConfiguration { + if b.Annotations == nil && len(entries) > 0 { + b.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Annotations[k] = v + } + return b +} diff --git a/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerspec.go b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerspec.go new file mode 100644 index 000000000..7f8659cda --- /dev/null +++ b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerspec.go @@ -0,0 +1,32 @@ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// TrustManagerSpecApplyConfiguration represents a declarative configuration of the TrustManagerSpec type for use +// with apply. +type TrustManagerSpecApplyConfiguration struct { + TrustManagerConfig *TrustManagerConfigApplyConfiguration `json:"trustManagerConfig,omitempty"` + ControllerConfig *TrustManagerControllerConfigApplyConfiguration `json:"controllerConfig,omitempty"` +} + +// TrustManagerSpecApplyConfiguration constructs a declarative configuration of the TrustManagerSpec type for use with +// apply. +func TrustManagerSpec() *TrustManagerSpecApplyConfiguration { + return &TrustManagerSpecApplyConfiguration{} +} + +// WithTrustManagerConfig sets the TrustManagerConfig field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TrustManagerConfig field is set to the value of the last call. +func (b *TrustManagerSpecApplyConfiguration) WithTrustManagerConfig(value *TrustManagerConfigApplyConfiguration) *TrustManagerSpecApplyConfiguration { + b.TrustManagerConfig = value + return b +} + +// WithControllerConfig sets the ControllerConfig field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ControllerConfig field is set to the value of the last call. +func (b *TrustManagerSpecApplyConfiguration) WithControllerConfig(value *TrustManagerControllerConfigApplyConfiguration) *TrustManagerSpecApplyConfiguration { + b.ControllerConfig = value + return b +} diff --git a/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerstatus.go b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerstatus.go new file mode 100644 index 000000000..fce632a82 --- /dev/null +++ b/pkg/operator/applyconfigurations/operator/v1alpha1/trustmanagerstatus.go @@ -0,0 +1,78 @@ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + operatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// TrustManagerStatusApplyConfiguration represents a declarative configuration of the TrustManagerStatus type for use +// with apply. +type TrustManagerStatusApplyConfiguration struct { + ConditionalStatusApplyConfiguration `json:",omitempty,inline"` + TrustManagerImage *string `json:"trustManagerImage,omitempty"` + TrustNamespace *string `json:"trustNamespace,omitempty"` + SecretTargetsPolicy *operatorv1alpha1.SecretTargetsPolicy `json:"secretTargetsPolicy,omitempty"` + DefaultCAPackagePolicy *operatorv1alpha1.DefaultCAPackagePolicy `json:"defaultCAPackagePolicy,omitempty"` + FilterExpiredCertificatesPolicy *operatorv1alpha1.FilterExpiredCertificatesPolicy `json:"filterExpiredCertificatesPolicy,omitempty"` +} + +// TrustManagerStatusApplyConfiguration constructs a declarative configuration of the TrustManagerStatus type for use with +// apply. +func TrustManagerStatus() *TrustManagerStatusApplyConfiguration { + return &TrustManagerStatusApplyConfiguration{} +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *TrustManagerStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *TrustManagerStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.ConditionalStatusApplyConfiguration.Conditions = append(b.ConditionalStatusApplyConfiguration.Conditions, *values[i]) + } + return b +} + +// WithTrustManagerImage sets the TrustManagerImage field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TrustManagerImage field is set to the value of the last call. +func (b *TrustManagerStatusApplyConfiguration) WithTrustManagerImage(value string) *TrustManagerStatusApplyConfiguration { + b.TrustManagerImage = &value + return b +} + +// WithTrustNamespace sets the TrustNamespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TrustNamespace field is set to the value of the last call. +func (b *TrustManagerStatusApplyConfiguration) WithTrustNamespace(value string) *TrustManagerStatusApplyConfiguration { + b.TrustNamespace = &value + return b +} + +// WithSecretTargetsPolicy sets the SecretTargetsPolicy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SecretTargetsPolicy field is set to the value of the last call. +func (b *TrustManagerStatusApplyConfiguration) WithSecretTargetsPolicy(value operatorv1alpha1.SecretTargetsPolicy) *TrustManagerStatusApplyConfiguration { + b.SecretTargetsPolicy = &value + return b +} + +// WithDefaultCAPackagePolicy sets the DefaultCAPackagePolicy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DefaultCAPackagePolicy field is set to the value of the last call. +func (b *TrustManagerStatusApplyConfiguration) WithDefaultCAPackagePolicy(value operatorv1alpha1.DefaultCAPackagePolicy) *TrustManagerStatusApplyConfiguration { + b.DefaultCAPackagePolicy = &value + return b +} + +// WithFilterExpiredCertificatesPolicy sets the FilterExpiredCertificatesPolicy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the FilterExpiredCertificatesPolicy field is set to the value of the last call. +func (b *TrustManagerStatusApplyConfiguration) WithFilterExpiredCertificatesPolicy(value operatorv1alpha1.FilterExpiredCertificatesPolicy) *TrustManagerStatusApplyConfiguration { + b.FilterExpiredCertificatesPolicy = &value + return b +} diff --git a/pkg/operator/applyconfigurations/utils.go b/pkg/operator/applyconfigurations/utils.go index 11e43885f..50f42a187 100644 --- a/pkg/operator/applyconfigurations/utils.go +++ b/pkg/operator/applyconfigurations/utils.go @@ -34,6 +34,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &operatorv1alpha1.ConfigMapReferenceApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ControllerConfig"): return &operatorv1alpha1.ControllerConfigApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("DefaultCAPackageConfig"): + return &operatorv1alpha1.DefaultCAPackageConfigApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("DeploymentConfig"): return &operatorv1alpha1.DeploymentConfigApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("IstioConfig"): @@ -50,8 +52,20 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &operatorv1alpha1.IstiodTLSConfigApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("NetworkPolicy"): return &operatorv1alpha1.NetworkPolicyApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SecretTargetsConfig"): + return &operatorv1alpha1.SecretTargetsConfigApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ServerConfig"): return &operatorv1alpha1.ServerConfigApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("TrustManager"): + return &operatorv1alpha1.TrustManagerApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("TrustManagerConfig"): + return &operatorv1alpha1.TrustManagerConfigApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("TrustManagerControllerConfig"): + return &operatorv1alpha1.TrustManagerControllerConfigApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("TrustManagerSpec"): + return &operatorv1alpha1.TrustManagerSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("TrustManagerStatus"): + return &operatorv1alpha1.TrustManagerStatusApplyConfiguration{} } return nil diff --git a/pkg/operator/assets/bindata.go b/pkg/operator/assets/bindata.go index d83fcb9e1..f1e6aae05 100644 --- a/pkg/operator/assets/bindata.go +++ b/pkg/operator/assets/bindata.go @@ -63,6 +63,16 @@ // bindata/networkpolicies/istio-csr-allow-ingress-to-grpc-networkpolicy.yaml // bindata/networkpolicies/istio-csr-allow-ingress-to-metrics-networkpolicy.yaml // bindata/networkpolicies/istio-csr-deny-all-networkpolicy.yaml +// bindata/trust-manager/trust-manager-clusterrole.yaml +// bindata/trust-manager/trust-manager-clusterrolebinding.yaml +// bindata/trust-manager/trust-manager-deployment.yaml +// bindata/trust-manager/trust-manager-metrics-service.yaml +// bindata/trust-manager/trust-manager-role.yaml +// bindata/trust-manager/trust-manager-rolebinding.yaml +// bindata/trust-manager/trust-manager-serviceaccount.yaml +// bindata/trust-manager/trust-manager-validatingwebhookconfiguration.yaml +// bindata/trust-manager/trust-manager-webhook-certificate.yaml +// bindata/trust-manager/trust-manager-webhook-service.yaml package assets import ( @@ -3072,6 +3082,489 @@ func networkpoliciesIstioCsrDenyAllNetworkpolicyYaml() (*asset, error) { return a, nil } +var _trustManagerTrustManagerClusterroleYaml = []byte(`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/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +rules: + - apiGroups: + - trust.cert-manager.io + resources: + - bundles + verbs: + - get + - list + - watch + - apiGroups: + - trust.cert-manager.io + resources: + - bundles/status + verbs: + - patch + - apiGroups: + - trust.cert-manager.io + resources: + - bundles/finalizers + verbs: + - update + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - create + - update + - patch + - watch + - delete + - apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +`) + +func trustManagerTrustManagerClusterroleYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerClusterroleYaml, nil +} + +func trustManagerTrustManagerClusterroleYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerClusterroleYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-clusterrole.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerClusterrolebindingYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: trust-manager + labels: + app: trust-manager + app.kubernetes.io/name: trust-manager + app.kubernetes.io/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: trust-manager +subjects: + - kind: ServiceAccount + name: trust-manager + namespace: cert-manager +`) + +func trustManagerTrustManagerClusterrolebindingYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerClusterrolebindingYaml, nil +} + +func trustManagerTrustManagerClusterrolebindingYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerClusterrolebindingYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-clusterrolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerDeploymentYaml = []byte(`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/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: 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: trust-manager:latest + imagePullPolicy: IfNotPresent + args: + - --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 + ports: + - containerPort: 6443 + protocol: TCP + - containerPort: 9402 + protocol: TCP + readinessProbe: + httpGet: + path: /readyz + port: 6060 + initialDelaySeconds: 3 + periodSeconds: 7 + resources: + requests: + cpu: 25m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + capabilities: + drop: + - ALL + volumeMounts: + - name: tls + mountPath: /tls + readOnly: true + volumes: + - name: tls + secret: + secretName: trust-manager-tls + defaultMode: 420 + nodeSelector: + kubernetes.io/os: linux +`) + +func trustManagerTrustManagerDeploymentYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerDeploymentYaml, nil +} + +func trustManagerTrustManagerDeploymentYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerDeploymentYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-deployment.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerMetricsServiceYaml = []byte(`apiVersion: v1 +kind: Service +metadata: + name: trust-manager-metrics + namespace: cert-manager + labels: + app: trust-manager + app.kubernetes.io/name: trust-manager + app.kubernetes.io/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +spec: + type: ClusterIP + ports: + - port: 9402 + targetPort: 9402 + protocol: TCP + name: metrics + selector: + app: trust-manager +`) + +func trustManagerTrustManagerMetricsServiceYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerMetricsServiceYaml, nil +} + +func trustManagerTrustManagerMetricsServiceYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerMetricsServiceYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-metrics-service.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerRoleYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: trust-manager + app.kubernetes.io/name: trust-manager + app.kubernetes.io/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - create + - update + - patch + - watch + - delete + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - create + - update + - watch + - list +`) + +func trustManagerTrustManagerRoleYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerRoleYaml, nil +} + +func trustManagerTrustManagerRoleYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerRoleYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-role.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerRolebindingYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: trust-manager + app.kubernetes.io/name: trust-manager + app.kubernetes.io/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: trust-manager +subjects: + - kind: ServiceAccount + name: trust-manager + namespace: cert-manager +`) + +func trustManagerTrustManagerRolebindingYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerRolebindingYaml, nil +} + +func trustManagerTrustManagerRolebindingYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerRolebindingYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-rolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerServiceaccountYaml = []byte(`apiVersion: v1 +kind: ServiceAccount +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: trust-manager + app.kubernetes.io/name: trust-manager + app.kubernetes.io/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +`) + +func trustManagerTrustManagerServiceaccountYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerServiceaccountYaml, nil +} + +func trustManagerTrustManagerServiceaccountYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerServiceaccountYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-serviceaccount.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerValidatingwebhookconfigurationYaml = []byte(`apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: trust-manager + labels: + app: trust-manager + app.kubernetes.io/name: trust-manager + app.kubernetes.io/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator + annotations: + cert-manager.io/inject-ca-from: cert-manager/trust-manager +webhooks: + - name: trust.cert-manager.io + rules: + - apiGroups: + - trust.cert-manager.io + apiVersions: + - "*" + operations: + - CREATE + - UPDATE + resources: + - bundles + admissionReviewVersions: + - v1 + timeoutSeconds: 5 + failurePolicy: Fail + sideEffects: None + clientConfig: + service: + name: trust-manager + namespace: cert-manager + path: /validate-trust-cert-manager-io-v1alpha1-bundle +`) + +func trustManagerTrustManagerValidatingwebhookconfigurationYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerValidatingwebhookconfigurationYaml, nil +} + +func trustManagerTrustManagerValidatingwebhookconfigurationYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerValidatingwebhookconfigurationYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-validatingwebhookconfiguration.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerWebhookCertificateYaml = []byte(`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/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +spec: + commonName: "trust-manager.cert-manager.svc" + dnsNames: + - "trust-manager.cert-manager.svc" + secretName: trust-manager-tls + revisionHistoryLimit: 1 + issuerRef: + name: cert-manager-operator-selfsigned-issuer + kind: Issuer + group: cert-manager.io +`) + +func trustManagerTrustManagerWebhookCertificateYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerWebhookCertificateYaml, nil +} + +func trustManagerTrustManagerWebhookCertificateYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerWebhookCertificateYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-webhook-certificate.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _trustManagerTrustManagerWebhookServiceYaml = []byte(`apiVersion: v1 +kind: Service +metadata: + name: trust-manager + namespace: cert-manager + labels: + app: trust-manager + app.kubernetes.io/name: trust-manager + app.kubernetes.io/instance: trust-manager + app.kubernetes.io/managed-by: cert-manager-operator + app.kubernetes.io/part-of: cert-manager-operator +spec: + type: ClusterIP + ports: + - port: 443 + targetPort: 6443 + protocol: TCP + name: webhook + selector: + app: trust-manager +`) + +func trustManagerTrustManagerWebhookServiceYamlBytes() ([]byte, error) { + return _trustManagerTrustManagerWebhookServiceYaml, nil +} + +func trustManagerTrustManagerWebhookServiceYaml() (*asset, error) { + bytes, err := trustManagerTrustManagerWebhookServiceYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "trust-manager/trust-manager-webhook-service.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + // Asset loads and returns the asset for the given name. // It returns an error if the asset could not be found or // could not be loaded. @@ -3187,6 +3680,16 @@ var _bindata = map[string]func() (*asset, error){ "networkpolicies/istio-csr-allow-ingress-to-grpc-networkpolicy.yaml": networkpoliciesIstioCsrAllowIngressToGrpcNetworkpolicyYaml, "networkpolicies/istio-csr-allow-ingress-to-metrics-networkpolicy.yaml": networkpoliciesIstioCsrAllowIngressToMetricsNetworkpolicyYaml, "networkpolicies/istio-csr-deny-all-networkpolicy.yaml": networkpoliciesIstioCsrDenyAllNetworkpolicyYaml, + "trust-manager/trust-manager-clusterrole.yaml": trustManagerTrustManagerClusterroleYaml, + "trust-manager/trust-manager-clusterrolebinding.yaml": trustManagerTrustManagerClusterrolebindingYaml, + "trust-manager/trust-manager-deployment.yaml": trustManagerTrustManagerDeploymentYaml, + "trust-manager/trust-manager-metrics-service.yaml": trustManagerTrustManagerMetricsServiceYaml, + "trust-manager/trust-manager-role.yaml": trustManagerTrustManagerRoleYaml, + "trust-manager/trust-manager-rolebinding.yaml": trustManagerTrustManagerRolebindingYaml, + "trust-manager/trust-manager-serviceaccount.yaml": trustManagerTrustManagerServiceaccountYaml, + "trust-manager/trust-manager-validatingwebhookconfiguration.yaml": trustManagerTrustManagerValidatingwebhookconfigurationYaml, + "trust-manager/trust-manager-webhook-certificate.yaml": trustManagerTrustManagerWebhookCertificateYaml, + "trust-manager/trust-manager-webhook-service.yaml": trustManagerTrustManagerWebhookServiceYaml, } // AssetDir returns the file names below a certain @@ -3309,6 +3812,18 @@ var _bintree = &bintree{nil, map[string]*bintree{ "istio-csr-allow-ingress-to-metrics-networkpolicy.yaml": {networkpoliciesIstioCsrAllowIngressToMetricsNetworkpolicyYaml, map[string]*bintree{}}, "istio-csr-deny-all-networkpolicy.yaml": {networkpoliciesIstioCsrDenyAllNetworkpolicyYaml, map[string]*bintree{}}, }}, + "trust-manager": {nil, map[string]*bintree{ + "trust-manager-clusterrole.yaml": {trustManagerTrustManagerClusterroleYaml, map[string]*bintree{}}, + "trust-manager-clusterrolebinding.yaml": {trustManagerTrustManagerClusterrolebindingYaml, map[string]*bintree{}}, + "trust-manager-deployment.yaml": {trustManagerTrustManagerDeploymentYaml, map[string]*bintree{}}, + "trust-manager-metrics-service.yaml": {trustManagerTrustManagerMetricsServiceYaml, map[string]*bintree{}}, + "trust-manager-role.yaml": {trustManagerTrustManagerRoleYaml, map[string]*bintree{}}, + "trust-manager-rolebinding.yaml": {trustManagerTrustManagerRolebindingYaml, map[string]*bintree{}}, + "trust-manager-serviceaccount.yaml": {trustManagerTrustManagerServiceaccountYaml, map[string]*bintree{}}, + "trust-manager-validatingwebhookconfiguration.yaml": {trustManagerTrustManagerValidatingwebhookconfigurationYaml, map[string]*bintree{}}, + "trust-manager-webhook-certificate.yaml": {trustManagerTrustManagerWebhookCertificateYaml, map[string]*bintree{}}, + "trust-manager-webhook-service.yaml": {trustManagerTrustManagerWebhookServiceYaml, map[string]*bintree{}}, + }}, }} // RestoreAsset restores an asset under the given directory diff --git a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go index cc50d82f9..aaca26cb7 100644 --- a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go +++ b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go @@ -20,6 +20,10 @@ func (c *FakeOperatorV1alpha1) IstioCSRs(namespace string) v1alpha1.IstioCSRInte return newFakeIstioCSRs(c, namespace) } +func (c *FakeOperatorV1alpha1) TrustManagers() v1alpha1.TrustManagerInterface { + return newFakeTrustManagers(c) +} + // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. func (c *FakeOperatorV1alpha1) RESTClient() rest.Interface { diff --git a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_trustmanager.go b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_trustmanager.go new file mode 100644 index 000000000..ce9077b56 --- /dev/null +++ b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/fake/fake_trustmanager.go @@ -0,0 +1,37 @@ +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + operatorv1alpha1 "github.com/openshift/cert-manager-operator/pkg/operator/applyconfigurations/operator/v1alpha1" + typedoperatorv1alpha1 "github.com/openshift/cert-manager-operator/pkg/operator/clientset/versioned/typed/operator/v1alpha1" + gentype "k8s.io/client-go/gentype" +) + +// fakeTrustManagers implements TrustManagerInterface +type fakeTrustManagers struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.TrustManager, *v1alpha1.TrustManagerList, *operatorv1alpha1.TrustManagerApplyConfiguration] + Fake *FakeOperatorV1alpha1 +} + +func newFakeTrustManagers(fake *FakeOperatorV1alpha1) typedoperatorv1alpha1.TrustManagerInterface { + return &fakeTrustManagers{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.TrustManager, *v1alpha1.TrustManagerList, *operatorv1alpha1.TrustManagerApplyConfiguration]( + fake.Fake, + "", + v1alpha1.SchemeGroupVersion.WithResource("trustmanagers"), + v1alpha1.SchemeGroupVersion.WithKind("TrustManager"), + func() *v1alpha1.TrustManager { return &v1alpha1.TrustManager{} }, + func() *v1alpha1.TrustManagerList { return &v1alpha1.TrustManagerList{} }, + func(dst, src *v1alpha1.TrustManagerList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.TrustManagerList) []*v1alpha1.TrustManager { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha1.TrustManagerList, items []*v1alpha1.TrustManager) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go index 56f852de5..df39e06da 100644 --- a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go +++ b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go @@ -5,3 +5,5 @@ package v1alpha1 type CertManagerExpansion interface{} type IstioCSRExpansion interface{} + +type TrustManagerExpansion interface{} diff --git a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/operator_client.go b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/operator_client.go index 67d7b0aee..9eabd32fe 100644 --- a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/operator_client.go +++ b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/operator_client.go @@ -14,6 +14,7 @@ type OperatorV1alpha1Interface interface { RESTClient() rest.Interface CertManagersGetter IstioCSRsGetter + TrustManagersGetter } // OperatorV1alpha1Client is used to interact with features provided by the operator.openshift.io group. @@ -29,6 +30,10 @@ func (c *OperatorV1alpha1Client) IstioCSRs(namespace string) IstioCSRInterface { return newIstioCSRs(c, namespace) } +func (c *OperatorV1alpha1Client) TrustManagers() TrustManagerInterface { + return newTrustManagers(c) +} + // NewForConfig creates a new OperatorV1alpha1Client for the given config. // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), // where httpClient was generated with rest.HTTPClientFor(c). diff --git a/pkg/operator/clientset/versioned/typed/operator/v1alpha1/trustmanager.go b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/trustmanager.go new file mode 100644 index 000000000..cea6d2742 --- /dev/null +++ b/pkg/operator/clientset/versioned/typed/operator/v1alpha1/trustmanager.go @@ -0,0 +1,58 @@ +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + + operatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + applyconfigurationsoperatorv1alpha1 "github.com/openshift/cert-manager-operator/pkg/operator/applyconfigurations/operator/v1alpha1" + scheme "github.com/openshift/cert-manager-operator/pkg/operator/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" +) + +// TrustManagersGetter has a method to return a TrustManagerInterface. +// A group's client should implement this interface. +type TrustManagersGetter interface { + TrustManagers() TrustManagerInterface +} + +// TrustManagerInterface has methods to work with TrustManager resources. +type TrustManagerInterface interface { + Create(ctx context.Context, trustManager *operatorv1alpha1.TrustManager, opts v1.CreateOptions) (*operatorv1alpha1.TrustManager, error) + Update(ctx context.Context, trustManager *operatorv1alpha1.TrustManager, opts v1.UpdateOptions) (*operatorv1alpha1.TrustManager, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, trustManager *operatorv1alpha1.TrustManager, opts v1.UpdateOptions) (*operatorv1alpha1.TrustManager, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*operatorv1alpha1.TrustManager, error) + List(ctx context.Context, opts v1.ListOptions) (*operatorv1alpha1.TrustManagerList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *operatorv1alpha1.TrustManager, err error) + Apply(ctx context.Context, trustManager *applyconfigurationsoperatorv1alpha1.TrustManagerApplyConfiguration, opts v1.ApplyOptions) (result *operatorv1alpha1.TrustManager, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, trustManager *applyconfigurationsoperatorv1alpha1.TrustManagerApplyConfiguration, opts v1.ApplyOptions) (result *operatorv1alpha1.TrustManager, err error) + TrustManagerExpansion +} + +// trustManagers implements TrustManagerInterface +type trustManagers struct { + *gentype.ClientWithListAndApply[*operatorv1alpha1.TrustManager, *operatorv1alpha1.TrustManagerList, *applyconfigurationsoperatorv1alpha1.TrustManagerApplyConfiguration] +} + +// newTrustManagers returns a TrustManagers +func newTrustManagers(c *OperatorV1alpha1Client) *trustManagers { + return &trustManagers{ + gentype.NewClientWithListAndApply[*operatorv1alpha1.TrustManager, *operatorv1alpha1.TrustManagerList, *applyconfigurationsoperatorv1alpha1.TrustManagerApplyConfiguration]( + "trustmanagers", + c.RESTClient(), + scheme.ParameterCodec, + "", + func() *operatorv1alpha1.TrustManager { return &operatorv1alpha1.TrustManager{} }, + func() *operatorv1alpha1.TrustManagerList { return &operatorv1alpha1.TrustManagerList{} }, + ), + } +} diff --git a/pkg/operator/informers/externalversions/generic.go b/pkg/operator/informers/externalversions/generic.go index 0c542fe66..7dc954ca9 100644 --- a/pkg/operator/informers/externalversions/generic.go +++ b/pkg/operator/informers/externalversions/generic.go @@ -41,6 +41,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Operator().V1alpha1().CertManagers().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("istiocsrs"): return &genericInformer{resource: resource.GroupResource(), informer: f.Operator().V1alpha1().IstioCSRs().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("trustmanagers"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Operator().V1alpha1().TrustManagers().Informer()}, nil } diff --git a/pkg/operator/informers/externalversions/operator/v1alpha1/interface.go b/pkg/operator/informers/externalversions/operator/v1alpha1/interface.go index 5eb8c8ede..422750840 100644 --- a/pkg/operator/informers/externalversions/operator/v1alpha1/interface.go +++ b/pkg/operator/informers/externalversions/operator/v1alpha1/interface.go @@ -12,6 +12,8 @@ type Interface interface { CertManagers() CertManagerInformer // IstioCSRs returns a IstioCSRInformer. IstioCSRs() IstioCSRInformer + // TrustManagers returns a TrustManagerInformer. + TrustManagers() TrustManagerInformer } type version struct { @@ -34,3 +36,8 @@ func (v *version) CertManagers() CertManagerInformer { func (v *version) IstioCSRs() IstioCSRInformer { return &istioCSRInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } + +// TrustManagers returns a TrustManagerInformer. +func (v *version) TrustManagers() TrustManagerInformer { + return &trustManagerInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/operator/informers/externalversions/operator/v1alpha1/trustmanager.go b/pkg/operator/informers/externalversions/operator/v1alpha1/trustmanager.go new file mode 100644 index 000000000..cdb0943ba --- /dev/null +++ b/pkg/operator/informers/externalversions/operator/v1alpha1/trustmanager.go @@ -0,0 +1,85 @@ +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + time "time" + + apioperatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + versioned "github.com/openshift/cert-manager-operator/pkg/operator/clientset/versioned" + internalinterfaces "github.com/openshift/cert-manager-operator/pkg/operator/informers/externalversions/internalinterfaces" + operatorv1alpha1 "github.com/openshift/cert-manager-operator/pkg/operator/listers/operator/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// TrustManagerInformer provides access to a shared informer and lister for +// TrustManagers. +type TrustManagerInformer interface { + Informer() cache.SharedIndexInformer + Lister() operatorv1alpha1.TrustManagerLister +} + +type trustManagerInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewTrustManagerInformer constructs a new informer for TrustManager type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewTrustManagerInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredTrustManagerInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredTrustManagerInformer constructs a new informer for TrustManager type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredTrustManagerInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OperatorV1alpha1().TrustManagers().List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OperatorV1alpha1().TrustManagers().Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OperatorV1alpha1().TrustManagers().List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OperatorV1alpha1().TrustManagers().Watch(ctx, options) + }, + }, + &apioperatorv1alpha1.TrustManager{}, + resyncPeriod, + indexers, + ) +} + +func (f *trustManagerInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredTrustManagerInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *trustManagerInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apioperatorv1alpha1.TrustManager{}, f.defaultInformer) +} + +func (f *trustManagerInformer) Lister() operatorv1alpha1.TrustManagerLister { + return operatorv1alpha1.NewTrustManagerLister(f.Informer().GetIndexer()) +} diff --git a/pkg/operator/listers/operator/v1alpha1/expansion_generated.go b/pkg/operator/listers/operator/v1alpha1/expansion_generated.go index c91ed34e9..1692896d0 100644 --- a/pkg/operator/listers/operator/v1alpha1/expansion_generated.go +++ b/pkg/operator/listers/operator/v1alpha1/expansion_generated.go @@ -13,3 +13,7 @@ type IstioCSRListerExpansion interface{} // IstioCSRNamespaceListerExpansion allows custom methods to be added to // IstioCSRNamespaceLister. type IstioCSRNamespaceListerExpansion interface{} + +// TrustManagerListerExpansion allows custom methods to be added to +// TrustManagerLister. +type TrustManagerListerExpansion interface{} diff --git a/pkg/operator/listers/operator/v1alpha1/trustmanager.go b/pkg/operator/listers/operator/v1alpha1/trustmanager.go new file mode 100644 index 000000000..96293ae92 --- /dev/null +++ b/pkg/operator/listers/operator/v1alpha1/trustmanager.go @@ -0,0 +1,32 @@ +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + operatorv1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// TrustManagerLister helps list TrustManagers. +// All objects returned here must be treated as read-only. +type TrustManagerLister interface { + // List lists all TrustManagers in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*operatorv1alpha1.TrustManager, err error) + // Get retrieves the TrustManager from the index for a given name. + // Objects returned here must be treated as read-only. + Get(name string) (*operatorv1alpha1.TrustManager, error) + TrustManagerListerExpansion +} + +// trustManagerLister implements the TrustManagerLister interface. +type trustManagerLister struct { + listers.ResourceIndexer[*operatorv1alpha1.TrustManager] +} + +// NewTrustManagerLister returns a new TrustManagerLister. +func NewTrustManagerLister(indexer cache.Indexer) TrustManagerLister { + return &trustManagerLister{listers.New[*operatorv1alpha1.TrustManager](indexer, operatorv1alpha1.Resource("trustmanager"))} +} diff --git a/pkg/operator/setup_manager.go b/pkg/operator/setup_manager.go index ba8c49602..765b26518 100644 --- a/pkg/operator/setup_manager.go +++ b/pkg/operator/setup_manager.go @@ -4,16 +4,22 @@ import ( "context" "fmt" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/selection" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -21,6 +27,8 @@ import ( v1alpha1 "github.com/openshift/cert-manager-operator/api/operator/v1alpha1" "github.com/openshift/cert-manager-operator/pkg/controller/istiocsr" + "github.com/openshift/cert-manager-operator/pkg/controller/trustmanager" + "github.com/openshift/cert-manager-operator/pkg/features" "github.com/openshift/cert-manager-operator/pkg/version" ) @@ -37,25 +45,112 @@ func init() { utilruntime.Must(corev1.AddToScheme(scheme)) utilruntime.Must(networkingv1.AddToScheme(scheme)) utilruntime.Must(rbacv1.AddToScheme(scheme)) + utilruntime.Must(admissionregistrationv1.AddToScheme(scheme)) utilruntime.Must(certmanagerv1.AddToScheme(scheme)) utilruntime.Must(v1alpha1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } -// Manager holds the manager resource for the istio-csr controller +// Manager holds the manager resource for the operator controllers type Manager struct { manager manager.Manager } +// newCombinedCacheBuilder returns a cache builder function that configures label selectors +// for resources managed by both the istiocsr and trustmanager controllers. When the TrustManager +// feature gate is enabled, the cache includes entries for trust-manager managed resources. +func newCombinedCacheBuilder(trustManagerEnabled bool) cache.NewCacheFunc { + return func(config *rest.Config, opts cache.Options) (cache.Cache, error) { + istiocsrLabelReq, err := labels.NewRequirement("app", selection.Equals, []string{"cert-manager-istio-csr"}) + if err != nil { + return nil, fmt.Errorf("invalid cache label requirement for istiocsr: %w", err) + } + istiocsrSelector := labels.NewSelector().Add(*istiocsrLabelReq) + + // Configure cache with label selectors for istiocsr managed resources + opts.ByObject = map[client.Object]cache.ByObject{ + &v1alpha1.IstioCSR{}: {}, + &certmanagerv1.Certificate{}: { + Label: istiocsrSelector, + }, + &appsv1.Deployment{}: { + Label: istiocsrSelector, + }, + &rbacv1.ClusterRole{}: { + Label: istiocsrSelector, + }, + &rbacv1.ClusterRoleBinding{}: { + Label: istiocsrSelector, + }, + &rbacv1.Role{}: { + Label: istiocsrSelector, + }, + &rbacv1.RoleBinding{}: { + Label: istiocsrSelector, + }, + &corev1.Service{}: { + Label: istiocsrSelector, + }, + &corev1.ServiceAccount{}: { + Label: istiocsrSelector, + }, + &networkingv1.NetworkPolicy{}: { + Label: istiocsrSelector, + }, + } + + if trustManagerEnabled { + trustManagerLabelReq, err := labels.NewRequirement("app", selection.Equals, []string{"trust-manager"}) + if err != nil { + return nil, fmt.Errorf("invalid cache label requirement for trustmanager: %w", err) + } + trustManagerSelector := labels.NewSelector().Add(*trustManagerLabelReq) + + // For shared resource types, use a selector that matches either controller's label + eitherLabelReq, err := labels.NewRequirement("app", selection.In, []string{"cert-manager-istio-csr", "trust-manager"}) + if err != nil { + return nil, fmt.Errorf("invalid cache label requirement for combined selector: %w", err) + } + eitherSelector := labels.NewSelector().Add(*eitherLabelReq) + + // Add TrustManager CR to cache + opts.ByObject[&v1alpha1.TrustManager{}] = cache.ByObject{} + + // Add ValidatingWebhookConfiguration for trust-manager + opts.ByObject[&admissionregistrationv1.ValidatingWebhookConfiguration{}] = cache.ByObject{ + Label: trustManagerSelector, + } + + // Update shared resource types to use the combined selector + opts.ByObject[&certmanagerv1.Certificate{}] = cache.ByObject{Label: eitherSelector} + opts.ByObject[&appsv1.Deployment{}] = cache.ByObject{Label: eitherSelector} + opts.ByObject[&rbacv1.ClusterRole{}] = cache.ByObject{Label: eitherSelector} + opts.ByObject[&rbacv1.ClusterRoleBinding{}] = cache.ByObject{Label: eitherSelector} + opts.ByObject[&rbacv1.Role{}] = cache.ByObject{Label: eitherSelector} + opts.ByObject[&rbacv1.RoleBinding{}] = cache.ByObject{Label: eitherSelector} + opts.ByObject[&corev1.Service{}] = cache.ByObject{Label: eitherSelector} + opts.ByObject[&corev1.ServiceAccount{}] = cache.ByObject{Label: eitherSelector} + + // ConfigMap is used by trust-manager for default CA package + opts.ByObject[&corev1.ConfigMap{}] = cache.ByObject{Label: trustManagerSelector} + } + + return cache.New(config, opts) + } +} + // NewControllerManager creates a new manager. func NewControllerManager() (*Manager, error) { setupLog.Info("setting up operator manager", "controller", istiocsr.ControllerName) setupLog.Info("controller", "version", version.Get()) + trustManagerEnabled := features.DefaultFeatureGate.Enabled(v1alpha1.FeatureTrustManager) + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, - // Use custom cache builder to configure label selectors for managed resources - NewCache: istiocsr.NewCacheBuilder, + // Use combined cache builder to configure label selectors for resources + // managed by both istiocsr and trustmanager controllers. + NewCache: newCombinedCacheBuilder(trustManagerEnabled), Logger: ctrl.Log.WithName("operator-manager"), }) if err != nil { @@ -69,6 +164,20 @@ func NewControllerManager() (*Manager, error) { if err := r.SetupWithManager(mgr); err != nil { return nil, fmt.Errorf("failed to create %s controller: %w", istiocsr.ControllerName, err) } + + // Register trust-manager controller if the TrustManager feature gate is enabled + if trustManagerEnabled { + setupLog.Info("TrustManager feature gate is enabled, registering trust-manager controller") + tmReconciler, err := trustmanager.New(mgr) + if err != nil { + return nil, fmt.Errorf("failed to create %s reconciler object: %w", trustmanager.ControllerName, err) + } + if err := tmReconciler.SetupWithManager(mgr); err != nil { + return nil, fmt.Errorf("failed to create %s controller: %w", trustmanager.ControllerName, err) + } + } else { + setupLog.Info("TrustManager feature gate is disabled, skipping trust-manager controller registration") + } // +kubebuilder:scaffold:builder return &Manager{