#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...
}
Несколько моментов, на которые следует обратить внимание:
- NewServerConnection основано на документации Google: получение токена OIDC для учетной записи службы по умолчанию и отправка запросов gRPC с аутентификацией
- Мы объявляем
myServer
объект глобально и инициализируем его один раз. Это делается для того, чтобы избежать ненужных обращений к базовому серверу метаданных для получения учетных данных Google по умолчанию, то есть токена. Вот ссылка на эту концепцию из документации Google - После «инициализации» у нас есть объект ctx, который содержит токен-носитель, который мы затем используем при каждом вызове любого из методов rpc клиента.
Вопросы:
- Является ли описанный выше элегантный способ доступа к облачному запуску?
- В настоящее время мы должны добавить
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
}