Skip to content
Open
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
157 changes: 148 additions & 9 deletions cmd/cluster-network-operator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,31 @@ import (
"fmt"
"os"

operatorv1alpha1 "github.com/openshift/api/operator/v1alpha1"
cnoclient "github.com/openshift/cluster-network-operator/pkg/client"
"github.com/openshift/cluster-network-operator/pkg/hypershift"
"github.com/openshift/cluster-network-operator/pkg/names"
"github.com/openshift/cluster-network-operator/pkg/network"
"github.com/openshift/cluster-network-operator/pkg/operator"
"github.com/openshift/cluster-network-operator/pkg/version"
libgoclient "github.com/openshift/library-go/pkg/config/client"
"github.com/openshift/library-go/pkg/controller/controllercmd"
"github.com/openshift/library-go/pkg/crypto"
"github.com/openshift/library-go/pkg/operator/events"
"github.com/openshift/library-go/pkg/serviceability"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/apiserver/pkg/server"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"

"k8s.io/client-go/rest"
utilflag "k8s.io/component-base/cli/flag"
"k8s.io/component-base/logs"
"k8s.io/klog/v2"
"k8s.io/utils/clock"
)

const componentName = "network-operator"

func main() {
pflag.CommandLine.SetNormalizeFunc(utilflag.WordSepNormalizeFunc)
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
Expand All @@ -44,20 +56,147 @@ func newNetworkOperatorCommand() *cobra.Command {
os.Exit(1)
},
}
var extraClusters *map[string]string
var inClusterClientName *string
cmdcfg := controllercmd.NewControllerCommandConfig("network-operator", version.Get(), func(ctx context.Context, controllerConfig *controllercmd.ControllerContext) error {
return operator.RunOperator(ctx, controllerConfig, *inClusterClientName, *extraClusters)
}, clock.RealClock{})

cmd2 := cmdcfg.NewCommand()
cmdcfg := controllercmd.NewControllerCommandConfig(componentName, version.Get(), nil, clock.RealClock{})

cmd2 := newCommandWithTLSCustomization(cmdcfg)
cmd2.Use = "start"
cmd2.Short = "Start the cluster network operator"
extraClusters = cmd2.Flags().StringToString("extra-clusters", nil, "extra clusters, pairs of cluster name and kubeconfig path")
inClusterClientName = cmd2.Flags().String("in-cluster-client-name", names.DefaultClusterName, "client name for in-cluster config(service account or kubeconfig)")
cmd.AddCommand(cmd2)

cmd.AddCommand(newMTUProberCommand())

return cmd
}

// newCommandWithTLSCustomization creates a custom command that allows customizing TLS
// settings based on the cluster's TLS security profile
func newCommandWithTLSCustomization(cmdcfg *controllercmd.ControllerCommandConfig) *cobra.Command {
cmd := cmdcfg.NewCommandWithContext(context.Background())

// Add custom flags
var extraClusters map[string]string
var inClusterClientName string
cmd.Flags().StringToStringVar(&extraClusters, "extra-clusters", nil, "extra clusters, pairs of cluster name and kubeconfig path")
cmd.Flags().StringVar(&inClusterClientName, "in-cluster-client-name", names.DefaultClusterName, "client name for in-cluster config(service account or kubeconfig)")

// Replace with custom Run that intercepts to customize TLS
cmd.Run = func(cmd *cobra.Command, args []string) {
// Standard boilerplate from library-go
logs.InitLogs()

ctx := server.SetupSignalContext()

defer logs.FlushLogs()
defer serviceability.BehaviorOnPanic(os.Getenv("OPENSHIFT_ON_PANIC"), version.Get())()
defer serviceability.Profile(os.Getenv("OPENSHIFT_PROFILE")).Stop()

serviceability.StartProfiler()

// Get kubeconfig and namespace from the parsed flags. Unfortunately we can't access cmdcfg.basicFlags directly.
kubeConfigFile, _ := cmd.Flags().GetString("kubeconfig")
namespace, _ := cmd.Flags().GetString("namespace")

if err := startControllerWithTLSCustomization(ctx, cmdcfg, extraClusters, inClusterClientName, kubeConfigFile, namespace); err != nil {
klog.Fatal(err)
}
}

return cmd
}

