Skip to content

Commit 419bb54

Browse files
committed
component-helpers, tls_watcher: Add ManagedTLSWatcher
ManagedTLSWatcher watches the MigController CR's tlsSecurityProfile to dynamically update the TLS configuration for incoming connections on exposed endpoints. Signed-off-by: Adi Aloni <aaloni@redhat.com>
1 parent 3b6d3ef commit 419bb54

File tree

1 file changed

+184
-0
lines changed

1 file changed

+184
-0
lines changed
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/*
2+
Copyright The KubeVirt Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package componenthelpers
18+
19+
import (
20+
"context"
21+
"crypto/tls"
22+
"fmt"
23+
"sync"
24+
25+
ctrl "sigs.k8s.io/controller-runtime"
26+
"sigs.k8s.io/controller-runtime/pkg/cache"
27+
28+
migrationsv1alpha1 "kubevirt.io/kubevirt-migration-operator/api/v1alpha1"
29+
)
30+
31+
var (
32+
tlsVersionMap = map[string]uint16{
33+
"VersionTLS10": tls.VersionTLS10,
34+
"VersionTLS11": tls.VersionTLS11,
35+
"VersionTLS12": tls.VersionTLS12,
36+
"VersionTLS13": tls.VersionTLS13,
37+
}
38+
cipherSuites = tls.CipherSuites()
39+
extraCipherSuites = map[string]uint16{
40+
"ECDHE-ECDSA-AES128-GCM-SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
41+
"ECDHE-RSA-AES128-GCM-SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
42+
"ECDHE-ECDSA-AES256-GCM-SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
43+
"ECDHE-RSA-AES256-GCM-SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
44+
"ECDHE-ECDSA-CHACHA20-POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
45+
"ECDHE-RSA-CHACHA20-POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
46+
"ECDHE-ECDSA-AES128-SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
47+
"ECDHE-RSA-AES128-SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
48+
"ECDHE-ECDSA-AES128-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
49+
"ECDHE-RSA-AES128-SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
50+
"ECDHE-ECDSA-AES256-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
51+
"ECDHE-RSA-AES256-SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
52+
"AES128-GCM-SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
53+
"AES256-GCM-SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
54+
"AES128-SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
55+
"AES128-SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
56+
"AES256-SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
57+
"DES-CBC3-SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
58+
}
59+
cipherNameToID = func() map[string]uint16 {
60+
m := make(map[string]uint16, len(cipherSuites)+len(extraCipherSuites))
61+
for k, v := range extraCipherSuites {
62+
m[k] = v
63+
}
64+
for _, cs := range cipherSuites {
65+
m[cs.Name] = cs.ID
66+
}
67+
return m
68+
}()
69+
70+
log = ctrl.Log.WithName("managed-tls-watcher")
71+
)
72+
73+
type cryptoConfig struct {
74+
CipherSuites []uint16
75+
MinVersion uint16
76+
}
77+
78+
type ManagedTLSWatcher struct {
79+
mu sync.RWMutex
80+
cache cache.Cache
81+
defaultConfig *cryptoConfig
82+
ready bool
83+
}
84+
85+
func NewManagedTLSWatcher() *ManagedTLSWatcher {
86+
return &ManagedTLSWatcher{
87+
defaultConfig: cryptoConfigFromSpec(nil),
88+
}
89+
}
90+
91+
func (m *ManagedTLSWatcher) SetCache(c cache.Cache) {
92+
m.mu.Lock()
93+
defer m.mu.Unlock()
94+
m.cache = c
95+
}
96+
97+
func (m *ManagedTLSWatcher) Start(ctx context.Context) error {
98+
m.mu.RLock()
99+
c := m.cache
100+
m.mu.RUnlock()
101+
102+
if c == nil {
103+
return fmt.Errorf("no cache provided for tls watcher")
104+
}
105+
log.Info("ManagedTLSWatcher: starting, waiting for cache sync")
106+
if !c.WaitForCacheSync(ctx) {
107+
return fmt.Errorf("failed to wait for caches to sync")
108+
}
109+
110+
list := &migrationsv1alpha1.MigControllerList{}
111+
if err := c.List(ctx, list); err != nil {
112+
log.Info("MigController CRD not available, using default TLS configuration", "error", err)
113+
<-ctx.Done()
114+
return nil
115+
}
116+
117+
m.mu.Lock()
118+
m.ready = true
119+
m.mu.Unlock()
120+
log.Info("ManagedTLSWatcher: ready")
121+
122+
<-ctx.Done()
123+
return nil
124+
}
125+
126+
func (m *ManagedTLSWatcher) NeedLeaderElection() bool {
127+
return false
128+
}
129+
130+
func (m *ManagedTLSWatcher) GetTLSConfig(ctx context.Context) *cryptoConfig {
131+
m.mu.RLock()
132+
ready := m.ready
133+
c := m.cache
134+
m.mu.RUnlock()
135+
136+
if !ready || c == nil {
137+
return m.defaultConfig
138+
}
139+
140+
list := &migrationsv1alpha1.MigControllerList{}
141+
if err := c.List(ctx, list); err != nil || len(list.Items) == 0 {
142+
return m.defaultConfig
143+
}
144+
145+
return cryptoConfigFromSpec(list.Items[0].Spec.TLSSecurityProfile)
146+
}
147+
148+
func cryptoConfigFromSpec(profile *migrationsv1alpha1.TLSSecurityProfile) *cryptoConfig {
149+
cipherNames, minTypedTLSVersion := selectCipherSuitesAndMinTLSVersion(profile)
150+
minTLSVersion, ok := tlsVersionMap[string(minTypedTLSVersion)]
151+
if !ok {
152+
log.Info("unknown TLS version %q, defaulting to TLS 1.2", minTypedTLSVersion)
153+
minTLSVersion = tls.VersionTLS12
154+
}
155+
return &cryptoConfig{
156+
CipherSuites: cipherSuitesIDs(cipherNames),
157+
MinVersion: minTLSVersion,
158+
}
159+
}
160+
161+
func selectCipherSuitesAndMinTLSVersion(
162+
profile *migrationsv1alpha1.TLSSecurityProfile,
163+
) ([]string, migrationsv1alpha1.TLSProtocolVersion) {
164+
if profile == nil {
165+
profile = &migrationsv1alpha1.TLSSecurityProfile{
166+
Type: migrationsv1alpha1.TLSProfileIntermediateType,
167+
Intermediate: &migrationsv1alpha1.IntermediateTLSProfile{},
168+
}
169+
}
170+
if profile.Custom != nil {
171+
return profile.Custom.TLSProfileSpec.Ciphers, profile.Custom.TLSProfileSpec.MinTLSVersion
172+
}
173+
return migrationsv1alpha1.TLSProfiles[profile.Type].Ciphers, migrationsv1alpha1.TLSProfiles[profile.Type].MinTLSVersion
174+
}
175+
176+
func cipherSuitesIDs(names []string) []uint16 {
177+
var ids []uint16
178+
for _, name := range names {
179+
if id, ok := cipherNameToID[name]; ok {
180+
ids = append(ids, id)
181+
}
182+
}
183+
return ids
184+
}

0 commit comments

Comments
 (0)