Skip to content

Commit ecd8d51

Browse files
authored
Merge pull request #820 from tesshuflower/add-tls-min-version
feat: add --tls-min-version flag to proxy server
2 parents ad9cad2 + 1a8c0d0 commit ecd8d51

5 files changed

Lines changed: 108 additions & 7 deletions

File tree

cmd/server/app/options/options.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package options
1818

1919
import (
20+
"crypto/tls"
2021
"fmt"
2122
"os"
2223
"time"
@@ -104,7 +105,11 @@ type ProxyRunOptions struct {
104105
// also checks if given comma separated list contains cipher from tls.InsecureCipherSuites().
105106
// NOTE that cipher suites are not configurable for TLS1.3,
106107
// see: https://pkg.go.dev/crypto/tls#Config, so in that case, this option won't have any effect.
107-
CipherSuites []string
108+
CipherSuites []string
109+
// Minimum TLS version for server connections.
110+
// Accepted values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13.
111+
// If empty, defaults to VersionTLS12.
112+
TLSMinVersion string
108113
XfrChannelSize int
109114

110115
// Lease controller configuration
@@ -153,6 +158,7 @@ func (o *ProxyRunOptions) Flags() *pflag.FlagSet {
153158
flags.StringVar(&o.AuthenticationAudience, "authentication-audience", o.AuthenticationAudience, "Expected agent's token authentication audience (used with agent-namespace, agent-service-account, kubeconfig).")
154159
flags.StringVar(&o.ProxyStrategies, "proxy-strategies", o.ProxyStrategies, "The list of proxy strategies used by the server to pick an agent/tunnel, available strategies are: default, destHost, defaultRoute.")
155160
flags.StringSliceVar(&o.CipherSuites, "cipher-suites", o.CipherSuites, "The comma separated list of allowed cipher suites. Has no effect on TLS1.3. Empty means allow default list.")
161+
flags.StringVar(&o.TLSMinVersion, "tls-min-version", o.TLSMinVersion, "Minimum TLS version for server connections. Accepted values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13. Empty defaults to VersionTLS12.")
156162
flags.IntVar(&o.XfrChannelSize, "xfr-channel-size", o.XfrChannelSize, "The size of the two KNP server channels used in server for transferring data. One channel is for data coming from the Kubernetes API Server, and the other one is for data coming from the KNP agent.")
157163
flags.BoolVar(&o.EnableLeaseController, "enable-lease-controller", o.EnableLeaseController, "Enable lease controller to publish and garbage collect proxy server leases.")
158164
flags.StringVar(&o.LeaseNamespace, "lease-namespace", o.LeaseNamespace, "The namespace where lease objects are managed by the controller.")
@@ -200,6 +206,7 @@ func (o *ProxyRunOptions) Print() {
200206
klog.V(1).Infof("LeaseNamespace set to %s.\n", o.LeaseNamespace)
201207
klog.V(1).Infof("LeaseLabel set to %s.\n", o.LeaseLabel)
202208
klog.V(1).Infof("CipherSuites set to %q.\n", o.CipherSuites)
209+
klog.V(1).Infof("TLSMinVersion set to %q.\n", o.TLSMinVersion)
203210
klog.V(1).Infof("XfrChannelSize set to %d.\n", o.XfrChannelSize)
204211
klog.V(1).Infof("GracefulShutdownTimeout set to %v.\n", o.GracefulShutdownTimeout)
205212
}
@@ -329,6 +336,16 @@ func (o *ProxyRunOptions) Validate() error {
329336
if o.XfrChannelSize <= 0 {
330337
return fmt.Errorf("channel size %d must be greater than 0", o.XfrChannelSize)
331338
}
339+
// validate the TLS min version
340+
if o.TLSMinVersion != "" {
341+
tlsVer, err := util.GetTLSVersion(o.TLSMinVersion)
342+
if err != nil {
343+
return err
344+
}
345+
if tlsVer < tls.VersionTLS12 {
346+
klog.Warningf("--tls-min-version=%s is below TLS 1.2 and is considered insecure (RFC 8996)", o.TLSMinVersion)
347+
}
348+
}
332349
// validate the cipher suites
333350
if len(o.CipherSuites) != 0 {
334351
acceptedCiphers := util.GetAcceptedCiphers()
@@ -391,6 +408,7 @@ func NewProxyRunOptions() *ProxyRunOptions {
391408
AuthenticationAudience: "",
392409
ProxyStrategies: "default",
393410
CipherSuites: make([]string, 0),
411+
TLSMinVersion: "",
394412
XfrChannelSize: 10,
395413
EnableLeaseController: false,
396414
LeaseNamespace: "kube-system",

cmd/server/app/options/options_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ func TestDefaultServerOptions(t *testing.T) {
6262
assertDefaultValue(t, "AuthenticationAudience", defaultServerOptions.AuthenticationAudience, "")
6363
assertDefaultValue(t, "ProxyStrategies", defaultServerOptions.ProxyStrategies, "default")
6464
assertDefaultValue(t, "CipherSuites", defaultServerOptions.CipherSuites, make([]string, 0))
65+
assertDefaultValue(t, "TLSMinVersion", defaultServerOptions.TLSMinVersion, "")
6566
assertDefaultValue(t, "XfrChannelSize", defaultServerOptions.XfrChannelSize, 10)
6667
assertDefaultValue(t, "APIContentType", defaultServerOptions.APIContentType, "application/vnd.kubernetes.protobuf")
6768
assertDefaultValue(t, "GracefulShutdownTimeout", defaultServerOptions.GracefulShutdownTimeout, 0*time.Second)
@@ -169,6 +170,21 @@ func TestValidate(t *testing.T) {
169170
value: "TLS_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
170171
expected: nil,
171172
},
173+
"ValidTLSMinVersion12": {
174+
field: "TLSMinVersion",
175+
value: "VersionTLS12",
176+
expected: nil,
177+
},
178+
"ValidTLSMinVersion13": {
179+
field: "TLSMinVersion",
180+
value: "VersionTLS13",
181+
expected: nil,
182+
},
183+
"InvalidTLSMinVersion": {
184+
field: "TLSMinVersion",
185+
value: "InvalidVersion",
186+
expected: fmt.Errorf("unsupported TLS version \"InvalidVersion\", supported values are: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13"),
187+
},
172188
"Empty proxy strategies": {
173189
field: "ProxyStrategies",
174190
value: "",

cmd/server/app/server.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -404,16 +404,21 @@ func (p *Proxy) runUDSFrontendServer(ctx context.Context, o *options.ProxyRunOpt
404404
return stop, nil
405405
}
406406

407-
func (p *Proxy) getTLSConfig(caFile, certFile, keyFile string, cipherSuites []string) (*tls.Config, error) {
407+
func (p *Proxy) getTLSConfig(caFile, certFile, keyFile string, cipherSuites []string, tlsMinVersion string) (*tls.Config, error) {
408408
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
409409
if err != nil {
410410
return nil, fmt.Errorf("failed to load X509 key pair %s and %s: %v", certFile, keyFile, err)
411411
}
412412

413413
cipherSuiteIDs := tlsCipherSuites(cipherSuites)
414414

415+
minVersion, err := util.GetTLSVersion(tlsMinVersion)
416+
if err != nil {
417+
return nil, fmt.Errorf("failed to parse TLS min version: %v", err)
418+
}
419+
415420
if caFile == "" {
416-
return &tls.Config{Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS12, CipherSuites: cipherSuiteIDs}, nil
421+
return &tls.Config{Certificates: []tls.Certificate{cert}, MinVersion: minVersion, CipherSuites: cipherSuiteIDs}, nil // #nosec G402
417422
}
418423

419424
certPool := x509.NewCertPool()
@@ -426,11 +431,11 @@ func (p *Proxy) getTLSConfig(caFile, certFile, keyFile string, cipherSuites []st
426431
return nil, fmt.Errorf("failed to append cluster CA cert to the cert pool")
427432
}
428433

429-
tlsConfig := &tls.Config{
434+
tlsConfig := &tls.Config{ // #nosec G402
430435
ClientAuth: tls.RequireAndVerifyClientCert,
431436
Certificates: []tls.Certificate{cert},
432437
ClientCAs: certPool,
433-
MinVersion: tls.VersionTLS12,
438+
MinVersion: minVersion,
434439
CipherSuites: cipherSuiteIDs,
435440
}
436441

@@ -442,7 +447,7 @@ func (p *Proxy) runMTLSFrontendServer(_ context.Context, o *options.ProxyRunOpti
442447

443448
var tlsConfig *tls.Config
444449
var err error
445-
if tlsConfig, err = p.getTLSConfig(o.ServerCaCert, o.ServerCert, o.ServerKey, o.CipherSuites); err != nil {
450+
if tlsConfig, err = p.getTLSConfig(o.ServerCaCert, o.ServerCert, o.ServerKey, o.CipherSuites, o.TLSMinVersion); err != nil {
446451
return nil, err
447452
}
448453

@@ -500,7 +505,7 @@ func (p *Proxy) runMTLSFrontendServer(_ context.Context, o *options.ProxyRunOpti
500505
func (p *Proxy) runAgentServer(o *options.ProxyRunOptions, server *server.ProxyServer) error {
501506
var tlsConfig *tls.Config
502507
var err error
503-
if tlsConfig, err = p.getTLSConfig(o.ClusterCaCert, o.ClusterCert, o.ClusterKey, o.CipherSuites); err != nil {
508+
if tlsConfig, err = p.getTLSConfig(o.ClusterCaCert, o.ClusterCert, o.ClusterKey, o.CipherSuites, o.TLSMinVersion); err != nil {
504509
return err
505510
}
506511

pkg/util/net.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package util //nolint:revive
1818

1919
import (
2020
"crypto/tls"
21+
"fmt"
2122
"strings"
2223
)
2324

@@ -58,3 +59,25 @@ func GetAcceptedCiphers() map[string]uint16 {
5859
}
5960
return acceptedCiphers
6061
}
62+
63+
// tlsVersions maps TLS version name strings to their crypto/tls constant values.
64+
var tlsVersions = map[string]uint16{
65+
"VersionTLS10": tls.VersionTLS10,
66+
"VersionTLS11": tls.VersionTLS11,
67+
"VersionTLS12": tls.VersionTLS12,
68+
"VersionTLS13": tls.VersionTLS13,
69+
}
70+
71+
// GetTLSVersion returns the TLS version ID for the given version name.
72+
// If the name is empty, it returns the default (TLS 1.2).
73+
// Returns an error if the name is not recognized.
74+
func GetTLSVersion(versionName string) (uint16, error) {
75+
if versionName == "" {
76+
return tls.VersionTLS12, nil
77+
}
78+
version, ok := tlsVersions[versionName]
79+
if !ok {
80+
return 0, fmt.Errorf("unsupported TLS version %q, supported values are: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13", versionName)
81+
}
82+
return version, nil
83+
}

pkg/util/net_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package util //nolint:revive
1818

1919
import (
20+
"crypto/tls"
2021
"testing"
2122
)
2223

@@ -57,3 +58,41 @@ func TestRemovePortFromHost(t *testing.T) {
5758
t.Run(st.name, tf)
5859
}
5960
}
61+
62+
func TestGetTLSVersion(t *testing.T) {
63+
tests := []struct {
64+
name string
65+
versionName string
66+
expected uint16
67+
expectError bool
68+
}{
69+
{"EmptyDefaultsToTLS12", "", tls.VersionTLS12, false},
70+
{"VersionTLS10", "VersionTLS10", tls.VersionTLS10, false},
71+
{"VersionTLS11", "VersionTLS11", tls.VersionTLS11, false},
72+
{"VersionTLS12", "VersionTLS12", tls.VersionTLS12, false},
73+
{"VersionTLS13", "VersionTLS13", tls.VersionTLS13, false},
74+
{"InvalidVersion", "VersionTLS99", 0, true},
75+
}
76+
77+
for _, tt := range tests {
78+
st := tt
79+
t.Run(st.name, func(t *testing.T) {
80+
t.Parallel()
81+
got, err := GetTLSVersion(st.versionName)
82+
if st.expectError {
83+
if err == nil {
84+
t.Fatalf("\t%s\texpected error for input %q, but got none", failed, st.versionName)
85+
}
86+
t.Logf("\t%s\tgot expected error: %v", succeed, err)
87+
return
88+
}
89+
if err != nil {
90+
t.Fatalf("\t%s\tunexpected error for input %q: %v", failed, st.versionName, err)
91+
}
92+
if got != st.expected {
93+
t.Fatalf("\t%s\texpect %v, but got %v", failed, st.expected, got)
94+
}
95+
t.Logf("\t%s\texpect %v, got %v", succeed, st.expected, got)
96+
})
97+
}
98+
}

0 commit comments

Comments
 (0)