// startControllerWithTLSCustomization starts the controller with customized TLS settings
func startControllerWithTLSCustomization(ctx context.Context, cmdcfg *controllercmd.ControllerCommandConfig,
extraClusters map[string]string, inClusterClientName string, kubeConfigFile string, namespace string) error {
// Get the base config
unstructuredConfig, config, configContent, err := cmdcfg.Config()
if err != nil {
return err
}

// Let library-go set up certificate rotation (handles service-serving-cert)
startingFileContent, observedFiles, err := cmdcfg.AddDefaultRotationToConfig(config, configContent)
if err != nil {
return err
}

// Customize TLS settings based on cluster profile
if err := applyClusterTLSProfile(ctx, config, kubeConfigFile, inClusterClientName, extraClusters); err != nil {
return fmt.Errorf("failed to apply cluster TLS profile: %w", err)
}

controllerCtx, cancel := context.WithCancel(ctx)
defer cancel()

// exitOnChangeReactorCh is used by the file watcher to trigger restart on cert file changes
exitOnChangeReactorCh := make(chan struct{})
go func() {
<-exitOnChangeReactorCh
klog.Infof("Certificate file change detected, triggering graceful restart")
cancel()
}()

// Create startFunc that passes the cancel function to RunOperator
// The TLS controller will call cancel() when TLS profile changes are detected
startFunc := func(ctx context.Context, controllerConfig *controllercmd.ControllerContext) error {
return operator.RunOperator(ctx, controllerConfig, inClusterClientName, extraClusters, cancel)
}

// Build the controller with our customized ServingInfo
builder := controllercmd.NewController(componentName, startFunc, clock.RealClock{}).
WithKubeConfigFile(kubeConfigFile, nil).
WithComponentNamespace(namespace).
WithLeaderElection(config.LeaderElection, namespace, componentName+"-lock").
WithVersion(version.Get()).
WithEventRecorderOptions(events.RecommendedClusterSingletonCorrelatorOptions()).
WithRestartOnChange(exitOnChangeReactorCh, startingFileContent, observedFiles...).
WithComponentOwnerReference(cmdcfg.ComponentOwnerReference).
WithServer(config.ServingInfo, config.Authentication, config.Authorization)

return builder.Run(controllerCtx, unstructuredConfig)
}

// applyClusterTLSProfile fetches the cluster's TLS security profile and applies it to the config
func applyClusterTLSProfile(ctx context.Context, config *operatorv1alpha1.GenericOperatorConfig, kubeConfigFile string, inClusterClientName string, extraClusters map[string]string) error {
restConfig, err := libgoclient.GetKubeConfigOrInClusterConfig(kubeConfigFile, nil)
if err != nil {
return fmt.Errorf("failed to build kubeconfig: %w", err)
}

// Create protoConfig for performance (used by kubernetes.Interface)
protoConfig := rest.CopyConfig(restConfig)
protoConfig.AcceptContentTypes = "application/vnd.kubernetes.protobuf,application/json"
protoConfig.ContentType = "application/vnd.kubernetes.protobuf"

client, err := cnoclient.NewClient(restConfig, protoConfig, inClusterClientName, extraClusters)
if err != nil {
return fmt.Errorf("failed to create CNO client: %w", err)
}

// Fetch HostedControlPlane for HyperShift (if applicable)
hcp, err := hypershift.GetHostedControlPlane(client)
if err != nil {
return fmt.Errorf("failed to get HostedControlPlane: %w", err)
}

// Fetch TLS profile using network.GetTLSProfile (handles both standalone and HyperShift)
tlsProfile, err := network.GetTLSProfile(client, hcp)
if err != nil {
return fmt.Errorf("failed to get TLS profile: %w", err)
}

// Check if we should honor the cluster TLS profile
if !crypto.ShouldHonorClusterTLSProfile(tlsProfile.Adherence) {
klog.Infof("TLS adherence policy is %q, using default TLS settings", tlsProfile.Adherence)
return nil
}

// Apply the TLS settings to the serving config
config.ServingInfo.MinTLSVersion = string(tlsProfile.Spec.MinTLSVersion)
config.ServingInfo.CipherSuites = crypto.OpenSSLToIANACipherSuites(tlsProfile.Spec.Ciphers)

klog.Infof("Applied cluster TLS profile: minTLSVersion=%s, ciphers=%v",
tlsProfile.Spec.MinTLSVersion, tlsProfile.Spec.Ciphers)

return nil
}
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ require (
k8s.io/klog/v2 v2.130.1
k8s.io/kube-proxy v0.35.2
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2
sigs.k8s.io/controller-runtime v0.23.1
sigs.k8s.io/controller-runtime v0.23.3
)

