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
107 changes: 95 additions & 12 deletions cmd/cluster-node-tuning-operator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
paocontroller "github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller"
"github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/components/handler"
"github.com/openshift/cluster-node-tuning-operator/pkg/performanceprofile/controller/performanceprofile/status"
tlspkg "github.com/openshift/controller-runtime-common/pkg/tls"
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
"github.com/openshift/library-go/pkg/operator/events"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -122,16 +123,6 @@ func operatorRun() {
return
}

tlsOpts := []func(*tls.Config){
func(c *tls.Config) {
// CVE-2023-44487
c.NextProtos = []string{"http/1.1"}
// Default minimum version is TLS 1.3. PQ algorithms will only be supported in TLS 1.3+.
// Hybrid key agreements for TLS 1.3 X25519MLKEM768 is supported by default in go 1.24.
c.MinVersion = tls.VersionTLS13
},
}

// We have two namespaces that we need to watch:
// 1. NTO namespace: for NTO resources. Note this is not necessarily where the operator itself
// runs, for example operator managing HyperShift hosted clusters.
Expand All @@ -142,6 +133,20 @@ func operatorRun() {
metav1.NamespaceNone: {},
}
restConfig := ctrl.GetConfigOrDie()

tlsSecurityProfileSpec, tlsConfig := getInitialTLSProfile(restConfig)
tlsOpts := []func(*tls.Config){
func(c *tls.Config) {
// CVE-2023-44487
c.NextProtos = []string{"http/1.1"}
},
tlsConfig,
}

// Create a context that can be cancelled when there is a need to shut down the manager.
ctx, cancel := context.WithCancel(ctrl.SetupSignalHandler())
defer cancel()

le := util.GetLeaderElectionConfig(restConfig, enableLeaderElection)
mgr, err := ctrl.NewManager(rest.AddUserAgent(restConfig, version.OperatorFilename), ctrl.Options{
Cache: cache.Options{DefaultNamespaces: namespaces},
Expand Down Expand Up @@ -175,6 +180,9 @@ func operatorRun() {
klog.Fatalf("failed to add new controller to the manager: %v", err)
}

// Configure the metrics server to use the central TLS profile.
metrics.SetTLSConfigFunc(tlsConfig)

if err := mgr.Add(metrics.Server{}); err != nil {
klog.Fatalf("unable to add metrics server as runnable under the manager: %v", err)
}
Expand Down Expand Up @@ -249,9 +257,20 @@ func operatorRun() {
klog.Exitf("unable to create PerformanceProfile controller: %v", err)
}
}
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
klog.Exitf("manager exited with non-zero code: %v", err)

// Set up the TLS security profile watcher controller for both classic and HyperShift clusters.
// This will trigger a graceful shutdown when the TLS profile changes.
// In classic mode, this watches the local cluster's APIServer.
// In HyperShift mode, this watches the hosted cluster's APIServer.
if err := setupTLSProfileWatcher(mgr, cancel, tlsSecurityProfileSpec); err != nil {
klog.Fatalf("unable to create TLS security profile watcher controller: %v", err)
}

Comment thread
jmencak marked this conversation as resolved.
if err := mgr.Start(ctx); err != nil {
klog.Exitf("manager stopped with an error: %v", err)
}

klog.Infof("manager stopped cleanly")
}

