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
31 changes: 31 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ import (
"kubevirt.io/kubevirt-migration-controller/internal/controller/multinamespacestoragemigplan"
storagemig "kubevirt.io/kubevirt-migration-controller/internal/controller/storagemig"
storagemigplan "kubevirt.io/kubevirt-migration-controller/internal/controller/storagemigplan"
componenthelpers "kubevirt.io/kubevirt-migration-controller/pkg/component-helpers"
migrationsv1alpha1 "kubevirt.io/kubevirt-migration-operator/api/v1alpha1"
// +kubebuilder:scaffold:imports
)

Expand All @@ -71,6 +73,7 @@ func init() {
utilruntime.Must(routev1.AddToScheme(scheme))
utilruntime.Must(ocpconfigv1.AddToScheme(scheme))
utilruntime.Must(migrations.AddToScheme(scheme))
utilruntime.Must(migrationsv1alpha1.AddToScheme(scheme))
// +kubebuilder:scaffold:scheme
}

Expand Down Expand Up @@ -124,6 +127,23 @@ func main() {
tlsOpts = append(tlsOpts, disableHTTP2)
}

managedTLSWatcher := componenthelpers.NewManagedTLSWatcher()

cryptoPolicyOpt := func(c *tls.Config) {
c.GetConfigForClient = func(t *tls.ClientHelloInfo) (*tls.Config, error) {
config := c.Clone()
if managedTLSWatcher != nil {
ctx := t.Context()
cc := managedTLSWatcher.GetTLSConfig(ctx)
config.CipherSuites = cc.CipherSuites
config.MinVersion = cc.MinVersion
}
return config, nil
}
}

tlsOpts = append(tlsOpts, cryptoPolicyOpt)

// Create watchers for metrics and webhooks certificates
var metricsCertWatcher, webhookCertWatcher *certwatcher.CertWatcher

Expand Down Expand Up @@ -261,6 +281,12 @@ func main() {
setupLog.Error(err, "unable to create controller", "controller", "MultiNamespaceStorageMigration")
os.Exit(1)
}

managedTLSWatcher.SetCache(mgr.GetCache())
if err := mgr.Add(managedTLSWatcher); err != nil {
setupLog.Error(err, "unable to add TLS watcher to manager")
os.Exit(1)
}
// +kubebuilder:scaffold:builder

