gRPC, Go

#grpc #google-cloud-run #google-iam #grpc-go

#grpc #google-облачный запуск #google-iam #grpc-go

Вопрос:

У нас есть сервер gRPC, развернутый на экземпляре Google Cloud Run, к которому мы хотели бы получить доступ из других облачных сред Google (в частности, GKE и Cloud Run).

У нас есть следующий код для получения объекта подключения, а также контекста с токеном-носителем, сгенерированным из потока учетных данных Google по умолчанию:

 import (
    "context"
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "os"
    "regexp"

    "google.golang.org/api/idtoken"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    grpcMetadata "google.golang.org/grpc/metadata"
)

type ServerConnection struct {
    Conn   *grpc.ClientConn
    Ctx    context.Context
}

// NewServerConnection creates a new gRPC connection and request a Token to be used in the context.
//
// The host should be the domain where the Service is hosted, e.g., my-cloudrun-url-v1-inb33tjqiq-ew.a.run.app
//
// 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) (*ServerConnection, error) {

    // 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(amp;tls.Config{
        RootCAs: systemRoots,
    })
    opts = append(opts, grpc.WithTransportCredentials(cred))
    opts = append(opts, grpc.WithPerRPCCredentials())

    conn, err := grpc.Dial(host ":443", opts...)

    // Creates an identity token.
    // A given TokenSource is specific to the audience.
    tokenSource, err := idtoken.NewTokenSource(ctx, "https://" host)
    if err != nil {
        return nil, err
    }
    token, err := tokenSource.Token()
    if err != nil {
        return nil, err
    }

    // Add token to gRPC Request.
    ctx = grpcMetadata.AppendToOutgoingContext(ctx, "authorization", "Bearer " token.AccessToken)

    return amp;ServerConnection{
        Conn: conn,
        Ctx:  ctx,
    }, nil
}
 

Затем, используя приведенное выше:

 // Declare Globally
var myServer *ServerConnection

func TestNewServerConnection(t *testing.T) {
    // Connects to the server and add token to ctx.
    // In cloud run this is done once, populating the global variable
    ctx := context.Background()
    var err error;
    myServer, _ = NewServerConnection(ctx, "my-cloudrun-url-v1-inb33tjqiq-ew.a.run.app")

    // Now that we have a connection as well as a Context object with the Token 
    // we would like to make many client calls.
    client := pb.NewBookstoreClient(myServer.Conn)
    result, err := client.CreateBook(myServer.Ctx, amp;pb.Book{})
    if err != nil {
        // TODO: handle error
    }
    // Use result
    _ = result
    
    // ... make more client procedure calls here...
}
 

Несколько моментов, на которые следует обратить внимание:

Вопросы:

  • Является ли описанный выше элегантный способ доступа к облачному запуску?
  • В настоящее время мы должны добавить myServer.Ctx ко всем вызовам наших клиентских процедур — есть ли способ «встроить» это в myServer.Conn ? Может ли WithPerRPCCredentials быть полезным здесь?
  • Как можно обрабатывать токены с истекшим сроком действия? Срок действия токена по умолчанию составляет 1 час, любые вызовы клиентских процедур, выполненные более чем через 1 час после первоначального создания экземпляра, завершатся ошибкой. Есть ли элегантный способ «обновить» или сгенерировать новый токен?

Надеюсь, все это имеет смысл! Cloudrun, gRPC и IAM для управления доступом — потенциально действительно элегантная настройка при запуске служб в Google Cloud.

Комментарии:

1. Здравствуйте, я наткнулся на эти статьи gRPC Auth с помощью GCR , облачный запуск с помощью gRPC , токены OpenID с помощью GCR , которые могут оказаться полезными.

Ответ №1:

Вот что-то довольно элегантное. Он использует учетные данные приложения Google и присоединяет NewTokenSource объект к объекту подключения gRPC. Насколько я понимаю, это позволит автоматически обновлять токены, если это необходимо, при каждом вызове gRPC.

 // 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(amp;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
}
 

Который можно использовать следующим образом:

 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(), amp;pb.Book{})
    if err != nil {
        // TODO: handle error
    }
    // Use the result
    _ = result1

    // Another call
    result2, err := client.CreateBook(context.Background(), amp;pb.Book{})
    if err != nil {
        // TODO: handle error
    }

    // Use the result
    _ = result2
}