forked from grpc/grpc-go
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'v1.70.x' into release_version_1.70.1-dev
- Loading branch information
Showing
12 changed files
with
1,563 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
/* | ||
* | ||
* Copyright 2025 gRPC authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
*/ | ||
|
||
package credentials | ||
|
||
import ( | ||
"context" | ||
"crypto/tls" | ||
"net" | ||
"strings" | ||
"testing" | ||
"time" | ||
|
||
"google.golang.org/grpc/credentials" | ||
"google.golang.org/grpc/internal/grpctest" | ||
"google.golang.org/grpc/testdata" | ||
) | ||
|
||
const defaultTestTimeout = 10 * time.Second | ||
|
||
type s struct { | ||
grpctest.Tester | ||
} | ||
|
||
func Test(t *testing.T) { | ||
grpctest.RunSubTests(t, s{}) | ||
} | ||
|
||
func (s) TestTLSOverrideServerName(t *testing.T) { | ||
expectedServerName := "server.name" | ||
c := NewTLSWithALPNDisabled(nil) | ||
c.OverrideServerName(expectedServerName) | ||
if c.Info().ServerName != expectedServerName { | ||
t.Fatalf("c.Info().ServerName = %v, want %v", c.Info().ServerName, expectedServerName) | ||
} | ||
} | ||
|
||
func (s) TestTLSClone(t *testing.T) { | ||
expectedServerName := "server.name" | ||
c := NewTLSWithALPNDisabled(nil) | ||
c.OverrideServerName(expectedServerName) | ||
cc := c.Clone() | ||
if cc.Info().ServerName != expectedServerName { | ||
t.Fatalf("cc.Info().ServerName = %v, want %v", cc.Info().ServerName, expectedServerName) | ||
} | ||
cc.OverrideServerName("") | ||
if c.Info().ServerName != expectedServerName { | ||
t.Fatalf("Change in clone should not affect the original, c.Info().ServerName = %v, want %v", c.Info().ServerName, expectedServerName) | ||
} | ||
|
||
} | ||
|
||
type serverHandshake func(net.Conn) (credentials.AuthInfo, error) | ||
|
||
func (s) TestClientHandshakeReturnsAuthInfo(t *testing.T) { | ||
tcs := []struct { | ||
name string | ||
address string | ||
}{ | ||
{ | ||
name: "localhost", | ||
address: "localhost:0", | ||
}, | ||
{ | ||
name: "ipv4", | ||
address: "127.0.0.1:0", | ||
}, | ||
{ | ||
name: "ipv6", | ||
address: "[::1]:0", | ||
}, | ||
} | ||
|
||
for _, tc := range tcs { | ||
t.Run(tc.name, func(t *testing.T) { | ||
done := make(chan credentials.AuthInfo, 1) | ||
lis := launchServerOnListenAddress(t, tlsServerHandshake, done, tc.address) | ||
defer lis.Close() | ||
lisAddr := lis.Addr().String() | ||
clientAuthInfo := clientHandle(t, gRPCClientHandshake, lisAddr) | ||
// wait until server sends serverAuthInfo or fails. | ||
serverAuthInfo, ok := <-done | ||
if !ok { | ||
t.Fatalf("Error at server-side") | ||
} | ||
if !compare(clientAuthInfo, serverAuthInfo) { | ||
t.Fatalf("c.ClientHandshake(_, %v, _) = %v, want %v.", lisAddr, clientAuthInfo, serverAuthInfo) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func (s) TestServerHandshakeReturnsAuthInfo(t *testing.T) { | ||
done := make(chan credentials.AuthInfo, 1) | ||
lis := launchServer(t, gRPCServerHandshake, done) | ||
defer lis.Close() | ||
clientAuthInfo := clientHandle(t, tlsClientHandshake, lis.Addr().String()) | ||
// wait until server sends serverAuthInfo or fails. | ||
serverAuthInfo, ok := <-done | ||
if !ok { | ||
t.Fatalf("Error at server-side") | ||
} | ||
if !compare(clientAuthInfo, serverAuthInfo) { | ||
t.Fatalf("ServerHandshake(_) = %v, want %v.", serverAuthInfo, clientAuthInfo) | ||
} | ||
} | ||
|
||
func (s) TestServerAndClientHandshake(t *testing.T) { | ||
done := make(chan credentials.AuthInfo, 1) | ||
lis := launchServer(t, gRPCServerHandshake, done) | ||
defer lis.Close() | ||
clientAuthInfo := clientHandle(t, gRPCClientHandshake, lis.Addr().String()) | ||
// wait until server sends serverAuthInfo or fails. | ||
serverAuthInfo, ok := <-done | ||
if !ok { | ||
t.Fatalf("Error at server-side") | ||
} | ||
if !compare(clientAuthInfo, serverAuthInfo) { | ||
t.Fatalf("AuthInfo returned by server: %v and client: %v aren't same", serverAuthInfo, clientAuthInfo) | ||
} | ||
} | ||
|
||
func compare(a1, a2 credentials.AuthInfo) bool { | ||
if a1.AuthType() != a2.AuthType() { | ||
return false | ||
} | ||
switch a1.AuthType() { | ||
case "tls": | ||
state1 := a1.(credentials.TLSInfo).State | ||
state2 := a2.(credentials.TLSInfo).State | ||
if state1.Version == state2.Version && | ||
state1.HandshakeComplete == state2.HandshakeComplete && | ||
state1.CipherSuite == state2.CipherSuite && | ||
state1.NegotiatedProtocol == state2.NegotiatedProtocol { | ||
return true | ||
} | ||
return false | ||
default: | ||
return false | ||
} | ||
} | ||
|
||
func launchServer(t *testing.T, hs serverHandshake, done chan credentials.AuthInfo) net.Listener { | ||
return launchServerOnListenAddress(t, hs, done, "localhost:0") | ||
} | ||
|
||
func launchServerOnListenAddress(t *testing.T, hs serverHandshake, done chan credentials.AuthInfo, address string) net.Listener { | ||
lis, err := net.Listen("tcp", address) | ||
if err != nil { | ||
if strings.Contains(err.Error(), "bind: cannot assign requested address") || | ||
strings.Contains(err.Error(), "socket: address family not supported by protocol") { | ||
t.Skipf("no support for address %v", address) | ||
} | ||
t.Fatalf("Failed to listen: %v", err) | ||
} | ||
go serverHandle(t, hs, done, lis) | ||
return lis | ||
} | ||
|
||
// Is run in a separate goroutine. | ||
func serverHandle(t *testing.T, hs serverHandshake, done chan credentials.AuthInfo, lis net.Listener) { | ||
serverRawConn, err := lis.Accept() | ||
if err != nil { | ||
t.Errorf("Server failed to accept connection: %v", err) | ||
close(done) | ||
return | ||
} | ||
serverAuthInfo, err := hs(serverRawConn) | ||
if err != nil { | ||
t.Errorf("Server failed while handshake. Error: %v", err) | ||
serverRawConn.Close() | ||
close(done) | ||
return | ||
} | ||
done <- serverAuthInfo | ||
} | ||
|
||
func clientHandle(t *testing.T, hs func(net.Conn, string) (credentials.AuthInfo, error), lisAddr string) credentials.AuthInfo { | ||
conn, err := net.Dial("tcp", lisAddr) | ||
if err != nil { | ||
t.Fatalf("Client failed to connect to %s. Error: %v", lisAddr, err) | ||
} | ||
defer conn.Close() | ||
clientAuthInfo, err := hs(conn, lisAddr) | ||
if err != nil { | ||
t.Fatalf("Error on client while handshake. Error: %v", err) | ||
} | ||
return clientAuthInfo | ||
} | ||
|
||
// Server handshake implementation in gRPC. | ||
func gRPCServerHandshake(conn net.Conn) (credentials.AuthInfo, error) { | ||
serverTLS, err := NewServerTLSFromFileWithALPNDisabled(testdata.Path("x509/server1_cert.pem"), testdata.Path("x509/server1_key.pem")) | ||
if err != nil { | ||
return nil, err | ||
} | ||
_, serverAuthInfo, err := serverTLS.ServerHandshake(conn) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return serverAuthInfo, nil | ||
} | ||
|
||
// Client handshake implementation in gRPC. | ||
func gRPCClientHandshake(conn net.Conn, lisAddr string) (credentials.AuthInfo, error) { | ||
clientTLS := NewTLSWithALPNDisabled(&tls.Config{InsecureSkipVerify: true}) | ||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) | ||
defer cancel() | ||
_, authInfo, err := clientTLS.ClientHandshake(ctx, lisAddr, conn) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return authInfo, nil | ||
} | ||
|
||
func tlsServerHandshake(conn net.Conn) (credentials.AuthInfo, error) { | ||
cert, err := tls.LoadX509KeyPair(testdata.Path("x509/server1_cert.pem"), testdata.Path("x509/server1_key.pem")) | ||
if err != nil { | ||
return nil, err | ||
} | ||
serverTLSConfig := &tls.Config{Certificates: []tls.Certificate{cert}} | ||
serverConn := tls.Server(conn, serverTLSConfig) | ||
err = serverConn.Handshake() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return credentials.TLSInfo{State: serverConn.ConnectionState(), CommonAuthInfo: credentials.CommonAuthInfo{SecurityLevel: credentials.PrivacyAndIntegrity}}, nil | ||
} | ||
|
||
func tlsClientHandshake(conn net.Conn, _ string) (credentials.AuthInfo, error) { | ||
clientTLSConfig := &tls.Config{InsecureSkipVerify: true} | ||
clientConn := tls.Client(conn, clientTLSConfig) | ||
if err := clientConn.Handshake(); err != nil { | ||
return nil, err | ||
} | ||
return credentials.TLSInfo{State: clientConn.ConnectionState(), CommonAuthInfo: credentials.CommonAuthInfo{SecurityLevel: credentials.PrivacyAndIntegrity}}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
/* | ||
* | ||
* Copyright 2025 gRPC authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
*/ | ||
|
||
// Package internal defines APIs for parsing SPIFFE ID. | ||
// | ||
// All APIs in this package are experimental. | ||
package internal | ||
|
||
import ( | ||
"crypto/tls" | ||
"crypto/x509" | ||
"net/url" | ||
|
||
"google.golang.org/grpc/grpclog" | ||
) | ||
|
||
var logger = grpclog.Component("credentials") | ||
|
||
// SPIFFEIDFromState parses the SPIFFE ID from State. If the SPIFFE ID format | ||
// is invalid, return nil with warning. | ||
func SPIFFEIDFromState(state tls.ConnectionState) *url.URL { | ||
if len(state.PeerCertificates) == 0 || len(state.PeerCertificates[0].URIs) == 0 { | ||
return nil | ||
} | ||
return SPIFFEIDFromCert(state.PeerCertificates[0]) | ||
} | ||
|
||
// SPIFFEIDFromCert parses the SPIFFE ID from x509.Certificate. If the SPIFFE | ||
// ID format is invalid, return nil with warning. | ||
func SPIFFEIDFromCert(cert *x509.Certificate) *url.URL { | ||
if cert == nil || cert.URIs == nil { | ||
return nil | ||
} | ||
var spiffeID *url.URL | ||
for _, uri := range cert.URIs { | ||
if uri == nil || uri.Scheme != "spiffe" || uri.Opaque != "" || (uri.User != nil && uri.User.Username() != "") { | ||
continue | ||
} | ||
// From this point, we assume the uri is intended for a SPIFFE ID. | ||
if len(uri.String()) > 2048 { | ||
logger.Warning("invalid SPIFFE ID: total ID length larger than 2048 bytes") | ||
return nil | ||
} | ||
if len(uri.Host) == 0 || len(uri.Path) == 0 { | ||
logger.Warning("invalid SPIFFE ID: domain or workload ID is empty") | ||
return nil | ||
} | ||
if len(uri.Host) > 255 { | ||
logger.Warning("invalid SPIFFE ID: domain length larger than 255 characters") | ||
return nil | ||
} | ||
// A valid SPIFFE certificate can only have exactly one URI SAN field. | ||
if len(cert.URIs) > 1 { | ||
logger.Warning("invalid SPIFFE ID: multiple URI SANs") | ||
return nil | ||
} | ||
spiffeID = uri | ||
} | ||
return spiffeID | ||
} |
Oops, something went wrong.