调用带有 Firebase 认证的 Dart 中的 Google Cloud Run gRPC:证书由未知机构签署

huangapple go评论133阅读模式
英文:

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

问题

服务器

我使用 gRPC 中间件来检查流中的 Firebase 认证令牌:

  1. package main
  2. ...
  3. func main() {
  4. port := os.Getenv("PORT")
  5. if port == "" {
  6. port = "8080"
  7. }
  8. grpcEndpoint := fmt.Sprintf(":%s", port)
  9. log.Printf("gRPC endpoint [%s]", grpcEndpoint)
  10. logger, err := zap.NewProduction()
  11. if err != nil {
  12. log.Fatalf("Failed to init logger: %v", err)
  13. }
  14. defer logger.Sync() // flushes buffer, if any
  15. grpcServer := grpc.NewServer(
  16. grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
  17. grpc_ctxtags.StreamServerInterceptor(),
  18. grpc_zap.StreamServerInterceptor(logger),
  19. grpc_auth.StreamServerInterceptor(server.AuthFunc))),
  20. )
  21. ctx := context.Background()
  22. fb, err := firebase.NewApp(ctx, &firebase.Config{
  23. ProjectID: "my-firebase-project",
  24. })
  25. server.App = fb
  26. if err != nil {
  27. panic(fmt.Sprintf("Failed to init firebase: %v", err))
  28. }
  29. pb.RegisterMyAwesomeServer(grpcServer, server.NewServer())
  30. listen, err := net.Listen("tcp", grpcEndpoint)
  31. if err != nil {
  32. log.Fatal(err)
  33. }
  34. log.Printf("Starting: gRPC Listener [%s]\n", grpcEndpoint)
  35. log.Fatal(grpcServer.Serve(listen))
  36. }
  1. package server
  2. ...
  3. func parseToken(ctx context.Context, token string) (*auth.Token, error) {
  4. client, err := App.Auth(ctx)
  5. if err != nil {
  6. return nil, err
  7. }
  8. nt, err := client.VerifyIDToken(ctx, token)
  9. if err != nil {
  10. return nil, err
  11. }
  12. return nt, nil
  13. }
  14. type AuthToken string
  15. func AuthFunc(ctx context.Context) (context.Context, error) {
  16. token, err := grpc_auth.AuthFromMD(ctx, "bearer")
  17. if err != nil {
  18. return nil, err
  19. }
  20. tokenInfo, err := parseToken(ctx, token)
  21. if err != nil {
  22. return nil, status.Errorf(codes.Unauthenticated, "invalid auth token: %v", err)
  23. }
  24. grpc_ctxtags.Extract(ctx).Set("auth.uid", tokenInfo.UID)
  25. newCtx := context.WithValue(ctx, AuthToken("tokenInfo"), tokenInfo)
  26. return newCtx, nil
  27. }

客户端

客户端只需将其 Firebase 认证令牌传递给每个流请求:

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

在本地运行服务器时(并切换到不安全模式),它可以正常工作。
在部署时,客户端应该使用 ChannelCredentials.secure(),因为 GCP run 会自己管理 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 run 启用了 HTTP2,并且选择了 "Allow unauthenticated invocations"。

非常感谢。

英文:

Server

I use a gRPC middleware to check the Firebase authentication token in streams:

  1. package main
  2. ...
  3. func main() {
  4. port := os.Getenv(&quot;PORT&quot;)
  5. if port == &quot;&quot; {
  6. port = &quot;8080&quot;
  7. }
  8. grpcEndpoint := fmt.Sprintf(&quot;:%s&quot;, port)
  9. log.Printf(&quot;gRPC endpoint [%s]&quot;, grpcEndpoint)
  10. logger, err := zap.NewProduction()
  11. if err != nil {
  12. log.Fatalf(&quot;Failed to init logger: %v&quot;, err)
  13. }
  14. defer logger.Sync() // flushes buffer, if any
  15. grpcServer := grpc.NewServer(
  16. grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
  17. grpc_ctxtags.StreamServerInterceptor(),
  18. grpc_zap.StreamServerInterceptor(logger),
  19. grpc_auth.StreamServerInterceptor(server.AuthFunc))),
  20. )
  21. ctx := context.Background()
  22. fb, err := firebase.NewApp(ctx, &amp;firebase.Config{
  23. ProjectID: &quot;my-firebase-project&quot;,
  24. })
  25. server.App = fb
  26. if err != nil {
  27. panic(fmt.Sprintf(&quot;Failed to init firebase: %v&quot;, err))
  28. }
  29. pb.RegisterMyAwesomeServer(grpcServer, server.NewServer())
  30. listen, err := net.Listen(&quot;tcp&quot;, grpcEndpoint)
  31. if err != nil {
  32. log.Fatal(err)
  33. }
  34. log.Printf(&quot;Starting: gRPC Listener [%s]\n&quot;, grpcEndpoint)
  35. log.Fatal(grpcServer.Serve(listen))
  36. }
  1. package server
  2. ...
  3. func parseToken(ctx context.Context, token string) (*auth.Token, error) {
  4. client, err := App.Auth(ctx)
  5. if err != nil {
  6. return nil, err
  7. }
  8. nt, err := client.VerifyIDToken(ctx, token)
  9. if err != nil {
  10. return nil, err
  11. }
  12. return nt, nil
  13. }
  14. type AuthToken string
  15. func AuthFunc(ctx context.Context) (context.Context, error) {
  16. token, err := grpc_auth.AuthFromMD(ctx, &quot;bearer&quot;)
  17. if err != nil {
  18. return nil, err
  19. }
  20. tokenInfo, err := parseToken(ctx, token)
  21. if err != nil {
  22. return nil, status.Errorf(codes.Unauthenticated, &quot;invalid auth token: %v&quot;, err)
  23. }
  24. grpc_ctxtags.Extract(ctx).Set(&quot;auth.uid&quot;, tokenInfo.UID)
  25. newCtx := context.WithValue(ctx, AuthToken(&quot;tokenInfo&quot;), tokenInfo)
  26. return newCtx, nil
  27. }

