Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pkg/lib/apiserver/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
27 changes: 19 additions & 8 deletions pkg/lib/apiserver/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,15 @@ 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()

h.config.MinVersion = minVersion
// 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
}

Expand All @@ -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
}

Expand All @@ -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
Expand All @@ -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()

Expand All @@ -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
}

Expand Down
6 changes: 3 additions & 3 deletions pkg/lib/apiserver/syncer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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")
Expand Down Expand Up @@ -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")
Expand Down
46 changes: 42 additions & 4 deletions pkg/lib/apiserver/tlsconfig.go
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
Comment thread
tmshort marked this conversation as resolved.
Resolved
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 {
Comment thread
perdasilva marked this conversation as resolved.
Resolved
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
Expand Down Expand Up @@ -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
}
Expand Down
13 changes: 7 additions & 6 deletions pkg/lib/apiserver/tlsconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand Down Expand Up @@ -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")
})
Expand Down
12 changes: 9 additions & 3 deletions pkg/package-server/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.

@fgiudici fgiudici Oct 23, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

olmapiserver.GetSecurityProfileConfig() is called by_applyClusterTLSProfileWithClients()_ which in turn is called only via applyClusterTLSProfile().
applyClusterTLSProfile() is guarded by if o.SecureServing.MinTLSVersion == "": so when --tls-min-version is set, packageserver never fetches the APIServer profile and leaves CurvePreferences empty, using Go’s default groups instead of the cluster allowlist.

This issue (getting olmapiserver.GetSecurityProfileConfig() in the call stack only when --tls-min-version is not set) predates this PR and could be addressed in a separate PR (if worth it).

// 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)
Expand All @@ -390,14 +390,20 @@ 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)
}
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 {
Comment thread
tmshort marked this conversation as resolved.
Resolved
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
}
10 changes: 9 additions & 1 deletion pkg/package-server/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package server

import (
"context"
"crypto/tls"
"testing"

apiconfigv1 "github.com/openshift/api/config/v1"
Expand Down Expand Up @@ -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.
Comment thread
tmshort marked this conversation as resolved.
Resolved
func TestApplyClusterTLSProfileWithClients_IntermediateProfile(t *testing.T) {
apiServer := clusterAPIServer(&apiconfigv1.TLSSecurityProfile{
Type: apiconfigv1.TLSProfileIntermediateType,
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down
Loading