Skip to content

Commit 514de04

Browse files
committed
Control ALPN enabled verification using an env var
1 parent bb33388 commit 514de04

File tree

3 files changed

+100
-35
lines changed

3 files changed

+100
-35
lines changed

credentials/tls.go

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,13 @@ import (
2727
"net/url"
2828
"os"
2929

30+
"google.golang.org/grpc/grpclog"
3031
credinternal "google.golang.org/grpc/internal/credentials"
32+
"google.golang.org/grpc/internal/envconfig"
3133
)
3234

35+
var logger = grpclog.Component("credentials")
36+
3337
// TLSInfo contains the auth information for a TLS authenticated connection.
3438
// It implements the AuthInfo interface.
3539
type TLSInfo struct {
@@ -113,17 +117,20 @@ func (c *tlsCreds) ClientHandshake(ctx context.Context, authority string, rawCon
113117
return nil, nil, ctx.Err()
114118
}
115119

116-
// The negotiated protocol can be either of the following:
117-
// 1. h2: When the server supports ALPN. Only HTTP/2 can be negotiated since
118-
// it is the only protocol advertised by the client during the handshake.
119-
// The tls library ensures that the server chooses a protocol advertised
120-
// by the client.
121-
// 2. "" (empty string): If the server doesn't support ALPN. ALPN is a requirement
122-
// for using HTTP/2 over TLS. We can terminate the connection immediately.
123-
np := conn.ConnectionState().NegotiatedProtocol
124-
if np == "" {
125-
_ = conn.Close()
126-
return nil, nil, fmt.Errorf("cannot check peer: missing selected ALPN property")
120+
// The negotiated protocol can be either of the following:
121+
// 1. h2: When the server supports ALPN. Only HTTP/2 can be negotiated since
122+
// it is the only protocol advertised by the client during the handshake.
123+
// The tls library ensures that the server chooses a protocol advertised
124+
// by the client.
125+
// 2. "" (empty string): If the server doesn't support ALPN. ALPN is a requirement
126+
// for using HTTP/2 over TLS. We can terminate the connection immediately.
127+
np := conn.ConnectionState().NegotiatedProtocol
128+
if np == "" {
129+
if envconfig.EnforceALPNEnabled {
130+
_ = conn.Close()
131+
return nil, nil, fmt.Errorf("cannot check peer: missing selected ALPN property")
132+
}
133+
logger.Warning("Allowing TLS connection to server %q with ALPN disabled")
127134
}
128135
tlsInfo := TLSInfo{
129136
State: conn.ConnectionState(),

credentials/tls_ext_test.go

Lines changed: 76 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@ import (
2424
"crypto/x509"
2525
"fmt"
2626
"os"
27+
"regexp"
2728
"strings"
2829
"testing"
2930
"time"
3031

3132
"google.golang.org/grpc"
3233
"google.golang.org/grpc/codes"
3334
"google.golang.org/grpc/credentials"
35+
"google.golang.org/grpc/internal/envconfig"
3436
"google.golang.org/grpc/internal/grpctest"
3537
"google.golang.org/grpc/internal/stubserver"
3638
"google.golang.org/grpc/status"
@@ -243,6 +245,11 @@ func (s) TestTLS_DisabledALPN(t *testing.T) {
243245
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
244246
defer cancel()
245247

248+
initialVal := envconfig.EnforceALPNEnabled
249+
defer func() {
250+
envconfig.EnforceALPNEnabled = initialVal
251+
}()
252+
246253
// Start a non gRPC TLS server.
247254
config := &tls.Config{
248255
Certificates: []tls.Certificate{serverCert},
@@ -254,31 +261,76 @@ func (s) TestTLS_DisabledALPN(t *testing.T) {
254261
}
255262
defer listner.Close()
256263

257-
// Start listening for server requests in a new go routine.
258-
go func() {
259-
conn, err := listner.Accept()
260-
if err != nil {
261-
t.Errorf("tls.Accept failed err = %v", err)
262-
} else {
263-
_, _ = conn.Write([]byte("Hello, World!"))
264-
_ = conn.Close()
265-
}
266-
}()
267-
268-
clientCreds := credentials.NewTLS(&tls.Config{
269-
ServerName: serverName,
270-
RootCAs: certPool,
271-
})
272-
273-
cc, err := grpc.NewClient("dns:"+listner.Addr().String(), grpc.WithTransportCredentials(clientCreds))
274-
if err != nil {
275-
t.Fatalf("grpc.NewClient error: %v", err)
264+
tests := []struct {
265+
description string
266+
alpnEnforced bool
267+
wantErrMatchPattern string
268+
wantErrNonMatchPattern string
269+
}{
270+
{
271+
description: "enforced",
272+
alpnEnforced: true,
273+
wantErrMatchPattern: "transport: .*missing selected ALPN property",
274+
},
275+
{
276+
description: "not_enforced",
277+
wantErrNonMatchPattern: "transport:",
278+
},
279+
{
280+
description: "default_value",
281+
wantErrNonMatchPattern: "transport:",
282+
alpnEnforced: initialVal,
283+
},
276284
}
277-
defer cc.Close()
278-
client := testgrpc.NewTestServiceClient(cc)
279285

280-
const wantStr = "missing selected ALPN property"
281-
if _, err = client.EmptyCall(ctx, &testpb.Empty{}); status.Code(err) != codes.Unavailable || !strings.Contains(status.Convert(err).Message(), wantStr) {
282-
t.Fatalf("EmptyCall err = %v; want code=%v, message contains %q", err, codes.Unavailable, wantStr)
286+
for _, tc := range tests {
287+
t.Run(tc.description, func(t *testing.T) {
288+
envconfig.EnforceALPNEnabled = tc.alpnEnforced
289+
// Listen to one TCP connection request.
290+
go func() {
291+
conn, err := listner.Accept()
292+
if err != nil {
293+
t.Errorf("tls.Accept failed err = %v", err)
294+
} else {
295+
_, _ = conn.Write([]byte("Hello, World!"))
296+
_ = conn.Close()
297+
}
298+
}()
299+
300+
clientCreds := credentials.NewTLS(&tls.Config{
301+
ServerName: serverName,
302+
RootCAs: certPool,
303+
})
304+
305+
cc, err := grpc.NewClient("dns:"+listner.Addr().String(), grpc.WithTransportCredentials(clientCreds))
306+
if err != nil {
307+
t.Fatalf("grpc.NewClient error: %v", err)
308+
}
309+
defer cc.Close()
310+
client := testgrpc.NewTestServiceClient(cc)
311+
_, rpcErr := client.EmptyCall(ctx, &testpb.Empty{})
312+
313+
if gotCode := status.Code(rpcErr); gotCode != codes.Unavailable {
314+
t.Errorf("EmptyCall returned unexpected code: got=%v, want=%v", gotCode, codes.Unavailable)
315+
}
316+
317+
matchPat, err := regexp.Compile(tc.wantErrMatchPattern)
318+
if err != nil {
319+
t.Fatalf("Error message match pattern %q is invalid due to error: %v", tc.wantErrMatchPattern, err)
320+
}
321+
322+
if tc.wantErrMatchPattern != "" && !matchPat.MatchString(status.Convert(rpcErr).Message()) {
323+
t.Errorf("EmptyCall err = %v; want pattern match %q", rpcErr, matchPat)
324+
}
325+
nonMatchPat, err := regexp.Compile(tc.wantErrNonMatchPattern)
326+
if err != nil {
327+
t.Fatalf("Error message non-match pattern %q is invalid due to error: %v", tc.wantErrNonMatchPattern, err)
328+
}
329+
330+
if tc.wantErrNonMatchPattern != "" && nonMatchPat.MatchString(status.Convert(rpcErr).Message()) {
331+
t.Errorf("EmptyCall err = %v; want pattern missing %q", rpcErr, nonMatchPat)
332+
}
333+
})
334+
283335
}
284336
}

internal/envconfig/envconfig.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ var (
4343
// ALTSMaxConcurrentHandshakes is the maximum number of concurrent ALTS
4444
// handshakes that can be performed.
4545
ALTSMaxConcurrentHandshakes = uint64FromEnv("GRPC_ALTS_MAX_CONCURRENT_HANDSHAKES", 100, 1, 100)
46+
// EnforceALPNEnabled is set if TLS connections to servers with ALPN disabled
47+
// should be rejected. The HTTP/2 protocol requires ALPN to be enabled, this
48+
// option is present for backward compatibility. This option may be overridden
49+
// by setting the environment variable "GRPC_ENFORCE_ALPN_ENABLED" to "true"
50+
// or "false".
51+
EnforceALPNEnabled = boolFromEnv("GRPC_ENFORCE_ALPN_ENABLED", false)
4652
)
4753

4854
func boolFromEnv(envVar string, def bool) bool {

0 commit comments

Comments
 (0)