if metricsCertWatcher != nil {
Expand Down Expand Up @@ -301,6 +327,11 @@ func main() {
func getCacheOptions(apiClient client.Client) cache.Options {
ns := getNamespace("/var/run/secrets/kubernetes.io/serviceaccount/namespace")

// MigController is intentionally not listed in ByObject.
// controller-runtime iterates ByObject entries at cache init
// time and calls apiutil.IsObjectNamespaced for each, which
// fails if the CRD is not registered in the API server.
// See: https://github.com/kubernetes-sigs/controller-runtime/issues/2456
cacheOptions := cache.Options{
ByObject: map[client.Object]cache.ByObject{
&v1.ConfigMap{}: {
Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ require (
github.com/onsi/gomega v1.38.2
github.com/openshift/api v0.0.0
github.com/openshift/library-go v0.0.0-20250128093732-a69305d8f397
github.com/prometheus/client_golang v1.22.0
github.com/prometheus/common v0.62.0
google.golang.org/grpc v1.68.1
k8s.io/api v0.34.1
k8s.io/apimachinery v0.34.1
Expand All @@ -24,6 +22,7 @@ require (
kubevirt.io/api v1.7.1
kubevirt.io/containerized-data-importer-api v1.63.1
kubevirt.io/kubevirt v1.7.1
kubevirt.io/kubevirt-migration-operator v0.2.0-rc.0.0.20260330115220-abf7dfe8b0a1
sigs.k8s.io/controller-runtime v0.20.4
)

Expand Down Expand Up @@ -66,7 +65,9 @@ require (
github.com/openshift/custom-resource-status v1.1.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spf13/cobra v1.8.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
Expand Down
7 changes: 2 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,6 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
Expand Down Expand Up @@ -221,8 +219,6 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
Expand Down Expand Up @@ -659,7 +655,6 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down Expand Up @@ -711,6 +706,8 @@ kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6
kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90/go.mod h1:018lASpFYBsYN6XwmA2TIrPCx6e0gviTd/ZNtSitKgc=
kubevirt.io/kubevirt v1.7.1 h1:rEt9dGvnjKD9FUsvDQA4Fvti4HXjKWfGcol6QkbXMHQ=
kubevirt.io/kubevirt v1.7.1/go.mod h1:Oupi7549kmxDCZD3KfNu/9poPhh6rKcdKHsGSMkWFWk=
kubevirt.io/kubevirt-migration-operator v0.2.0-rc.0.0.20260330115220-abf7dfe8b0a1 h1:9biKWjDWU59Y8D8CfkRtS53x3mXN9dnOSQIj9upc4Cw=
kubevirt.io/kubevirt-migration-operator v0.2.0-rc.0.0.20260330115220-abf7dfe8b0a1/go.mod h1:6Z56o3fIddPOAmEf7E6eKz/peQuxLdkFZDn+i3Azo1g=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU=
Expand Down
184 changes: 184 additions & 0 deletions pkg/component-helpers/tls_watcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
Copyright The KubeVirt Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package componenthelpers

import (
"context"
"crypto/tls"
"fmt"
"sync"

ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"

migrationsv1alpha1 "kubevirt.io/kubevirt-migration-operator/api/v1alpha1"
)

var (
tlsVersionMap = map[string]uint16{
"VersionTLS10": tls.VersionTLS10,
"VersionTLS11": tls.VersionTLS11,
"VersionTLS12": tls.VersionTLS12,
"VersionTLS13": tls.VersionTLS13,
}
cipherSuites = tls.CipherSuites()
extraCipherSuites = map[string]uint16{
"ECDHE-ECDSA-AES128-GCM-SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
"ECDHE-RSA-AES128-GCM-SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
"ECDHE-ECDSA-AES256-GCM-SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
"ECDHE-RSA-AES256-GCM-SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
"ECDHE-ECDSA-CHACHA20-POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
"ECDHE-RSA-CHACHA20-POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
"ECDHE-ECDSA-AES128-SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
"ECDHE-RSA-AES128-SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
"ECDHE-ECDSA-AES128-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
"ECDHE-RSA-AES128-SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
"ECDHE-ECDSA-AES256-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
"ECDHE-RSA-AES256-SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
"AES128-GCM-SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
"AES256-GCM-SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
"AES128-SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
"AES128-SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
"AES256-SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
"DES-CBC3-SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
}
cipherNameToID = func() map[string]uint16 {
m := make(map[string]uint16, len(cipherSuites)+len(extraCipherSuites))
for k, v := range extraCipherSuites {
m[k] = v
}
for _, cs := range cipherSuites {
m[cs.Name] = cs.ID
}
return m
}()

log = ctrl.Log.WithName("managed-tls-watcher")
)

type cryptoConfig struct {
CipherSuites []uint16
MinVersion uint16
}

type ManagedTLSWatcher struct {
mu sync.RWMutex
cache cache.Cache
defaultConfig *cryptoConfig
ready bool
}

func NewManagedTLSWatcher() *ManagedTLSWatcher {
return &ManagedTLSWatcher{
defaultConfig: cryptoConfigFromSpec(nil),
}
}

func (m *ManagedTLSWatcher) SetCache(c cache.Cache) {
m.mu.Lock()
defer m.mu.Unlock()
m.cache = c
}

func (m *ManagedTLSWatcher) Start(ctx context.Context) error {
m.mu.RLock()
c := m.cache
m.mu.RUnlock()

if c == nil {
return fmt.Errorf("no cache provided for tls watcher")
}
log.Info("ManagedTLSWatcher: starting, waiting for cache sync")
if !c.WaitForCacheSync(ctx) {
return fmt.Errorf("failed to wait for caches to sync")
}

list := &migrationsv1alpha1.MigControllerList{}
if err := c.List(ctx, list); err != nil {
log.Info("MigController CRD not available, using default TLS configuration", "error", err)
<-ctx.Done()
Comment on lines +110 to +113
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is not a part of the operator tls watcher impl. are we sure we need this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The reason for this is because we're unaware of whether the MigController CR even exists to begin with so we fallback if it's unavailable and let controller-runtime dynamically create the informer from default cache on first use.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ctx.Done() would just kill it, no?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It won't be dynamic that's for sure... but I think it's worth arguing that we won't ever hit the dynamic TLS usecase without a MigController CRD installed, so we could probably get away with conditionally setting the TLSWatcher to ready.

return nil
}

m.mu.Lock()
m.ready = true
m.mu.Unlock()
log.Info("ManagedTLSWatcher: ready")

<-ctx.Done()
return nil
}

func (m *ManagedTLSWatcher) NeedLeaderElection() bool {
return false
}

func (m *ManagedTLSWatcher) GetTLSConfig(ctx context.Context) *cryptoConfig {
m.mu.RLock()
ready := m.ready
c := m.cache
m.mu.RUnlock()

if !ready || c == nil {
return m.defaultConfig
}

list := &migrationsv1alpha1.MigControllerList{}
if err := c.List(ctx, list); err != nil || len(list.Items) == 0 {
return m.defaultConfig
}

return cryptoConfigFromSpec(list.Items[0].Spec.TLSSecurityProfile)
}

func cryptoConfigFromSpec(profile *migrationsv1alpha1.TLSSecurityProfile) *cryptoConfig {
cipherNames, minTypedTLSVersion := selectCipherSuitesAndMinTLSVersion(profile)
minTLSVersion, ok := tlsVersionMap[string(minTypedTLSVersion)]
if !ok {
log.Info("unknown TLS version %q, defaulting to TLS 1.2", minTypedTLSVersion)
minTLSVersion = tls.VersionTLS12
}
return &cryptoConfig{
CipherSuites: cipherSuitesIDs(cipherNames),
MinVersion: minTLSVersion,
}
}

func selectCipherSuitesAndMinTLSVersion(
profile *migrationsv1alpha1.TLSSecurityProfile,
) ([]string, migrationsv1alpha1.TLSProtocolVersion) {
if profile == nil {
profile = &migrationsv1alpha1.TLSSecurityProfile{
Type: migrationsv1alpha1.TLSProfileIntermediateType,
Intermediate: &migrationsv1alpha1.IntermediateTLSProfile{},
}
}
if profile.Custom != nil {
return profile.Custom.TLSProfileSpec.Ciphers, profile.Custom.TLSProfileSpec.MinTLSVersion
}
return migrationsv1alpha1.TLSProfiles[profile.Type].Ciphers, migrationsv1alpha1.TLSProfiles[profile.Type].MinTLSVersion
}

func cipherSuitesIDs(names []string) []uint16 {
var ids []uint16
for _, name := range names {
if id, ok := cipherNameToID[name]; ok {
ids = append(ids, id)
}
}
return ids
}
Loading