require (
Expand Down Expand Up @@ -102,9 +102,11 @@ require (
require (
github.com/openshift/api v0.0.0-20260609121705-d3390bd1109f
github.com/openshift/client-go v0.0.0-20260603140539-6892dc3e1ffc
github.com/openshift/library-go v0.0.0-20260303171201-5d9eb6295ff6
github.com/openshift/controller-runtime-common v0.0.0-20260428152732-64ee174f5e2e
github.com/openshift/library-go v0.0.0-20260318140748-04979c746b4d
github.com/openshift/machine-config-operator v0.0.1-0.20250724162154-ab14c8e2843b
k8s.io/apiextensions-apiserver v0.35.2
k8s.io/apiserver v0.35.2
k8s.io/client-go v0.35.2
sigs.k8s.io/controller-tools v0.20.1
)
Expand Down Expand Up @@ -140,7 +142,6 @@ require (
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/onsi/ginkgo/v2 v2.28.1 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/stretchr/objx v0.5.3 // indirect
Expand All @@ -154,7 +155,6 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
k8s.io/apiserver v0.35.2 // indirect
k8s.io/gengo/v2 v2.0.0-20251215205346-5ee0d033ba5b // indirect
k8s.io/kms v0.35.2 // indirect
k8s.io/kube-aggregator v0.35.1 // indirect
Expand Down
10 changes: 6 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,10 @@ 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-20260603140539-6892dc3e1ffc h1:yCLc/pmoZ4YZbMWlAnvYZ2YWkLZoPCilO4Fk/oAu2/E=
github.com/openshift/client-go v0.0.0-20260603140539-6892dc3e1ffc/go.mod h1:eqfaEX/V7xHMZ8Mpf72J03RnnY/kEqoZVLpkpjy5p6s=
github.com/openshift/library-go v0.0.0-20260303171201-5d9eb6295ff6 h1:xjqy0OolrFdJ+ofI/aD0+2k9+MSk5anP5dXifFt539Q=
github.com/openshift/library-go v0.0.0-20260303171201-5d9eb6295ff6/go.mod h1:D797O/ssKTNglbrGchjIguFq+DbyRYdeds5w4/VTrKM=
github.com/openshift/controller-runtime-common v0.0.0-20260428152732-64ee174f5e2e h1:k89oIo2EjX0PRSdi1kesktCyWp50SC9WwKurvupvRGs=
github.com/openshift/controller-runtime-common v0.0.0-20260428152732-64ee174f5e2e/go.mod h1:XGabTMnNbz0M5Oa7IbscZp/jmcc7aHobvOCUWwkzKvM=
github.com/openshift/library-go v0.0.0-20260318140748-04979c746b4d h1:i3STSVfFi+39gOAramKPySB6WrvuD0rAIssSNOnTwyg=
github.com/openshift/library-go v0.0.0-20260318140748-04979c746b4d/go.mod h1:3bi4pLpYRdVd1aEhsHfRTJkwxwPLfRZ+ZePn3RmJd2k=
github.com/openshift/machine-config-operator v0.0.1-0.20250724162154-ab14c8e2843b h1:LvoFr/2IEj0BWy7mKBdR7ueAHpMJGju1EkEIZrXa+DM=
github.com/openshift/machine-config-operator v0.0.1-0.20250724162154-ab14c8e2843b/go.mod h1:UL1OVkRAUkB4aaFZrLlSvuY0jayfdF+o+ZxKiKaaArc=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
Expand Down Expand Up @@ -482,8 +484,8 @@ k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0x
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
sigs.k8s.io/controller-runtime v0.23.1 h1:TjJSM80Nf43Mg21+RCy3J70aj/W6KyvDtOlpKf+PupE=
sigs.k8s.io/controller-runtime v0.23.1/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0=
sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80=
sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0=
sigs.k8s.io/controller-tools v0.20.1 h1:gkfMt9YodI0K85oT8rVi80NTXO/kDmabKR5Ajn5GYxs=
sigs.k8s.io/controller-tools v0.20.1/go.mod h1:b4qPmjGU3iZwqn34alUU5tILhNa9+VXK+J3QV0fT/uU=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
Expand Down
6 changes: 6 additions & 0 deletions pkg/bootstrap/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ type BootstrapResult struct {

OVN OVNBootstrapResult
IPTablesAlerter IPTablesAlerterBootstrapResult
TLSProfile TLSProfile
}

type InfraStatus struct {
Expand Down Expand Up @@ -176,3 +177,8 @@ type FlowsConfig struct {
// Sampling is the sampling rate on the reporter. 100 means one flow on 100 is sent. 0 means disabled.
Sampling *uint
}

type TLSProfile struct {
Spec configv1.TLSProfileSpec
Adherence configv1.TLSAdherencePolicy
}
13 changes: 12 additions & 1 deletion pkg/client/fake/fake_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,20 @@ func NewFakeClient(objs ...crclient.Object) cnoclient.Client {
crclient: crfake.NewClientBuilder().WithStatusSubresource(co, proxy).WithObjects(objs...).Build(),
osOperClient: osoperfakeclient.NewClientset(),
}

// Create a management cluster client for HyperShift scenarios
// This represents a separate cluster, so it starts empty
managementClient := FakeClusterClient{
kClient: faketyped.NewClientset(),
dynclient: fakedynamic.NewSimpleDynamicClient(scheme.Scheme),
crclient: crfake.NewClientBuilder().WithStatusSubresource(co).Build(),
osOperClient: osoperfakeclient.NewClientset(),
}

return &FakeClient{
clusterClients: map[string]*FakeClusterClient{
names.DefaultClusterName: &fc,
names.DefaultClusterName: &fc,
names.ManagementClusterName: &managementClient,
},
}
}
Expand Down
Loading