diff --git a/pkg/package-server-manager/controller.go b/pkg/package-server-manager/controller.go index 1a3757b7f5..66291c7a24 100644 --- a/pkg/package-server-manager/controller.go +++ b/pkg/package-server-manager/controller.go @@ -18,7 +18,9 @@ package controllers import ( "context" + "crypto/tls" "fmt" + "strconv" "strings" "sync" @@ -27,8 +29,8 @@ import ( 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" + olmapiserver "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/apiserver" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -95,14 +97,18 @@ func (r *PackageServerCSVReconciler) Reconcile(ctx context.Context, req ctrl.Req return ctrl.Result{}, err } } else { - minVersion, cipherSuites := olmapiserver.GetSecurityProfileConfig(apiServer.Spec.TLSSecurityProfile) + minVersion, cipherSuites, curvePreferences := olmapiserver.GetSecurityProfileConfig(apiServer.Spec.TLSSecurityProfile) minVersionStr := libcrypto.TLSVersionToNameOrDie(minVersion) cipherSuitesStr := strings.Join(libcrypto.CipherSuitesToNamesOrDie(cipherSuites), ",") + curvePreferencesStr := curvePreferencesToString(curvePreferences) flags = append(flags, "--tls-min-version", minVersionStr, "--tls-cipher-suites", cipherSuitesStr, ) - log.Info("applying cluster TLS security profile to packageserver", "minVersion", minVersionStr, "cipherSuites", cipherSuitesStr) + if curvePreferencesStr != "" { + flags = append(flags, "--tls-curve-preferences", curvePreferencesStr) + } + log.Info("applying cluster TLS security profile to packageserver", "minVersion", minVersionStr, "cipherSuites", cipherSuitesStr, "curvePreferences", curvePreferencesStr) } required, err := manifests.NewPackageServerCSV( @@ -128,6 +134,16 @@ func (r *PackageServerCSVReconciler) Reconcile(ctx context.Context, req ctrl.Req return ctrl.Result{}, nil } +// curvePreferencesToString converts Go TLS curve IDs to the comma-separated +// numeric values accepted by the Kubernetes tls-curve-preferences flag. +func curvePreferencesToString(curvePreferences []tls.CurveID) string { + values := make([]string, len(curvePreferences)) + for i, curvePreference := range curvePreferences { + values[i] = strconv.FormatInt(int64(curvePreference), 10) + } + return strings.Join(values, ",") +} + func ensureRBAC(client client.Client, ctx context.Context, namespace string, log logr.Logger) error { log.Info("checking to see if required RBAC exists") if err := client.Get(ctx, types.NamespacedName{Name: "olm-operator-serviceaccount", Namespace: namespace}, &corev1.ServiceAccount{}); err != nil { diff --git a/pkg/package-server-manager/controller_test.go b/pkg/package-server-manager/controller_test.go index 2293329cf1..9edcbf89ef 100644 --- a/pkg/package-server-manager/controller_test.go +++ b/pkg/package-server-manager/controller_test.go @@ -1,6 +1,7 @@ package controllers import ( + "context" "testing" semver "github.com/blang/semver/v4" @@ -11,12 +12,69 @@ import ( "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/log/zap" ) +func TestReconcileAppliesTLSCurvePreferences(t *testing.T) { + command := reconcilePackageServerCommandForTLSGroups(t, []configv1.TLSGroup{configv1.TLSGroupSecP384r1, configv1.TLSGroupSecP256r1}) + for i, arg := range command { + if arg == "--tls-curve-preferences" { + require.Less(t, i+1, len(command)) + require.Equal(t, "24,23", command[i+1]) + return + } + } + t.Fatal("package-server command is missing --tls-curve-preferences") +} + +func TestReconcileOmitsEmptyTLSCurvePreferences(t *testing.T) { + command := reconcilePackageServerCommandForTLSGroups(t, nil) + require.NotContains(t, command, "--tls-curve-preferences") +} + +func reconcilePackageServerCommandForTLSGroups(t *testing.T, groups []configv1.TLSGroup) []string { + t.Helper() + scheme := runtime.NewScheme() + require.NoError(t, configv1.AddToScheme(scheme)) + require.NoError(t, olmv1alpha1.AddToScheme(scheme)) + require.NoError(t, corev1.AddToScheme(scheme)) + require.NoError(t, rbacv1.AddToScheme(scheme)) + + client := fake.NewClientBuilder().WithScheme(scheme).WithObjects( + &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: "olm-operator-serviceaccount", Namespace: namespace}}, + &rbacv1.ClusterRole{ObjectMeta: metav1.ObjectMeta{Name: "system:controller:operator-lifecycle-manager"}}, + &rbacv1.ClusterRoleBinding{ObjectMeta: metav1.ObjectMeta{Name: "olm-operator-binding-openshift-operator-lifecycle-manager"}}, + &configv1.Infrastructure{ObjectMeta: metav1.ObjectMeta{Name: infrastructureName}}, + &configv1.APIServer{ + ObjectMeta: metav1.ObjectMeta{Name: "cluster"}, + Spec: configv1.APIServerSpec{TLSSecurityProfile: &configv1.TLSSecurityProfile{ + Type: configv1.TLSProfileCustomType, + Custom: &configv1.CustomTLSProfile{TLSProfileSpec: configv1.TLSProfileSpec{ + Ciphers: []string{"ECDHE-RSA-AES128-GCM-SHA256"}, + MinTLSVersion: configv1.VersionTLS12, + Groups: groups, + }}, + }}, + }, + ).Build() + reconciler := &PackageServerCSVReconciler{Client: client, Log: logger, Name: name, Namespace: namespace, Image: image} + + _, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: name, Namespace: namespace}}) + require.NoError(t, err) + + csv := &olmv1alpha1.ClusterServiceVersion{} + require.NoError(t, client.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, csv)) + return csv.Spec.InstallStrategy.StrategySpec.DeploymentSpecs[0].Spec.Template.Spec.Containers[0].Command +} + var ( logger = zap.New() name = "packageserver" diff --git a/staging/operator-lifecycle-manager/pkg/lib/apiserver/querier.go b/staging/operator-lifecycle-manager/pkg/lib/apiserver/querier.go index 94963890e1..153c4a303c 100644 --- a/staging/operator-lifecycle-manager/pkg/lib/apiserver/querier.go +++ b/staging/operator-lifecycle-manager/pkg/lib/apiserver/querier.go @@ -14,7 +14,8 @@ func NoopQuerier() Querier { // Querier is an interface that wraps the QueryTLSConfig method. // // QueryTLSConfig updates the provided TLS configuration with cluster-wide -// TLS security profile settings (MinVersion, CipherSuites, PreferServerCipherSuites). +// TLS security profile settings (MinVersion, CipherSuites, CurvePreferences, +// PreferServerCipherSuites). type Querier interface { QueryTLSConfig(config *tls.Config) error } diff --git a/staging/operator-lifecycle-manager/pkg/lib/apiserver/syncer.go b/staging/operator-lifecycle-manager/pkg/lib/apiserver/syncer.go index 0d9e68a5d1..1d28c5295d 100644 --- a/staging/operator-lifecycle-manager/pkg/lib/apiserver/syncer.go +++ b/staging/operator-lifecycle-manager/pkg/lib/apiserver/syncer.go @@ -131,7 +131,7 @@ func newTLSConfigHolder() *tlsConfigHolder { } // update atomically updates the stored TLS configuration. -func (h *tlsConfigHolder) update(minVersion uint16, cipherSuites []uint16) { +func (h *tlsConfigHolder) update(minVersion uint16, cipherSuites []uint16, curvePreferences []tls.CurveID) { h.mu.Lock() defer h.mu.Unlock() @@ -139,6 +139,7 @@ func (h *tlsConfigHolder) update(minVersion uint16, cipherSuites []uint16) { // Make a defensive copy of the slice h.config.CipherSuites = make([]uint16, len(cipherSuites)) copy(h.config.CipherSuites, cipherSuites) + h.config.CurvePreferences = append([]tls.CurveID(nil), curvePreferences...) h.config.PreferServerCipherSuites = true } @@ -152,6 +153,7 @@ func (h *tlsConfigHolder) copyTo(config *tls.Config) { config.MinVersion = h.config.MinVersion config.CipherSuites = make([]uint16, len(h.config.CipherSuites)) copy(config.CipherSuites, h.config.CipherSuites) + config.CurvePreferences = append([]tls.CurveID(nil), h.config.CurvePreferences...) config.PreferServerCipherSuites = h.config.PreferServerCipherSuites } @@ -177,21 +179,21 @@ func (w *Syncer) SyncAPIServer(object interface{}) error { } // Convert the TLS security profile to get new settings - minVersion, cipherSuites := GetSecurityProfileConfig(apiserver.Spec.TLSSecurityProfile) + minVersion, cipherSuites, curvePreferences := GetSecurityProfileConfig(apiserver.Spec.TLSSecurityProfile) // Check if configuration changed (before updating) - changed := w.hasConfigChanged(minVersion, cipherSuites) + changed := w.hasConfigChanged(minVersion, cipherSuites, curvePreferences) // Update the stored configuration atomically - w.currentConfig.update(minVersion, cipherSuites) + w.currentConfig.update(minVersion, cipherSuites, curvePreferences) // Log if configuration changed if changed { profileName := getProfileName(apiserver.Spec.TLSSecurityProfile) - w.logger.Infof("APIServer TLS configuration changed: profile=%s, minVersion=%s, cipherCount=%d", + w.logger.Infof("APIServer TLS configuration changed: profile=%s, minVersion=%s, cipherCount=%d, curvePreferences=%v", profileName, tlsVersionToString(minVersion), - len(cipherSuites)) + len(cipherSuites), curvePreferences) } return nil @@ -206,14 +208,15 @@ func (w *Syncer) HandleAPIServerDelete(object interface{}) { } // Reset to secure defaults (Intermediate profile) - w.currentConfig.update(GetSecurityProfileConfig(nil)) + minVersion, cipherSuites, curvePreferences := GetSecurityProfileConfig(nil) + w.currentConfig.update(minVersion, cipherSuites, curvePreferences) w.logger.Info("APIServer TLS configuration deleted, reverted to secure defaults") return } // hasConfigChanged checks if the new TLS settings differ from the current cached settings. -func (w *Syncer) hasConfigChanged(minVersion uint16, cipherSuites []uint16) bool { +func (w *Syncer) hasConfigChanged(minVersion uint16, cipherSuites []uint16, curvePreferences []tls.CurveID) bool { w.currentConfig.mu.RLock() defer w.currentConfig.mu.RUnlock() @@ -228,6 +231,14 @@ func (w *Syncer) hasConfigChanged(minVersion uint16, cipherSuites []uint16) bool return true } } + if len(w.currentConfig.config.CurvePreferences) != len(curvePreferences) { + return true + } + for i := range curvePreferences { + if w.currentConfig.config.CurvePreferences[i] != curvePreferences[i] { + return true + } + } return false } diff --git a/staging/operator-lifecycle-manager/pkg/lib/apiserver/syncer_test.go b/staging/operator-lifecycle-manager/pkg/lib/apiserver/syncer_test.go index 282eaad3e8..73a4625826 100644 --- a/staging/operator-lifecycle-manager/pkg/lib/apiserver/syncer_test.go +++ b/staging/operator-lifecycle-manager/pkg/lib/apiserver/syncer_test.go @@ -58,7 +58,7 @@ func TestSyncer_SyncAPIServer_IntermediateProfile(t *testing.T) { } // Test that GetSecurityProfileConfig returns expected values for Intermediate - minVersion, cipherSuites := apiserver.GetSecurityProfileConfig(server.Spec.TLSSecurityProfile) + minVersion, cipherSuites, _ := apiserver.GetSecurityProfileConfig(server.Spec.TLSSecurityProfile) assert.Equal(t, uint16(tls.VersionTLS12), minVersion, "Intermediate should use TLS 1.2") assert.NotEmpty(t, cipherSuites, "Should have cipher suites") @@ -79,7 +79,7 @@ func TestSyncer_SyncAPIServer_ModernProfile(t *testing.T) { } // Test that GetSecurityProfileConfig returns expected values for Modern - minVersion, cipherSuites := apiserver.GetSecurityProfileConfig(server.Spec.TLSSecurityProfile) + minVersion, cipherSuites, _ := apiserver.GetSecurityProfileConfig(server.Spec.TLSSecurityProfile) assert.Equal(t, uint16(tls.VersionTLS13), minVersion, "Modern should use TLS 1.3") assert.NotEmpty(t, cipherSuites, "Should have cipher suites") @@ -108,7 +108,7 @@ func TestSyncer_SyncAPIServer_CustomProfile(t *testing.T) { } // Test that GetSecurityProfileConfig returns expected values for Custom - minVersion, cipherSuites := apiserver.GetSecurityProfileConfig(server.Spec.TLSSecurityProfile) + minVersion, cipherSuites, _ := apiserver.GetSecurityProfileConfig(server.Spec.TLSSecurityProfile) assert.Equal(t, uint16(tls.VersionTLS13), minVersion, "Custom should respect MinTLSVersion") assert.NotEmpty(t, cipherSuites, "Should have cipher suites") diff --git a/staging/operator-lifecycle-manager/pkg/lib/apiserver/tlsconfig.go b/staging/operator-lifecycle-manager/pkg/lib/apiserver/tlsconfig.go index 29a240b179..3955ebb432 100644 --- a/staging/operator-lifecycle-manager/pkg/lib/apiserver/tlsconfig.go +++ b/staging/operator-lifecycle-manager/pkg/lib/apiserver/tlsconfig.go @@ -1,16 +1,19 @@ package apiserver import ( + "crypto/fips140" "crypto/tls" apiconfigv1 "github.com/openshift/api/config/v1" "github.com/openshift/library-go/pkg/crypto" + "k8s.io/klog/v2" ) -// GetSecurityProfileConfig extracts the minimum TLS version and cipher suites -// from a TLSSecurityProfile object. Converts OpenSSL cipher names to Go TLS cipher IDs. +// GetSecurityProfileConfig extracts the minimum TLS version, cipher suites, and +// curve preferences from a TLSSecurityProfile object. Converts OpenSSL cipher +// names and TLS groups to Go TLS identifiers. // If profile is nil, returns config defined by the Intermediate TLS Profile. -func GetSecurityProfileConfig(profile *apiconfigv1.TLSSecurityProfile) (uint16, []uint16) { +func GetSecurityProfileConfig(profile *apiconfigv1.TLSSecurityProfile) (uint16, []uint16, []tls.CurveID) { var profileType apiconfigv1.TLSProfileType if profile == nil { profileType = apiconfigv1.TLSProfileIntermediateType @@ -42,8 +45,40 @@ func GetSecurityProfileConfig(profile *apiconfigv1.TLSSecurityProfile) (uint16, // Convert OpenSSL cipher names to IANA names, then to Go cipher suite IDs ianaCipherNames := crypto.OpenSSLToIANACipherSuites(profileSpec.Ciphers) cipherSuites := CipherNamesToIDs(ianaCipherNames) + curvePreferences, unrecognizedGroups := crypto.TLSGroupsToCurveIDs(profileSpec.Groups) + if len(unrecognizedGroups) > 0 { + klog.Warningf("Dropping TLS groups not supported by this Go version: %v", unrecognizedGroups) + } + if fips140.Enabled() { + curvePreferences = filterFIPSCurvePreferences(curvePreferences) + if len(profileSpec.Groups) > 0 && len(curvePreferences) == 0 { + klog.Warning("No configured TLS groups are FIPS-approved; using the FIPS-safe P-curve fallback") + curvePreferences = fipsSafeCurvePreferences() + } + } + + return minTLSVersion, cipherSuites, curvePreferences +} - return minTLSVersion, cipherSuites +// filterFIPSCurvePreferences retains only the NIST P-curves. This deliberately +// excludes X25519MLKEM768 even though Go's native FIPS module permits it: OLM +// must also support the OpenSSL FIPS backend, which rejects its X25519 component. +func filterFIPSCurvePreferences(preferences []tls.CurveID) []tls.CurveID { + approved := make([]tls.CurveID, 0, len(preferences)) + for _, preference := range preferences { + switch preference { + case tls.CurveP256, tls.CurveP384, tls.CurveP521: + approved = append(approved, preference) + } + } + return approved +} + +// fipsSafeCurvePreferences returns the NIST P-curves used when FIPS filtering +// removes every configured TLS group. Returning a non-empty list preserves the +// configured restriction instead of allowing Go to select its defaults. +func fipsSafeCurvePreferences() []tls.CurveID { + return []tls.CurveID{tls.CurveP256, tls.CurveP384, tls.CurveP521} } // CipherNamesToIDs converts IANA cipher suite names to Go TLS cipher suite IDs @@ -73,6 +108,9 @@ func ApplySecureDefaults(config *tls.Config) error { if len(config.CipherSuites) == 0 { config.CipherSuites = crypto.DefaultCiphers() } + if len(config.CurvePreferences) == 0 { + _, _, config.CurvePreferences = GetSecurityProfileConfig(nil) + } config.PreferServerCipherSuites = true return nil } diff --git a/staging/operator-lifecycle-manager/pkg/lib/apiserver/tlsconfig_test.go b/staging/operator-lifecycle-manager/pkg/lib/apiserver/tlsconfig_test.go index 1af7cbbf97..a6a5b45046 100644 --- a/staging/operator-lifecycle-manager/pkg/lib/apiserver/tlsconfig_test.go +++ b/staging/operator-lifecycle-manager/pkg/lib/apiserver/tlsconfig_test.go @@ -12,7 +12,7 @@ import ( func TestGetSecurityProfileConfig_NilProfile(t *testing.T) { // When profile is nil, should use Intermediate defaults - minVersion, cipherSuites := apiserver.GetSecurityProfileConfig(nil) + minVersion, cipherSuites, _ := apiserver.GetSecurityProfileConfig(nil) assert.Equal(t, uint16(tls.VersionTLS12), minVersion, "Intermediate profile should use TLS 1.2") assert.NotEmpty(t, cipherSuites, "Should have cipher suites") @@ -23,7 +23,7 @@ func TestGetSecurityProfileConfig_IntermediateProfile(t *testing.T) { Type: apiconfigv1.TLSProfileIntermediateType, } - minVersion, cipherSuites := apiserver.GetSecurityProfileConfig(profile) + minVersion, cipherSuites, _ := apiserver.GetSecurityProfileConfig(profile) assert.Equal(t, uint16(tls.VersionTLS12), minVersion, "Intermediate profile should use TLS 1.2") assert.NotEmpty(t, cipherSuites, "Should have cipher suites") @@ -35,7 +35,7 @@ func TestGetSecurityProfileConfig_ModernProfile(t *testing.T) { Type: apiconfigv1.TLSProfileModernType, } - minVersion, cipherSuites := apiserver.GetSecurityProfileConfig(profile) + minVersion, cipherSuites, _ := apiserver.GetSecurityProfileConfig(profile) assert.Equal(t, uint16(tls.VersionTLS13), minVersion, "Modern profile should use TLS 1.3") assert.NotEmpty(t, cipherSuites, "Should have cipher suites") @@ -46,7 +46,7 @@ func TestGetSecurityProfileConfig_OldProfile(t *testing.T) { Type: apiconfigv1.TLSProfileOldType, } - minVersion, cipherSuites := apiserver.GetSecurityProfileConfig(profile) + minVersion, cipherSuites, _ := apiserver.GetSecurityProfileConfig(profile) assert.Equal(t, uint16(tls.VersionTLS10), minVersion, "Old profile should use TLS 1.0") assert.NotEmpty(t, cipherSuites, "Should have cipher suites") @@ -67,7 +67,7 @@ func TestGetSecurityProfileConfig_CustomProfile(t *testing.T) { }, } - minVersion, cipherSuites := apiserver.GetSecurityProfileConfig(profile) + minVersion, cipherSuites, _ := apiserver.GetSecurityProfileConfig(profile) assert.Equal(t, uint16(tls.VersionTLS13), minVersion, "Custom profile should respect MinTLSVersion") assert.NotEmpty(t, cipherSuites, "Should have cipher suites") @@ -80,7 +80,7 @@ func TestGetSecurityProfileConfig_CustomProfileWithoutSpec(t *testing.T) { Custom: nil, } - minVersion, cipherSuites := apiserver.GetSecurityProfileConfig(profile) + minVersion, cipherSuites, _ := apiserver.GetSecurityProfileConfig(profile) assert.Equal(t, uint16(tls.VersionTLS12), minVersion, "Should fall back to Intermediate") assert.NotEmpty(t, cipherSuites, "Should have cipher suites") @@ -118,6 +118,7 @@ func TestApplySecureDefaults(t *testing.T) { if tt.name == "EmptyConfig" { assert.NotZero(t, tt.config.MinVersion, "MinVersion should be set") assert.NotEmpty(t, tt.config.CipherSuites, "CipherSuites should be set") + assert.NotEmpty(t, tt.config.CurvePreferences, "CurvePreferences should be set") } assert.True(t, tt.config.PreferServerCipherSuites, "PreferServerCipherSuites should be true") }) 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 e2ef2a86f8..8eb6a0efc2 100644 --- a/staging/operator-lifecycle-manager/pkg/package-server/server/server.go +++ b/staging/operator-lifecycle-manager/pkg/package-server/server/server.go @@ -374,7 +374,7 @@ func applyClusterTLSProfile(ctx context.Context, config *rest.Config, serving *g // 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). +// but only for fields not already set by explicit TLS flags. // 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) @@ -390,7 +390,7 @@ func applyClusterTLSProfileWithClients(ctx context.Context, discovery apidiscove return fmt.Errorf("failed to get APIServer config: %w", err) } - minVersion, cipherSuites := olmapiserver.GetSecurityProfileConfig(apiServer.Spec.TLSSecurityProfile) + minVersion, cipherSuites, curvePreferences := olmapiserver.GetSecurityProfileConfig(apiServer.Spec.TLSSecurityProfile) // Only override fields not already set by explicit flags. if serving.MinTLSVersion == "" { serving.MinTLSVersion = libcrypto.TLSVersionToNameOrDie(minVersion) @@ -398,6 +398,12 @@ func applyClusterTLSProfileWithClients(ctx context.Context, discovery apidiscove 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) + if len(serving.CurvePreferences) == 0 { + serving.CurvePreferences = make([]int32, len(curvePreferences)) + for i, curvePreference := range curvePreferences { + serving.CurvePreferences[i] = int32(curvePreference) + } + } + log.Infof("Applying cluster TLS security profile: minVersion=%s cipherSuites=%v curvePreferences=%v", serving.MinTLSVersion, serving.CipherSuites, serving.CurvePreferences) return nil } 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 index 529c1257e5..18a836adbc 100644 --- a/staging/operator-lifecycle-manager/pkg/package-server/server/server_test.go +++ b/staging/operator-lifecycle-manager/pkg/package-server/server/server_test.go @@ -2,6 +2,7 @@ package server import ( "context" + "crypto/tls" "testing" apiconfigv1 "github.com/openshift/api/config/v1" @@ -56,7 +57,7 @@ func TestApplyClusterTLSProfileWithClients_NonOpenShift(t *testing.T) { } // TestApplyClusterTLSProfileWithClients_IntermediateProfile verifies that the -// Intermediate TLS profile populates MinTLSVersion and CipherSuites. +// Intermediate TLS profile populates MinTLSVersion, CipherSuites, and CurvePreferences. func TestApplyClusterTLSProfileWithClients_IntermediateProfile(t *testing.T) { apiServer := clusterAPIServer(&apiconfigv1.TLSSecurityProfile{ Type: apiconfigv1.TLSProfileIntermediateType, @@ -74,6 +75,9 @@ func TestApplyClusterTLSProfileWithClients_IntermediateProfile(t *testing.T) { if len(serving.CipherSuites) == 0 { t.Error("expected CipherSuites to be set for Intermediate profile") } + if len(serving.CurvePreferences) == 0 { + t.Error("expected CurvePreferences to be set for Intermediate profile") + } } // TestApplyClusterTLSProfileWithClients_ModernProfile verifies that the Modern @@ -105,6 +109,7 @@ func TestApplyClusterTLSProfileWithClients_FlagsTakePrecedence(t *testing.T) { // Simulate user-supplied flags. serving.MinTLSVersion = "VersionTLS12" serving.CipherSuites = []string{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"} + serving.CurvePreferences = []int32{int32(tls.CurveP256)} err := applyClusterTLSProfileWithClients(context.Background(), cfgClient.Discovery(), cfgClient, serving) if err != nil { @@ -116,6 +121,9 @@ func TestApplyClusterTLSProfileWithClients_FlagsTakePrecedence(t *testing.T) { 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) } + if len(serving.CurvePreferences) != 1 || serving.CurvePreferences[0] != int32(tls.CurveP256) { + t.Errorf("user-supplied CurvePreferences should not be overwritten, got %v", serving.CurvePreferences) + } } // TestApplyClusterTLSProfileWithClients_MissingAPIServerCR verifies that a diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/lib/apiserver/querier.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/lib/apiserver/querier.go index 94963890e1..153c4a303c 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/lib/apiserver/querier.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/lib/apiserver/querier.go @@ -14,7 +14,8 @@ func NoopQuerier() Querier { // Querier is an interface that wraps the QueryTLSConfig method. // // QueryTLSConfig updates the provided TLS configuration with cluster-wide -// TLS security profile settings (MinVersion, CipherSuites, PreferServerCipherSuites). +// TLS security profile settings (MinVersion, CipherSuites, CurvePreferences, +// PreferServerCipherSuites). type Querier interface { QueryTLSConfig(config *tls.Config) error } diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/lib/apiserver/syncer.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/lib/apiserver/syncer.go index 0d9e68a5d1..1d28c5295d 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/lib/apiserver/syncer.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/lib/apiserver/syncer.go @@ -131,7 +131,7 @@ func newTLSConfigHolder() *tlsConfigHolder { } // update atomically updates the stored TLS configuration. -func (h *tlsConfigHolder) update(minVersion uint16, cipherSuites []uint16) { +func (h *tlsConfigHolder) update(minVersion uint16, cipherSuites []uint16, curvePreferences []tls.CurveID) { h.mu.Lock() defer h.mu.Unlock() @@ -139,6 +139,7 @@ func (h *tlsConfigHolder) update(minVersion uint16, cipherSuites []uint16) { // Make a defensive copy of the slice h.config.CipherSuites = make([]uint16, len(cipherSuites)) copy(h.config.CipherSuites, cipherSuites) + h.config.CurvePreferences = append([]tls.CurveID(nil), curvePreferences...) h.config.PreferServerCipherSuites = true } @@ -152,6 +153,7 @@ func (h *tlsConfigHolder) copyTo(config *tls.Config) { config.MinVersion = h.config.MinVersion config.CipherSuites = make([]uint16, len(h.config.CipherSuites)) copy(config.CipherSuites, h.config.CipherSuites) + config.CurvePreferences = append([]tls.CurveID(nil), h.config.CurvePreferences...) config.PreferServerCipherSuites = h.config.PreferServerCipherSuites } @@ -177,21 +179,21 @@ func (w *Syncer) SyncAPIServer(object interface{}) error { } // Convert the TLS security profile to get new settings - minVersion, cipherSuites := GetSecurityProfileConfig(apiserver.Spec.TLSSecurityProfile) + minVersion, cipherSuites, curvePreferences := GetSecurityProfileConfig(apiserver.Spec.TLSSecurityProfile) // Check if configuration changed (before updating) - changed := w.hasConfigChanged(minVersion, cipherSuites) + changed := w.hasConfigChanged(minVersion, cipherSuites, curvePreferences) // Update the stored configuration atomically - w.currentConfig.update(minVersion, cipherSuites) + w.currentConfig.update(minVersion, cipherSuites, curvePreferences) // Log if configuration changed if changed { profileName := getProfileName(apiserver.Spec.TLSSecurityProfile) - w.logger.Infof("APIServer TLS configuration changed: profile=%s, minVersion=%s, cipherCount=%d", + w.logger.Infof("APIServer TLS configuration changed: profile=%s, minVersion=%s, cipherCount=%d, curvePreferences=%v", profileName, tlsVersionToString(minVersion), - len(cipherSuites)) + len(cipherSuites), curvePreferences) } return nil @@ -206,14 +208,15 @@ func (w *Syncer) HandleAPIServerDelete(object interface{}) { } // Reset to secure defaults (Intermediate profile) - w.currentConfig.update(GetSecurityProfileConfig(nil)) + minVersion, cipherSuites, curvePreferences := GetSecurityProfileConfig(nil) + w.currentConfig.update(minVersion, cipherSuites, curvePreferences) w.logger.Info("APIServer TLS configuration deleted, reverted to secure defaults") return } // hasConfigChanged checks if the new TLS settings differ from the current cached settings. -func (w *Syncer) hasConfigChanged(minVersion uint16, cipherSuites []uint16) bool { +func (w *Syncer) hasConfigChanged(minVersion uint16, cipherSuites []uint16, curvePreferences []tls.CurveID) bool { w.currentConfig.mu.RLock() defer w.currentConfig.mu.RUnlock() @@ -228,6 +231,14 @@ func (w *Syncer) hasConfigChanged(minVersion uint16, cipherSuites []uint16) bool return true } } + if len(w.currentConfig.config.CurvePreferences) != len(curvePreferences) { + return true + } + for i := range curvePreferences { + if w.currentConfig.config.CurvePreferences[i] != curvePreferences[i] { + return true + } + } return false } diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/lib/apiserver/tlsconfig.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/lib/apiserver/tlsconfig.go index 29a240b179..3955ebb432 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/lib/apiserver/tlsconfig.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/lib/apiserver/tlsconfig.go @@ -1,16 +1,19 @@ package apiserver import ( + "crypto/fips140" "crypto/tls" apiconfigv1 "github.com/openshift/api/config/v1" "github.com/openshift/library-go/pkg/crypto" + "k8s.io/klog/v2" ) -// GetSecurityProfileConfig extracts the minimum TLS version and cipher suites -// from a TLSSecurityProfile object. Converts OpenSSL cipher names to Go TLS cipher IDs. +// GetSecurityProfileConfig extracts the minimum TLS version, cipher suites, and +// curve preferences from a TLSSecurityProfile object. Converts OpenSSL cipher +// names and TLS groups to Go TLS identifiers. // If profile is nil, returns config defined by the Intermediate TLS Profile. -func GetSecurityProfileConfig(profile *apiconfigv1.TLSSecurityProfile) (uint16, []uint16) { +func GetSecurityProfileConfig(profile *apiconfigv1.TLSSecurityProfile) (uint16, []uint16, []tls.CurveID) { var profileType apiconfigv1.TLSProfileType if profile == nil { profileType = apiconfigv1.TLSProfileIntermediateType @@ -42,8 +45,40 @@ func GetSecurityProfileConfig(profile *apiconfigv1.TLSSecurityProfile) (uint16, // Convert OpenSSL cipher names to IANA names, then to Go cipher suite IDs ianaCipherNames := crypto.OpenSSLToIANACipherSuites(profileSpec.Ciphers) cipherSuites := CipherNamesToIDs(ianaCipherNames) + curvePreferences, unrecognizedGroups := crypto.TLSGroupsToCurveIDs(profileSpec.Groups) + if len(unrecognizedGroups) > 0 { + klog.Warningf("Dropping TLS groups not supported by this Go version: %v", unrecognizedGroups) + } + if fips140.Enabled() { + curvePreferences = filterFIPSCurvePreferences(curvePreferences) + if len(profileSpec.Groups) > 0 && len(curvePreferences) == 0 { + klog.Warning("No configured TLS groups are FIPS-approved; using the FIPS-safe P-curve fallback") + curvePreferences = fipsSafeCurvePreferences() + } + } + + return minTLSVersion, cipherSuites, curvePreferences +} - return minTLSVersion, cipherSuites +// filterFIPSCurvePreferences retains only the NIST P-curves. This deliberately +// excludes X25519MLKEM768 even though Go's native FIPS module permits it: OLM +// must also support the OpenSSL FIPS backend, which rejects its X25519 component. +func filterFIPSCurvePreferences(preferences []tls.CurveID) []tls.CurveID { + approved := make([]tls.CurveID, 0, len(preferences)) + for _, preference := range preferences { + switch preference { + case tls.CurveP256, tls.CurveP384, tls.CurveP521: + approved = append(approved, preference) + } + } + return approved +} + +// fipsSafeCurvePreferences returns the NIST P-curves used when FIPS filtering +// removes every configured TLS group. Returning a non-empty list preserves the +// configured restriction instead of allowing Go to select its defaults. +func fipsSafeCurvePreferences() []tls.CurveID { + return []tls.CurveID{tls.CurveP256, tls.CurveP384, tls.CurveP521} } // CipherNamesToIDs converts IANA cipher suite names to Go TLS cipher suite IDs @@ -73,6 +108,9 @@ func ApplySecureDefaults(config *tls.Config) error { if len(config.CipherSuites) == 0 { config.CipherSuites = crypto.DefaultCiphers() } + if len(config.CurvePreferences) == 0 { + _, _, config.CurvePreferences = GetSecurityProfileConfig(nil) + } config.PreferServerCipherSuites = true 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 e2ef2a86f8..8eb6a0efc2 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 @@ -374,7 +374,7 @@ func applyClusterTLSProfile(ctx context.Context, config *rest.Config, serving *g // 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). +// but only for fields not already set by explicit TLS flags. // 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) @@ -390,7 +390,7 @@ func applyClusterTLSProfileWithClients(ctx context.Context, discovery apidiscove return fmt.Errorf("failed to get APIServer config: %w", err) } - minVersion, cipherSuites := olmapiserver.GetSecurityProfileConfig(apiServer.Spec.TLSSecurityProfile) + minVersion, cipherSuites, curvePreferences := olmapiserver.GetSecurityProfileConfig(apiServer.Spec.TLSSecurityProfile) // Only override fields not already set by explicit flags. if serving.MinTLSVersion == "" { serving.MinTLSVersion = libcrypto.TLSVersionToNameOrDie(minVersion) @@ -398,6 +398,12 @@ func applyClusterTLSProfileWithClients(ctx context.Context, discovery apidiscove 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) + if len(serving.CurvePreferences) == 0 { + serving.CurvePreferences = make([]int32, len(curvePreferences)) + for i, curvePreference := range curvePreferences { + serving.CurvePreferences[i] = int32(curvePreference) + } + } + log.Infof("Applying cluster TLS security profile: minVersion=%s cipherSuites=%v curvePreferences=%v", serving.MinTLSVersion, serving.CipherSuites, serving.CurvePreferences) return nil }