diff --git a/internal/transport/http2_client.go b/internal/transport/http2_client.go index 6a4776bb1531..a5e4f66af9d3 100644 --- a/internal/transport/http2_client.go +++ b/internal/transport/http2_client.go @@ -557,8 +557,10 @@ func (t *http2Client) getCallAuthData(ctx context.Context, audience string, call // Note: if these credentials are provided both via dial options and call // options, then both sets of credentials will be applied. if callCreds := callHdr.Creds; callCreds != nil { - if !t.isSecure && callCreds.RequireTransportSecurity() { - return nil, status.Error(codes.Unauthenticated, "transport: cannot send secure credentials on an insecure connection") + if callCreds.RequireTransportSecurity() { + if err := credentials.CheckSecurityLevel(ctx, credentials.PrivacyAndIntegrity); err != nil || !t.isSecure { + return nil, status.Error(codes.Unauthenticated, "transport: cannot send secure credentials on an insecure connection") + } } data, err := callCreds.GetRequestMetadata(ctx, audience) if err != nil { diff --git a/test/insecure_creds_test.go b/test/insecure_creds_test.go index 81d5a5ba5d0b..bc4a607c7cb8 100644 --- a/test/insecure_creds_test.go +++ b/test/insecure_creds_test.go @@ -35,6 +35,17 @@ import ( const defaultTestTimeout = 5 * time.Second +// testLegacyPerRPCCredentials is a PerRPCCredentials that has yet incorporated security level. +type testLegacyPerRPCCredentials struct{} + +func (cr testLegacyPerRPCCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { + return nil, nil +} + +func (cr testLegacyPerRPCCredentials) RequireTransportSecurity() bool { + return true +} + // TestInsecureCreds tests the use of insecure creds on the server and client // side, and verifies that expect security level and auth info are returned. // Also verifies that this credential can interop with existing `WithInsecure` @@ -122,3 +133,65 @@ func (s) TestInsecureCreds(t *testing.T) { }) } } + +func (s) TestInsecureCredsWithPerRPCCredentials(t *testing.T) { + tests := []struct { + desc string + perRPCCredsViaDialOptions bool + perRPCCredsViaCallOptions bool + }{ + { + desc: "send PerRPCCredentials via DialOptions", + perRPCCredsViaDialOptions: false, + perRPCCredsViaCallOptions: true, + }, + } + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + ss := &stubServer{ + emptyCall: func(ctx context.Context, in *testpb.Empty) (*testpb.Empty, error) { + return &testpb.Empty{}, nil + }, + } + + sOpts := []grpc.ServerOption{} + sOpts = append(sOpts, grpc.Creds(insecure.NewCredentials())) + s := grpc.NewServer(sOpts...) + defer s.Stop() + + testpb.RegisterTestServiceServer(s, ss) + + lis, err := net.Listen("tcp", "localhost:0") + if err != nil { + t.Fatalf("net.Listen(tcp, localhost:0) failed: %v", err) + } + + go s.Serve(lis) + + addr := lis.Addr().String() + ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) + defer cancel() + cOpts := []grpc.DialOption{grpc.WithBlock()} + cOpts = append(cOpts, grpc.WithTransportCredentials(insecure.NewCredentials())) + + if test.perRPCCredsViaDialOptions { + cOpts = append(cOpts, grpc.WithPerRPCCredentials(testLegacyPerRPCCredentials{})) + cc, err := grpc.DialContext(ctx, addr, cOpts...) + if err != nil { + t.Fatalf("grpc.Dial(%q) failed: %v", addr, err) + } + defer cc.Close() + } + + if test.perRPCCredsViaCallOptions { + cc, err := grpc.DialContext(ctx, addr, cOpts...) + defer cc.Close() + + c := testpb.NewTestServiceClient(cc) + if _, err = c.EmptyCall(ctx, &testpb.Empty{}, grpc.PerRPCCredentials(testLegacyPerRPCCredentials{})); err != nil { + t.Fatalf("EmptyCall(_, _) = _, %v; want _, ", err) + } + } + }) + } +}