From 463eef9a6101c78b27cfa40513e25e33cbe1578f Mon Sep 17 00:00:00 2001 From: Thomas Jungblut Date: Thu, 31 Oct 2024 13:46:41 +0100 Subject: [PATCH] add tls min/max version to grpc proxy This adds the min and max TLS version support from #13506 and #15156 to the grpc proxy. Fixes #13506 Signed-off-by: Thomas Jungblut --- server/etcdmain/grpc_proxy.go | 73 +++++++++++++++++++++++++------- tests/e2e/etcd_grpcproxy_test.go | 38 +++++++++++++++++ 2 files changed, 96 insertions(+), 15 deletions(-) diff --git a/server/etcdmain/grpc_proxy.go b/server/etcdmain/grpc_proxy.go index 5946361b809..a0df3f99ae4 100644 --- a/server/etcdmain/grpc_proxy.go +++ b/server/etcdmain/grpc_proxy.go @@ -80,13 +80,16 @@ var ( // tls for clients connecting to proxy - grpcProxyListenCA string - grpcProxyListenCert string - grpcProxyListenKey string - grpcProxyListenCipherSuites []string - grpcProxyListenAutoTLS bool - grpcProxyListenCRL string - selfSignedCertValidity uint + grpcProxyListenCA string + grpcProxyListenCert string + grpcProxyListenKey string + grpcProxyListenCipherSuites []string + grpcProxyListenAutoTLS bool + grpcProxyListenCRL string + grpcProxyListenTLSMinVersion string + grpcProxyListenTLSMaxVersion string + + selfSignedCertValidity uint grpcProxyAdvertiseClientURL string grpcProxyResolverPrefix string @@ -169,6 +172,8 @@ func newGRPCProxyStartCommand() *cobra.Command { cmd.Flags().BoolVar(&grpcProxyListenAutoTLS, "auto-tls", false, "proxy TLS using generated certificates") cmd.Flags().StringVar(&grpcProxyListenCRL, "client-crl-file", "", "proxy client certificate revocation list file.") cmd.Flags().UintVar(&selfSignedCertValidity, "self-signed-cert-validity", 1, "The validity period of the proxy certificates, unit is year") + cmd.Flags().StringVar(&grpcProxyListenTLSMinVersion, "tls-min-version", string(tlsutil.TLSVersion12), "Minimum TLS version supported by grpc proxy. Possible values: TLS1.2, TLS1.3.") + cmd.Flags().StringVar(&grpcProxyListenTLSMaxVersion, "tls-max-version", string(tlsutil.TLSVersionDefault), "Maximum TLS version supported by grpc proxy. Possible values: TLS1.2, TLS1.3 (empty defers to Go).") // experimental flags cmd.Flags().BoolVar(&grpcProxyEnableOrdering, "experimental-serializable-ordering", false, "Ensure serializable reads have monotonically increasing store revisions across endpoints.") @@ -201,13 +206,6 @@ func startGRPCProxy(cmd *cobra.Command, args []string) { // The empty CN is required for grpcProxyCert. // Please see https://github.com/etcd-io/etcd/issues/11970#issuecomment-687875315 for more context. tlsInfo := newTLS(grpcProxyListenCA, grpcProxyListenCert, grpcProxyListenKey, false) - if len(grpcProxyListenCipherSuites) > 0 { - cs, err := tlsutil.GetCipherSuites(grpcProxyListenCipherSuites) - if err != nil { - log.Fatal(err) - } - tlsInfo.CipherSuites = cs - } if tlsInfo == nil && grpcProxyListenAutoTLS { host := []string{"https://" + grpcProxyListenAddr} dir := filepath.Join(grpcProxyDataDir, "fixtures", "proxy") @@ -217,10 +215,32 @@ func startGRPCProxy(cmd *cobra.Command, args []string) { } tlsInfo = &autoTLS } - if tlsInfo != nil { + if len(grpcProxyListenCipherSuites) > 0 { + cs, err := tlsutil.GetCipherSuites(grpcProxyListenCipherSuites) + if err != nil { + log.Fatal(err) + } + tlsInfo.CipherSuites = cs + } + if grpcProxyListenTLSMinVersion != "" { + version, err := tlsutil.GetTLSVersion(grpcProxyListenTLSMinVersion) + if err != nil { + log.Fatal(err) + } + tlsInfo.MinVersion = version + } + if grpcProxyListenTLSMaxVersion != "" { + version, err := tlsutil.GetTLSVersion(grpcProxyListenTLSMaxVersion) + if err != nil { + log.Fatal(err) + } + tlsInfo.MaxVersion = version + } + lg.Info("gRPC proxy server TLS", zap.String("tls-info", fmt.Sprintf("%+v", tlsInfo))) } + m := mustListenCMux(lg, tlsInfo) grpcl := m.Match(cmux.HTTP2()) defer func() { @@ -294,6 +314,29 @@ func checkArgs() { fmt.Fprintln(os.Stderr, fmt.Errorf("selfSignedCertValidity is invalid,it should be greater than 0")) os.Exit(1) } + + minVersion, err := tlsutil.GetTLSVersion(grpcProxyListenTLSMinVersion) + if err != nil { + fmt.Fprintln(os.Stderr, fmt.Errorf("tls-min-version is invalid: %w", err)) + os.Exit(1) + } + maxVersion, err := tlsutil.GetTLSVersion(grpcProxyListenTLSMaxVersion) + if err != nil { + fmt.Fprintln(os.Stderr, fmt.Errorf("tls-max-version is invalid: %w", err)) + os.Exit(1) + } + + // maxVersion == 0 means that Go selects the highest available version. + if maxVersion != 0 && minVersion > maxVersion { + fmt.Fprintln(os.Stderr, fmt.Errorf("min version (%s) is greater than max version (%s)", grpcProxyListenTLSMinVersion, grpcProxyListenTLSMaxVersion)) + os.Exit(1) + } + + // Check if user attempted to configure ciphers for TLS1.3 only: Go does not support that currently. + if minVersion == tls.VersionTLS13 && len(grpcProxyListenCipherSuites) > 0 { + fmt.Fprintln(os.Stderr, fmt.Errorf("cipher suites cannot be configured when only TLS1.3 is enabled")) + os.Exit(1) + } } func mustNewClient(lg *zap.Logger) *clientv3.Client { diff --git a/tests/e2e/etcd_grpcproxy_test.go b/tests/e2e/etcd_grpcproxy_test.go index fd3353ce6af..50f32c9811c 100644 --- a/tests/e2e/etcd_grpcproxy_test.go +++ b/tests/e2e/etcd_grpcproxy_test.go @@ -90,6 +90,44 @@ func TestGrpcProxyAutoSync(t *testing.T) { assert.Equal(t, []testutils.KV{{Key: "k1", Val: "v1"}}, kvs) } +func TestGrpcProxyTlsVersions(t *testing.T) { + e2e.SkipInShortMode(t) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + epc, err := e2e.NewEtcdProcessCluster(ctx, t, e2e.WithClusterSize(1)) + require.NoError(t, err) + defer func() { + assert.NoError(t, epc.Close()) + }() + + var ( + node1ClientURL = epc.Procs[0].Config().ClientURL + proxyClientURL = "127.0.0.1:42379" + ) + + // Run independent grpc-proxy instance + proxyProc, err := e2e.SpawnCmd([]string{e2e.BinPath.Etcd, "grpc-proxy", "start", + "--advertise-client-url", proxyClientURL, + "--listen-addr", proxyClientURL, + "--endpoints", node1ClientURL, + "--endpoints-auto-sync-interval", "1s", + "--cert-file", e2e.CertPath2, + "--key-file", e2e.PrivateKeyPath2, + "--tls-min-version", "TLS1.2", + "--tls-max-version", "TLS1.3", + }, nil) + require.NoError(t, err) + defer func() { + assert.NoError(t, proxyProc.Stop()) + }() + + _, err = proxyProc.ExpectFunc(ctx, func(s string) bool { + return strings.Contains(s, "started gRPC proxy") + }) + require.NoError(t, err) +} + func waitForEndpointInLog(ctx context.Context, proxyProc *expect.ExpectProcess, endpoint string) error { endpoint = strings.Replace(endpoint, "http://", "", 1)