Calling Google Cloud Run gRPC from Dart with Firebase authentication: certificate signed by unknown authority
Indeed, the backend was missing certificates...
Solved by using:
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
In 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"]
Louis Beaumont
Updated on January 01, 2023Comments
-
Louis Beaumont over 1 year
Server
I use a gRPC middleware to check the Firebase authentication token in streams:
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 }
Client
The client simply pass his Firebase authentication token to every stream requests:
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')))
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/[email protected]": 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.