Merge f67aec531a361e976ec24a064131ac33b455566a into c86c93ca2951338115159dcdd20711603044e1f1

This commit is contained in:
pschou 2024-09-26 22:00:12 +01:00 committed by GitHub
commit 4511870059
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 101 additions and 2 deletions

View File

@ -552,8 +552,35 @@ func (info TLSInfo) ServerConfig() (*tls.Config, error) {
cfg.ClientCAs = cp
}
// "h2" NextProtos is necessary for enabling HTTP2 for go's HTTP server
cfg.NextProtos = []string{"h2"}
// If manually defined ciphers are provided and TLS1.3 is not allowed, the
// list of ciphers may not allow HTTP2. Test for this and disable if this is the case.
//
// TODO: There is a longstanding upstream issue of testing for bad ciphers.
// This test could be simplified in the future once this is resolved:
// https://github.com/golang/go/issues/41068#issuecomment-722549561
if len(cfg.CipherSuites) > 0 && cfg.MaxVersion > 0 && cfg.MaxVersion < tls.VersionTLS13 {
// Test for the cases where TLS1.3 is not allowed, and thus http2 may not
// be possible when select cipherSuites are defined.
var hasHTTP2cipher bool
for _, c := range cfg.CipherSuites {
switch c {
// If one of these cipher suites are defined, then the h2 protocol is possible.
case tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
hasHTTP2cipher = true
break
}
}
if hasHTTP2cipher {
cfg.NextProtos = []string{"h2"}
} else {
// Display a warning that none of the h2 ciphers are available and likewise GRBC cannot be started.
info.Logger.Info("CipherSuites is missing an HTTP2 capable cipher (TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 or TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256). Disabling HTTP2 protocol.")
}
} else {
// "h2" NextProtos is necessary for enabling HTTP2 for go's HTTP server
cfg.NextProtos = []string{"h2"}
}
return cfg, nil
}

View File

@ -16,6 +16,7 @@ package embed
import (
"context"
"errors"
"fmt"
"io"
defaultLog "log"
@ -202,6 +203,19 @@ func (sctx *serveCtx) serve(
}
if grpcEnabled {
// Verify that HTTP2 is available, and if not terminate early
var hasH2 bool
for _, p := range tlscfg.NextProtos {
if p == "h2" {
hasH2 = true
}
}
if !hasH2 {
grpcStartError := errors.New("GRPC cannot be started without HTTP2 protocol. Please add at least one of the HTTP2 capable ciphers or set tls-max-version to TLS1.3.")
sctx.lg.Error("Configure https server failed", zap.Error(grpcStartError))
return grpcStartError
}
gs = v3rpc.Server(s, tlscfg, nil, gopts...)
v3electionpb.RegisterElectionServer(gs, servElection)
v3lockpb.RegisterLockServer(gs, servLock)

View File

@ -706,3 +706,61 @@ func TestEtcdTLSVersion(t *testing.T) {
proc.Wait() // ensure the port has been released
proc.Close()
}
func TestEtcdNonH2CiphersWithGRPC(t *testing.T) {
e2e.BeforeTest(t)
d := t.TempDir()
argsWithGRPCandH2andWithoutH2Ciphers := []string{
e2e.BinPath.Etcd,
"--data-dir", d,
"--name", "e1",
"--enable-grpc-gateway",
"--listen-client-urls", "https://0.0.0.0:0",
"--advertise-client-urls", "https://0.0.0.0:0",
"--listen-peer-urls", fmt.Sprintf("https://127.0.0.1:%d", e2e.EtcdProcessBasePort),
"--initial-advertise-peer-urls", fmt.Sprintf("https://127.0.0.1:%d", e2e.EtcdProcessBasePort),
"--initial-cluster", fmt.Sprintf("e1=https://127.0.0.1:%d", e2e.EtcdProcessBasePort),
"--peer-cert-file", e2e.CertPath,
"--peer-key-file", e2e.PrivateKeyPath,
"--cert-file", e2e.CertPath2,
"--key-file", e2e.PrivateKeyPath2,
"--cipher-suites", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
"--tls-min-version", "TLS1.2",
"--tls-max-version", "TLS1.2",
}
err := e2e.SpawnWithExpect(argsWithGRPCandH2andWithoutH2Ciphers, expect.ExpectedResponse{Value: "http2: TLSConfig.CipherSuites is missing an HTTP/2-required AES_128_GCM_SHA256 cipher (need at least one of TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 or TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)"})
require.ErrorContains(t, err, "GRPC cannot be started without HTTP2 protocol.")
}
func TestEtcdNonH2CiphersWithoutGRPC(t *testing.T) {
e2e.BeforeTest(t)
d := t.TempDir()
proc, err := e2e.SpawnCmd(
[]string{
e2e.BinPath.Etcd,
"--data-dir", d,
"--name", "e1",
"--listen-client-http-urls", "https://0.0.0.0:0",
"--listen-peer-urls", fmt.Sprintf("https://127.0.0.1:%d", e2e.EtcdProcessBasePort),
"--initial-advertise-peer-urls", fmt.Sprintf("https://127.0.0.1:%d", e2e.EtcdProcessBasePort),
"--initial-cluster", fmt.Sprintf("e1=https://127.0.0.1:%d", e2e.EtcdProcessBasePort),
"--peer-cert-file", e2e.CertPath,
"--peer-key-file", e2e.PrivateKeyPath,
"--cert-file", e2e.CertPath2,
"--key-file", e2e.PrivateKeyPath2,
"--cipher-suites", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
"--tls-min-version", "TLS1.2",
"--tls-max-version", "TLS1.2",
}, nil,
)
assert.NoError(t, err)
assert.NoError(t, e2e.WaitReadyExpectProc(context.TODO(), proc, e2e.EtcdServerReadyLines), "did not receive expected output from etcd process")
assert.NoError(t, proc.Stop())
proc.Wait() // ensure the port has been released
proc.Close()
}