From bd7a98d520175ca71b859170fa486c2193f5f835 Mon Sep 17 00:00:00 2001 From: Todd Short Date: Mon, 15 Jun 2026 11:36:35 -0400 Subject: [PATCH 1/2] Temp commit to test psm setting TLS parameters Signed-off-by: Todd Short --- go.mod | 2 +- pkg/manifests/csv.yaml | 6 ++ pkg/package-server-manager/config.go | 9 ++- pkg/package-server-manager/controller.go | 42 +++++++++- pkg/package-server-manager/controller_test.go | 2 +- .../_packageserver.clusterserviceversion.yaml | 8 ++ .../deploy/upstream/quickstart/olm.yaml | 8 ++ .../pkg/package-server/server/server.go | 81 ++++++++++++++++--- .../pkg/package-server/server/server.go | 81 ++++++++++++++++--- 9 files changed, 212 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index afd2f51979..c49dad8833 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/mikefarah/yq/v3 v3.0.0-20201202084205-8846255d1c37 github.com/onsi/ginkgo/v2 v2.31.0 github.com/openshift/api v0.0.0-20260204104751-e09e5a4ebcd0 + github.com/openshift/library-go v0.0.0-20260204111611-b7d4fa0e292a github.com/operator-framework/api v0.44.0 github.com/operator-framework/operator-lifecycle-manager v0.0.0-00010101000000-000000000000 github.com/operator-framework/operator-registry v1.72.0 @@ -155,7 +156,6 @@ require ( github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opencontainers/runtime-spec v1.3.0 // indirect github.com/openshift/client-go v0.0.0-20260108185524-48f4ccfc4e13 // indirect - github.com/openshift/library-go v0.0.0-20260204111611-b7d4fa0e292a // indirect github.com/otiai10/copy v1.14.1 // indirect github.com/otiai10/mint v1.6.3 // indirect github.com/pkg/errors v0.9.1 // indirect diff --git a/pkg/manifests/csv.yaml b/pkg/manifests/csv.yaml index c077a62ae0..1b18874729 100644 --- a/pkg/manifests/csv.yaml +++ b/pkg/manifests/csv.yaml @@ -69,6 +69,12 @@ spec: verbs: - get - list + - apiGroups: + - "config.openshift.io" + resources: + - apiservers + verbs: + - get deployments: - name: packageserver spec: diff --git a/pkg/package-server-manager/config.go b/pkg/package-server-manager/config.go index 8a8bd3f338..487ad2b1a1 100644 --- a/pkg/package-server-manager/config.go +++ b/pkg/package-server-manager/config.go @@ -66,17 +66,18 @@ func getTopologyModeFromInfra(infra *configv1.Infrastructure) bool { // resource matches that of the codified defaults and high availability configurations, where // codified defaults are defined by the csv returned by the manifests.NewPackageServerCSV // function. -func ensureCSV(log logr.Logger, image string, interval string, csv *olmv1alpha1.ClusterServiceVersion, highlyAvailableMode bool) (bool, error) { +func ensureCSV(log logr.Logger, image string, interval string, flags []string, csv *olmv1alpha1.ClusterServiceVersion, highlyAvailableMode bool) (bool, error) { - flags := []string{} + runFlags := []string{} if interval != "" { - flags = append(flags, "--interval", interval) + runFlags = append(runFlags, "--interval", interval) } + runFlags = append(runFlags, flags...) expectedCSV, err := manifests.NewPackageServerCSV( manifests.WithName(csv.Name), manifests.WithNamespace(csv.Namespace), manifests.WithImage(image), - manifests.WithRunFlags(flags), + manifests.WithRunFlags(runFlags), ) if err != nil { return false, err diff --git a/pkg/package-server-manager/controller.go b/pkg/package-server-manager/controller.go index 3878aa5e65..9cf652b7df 100644 --- a/pkg/package-server-manager/controller.go +++ b/pkg/package-server-manager/controller.go @@ -19,16 +19,20 @@ package controllers import ( "context" "fmt" + "strings" "sync" "github.com/go-logr/logr" configv1 "github.com/openshift/api/config/v1" + libcrypto "github.com/openshift/library-go/pkg/crypto" "github.com/openshift/operator-framework-olm/pkg/manifests" + olmapiserver "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/apiserver" olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -85,6 +89,22 @@ func (r *PackageServerCSVReconciler) Reconcile(ctx context.Context, req ctrl.Req flags = append(flags, "--interval", r.Interval) } + var apiServer configv1.APIServer + if err := r.Client.Get(ctx, types.NamespacedName{Name: "cluster"}, &apiServer); err != nil { + if !apierrors.IsNotFound(err) { + return ctrl.Result{}, err + } + } else { + minVersion, cipherSuites := olmapiserver.GetSecurityProfileConfig(apiServer.Spec.TLSSecurityProfile) + minVersionStr := libcrypto.TLSVersionToNameOrDie(minVersion) + cipherSuitesStr := strings.Join(libcrypto.CipherSuitesToNamesOrDie(cipherSuites), ",") + flags = append(flags, + "--tls-min-version", minVersionStr, + "--tls-cipher-suites", cipherSuitesStr, + ) + log.Info("applying cluster TLS security profile to packageserver", "minVersion", minVersionStr, "cipherSuites", cipherSuitesStr) + } + required, err := manifests.NewPackageServerCSV( manifests.WithName(r.Name), manifests.WithNamespace(r.Namespace), @@ -96,7 +116,7 @@ func (r *PackageServerCSVReconciler) Reconcile(ctx context.Context, req ctrl.Req return ctrl.Result{}, err } res, err := controllerutil.CreateOrUpdate(ctx, r.Client, required, func() error { - return reconcileCSV(r.Log, r.Image, r.Interval, required, highAvailabilityMode) + return reconcileCSV(r.Log, r.Image, r.Interval, flags, required, highAvailabilityMode) }) log.Info("reconciliation result", "res", res) @@ -123,12 +143,12 @@ func ensureRBAC(client client.Client, ctx context.Context, namespace string, log return nil } -func reconcileCSV(log logr.Logger, image string, interval string, csv *olmv1alpha1.ClusterServiceVersion, highAvailabilityMode bool) error { +func reconcileCSV(log logr.Logger, image string, interval string, flags []string, csv *olmv1alpha1.ClusterServiceVersion, highAvailabilityMode bool) error { if csv.ObjectMeta.CreationTimestamp.IsZero() { log.Info("attempting to create the packageserver csv") } - modified, err := ensureCSV(log, image, interval, csv, highAvailabilityMode) + modified, err := ensureCSV(log, image, interval, flags, csv, highAvailabilityMode) if err != nil { return fmt.Errorf("error ensuring CSV: %v", err) } @@ -158,10 +178,26 @@ func (r *PackageServerCSVReconciler) infrastructureHandler(_ context.Context, ob } } +func (r *PackageServerCSVReconciler) apiServerHandler(_ context.Context, obj client.Object) []reconcile.Request { + if obj.GetName() != "cluster" { + return nil + } + r.Log.Info("requeueing the packageserver deployment after encountering APIServer TLS profile change") + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: r.Name, + Namespace: r.Namespace, + }, + }, + } +} + // SetupWithManager sets up the controller with the Manager. func (r *PackageServerCSVReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&olmv1alpha1.ClusterServiceVersion{}). Watches(&configv1.Infrastructure{}, handler.EnqueueRequestsFromMapFunc(r.infrastructureHandler)). + Watches(&configv1.APIServer{}, handler.EnqueueRequestsFromMapFunc(r.apiServerHandler)). Complete(r) } diff --git a/pkg/package-server-manager/controller_test.go b/pkg/package-server-manager/controller_test.go index b5e4eee116..d567d55107 100644 --- a/pkg/package-server-manager/controller_test.go +++ b/pkg/package-server-manager/controller_test.go @@ -261,7 +261,7 @@ func TestEnsureCSV(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { - gotBool, gotErr := ensureCSV(logger, image, interval, tc.inputCSV, tc.highlyAvailable) + gotBool, gotErr := ensureCSV(logger, image, interval, nil, tc.inputCSV, tc.highlyAvailable) require.EqualValues(t, tc.want.expectedBool, gotBool) require.EqualValues(t, tc.want.expectedErr, gotErr) require.EqualValues(t, tc.inputCSV.Spec, tc.expectedCSV.Spec) diff --git a/staging/operator-lifecycle-manager/deploy/chart/templates/_packageserver.clusterserviceversion.yaml b/staging/operator-lifecycle-manager/deploy/chart/templates/_packageserver.clusterserviceversion.yaml index 5739dff461..38658adbb2 100644 --- a/staging/operator-lifecycle-manager/deploy/chart/templates/_packageserver.clusterserviceversion.yaml +++ b/staging/operator-lifecycle-manager/deploy/chart/templates/_packageserver.clusterserviceversion.yaml @@ -67,6 +67,14 @@ spec: verbs: - get - list + - apiGroups: + - "config.openshift.io" + resources: + - apiservers + resourceNames: + - cluster + verbs: + - get deployments: - name: packageserver {{- include "packageserver.deployment-spec" . | nindent 8 }} diff --git a/staging/operator-lifecycle-manager/deploy/upstream/quickstart/olm.yaml b/staging/operator-lifecycle-manager/deploy/upstream/quickstart/olm.yaml index 718ff64d55..1b8c3d0970 100644 --- a/staging/operator-lifecycle-manager/deploy/upstream/quickstart/olm.yaml +++ b/staging/operator-lifecycle-manager/deploy/upstream/quickstart/olm.yaml @@ -264,6 +264,14 @@ spec: verbs: - get - list + - apiGroups: + - "config.openshift.io" + resources: + - apiservers + resourceNames: + - cluster + verbs: + - get deployments: - name: packageserver spec: diff --git a/staging/operator-lifecycle-manager/pkg/package-server/server/server.go b/staging/operator-lifecycle-manager/pkg/package-server/server/server.go index 81ea882c78..ff24286c94 100644 --- a/staging/operator-lifecycle-manager/pkg/package-server/server/server.go +++ b/staging/operator-lifecycle-manager/pkg/package-server/server/server.go @@ -23,9 +23,13 @@ import ( "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/workqueue" + configv1client "github.com/openshift/client-go/config/clientset/versioned" + libcrypto "github.com/openshift/library-go/pkg/crypto" operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client" olminformers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions" + olmapiserver "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/apiserver" + "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/openshiftconfig" "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer" "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apiserver" genericpackageserver "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apiserver/generic" @@ -202,19 +206,13 @@ func (o *PackageServerOptions) Run(ctx context.Context) error { string(genericfeatures.UnauthenticatedHTTP2DOSMitigation): true, }) - // Grab the config for the API server - config, err := o.Config(ctx) - if err != nil { - return err - } - config.GenericConfig.EnableMetrics = true - - // Set up the client config + // Set up the client config before calling Config() so it can be used to + // apply the cluster TLS security profile to the serving options. var clientConfig *rest.Config + var err error if len(o.Kubeconfig) > 0 { loadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: o.Kubeconfig} loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{}) - clientConfig, err = loader.ClientConfig() } else { clientConfig, err = rest.InClusterConfig() @@ -223,6 +221,23 @@ func (o *PackageServerOptions) Run(ctx context.Context) error { return fmt.Errorf("unable to construct lister client config: %v", err) } + // If --tls-min-version was not supplied (e.g. no PSM-injected flags yet), fall + // back to a direct GET of the cluster APIServer CR so the packageserver still + // honours the cluster TLS security profile on first boot or during upgrades. + if o.SecureServing.MinTLSVersion == "" { + if err := applyClusterTLSProfile(ctx, clientConfig, o.SecureServing); err != nil { + return fmt.Errorf("failed to apply cluster TLS profile to serving options: %w", err) + } + } + + // Grab the config for the API server + var config *apiserver.Config + config, err = o.Config(ctx) + if err != nil { + return err + } + config.GenericConfig.EnableMetrics = true + kubeClient, err := kubernetes.NewForConfig(clientConfig) if err != nil { return fmt.Errorf("unable to construct lister client: %v", err) @@ -326,3 +341,51 @@ func (op *Operator) syncOLMConfig(obj interface{}) error { return nil } + +// applyClusterTLSProfile fetches the cluster-wide APIServer TLS security profile +// and applies it to the SecureServingOptions. It is a no-op on non-OpenShift clusters. +// This is the fallback path used when --tls-min-version is not provided via flags +// (i.e. before the PSM has had a chance to inject them). +func applyClusterTLSProfile(ctx context.Context, config *rest.Config, serving *genericoptions.SecureServingOptionsWithLoopback) error { + const lookupTimeout = 30 * time.Second + profileCtx, cancel := context.WithTimeout(ctx, lookupTimeout) + defer cancel() + + profileConfig := rest.CopyConfig(config) + if profileConfig.Timeout == 0 || profileConfig.Timeout > lookupTimeout { + profileConfig.Timeout = lookupTimeout + } + + kubeClient, err := kubernetes.NewForConfig(profileConfig) + if err != nil { + return fmt.Errorf("failed to create kubernetes client: %w", err) + } + available, err := openshiftconfig.IsAPIAvailable(kubeClient.Discovery()) + if err != nil { + return fmt.Errorf("failed to check OpenShift config API: %w", err) + } + if !available { + return nil + } + + configClient, err := configv1client.NewForConfig(profileConfig) + if err != nil { + return fmt.Errorf("failed to create config client: %w", err) + } + apiServer, err := configClient.ConfigV1().APIServers().Get(profileCtx, "cluster", metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get APIServer config: %w", err) + } + + minVersion, cipherSuites := olmapiserver.GetSecurityProfileConfig(apiServer.Spec.TLSSecurityProfile) + // Only override fields not already set by explicit flags so that + // --tls-min-version / --tls-cipher-suites take precedence. + if serving.MinTLSVersion == "" { + serving.MinTLSVersion = libcrypto.TLSVersionToNameOrDie(minVersion) + } + if len(serving.CipherSuites) == 0 { + serving.CipherSuites = libcrypto.CipherSuitesToNamesOrDie(cipherSuites) + } + log.Infof("Applying cluster TLS security profile: minVersion=%s cipherSuites=%v", serving.MinTLSVersion, serving.CipherSuites) + return nil +} diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/server/server.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/server/server.go index 81ea882c78..ff24286c94 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/server/server.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/server/server.go @@ -23,9 +23,13 @@ import ( "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/workqueue" + configv1client "github.com/openshift/client-go/config/clientset/versioned" + libcrypto "github.com/openshift/library-go/pkg/crypto" operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client" olminformers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions" + olmapiserver "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/apiserver" + "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/openshiftconfig" "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer" "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apiserver" genericpackageserver "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apiserver/generic" @@ -202,19 +206,13 @@ func (o *PackageServerOptions) Run(ctx context.Context) error { string(genericfeatures.UnauthenticatedHTTP2DOSMitigation): true, }) - // Grab the config for the API server - config, err := o.Config(ctx) - if err != nil { - return err - } - config.GenericConfig.EnableMetrics = true - - // Set up the client config + // Set up the client config before calling Config() so it can be used to + // apply the cluster TLS security profile to the serving options. var clientConfig *rest.Config + var err error if len(o.Kubeconfig) > 0 { loadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: o.Kubeconfig} loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{}) - clientConfig, err = loader.ClientConfig() } else { clientConfig, err = rest.InClusterConfig() @@ -223,6 +221,23 @@ func (o *PackageServerOptions) Run(ctx context.Context) error { return fmt.Errorf("unable to construct lister client config: %v", err) } + // If --tls-min-version was not supplied (e.g. no PSM-injected flags yet), fall + // back to a direct GET of the cluster APIServer CR so the packageserver still + // honours the cluster TLS security profile on first boot or during upgrades. + if o.SecureServing.MinTLSVersion == "" { + if err := applyClusterTLSProfile(ctx, clientConfig, o.SecureServing); err != nil { + return fmt.Errorf("failed to apply cluster TLS profile to serving options: %w", err) + } + } + + // Grab the config for the API server + var config *apiserver.Config + config, err = o.Config(ctx) + if err != nil { + return err + } + config.GenericConfig.EnableMetrics = true + kubeClient, err := kubernetes.NewForConfig(clientConfig) if err != nil { return fmt.Errorf("unable to construct lister client: %v", err) @@ -326,3 +341,51 @@ func (op *Operator) syncOLMConfig(obj interface{}) error { return nil } + +// applyClusterTLSProfile fetches the cluster-wide APIServer TLS security profile +// and applies it to the SecureServingOptions. It is a no-op on non-OpenShift clusters. +// This is the fallback path used when --tls-min-version is not provided via flags +// (i.e. before the PSM has had a chance to inject them). +func applyClusterTLSProfile(ctx context.Context, config *rest.Config, serving *genericoptions.SecureServingOptionsWithLoopback) error { + const lookupTimeout = 30 * time.Second + profileCtx, cancel := context.WithTimeout(ctx, lookupTimeout) + defer cancel() + + profileConfig := rest.CopyConfig(config) + if profileConfig.Timeout == 0 || profileConfig.Timeout > lookupTimeout { + profileConfig.Timeout = lookupTimeout + } + + kubeClient, err := kubernetes.NewForConfig(profileConfig) + if err != nil { + return fmt.Errorf("failed to create kubernetes client: %w", err) + } + available, err := openshiftconfig.IsAPIAvailable(kubeClient.Discovery()) + if err != nil { + return fmt.Errorf("failed to check OpenShift config API: %w", err) + } + if !available { + return nil + } + + configClient, err := configv1client.NewForConfig(profileConfig) + if err != nil { + return fmt.Errorf("failed to create config client: %w", err) + } + apiServer, err := configClient.ConfigV1().APIServers().Get(profileCtx, "cluster", metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get APIServer config: %w", err) + } + + minVersion, cipherSuites := olmapiserver.GetSecurityProfileConfig(apiServer.Spec.TLSSecurityProfile) + // Only override fields not already set by explicit flags so that + // --tls-min-version / --tls-cipher-suites take precedence. + if serving.MinTLSVersion == "" { + serving.MinTLSVersion = libcrypto.TLSVersionToNameOrDie(minVersion) + } + if len(serving.CipherSuites) == 0 { + serving.CipherSuites = libcrypto.CipherSuitesToNamesOrDie(cipherSuites) + } + log.Infof("Applying cluster TLS security profile: minVersion=%s cipherSuites=%v", serving.MinTLSVersion, serving.CipherSuites) + return nil +} From 76d034953d4dd322e8a64ee6b20ee9884db639f6 Mon Sep 17 00:00:00 2001 From: Todd Short Date: Thu, 18 Jun 2026 16:34:27 -0400 Subject: [PATCH 2/2] Refactor applyClusterTLSProfile for testability and add unit tests Extract applyClusterTLSProfileWithClients to accept injected discovery and config client interfaces, enabling unit testing without a live API server. Add tests covering non-OpenShift no-op, Intermediate and Modern profiles, flag precedence, and fail-closed behavior on missing CR. --- .../pkg/package-server/server/server.go | 24 ++-- .../pkg/package-server/server/server_test.go | 132 ++++++++++++++++++ .../pkg/package-server/server/server.go | 24 ++-- 3 files changed, 164 insertions(+), 16 deletions(-) create mode 100644 staging/operator-lifecycle-manager/pkg/package-server/server/server_test.go diff --git a/staging/operator-lifecycle-manager/pkg/package-server/server/server.go b/staging/operator-lifecycle-manager/pkg/package-server/server/server.go index ff24286c94..a6e8a3216c 100644 --- a/staging/operator-lifecycle-manager/pkg/package-server/server/server.go +++ b/staging/operator-lifecycle-manager/pkg/package-server/server/server.go @@ -34,6 +34,7 @@ import ( "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apiserver" genericpackageserver "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apiserver/generic" "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/provider" + apidiscovery "k8s.io/client-go/discovery" ) const DefaultWakeupInterval = 12 * time.Hour @@ -360,7 +361,19 @@ func applyClusterTLSProfile(ctx context.Context, config *rest.Config, serving *g if err != nil { return fmt.Errorf("failed to create kubernetes client: %w", err) } - available, err := openshiftconfig.IsAPIAvailable(kubeClient.Discovery()) + cfgClient, err := configv1client.NewForConfig(profileConfig) + if err != nil { + return fmt.Errorf("failed to create config client: %w", err) + } + return applyClusterTLSProfileWithClients(profileCtx, kubeClient.Discovery(), cfgClient, serving) +} + +// applyClusterTLSProfileWithClients is the testable core of applyClusterTLSProfile. +// It applies the cluster-wide APIServer TLS security profile to the SecureServingOptions, +// but only for fields not already set by explicit flags (--tls-min-version / --tls-cipher-suites). +// It is a no-op on non-OpenShift clusters. +func applyClusterTLSProfileWithClients(ctx context.Context, discovery apidiscovery.DiscoveryInterface, cfgClient configv1client.Interface, serving *genericoptions.SecureServingOptionsWithLoopback) error { + available, err := openshiftconfig.IsAPIAvailable(discovery) if err != nil { return fmt.Errorf("failed to check OpenShift config API: %w", err) } @@ -368,18 +381,13 @@ func applyClusterTLSProfile(ctx context.Context, config *rest.Config, serving *g return nil } - configClient, err := configv1client.NewForConfig(profileConfig) - if err != nil { - return fmt.Errorf("failed to create config client: %w", err) - } - apiServer, err := configClient.ConfigV1().APIServers().Get(profileCtx, "cluster", metav1.GetOptions{}) + apiServer, err := cfgClient.ConfigV1().APIServers().Get(ctx, "cluster", metav1.GetOptions{}) if err != nil { return fmt.Errorf("failed to get APIServer config: %w", err) } minVersion, cipherSuites := olmapiserver.GetSecurityProfileConfig(apiServer.Spec.TLSSecurityProfile) - // Only override fields not already set by explicit flags so that - // --tls-min-version / --tls-cipher-suites take precedence. + // Only override fields not already set by explicit flags. if serving.MinTLSVersion == "" { serving.MinTLSVersion = libcrypto.TLSVersionToNameOrDie(minVersion) } diff --git a/staging/operator-lifecycle-manager/pkg/package-server/server/server_test.go b/staging/operator-lifecycle-manager/pkg/package-server/server/server_test.go new file mode 100644 index 0000000000..27a7e1fefb --- /dev/null +++ b/staging/operator-lifecycle-manager/pkg/package-server/server/server_test.go @@ -0,0 +1,132 @@ +package server + +import ( + "context" + "testing" + + apiconfigv1 "github.com/openshift/api/config/v1" + configfake "github.com/openshift/client-go/config/clientset/versioned/fake" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + genericoptions "k8s.io/apiserver/pkg/server/options" + fakediscovery "k8s.io/client-go/discovery/fake" + k8sfake "k8s.io/client-go/kubernetes/fake" +) + +// clusterAPIServer returns a minimal APIServer singleton with the given TLS profile. +func clusterAPIServer(profile *apiconfigv1.TLSSecurityProfile) *apiconfigv1.APIServer { + return &apiconfigv1.APIServer{ + ObjectMeta: metav1.ObjectMeta{Name: "cluster"}, + Spec: apiconfigv1.APIServerSpec{TLSSecurityProfile: profile}, + } +} + +func newServing() *genericoptions.SecureServingOptionsWithLoopback { + return genericoptions.NewSecureServingOptions().WithLoopback() +} + +// nonOpenShiftDiscovery returns a fake discovery that advertises only core k8s +// groups — no config.openshift.io — simulating a vanilla Kubernetes cluster. +func nonOpenShiftDiscovery() *fakediscovery.FakeDiscovery { + k8sClient := k8sfake.NewSimpleClientset() + disc := k8sClient.Discovery().(*fakediscovery.FakeDiscovery) + // Set a non-empty resource list so ServerGroups doesn't return an empty + // list (which ServerSupportsVersion treats as "all supported"). + disc.Resources = []*metav1.APIResourceList{ + {GroupVersion: "v1"}, + } + return disc +} + +// TestApplyClusterTLSProfileWithClients_NonOpenShift verifies that the function +// is a no-op when the OpenShift config API is not present (vanilla Kubernetes). +func TestApplyClusterTLSProfileWithClients_NonOpenShift(t *testing.T) { + cfgClient := configfake.NewSimpleClientset() + serving := newServing() + + err := applyClusterTLSProfileWithClients(context.Background(), nonOpenShiftDiscovery(), cfgClient, serving) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if serving.MinTLSVersion != "" { + t.Errorf("expected MinTLSVersion to be unset, got %q", serving.MinTLSVersion) + } + if len(serving.CipherSuites) != 0 { + t.Errorf("expected CipherSuites to be unset, got %v", serving.CipherSuites) + } +} + +// TestApplyClusterTLSProfileWithClients_IntermediateProfile verifies that the +// Intermediate TLS profile populates MinTLSVersion and CipherSuites. +func TestApplyClusterTLSProfileWithClients_IntermediateProfile(t *testing.T) { + apiServer := clusterAPIServer(&apiconfigv1.TLSSecurityProfile{ + Type: apiconfigv1.TLSProfileIntermediateType, + }) + cfgClient := configfake.NewSimpleClientset(apiServer) + serving := newServing() + + err := applyClusterTLSProfileWithClients(context.Background(), cfgClient.Discovery(), cfgClient, serving) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if serving.MinTLSVersion == "" { + t.Error("expected MinTLSVersion to be set for Intermediate profile") + } + if len(serving.CipherSuites) == 0 { + t.Error("expected CipherSuites to be set for Intermediate profile") + } +} + +// TestApplyClusterTLSProfileWithClients_ModernProfile verifies that the Modern +// profile sets MinTLSVersion to VersionTLS13. +func TestApplyClusterTLSProfileWithClients_ModernProfile(t *testing.T) { + apiServer := clusterAPIServer(&apiconfigv1.TLSSecurityProfile{ + Type: apiconfigv1.TLSProfileModernType, + }) + cfgClient := configfake.NewSimpleClientset(apiServer) + serving := newServing() + + err := applyClusterTLSProfileWithClients(context.Background(), cfgClient.Discovery(), cfgClient, serving) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if serving.MinTLSVersion != "VersionTLS13" { + t.Errorf("expected MinTLSVersion=VersionTLS13 for Modern profile, got %q", serving.MinTLSVersion) + } +} + +// TestApplyClusterTLSProfileWithClients_FlagsTakePrecedence verifies that +// explicitly set flags are not overwritten by the cluster profile. +func TestApplyClusterTLSProfileWithClients_FlagsTakePrecedence(t *testing.T) { + apiServer := clusterAPIServer(&apiconfigv1.TLSSecurityProfile{ + Type: apiconfigv1.TLSProfileModernType, + }) + cfgClient := configfake.NewSimpleClientset(apiServer) + serving := newServing() + // Simulate user-supplied flags. + serving.MinTLSVersion = "VersionTLS12" + serving.CipherSuites = []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"} + + err := applyClusterTLSProfileWithClients(context.Background(), cfgClient.Discovery(), cfgClient, serving) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if serving.MinTLSVersion != "VersionTLS12" { + t.Errorf("user-supplied MinTLSVersion should not be overwritten, got %q", serving.MinTLSVersion) + } + if len(serving.CipherSuites) != 1 || serving.CipherSuites[0] != "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" { + t.Errorf("user-supplied CipherSuites should not be overwritten, got %v", serving.CipherSuites) + } +} + +// TestApplyClusterTLSProfileWithClients_MissingAPIServerCR verifies that a +// missing singleton APIServer CR propagates as an error (fail-closed). +func TestApplyClusterTLSProfileWithClients_MissingAPIServerCR(t *testing.T) { + // config client advertises the API group but has no APIServer object + cfgClient := configfake.NewSimpleClientset() + serving := newServing() + + err := applyClusterTLSProfileWithClients(context.Background(), cfgClient.Discovery(), cfgClient, serving) + if err == nil { + t.Fatal("expected an error when the APIServer CR is missing, got nil") + } +} diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/server/server.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/server/server.go index ff24286c94..a6e8a3216c 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/server/server.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/server/server.go @@ -34,6 +34,7 @@ import ( "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apiserver" genericpackageserver "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apiserver/generic" "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/provider" + apidiscovery "k8s.io/client-go/discovery" ) const DefaultWakeupInterval = 12 * time.Hour @@ -360,7 +361,19 @@ func applyClusterTLSProfile(ctx context.Context, config *rest.Config, serving *g if err != nil { return fmt.Errorf("failed to create kubernetes client: %w", err) } - available, err := openshiftconfig.IsAPIAvailable(kubeClient.Discovery()) + cfgClient, err := configv1client.NewForConfig(profileConfig) + if err != nil { + return fmt.Errorf("failed to create config client: %w", err) + } + return applyClusterTLSProfileWithClients(profileCtx, kubeClient.Discovery(), cfgClient, serving) +} + +// applyClusterTLSProfileWithClients is the testable core of applyClusterTLSProfile. +// It applies the cluster-wide APIServer TLS security profile to the SecureServingOptions, +// but only for fields not already set by explicit flags (--tls-min-version / --tls-cipher-suites). +// It is a no-op on non-OpenShift clusters. +func applyClusterTLSProfileWithClients(ctx context.Context, discovery apidiscovery.DiscoveryInterface, cfgClient configv1client.Interface, serving *genericoptions.SecureServingOptionsWithLoopback) error { + available, err := openshiftconfig.IsAPIAvailable(discovery) if err != nil { return fmt.Errorf("failed to check OpenShift config API: %w", err) } @@ -368,18 +381,13 @@ func applyClusterTLSProfile(ctx context.Context, config *rest.Config, serving *g return nil } - configClient, err := configv1client.NewForConfig(profileConfig) - if err != nil { - return fmt.Errorf("failed to create config client: %w", err) - } - apiServer, err := configClient.ConfigV1().APIServers().Get(profileCtx, "cluster", metav1.GetOptions{}) + apiServer, err := cfgClient.ConfigV1().APIServers().Get(ctx, "cluster", metav1.GetOptions{}) if err != nil { return fmt.Errorf("failed to get APIServer config: %w", err) } minVersion, cipherSuites := olmapiserver.GetSecurityProfileConfig(apiServer.Spec.TLSSecurityProfile) - // Only override fields not already set by explicit flags so that - // --tls-min-version / --tls-cipher-suites take precedence. + // Only override fields not already set by explicit flags. if serving.MinTLSVersion == "" { serving.MinTLSVersion = libcrypto.TLSVersionToNameOrDie(minVersion) }