Repository: etcd-io/etcd (language: go)

Issue:
TLS1.3 support
version:
![image](https://user-images.githubusercontent.com/11647782/143519029-2defa6b3-d10f-48c1-b467-4f99ea13e6b6.png)
config:
![image](https://user-images.githubusercontent.com/11647782/143519195-be91fb56-fc6a-4b8f-b675-197ded7606ff.png)
![image](https://user-images.githubusercontent.com/11647782/143519917-2a26da87-dd3a-4512-91e8-63f770e7d581.png)

Port detection status：
![image](https://user-images.githubusercontent.com/11647782/143519365-64b61c54-e5a4-4a83-b689-e742536fcd9d.png)
![image](https://user-images.githubusercontent.com/11647782/143519459-b187aa12-2669-4f36-8bb0-006ee3918a82.png)

but, old version(3.4.10) is ok:
version:
![image](https://user-images.githubusercontent.com/11647782/143519551-4f9cc96d-7949-4313-bcde-3a271e4a79f0.png)
config:
![image](https://user-images.githubusercontent.com/11647782/143519616-e496fd75-199d-49c0-a8d7-928e6381fb22.png)
![image](https://user-images.githubusercontent.com/11647782/143519639-69c52894-78e6-402e-8c10-1fbd3feb2041.png)
Port detection status：
![image](https://user-images.githubusercontent.com/11647782/143519751-128d2bff-fb47-445c-ba6b-b53ed15394ea.png)
![image](https://user-images.githubusercontent.com/11647782/143519828-672b21f2-5431-426c-8830-157f4511856f.png)



Discussion / hints:
I tried to reproduce the issue, but I didn't find an Etcd version that supports TLS1.3.

My setup
```
./bin/etcd-v3.4.10/etcd --auto-tls --peer-auto-tls   --listen-peer-urls=https://127.0.0.1:2380 --listen-client-urls=https://127.0.0.1:2379
```
I tested 3.5.1, 3.4.16, 3.4.10
Strange is that golang 1.12 should support TLS1.3 so maybe my methodology is not correct. Please let me know if I missed something
Found this issue that mentions TLS1.3 is supported 3.4.4+ https://github.com/etcd-io/etcd/issues/11692
Ok, I have reproduced the issue. On `v3.4` I needed to provide `GODEBUG=tls13=1` for TLS1.3 to work. However this no longer works on v3.5.1

Answer Etcd set max TLS to 1.2 

https://github.com/etcd-io/etcd/blob/7572a61a39d4eaad596ab8d9364f7df9a84ff4a3/client/pkg/transport/listener.go#L503
PR https://github.com/etcd-io/etcd/pull/11110

Looks like the reason is that cipher suites are no longer configurable in TLS1.3
cc @gyuho for more context
Overall answer is that Etcd never officially supported TLS1.3, however it was possible in v3.4 to enable it by using a golang debug flag `GODEBUG=tls13=1`. This no longer an option in v3.5.

I think we can consider enabling TLS1.3, however I don't think that lack support in v3.5 is nesesery a bug. cc @ptabor @hexfusion to confirm
While this is not a bug, I think there should be a way to configure support for TLS 1.3.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed after 21 days if no further activity occurs. Thank you for your contributions.

bump
also interested in seeing TLS 1.3 support.

side note: just encountered an issue where the use of a older version of a java library (didn't support TLS 1.2 I believe) resulted in issues connected to our etcd cluster. allowing developers to utilise 1.2 or 1.3 would be good since some apps may be built and not revisited for a while - if they get rebuilt with TLS 1.3 the likelihood of a revisit due to a change from 1.2 to 1.3 would not be needed).
bump again
This issue has been automatically marked as stale because it has not had recent activity. It will be closed after 21 days if no further activity occurs. Thank you for your contributions.

Hi @ahrtr @serathius 
Is ETCD planning to add the support of TLSv1.3 in coming release ?

Thanks,
Satya
Noone is currently working on it. Contributions are welcomed!
Is setting the min and max version configurable using config file?
> Is setting the min and max version configurable using config file?

Hey @NBK27 - It works for me via the following:

```yaml
tls-min-version: 'TLS1.2'
tls-max-version: 'TLS1.3'
```

I notice we don't include these fields in our [example etcd configuration file](https://github.com/etcd-io/etcd/blob/main/etcd.conf.yml.sample) so I'll raise a pr to update this.
it seems in #15156 we have missed out on setting the same min/max values for grpc proxy:
https://github.com/etcd-io/etcd/blob/main/server/etcdmain/grpc_proxy.go#L200-L223

cc @sanchezl 

/reopen


Fix patch:
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 {


Test patch:
diff --git a/tests/e2e/etcd_grpcproxy_test.go b/tests/e2e/etcd_grpcproxy_test.go
index fd3353ce6af..57f5be79b61 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)