Skip to content

Commit 3384c9f

Browse files
tuminoidas20203
andcommitted
add TLS configuration flags support
Add --tls-min-version and --tls-max-versin configuration flags. Same flags can be found in k8s, CAPI, CAPM3 etc. Co-authored-by: Jawad Zaheer <[email protected]> Signed-off-by: Tuomo Tanskanen <[email protected]>
1 parent f128d15 commit 3384c9f

File tree

2 files changed

+226
-2
lines changed

2 files changed

+226
-2
lines changed

main.go

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ package main
1717

1818
import (
1919
"context"
20+
"crypto/tls"
2021
"flag"
2122
"fmt"
2223
"net/http"
2324
_ "net/http/pprof"
2425
"os"
26+
"strings"
2527
"time"
2628

2729
"github.com/spf13/pflag"
@@ -55,9 +57,23 @@ import (
5557
"sigs.k8s.io/cluster-api-provider-openstack/version"
5658
)
5759

60+
// Constants for TLS versions.
61+
const (
62+
TLSVersion12 = "TLS12"
63+
TLSVersion13 = "TLS13"
64+
)
65+
66+
type TLSOptions struct {
67+
TLSMaxVersion string
68+
TLSMinVersion string
69+
TLSCipherSuites string
70+
}
71+
5872
var (
59-
scheme = runtime.NewScheme()
60-
setupLog = ctrl.Log.WithName("setup")
73+
scheme = runtime.NewScheme()
74+
setupLog = ctrl.Log.WithName("setup")
75+
tlsOptions = TLSOptions{}
76+
tlsSupportedVersions = []string{TLSVersion12, TLSVersion13}
6177

6278
// flags.
6379
diagnosticsOptions = flags.DiagnosticsOptions{}
@@ -157,6 +173,24 @@ func InitFlags(fs *pflag.FlagSet) {
157173
fs.IntVar(&scopeCacheMaxSize, "scope-cache-max-size", 10, "The maximum credentials count the operator should keep in cache. Setting this value to 0 means no cache.")
158174

159175
fs.BoolVar(&showVersion, "version", false, "Show current version and exit.")
176+
177+
fs.StringVar(&tlsOptions.TLSMinVersion, "tls-min-version", TLSVersion12,
178+
"The minimum TLS version in use by the webhook server.\n"+
179+
fmt.Sprintf("Possible values are %s.", strings.Join(tlsSupportedVersions, ", ")),
180+
)
181+
182+
fs.StringVar(&tlsOptions.TLSMaxVersion, "tls-max-version", TLSVersion13,
183+
"The maximum TLS version in use by the webhook server.\n"+
184+
fmt.Sprintf("Possible values are %s.", strings.Join(tlsSupportedVersions, ", ")),
185+
)
186+
187+
tlsCipherPreferredValues := cliflag.PreferredTLSCipherNames()
188+
tlsCipherInsecureValues := cliflag.InsecureTLSCipherNames()
189+
fs.StringVar(&tlsOptions.TLSCipherSuites, "tls-cipher-suites", "",
190+
"Comma-separated list of cipher suites for the webhook server. "+
191+
"If omitted, the default Go cipher suites will be used. \n"+
192+
"Preferred values: "+strings.Join(tlsCipherPreferredValues, ", ")+". \n"+
193+
"Insecure values: "+strings.Join(tlsCipherInsecureValues, ", ")+".")
160194
}
161195

162196
// Add RBAC for the authorized diagnostics endpoint.
@@ -189,6 +223,12 @@ func main() {
189223
}()
190224
}
191225

226+
tlsOptionOverrides, err := GetTLSOptionOverrideFuncs(tlsOptions)
227+
if err != nil {
228+
setupLog.Error(err, "unable to add TLS settings to the webhook server")
229+
os.Exit(1)
230+
}
231+
192232
cfg, err := config.GetConfigWithContext(os.Getenv("KUBECONTEXT"))
193233
if err != nil {
194234
setupLog.Error(err, "unable to get kubeconfig")
@@ -238,6 +278,7 @@ func main() {
238278
webhook.Options{
239279
Port: webhookPort,
240280
CertDir: webhookCertDir,
281+
TLSOpts: tlsOptionOverrides,
241282
},
242283
),
243284
HealthProbeBindAddress: healthAddr,
@@ -345,3 +386,75 @@ func setupWebhooks(mgr ctrl.Manager) {
345386
func concurrency(c int) controller.Options {
346387
return controller.Options{MaxConcurrentReconciles: c}
347388
}
389+
390+
// GetTLSOptionOverrideFuncs returns a list of TLS configuration overrides to be used
391+
// by the webhook server.
392+
func GetTLSOptionOverrideFuncs(options TLSOptions) ([]func(*tls.Config), error) {
393+
var tlsOptions []func(config *tls.Config)
394+
395+
tlsMinVersion, err := GetTLSVersion(options.TLSMinVersion)
396+
if err != nil {
397+
return nil, err
398+
}
399+
400+
tlsMaxVersion, err := GetTLSVersion(options.TLSMaxVersion)
401+
if err != nil {
402+
return nil, err
403+
}
404+
405+
if tlsMaxVersion != 0 && tlsMinVersion > tlsMaxVersion {
406+
return nil, fmt.Errorf("TLS version flag min version (%s) is greater than max version (%s)",
407+
options.TLSMinVersion, options.TLSMaxVersion)
408+
}
409+
410+
tlsOptions = append(tlsOptions, func(cfg *tls.Config) {
411+
cfg.MinVersion = tlsMinVersion
412+
})
413+
414+
tlsOptions = append(tlsOptions, func(cfg *tls.Config) {
415+
cfg.MaxVersion = tlsMaxVersion
416+
})
417+
// Cipher suites should not be set if empty.
418+
if tlsMinVersion >= tls.VersionTLS13 &&
419+
options.TLSCipherSuites != "" {
420+
setupLog.Info("warning: Cipher suites should not be set for TLS version 1.3. Ignoring ciphers")
421+
options.TLSCipherSuites = ""
422+
}
423+
424+
if options.TLSCipherSuites != "" {
425+
tlsCipherSuites := strings.Split(options.TLSCipherSuites, ",")
426+
suites, err := cliflag.TLSCipherSuites(tlsCipherSuites)
427+
if err != nil {
428+
return nil, err
429+
}
430+
431+
insecureCipherValues := cliflag.InsecureTLSCipherNames()
432+
for _, cipher := range tlsCipherSuites {
433+
for _, insecureCipherName := range insecureCipherValues {
434+
if insecureCipherName == cipher {
435+
setupLog.Info(fmt.Sprintf("warning: use of insecure cipher '%s' detected.", cipher))
436+
}
437+
}
438+
}
439+
tlsOptions = append(tlsOptions, func(cfg *tls.Config) {
440+
cfg.CipherSuites = suites
441+
})
442+
}
443+
444+
return tlsOptions, nil
445+
}
446+
447+
// GetTLSVersion returns the corresponding tls.Version or error.
448+
func GetTLSVersion(version string) (uint16, error) {
449+
var v uint16
450+
451+
switch version {
452+
case TLSVersion12:
453+
v = tls.VersionTLS12
454+
case TLSVersion13:
455+
v = tls.VersionTLS13
456+
default:
457+
return 0, fmt.Errorf("unexpected TLS version %q (must be one of: %s)", version, strings.Join(tlsSupportedVersions, ", "))
458+
}
459+
return v, nil
460+
}

main_test.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
Copyright 2023 The Kubernetes 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 main
18+
19+
import (
20+
"bytes"
21+
"testing"
22+
23+
. "github.com/onsi/gomega"
24+
"k8s.io/klog/v2"
25+
ctrl "sigs.k8s.io/controller-runtime"
26+
)
27+
28+
func TestTLSInsecureCiperSuite(t *testing.T) {
29+
t.Run("test insecure cipher suite passed as TLS flag", func(t *testing.T) {
30+
g := NewWithT(t)
31+
tlsMockOptions := TLSOptions{
32+
TLSMaxVersion: "TLS13",
33+
TLSMinVersion: "TLS12",
34+
TLSCipherSuites: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
35+
}
36+
ctrl.Log.WithName("setup")
37+
ctrl.SetLogger(klog.Background())
38+
39+
bufWriter := bytes.NewBuffer(nil)
40+
klog.SetOutput(bufWriter)
41+
klog.LogToStderr(false) // this is important, because klog by default logs to stderr only
42+
_, err := GetTLSOptionOverrideFuncs(tlsMockOptions)
43+
g.Expect(err).Should(BeNil())
44+
g.Expect(bufWriter.String()).Should(ContainSubstring("use of insecure cipher 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256' detected."))
45+
})
46+
}
47+
48+
func TestTLSMinAndMaxVersion(t *testing.T) {
49+
t.Run("should fail if TLS min version is greater than max version.", func(t *testing.T) {
50+
g := NewWithT(t)
51+
tlsMockOptions := TLSOptions{
52+
TLSMaxVersion: "TLS12",
53+
TLSMinVersion: "TLS13",
54+
}
55+
_, err := GetTLSOptionOverrideFuncs(tlsMockOptions)
56+
g.Expect(err.Error()).To(Equal("TLS version flag min version (TLS13) is greater than max version (TLS12)"))
57+
})
58+
}
59+
60+
func Test13CipherSuite(t *testing.T) {
61+
t.Run("should reset ciphersuite flag if TLS min and max version are set to 1.3", func(t *testing.T) {
62+
g := NewWithT(t)
63+
64+
// Here TLS_RSA_WITH_AES_128_CBC_SHA is a tls12 cipher suite.
65+
tlsMockOptions := TLSOptions{
66+
TLSMaxVersion: "TLS13",
67+
TLSMinVersion: "TLS13",
68+
TLSCipherSuites: "TLS_RSA_WITH_AES_128_CBC_SHA,TLS_AES_256_GCM_SHA384",
69+
}
70+
71+
ctrl.Log.WithName("setup")
72+
ctrl.SetLogger(klog.Background())
73+
74+
bufWriter := bytes.NewBuffer(nil)
75+
klog.SetOutput(bufWriter)
76+
klog.LogToStderr(false) // this is important, because klog by default logs to stderr only
77+
_, err := GetTLSOptionOverrideFuncs(tlsMockOptions)
78+
g.Expect(bufWriter.String()).Should(ContainSubstring("warning: Cipher suites should not be set for TLS version 1.3. Ignoring ciphers"))
79+
g.Expect(err).Should(BeNil())
80+
})
81+
}
82+
83+
func TestGetTLSVersion(t *testing.T) {
84+
t.Run("should error out when incorrect tls version passed", func(t *testing.T) {
85+
g := NewWithT(t)
86+
tlsVersion := "TLS11"
87+
_, err := GetTLSVersion(tlsVersion)
88+
g.Expect(err.Error()).Should(Equal("unexpected TLS version \"TLS11\" (must be one of: TLS12, TLS13)"))
89+
})
90+
t.Run("should pass and output correct tls version", func(t *testing.T) {
91+
const VersionTLS12 uint16 = 771
92+
g := NewWithT(t)
93+
tlsVersion := "TLS12"
94+
version, err := GetTLSVersion(tlsVersion)
95+
g.Expect(version).To(Equal(VersionTLS12))
96+
g.Expect(err).Should(BeNil())
97+
})
98+
}
99+
100+
func TestTLSOptions(t *testing.T) {
101+
t.Run("should pass with all the correct options below with no error.", func(t *testing.T) {
102+
g := NewWithT(t)
103+
tlsMockOptions := TLSOptions{
104+
TLSMinVersion: "TLS12",
105+
TLSMaxVersion: "TLS13",
106+
TLSCipherSuites: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
107+
}
108+
_, err := GetTLSOptionOverrideFuncs(tlsMockOptions)
109+
g.Expect(err).Should(BeNil())
110+
})
111+
}

0 commit comments

Comments
 (0)