func setupFeatureGates(ctx context.Context, config *rest.Config, operatorNamespace string) (featuregates.FeatureGate, error) {
Expand Down Expand Up @@ -296,6 +315,70 @@ func setupFeatureGates(ctx context.Context, config *rest.Config, operatorNamespa
return featureGateAccessor.CurrentFeatureGates()
}

// Try to fetch the TLS profile from the APIServer resource.
// Fall back to the default (intermediate) profile in case of errors.
// In HyperShift mode, the restConfig will point to the hosted cluster,
// so this function will fetch the TLS profile from the hosted cluster's APIServer.
//
// Note: According to the OpenShift centralized TLS configuration enhancement, SLOs
// *should* read the TLS profile from the `HostedCluster` CR spec in the management KAS.
// However, at the time of the implementation there is no library for doing this and it
// would make the implementation less readable. Reconsider fetching the HostedCluster
// object and getting the TLSProfile from there once there are official libraries supporting
// this.
func getInitialTLSProfile(restConfig *rest.Config) (apiconfigv1.TLSProfileSpec, func(*tls.Config)) {
// Helper function to get the default TLS profile.
getDefaultTLSProfile := func() apiconfigv1.TLSProfileSpec {
tlsSecurityProfileSpec, err := tlspkg.GetTLSProfileSpec(nil)
if err != nil {
// With the current implementation of GetTLSProfileSpec this should never happen, but be defensive.
klog.Fatalf("unable to get default TLS profile: %v", err)
}
return tlsSecurityProfileSpec
}

var tlsSecurityProfileSpec apiconfigv1.TLSProfileSpec

k8sClient, err := client.New(restConfig, client.Options{Scheme: scheme})
if err != nil {
klog.Warningf("unable to create Kubernetes client for TLS profile fetch, falling back to default TLS profile: %v", err)
tlsSecurityProfileSpec = getDefaultTLSProfile()
} else {
klog.V(2).Infof("Fetching TLS profile from APIServer resource")
tlsSecurityProfileSpec, err = tlspkg.FetchAPIServerTLSProfile(context.Background(), k8sClient)
if err != nil {
klog.Warningf("unable to get TLS profile from API server, falling back to default TLS profile: %v", err)
tlsSecurityProfileSpec = getDefaultTLSProfile()
}
}

// Create the TLS configuration function for the webhook and metrics servers.
tlsConfig, unsupportedCiphers := tlspkg.NewTLSConfigFromProfile(tlsSecurityProfileSpec)
if len(unsupportedCiphers) > 0 {
klog.Warningf("TLS configuration contains unsupported ciphers that will be ignored: %v", unsupportedCiphers)
}

klog.Infof("TLS security profile: MinTLSVersion=%s, Ciphers=%v", tlsSecurityProfileSpec.MinTLSVersion, tlsSecurityProfileSpec.Ciphers)

return tlsSecurityProfileSpec, tlsConfig
}

func setupTLSProfileWatcher(mgr ctrl.Manager, cancel context.CancelFunc, tlsSecurityProfileSpec apiconfigv1.TLSProfileSpec) error {
watcher := &tlspkg.SecurityProfileWatcher{
Client: mgr.GetClient(),
InitialTLSProfileSpec: tlsSecurityProfileSpec,
OnProfileChange: func(ctx context.Context, oldTLSProfileSpec, newTLSProfileSpec apiconfigv1.TLSProfileSpec) {
klog.Infof("TLS profile has changed, initiating a shutdown to reload it. %q: %+v, %q: %+v",
"old profile", oldTLSProfileSpec,
"new profile", newTLSProfileSpec,
)
cancel()
},
}

return watcher.SetupWithManager(mgr)
}

func main() {
prepareCommands()
if err := rootCmd.Execute(); err != nil {
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ require (
github.com/google/go-cmp v0.7.0
github.com/jaypipes/ghw v0.20.0
github.com/kevinburke/go-bindata v3.24.0+incompatible
github.com/onsi/ginkgo/v2 v2.28.0
github.com/onsi/ginkgo/v2 v2.28.1
github.com/onsi/gomega v1.39.1
github.com/openshift-eng/openshift-tests-extension v0.0.0-20260127124016-0fed2b824818
github.com/openshift-kni/debug-tools v0.2.6
github.com/openshift-kni/k8sreporter v1.0.7
github.com/openshift/api v0.0.0-20260223154456-de86ee3bf481
github.com/openshift/build-machinery-go v0.0.0-20251023084048-5d77c1a5e5af
github.com/openshift/client-go v0.0.0-20260219131751-7e63ce155298
github.com/openshift/controller-runtime-common v0.0.0-20260213175913-767fef058eca
github.com/openshift/custom-resource-status v1.1.2
github.com/openshift/hypershift/api v0.0.0-20260224085943-34e30acde920
github.com/openshift/library-go v0.0.0-20260223145824-7b234b47a906
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ github.com/openshift/build-machinery-go v0.0.0-20251023084048-5d77c1a5e5af h1:Ui
github.com/openshift/build-machinery-go v0.0.0-20251023084048-5d77c1a5e5af/go.mod h1:8jcm8UPtg2mCAsxfqKil1xrmRMI3a+XU2TZ9fF8A7TE=
github.com/openshift/client-go v0.0.0-20260219131751-7e63ce155298 h1:V8uz/2Z4hh+49TUxl0nYs0sDn1fB7lTdSMDKAa6TFY8=
github.com/openshift/client-go v0.0.0-20260219131751-7e63ce155298/go.mod h1:rtH0BhilT6+jn3nWybANEumaBO1vWCKaY8QpwipRy/Y=
github.com/openshift/controller-runtime-common v0.0.0-20260213175913-767fef058eca h1:EOc/lbyZxtn1b/BvRwnLpbgBg0F+2RUQd3xMiA4/JsQ=
github.com/openshift/controller-runtime-common v0.0.0-20260213175913-767fef058eca/go.mod h1:59nLF3/IfhAtoQZfUzlCyidTAdlVT6KiVeTicUi2wuA=
github.com/openshift/custom-resource-status v1.1.2 h1:C3DL44LEbvlbItfd8mT5jWrqPfHnSOQoQf/sypqA6A4=
github.com/openshift/custom-resource-status v1.1.2/go.mod h1:DB/Mf2oTeiAmVVX1gN+NEqweonAPY0TKUwADizj8+ZA=
github.com/openshift/hypershift/api v0.0.0-20260224085943-34e30acde920 h1:9qdggBqqBlO7EuRnmTTH+yxNFRsPtU4SoSKLh7WBios=
Expand Down
4 changes: 4 additions & 0 deletions manifests/40-rbac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ rules:
- apiGroups: ["config.openshift.io"]
resources: ["clusterversions", "featuregates"]
verbs: ["list", "watch"]
# Needed to for the centralized TLS profile
- apiGroups: ["config.openshift.io"]
resources: ["apiservers"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["replicasets"]
verbs: ["get"]
Expand Down
29 changes: 15 additions & 14 deletions pkg/metrics/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ const (
)

type Server struct {
caBundle string
caBundleCh chan string
caBundle string
caBundleCh chan string
tlsConfigFunc func(*tls.Config)
}

var (
Expand All @@ -48,6 +49,12 @@ func init() {
server = Server{caBundleCh: make(chan string)}
}

// SetTLSConfigFunc sets the TLS configuration function to be used by the metrics server.
// This should be called before starting the server.
func SetTLSConfigFunc(tlsConfigFunc func(*tls.Config)) {
server.tlsConfigFunc = tlsConfigFunc
}

// In classic clusters, DumpCA stores the root certificate bundle which is used to verify
// metric server client certificates. It uses an unbuffered channel to store the data and
// it does so only if the CA bundle changed from the current CA bundle on record.
Expand Down Expand Up @@ -75,19 +82,13 @@ func buildServer(port int, caBundle string) *http.Server {
caCertPool := x509.NewCertPool()
var clientAuthHandler http.Handler = handler

// Default minimum version is TLS 1.3. PQ algorithms will only be supported in TLS 1.3+.
// Hybrid key agreements for TLS 1.3 X25519MLKEM768 is supported by default in go 1.24.
tlsConfig.MinVersion = tls.VersionTLS13
tlsConfig.CipherSuites = []uint16{
// Drop
// - 64-bit block cipher 3DES as it is vulnerable to SWEET32 attack.
// - CBC encryption method.
// - RSA key exchange.
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
// Apply the TLS configuration from the central TLS profile.
if server.tlsConfigFunc != nil {
server.tlsConfigFunc(tlsConfig)
}
tlsConfig.NextProtos = []string{"http/1.1"} // CVE-2023-44487

// CVE-2023-44487 mitigation.
tlsConfig.NextProtos = []string{"http/1.1"}

if len(caBundle) > 0 {
if caCertPool.AppendCertsFromPEM([]byte(caBundle)) {
Expand Down
Loading