使用 Firebase 身份验证从 Dart 调用 Google Cloud 运行 gRPC:由未知授权机构签署的证书

Calling Google Cloud Run gRPC from Dart with Firebase authentication: certificate signed by unknown authority

服务器

我使用 gRPC 中间件检查流中的 Firebase 身份验证令牌:

package main
...
func main() {
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }

    grpcEndpoint := fmt.Sprintf(":%s", port)
    log.Printf("gRPC endpoint [%s]", grpcEndpoint)

    logger, err := zap.NewProduction()
    if err != nil {
        log.Fatalf("Failed to init logger: %v", err)
    }
    defer logger.Sync() // flushes buffer, if any

    grpcServer := grpc.NewServer(
        grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
            grpc_ctxtags.StreamServerInterceptor(),
            grpc_zap.StreamServerInterceptor(logger),
            grpc_auth.StreamServerInterceptor(server.AuthFunc))),
    )
    ctx := context.Background()
    fb, err := firebase.NewApp(ctx, &firebase.Config{
        ProjectID: "my-firebase-project",
    })
    server.App = fb
    if err != nil {
        panic(fmt.Sprintf("Failed to init firebase: %v", err))
    }
    pb.RegisterMyAwesomeServer(grpcServer, server.NewServer())

    listen, err := net.Listen("tcp", grpcEndpoint)
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("Starting: gRPC Listener [%s]\n", grpcEndpoint)
    log.Fatal(grpcServer.Serve(listen))
}
package server
...
func parseToken(ctx context.Context, token string) (*auth.Token, error) {
    client, err := App.Auth(ctx)
    if err != nil {
        return nil, err
    }

    nt, err := client.VerifyIDToken(ctx, token)
    if err != nil {
        return nil, err
    }

    return nt, nil
}

type AuthToken string
func AuthFunc(ctx context.Context) (context.Context, error) {
    token, err := grpc_auth.AuthFromMD(ctx, "bearer")
    if err != nil {
        return nil, err
    }

    tokenInfo, err := parseToken(ctx, token)
    if err != nil {
        return nil, status.Errorf(codes.Unauthenticated, "invalid auth token: %v", err)
    }
    grpc_ctxtags.Extract(ctx).Set("auth.uid", tokenInfo.UID)

    newCtx := context.WithValue(ctx, AuthToken("tokenInfo"), tokenInfo)

    return newCtx, nil
}

客户

客户端只需将他的 Firebase 身份验证令牌传递给每个流请求:

class ClientFirebaseAuthInterceptor implements ClientInterceptor {
  final String _authToken;
  ClientFirebaseAuthInterceptor(this._authToken);
  @override
  ResponseStream<R> interceptStreaming<Q, R>(
      ClientMethod<Q, R> method,
      Stream<Q> requests,
      CallOptions options,
      ClientStreamingInvoker<Q, R> invoker) {
    return invoker(
      method,
      requests,
      options = options.mergedWith(
        CallOptions(metadata: {'authorization': 'bearer $_authToken'}),
      ),
    );
  }
}
final token = await firebase.auth!.currentUser!.getIdToken();
final apiUrl = "my.gcp.run.url"
final channelOptions = ChannelOptions(ChannelCredentials.secure(
    authority: apiUrl,
));
    
final channel = ClientChannel(
    apiUrl,
    options: channelOptions,
    port: 443,
);
final client = MyAwesomeClient(
    channel!,
    options: CallOptions(
      timeout: Duration(seconds: 30),
    ),
    interceptors: [
      ClientFirebaseAuthInterceptor(token),
    ],
);
client.myAwesomeStream(Stream.value(MyAwesomeRequest(foo: 'bar')))

当 运行 在本地连接服务器(并转为不安全模式)时,它工作正常。 部署后我应该在客户端使用 ChannelCredentials.secure() 对吧?由于 GCP 运行 自行管理 SSL?我以某种方式收到此错误:

gRPC Error (code: 16, codeName: UNAUTHENTICATED, message: invalid auth token: Get "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com": x509: certificate signed by unknown authority, details: [], rawResponse: null, trailers: ...})

我应该向 ChannelCredentials.secure() 传递一些额外的参数吗?

我的 GCP 运行 启用了 HTTP2 并且“允许未经身份验证的调用 如果您正在创建 public API 或网站,请勾选此项。"

非常感谢。

确实,后端缺少证书...
通过使用解决:

COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

在 Dockerfile 中

FROM golang as build

WORKDIR /all

COPY . .

# Build static binary
RUN CGO_ENABLED=0 GOOS=linux \
    go build -a -installsuffix cgo \
    -o /go/bin/server \
    cmd/main/main.go

FROM scratch

COPY --from=build /go/bin/server /server
COPY --from=build /all/config.yaml /config.yaml
COPY --from=build /all/svc.dev.json /svc.dev.json

### THIS SOLVED
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
###

ENV GOOGLE_APPLICATION_CREDENTIALS /svc.dev.json

ENTRYPOINT ["/server", "./config.yaml"]