Here is something fairly elegant. It uses the Google Application Credentials and attaches a NewTokenSource
object to the gRPC connection object. My understanding is that this will allow for token refreshes automatically, if needed, at each gRPC Call.
// NewServerConnection creates a new gRPC connection.
//
// The host should be the domain where the Cloud Run Service is hosted
//
// This method also uses the Google Default Credentials workflow. To run this locally ensure that you have the
// environmental variable GOOGLE_APPLICATION_CREDENTIALS = ../key.json set.
//
// Best practise is to create a new connection at global level, which could be used to run many methods. This avoids
// unnecessary api calls to retrieve the required ID tokens each time a single method is called.
func NewServerConnection(ctx context.Context, host string) (*grpc.ClientConn, error) {
// Creates an identity token.
// With a global TokenSource tokens would be reused and auto-refreshed at need.
// A given TokenSource is specific to the audience.
tokenSource, err := idtoken.NewTokenSource(ctx, "https://"+host)
if err != nil {
return nil, status.Errorf(
codes.Unauthenticated,
"NewTokenSource: %s", err,
)
}
// Establishes a connection
var opts []grpc.DialOption
if host != "" {
opts = append(opts, grpc.WithAuthority(host+":443"))
}
systemRoots, err := x509.SystemCertPool()
if err != nil {
return nil, err
}
cred := credentials.NewTLS(&tls.Config{
RootCAs: systemRoots,
})
opts = append(opts, grpc.WithTransportCredentials(cred))
opts = append(opts, grpc.WithPerRPCCredentials(grpcTokenSource{
TokenSource: oauth.TokenSource{
tokenSource,
},
}))
conn, err := grpc.Dial(host+":443", opts...)
if err != nil {
return nil, status.Errorf(
codes.Unauthenticated,
"grpc.Dail: %s", err,
)
}
return conn, nil
}
Which could be used as follows:
import (
"context"
pb "path-to-your-protos"
"google.golang.org/grpc"
)
func ExampleNewServerConnection() {
// Creates the connection and Authorise using default credentials.
var err error
var myConn *grpc.ClientConn
myConn, err = NewServerConnection(context.Background(), "cloudrun-url-...-.app")
if err != nil {
// TODO: handle error
}
// Create a client from the server connection.
client := pb.NewServicesClient(myConn)
// Once the connection is created and tokens retrieved, make one or more calls to the respective methods.
result1, err := client.CreateBook(context.Background(), &pb.Book{})
if err != nil {
// TODO: handle error
}
// Use the result
_ = result1
// Another call
result2, err := client.CreateBook(context.Background(), &pb.Book{})
if err != nil {
// TODO: handle error
}
// Use the result
_ = result2
}
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…