Client

The client simply pass his Firebase authentication token to every stream requests:

  1. class ClientFirebaseAuthInterceptor implements ClientInterceptor {
  2. final String _authToken;
  3. ClientFirebaseAuthInterceptor(this._authToken);
  4. @override
  5. ResponseStream&lt;R&gt; interceptStreaming&lt;Q, R&gt;(
  6. ClientMethod&lt;Q, R&gt; method,
  7. Stream&lt;Q&gt; requests,
  8. CallOptions options,
  9. ClientStreamingInvoker&lt;Q, R&gt; invoker) {
  10. return invoker(
  11. method,
  12. requests,
  13. options = options.mergedWith(
  14. CallOptions(metadata: {&#39;authorization&#39;: &#39;bearer $_authToken&#39;}),
  15. ),
  16. );
  17. }
  18. }
  1. final token = await firebase.auth!.currentUser!.getIdToken();
  2. final apiUrl = &quot;my.gcp.run.url&quot;
  3. final channelOptions = ChannelOptions(ChannelCredentials.secure(
  4. authority: apiUrl,
  5. ));
  6. final channel = ClientChannel(
  7. apiUrl,
  8. options: channelOptions,
  9. port: 443,
  10. );
  11. final client = MyAwesomeClient(
  12. channel!,
  13. options: CallOptions(
  14. timeout: Duration(seconds: 30),
  15. ),
  16. interceptors: [
  17. ClientFirebaseAuthInterceptor(token),
  18. ],
  19. );
  1. client.myAwesomeStream(Stream.value(MyAwesomeRequest(foo: &#39;bar&#39;)))

It works fine when running the server locally (and turning to insecure mode).
When deployed I should use ChannelCredentials.secure() in the client right? As GCP run manage the SSL by itself? Somehow I get this error:

>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: ...})

Should I pass some additional arguments to ChannelCredentials.secure()?

My GCP run has HTTP2 enabled and "Allow unauthenticated invocations
Check this if you are creating a public API or website."

Thanks a lot.

答案1

得分: 3

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

在 Dockerfile 中添加:

  1. COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
  1. FROM golang as build
  2. WORKDIR /all
  3. COPY . .
  4. # 构建静态二进制文件
  5. RUN CGO_ENABLED=0 GOOS=linux \
  6. go build -a -installsuffix cgo \
  7. -o /go/bin/server \
  8. cmd/main/main.go
  9. FROM scratch
  10. COPY --from=build /go/bin/server /server
  11. COPY --from=build /all/config.yaml /config.yaml
  12. COPY --from=build /all/svc.dev.json /svc.dev.json
  13. ### 这个解决了问题
  14. COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
  15. ###
  16. ENV GOOGLE_APPLICATION_CREDENTIALS /svc.dev.json
  17. ENTRYPOINT ["/server", "./config.yaml"]

希望这能帮到你!

英文:

Indeed, the backend was missing certificates...
Solved by using:

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

In Dockerfile

  1. FROM golang as build
  2. WORKDIR /all
  3. COPY . .
  4. # Build static binary
  5. RUN CGO_ENABLED=0 GOOS=linux \
  6. go build -a -installsuffix cgo \
  7. -o /go/bin/server \
  8. cmd/main/main.go
  9. FROM scratch
  10. COPY --from=build /go/bin/server /server
  11. COPY --from=build /all/config.yaml /config.yaml
  12. COPY --from=build /all/svc.dev.json /svc.dev.json
  13. ### THIS SOLVED
  14. COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
  15. ###
  16. ENV GOOGLE_APPLICATION_CREDENTIALS /svc.dev.json
  17. ENTRYPOINT [&quot;/server&quot;, &quot;./config.yaml&quot;]

huangapple
  • 本文由 发表于 2021年10月13日 21:07:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/69556144.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定