Specific error message from the library
CompletedListGrpc.Core.RpcException: Status(StatusCode=Unavailable, Detail="Connect Failed")
When the firewall MITM feature is disabled for the firestore.googleapis.com the library works ok. When the MITM feature is enabled, it doesn't work.
Sub-questions:
1) Does the library code have a hardcoded certificate check? (I couldn't find one)
public static gaxgrpc::ServiceEndpoint DefaultEndpoint { get; } = new gaxgrpc::ServiceEndpoint("firestore.googleapis.com", 443);
2a) Does .Net Framework automatically trust certificates that are in the windows trust store? Is any code required to make this work?
It appears that .Net Framework works with Windows Certificate Store - see https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/working-with-certificates
2b) Perhaps the certificate is only trusted but the interactive user, and not the whole machine, and therefore service accounts don't see that certificate - I'll check this...
3) Will the library have a specific error about the certificate if that's the cause of a "Connect Failed"?
It's likely to be an issue between GRPC and Windows Certificate store. Whether that's a fundamental issue with GRPC (rather than .Net) or due to running it under a different account, hardcoding the correct Root CA for your firewall into the application will certainly give you total control.
You might be able to override the DefaultEndpoint/Channel to use your own ServicePoint that also includes a hardcoded certificate
see TLS support for GRPC in C#
var cacert = File.ReadAllText(@"../ca.crt");
var clientcert = File.ReadAllText(@"../client.crt");
var clientkey = File.ReadAllText(@"../client.key");
var ssl = new SslCredentials(cacert, new KeyCertificatePair(clientcert, clientkey));
var channel = new Channel("firestore.googleapis.com", 443, ssl);
//etc..
Create your own endpoint, then supply it explicitly so that it overrides the DefaultEndpoint. Here's the function you would call with your Channel. (see https://github.com/googleapis/google-cloud-dotnet/blob/master/apis/Google.Cloud.Firestore.V1/Google.Cloud.Firestore.V1/FirestoreClient.cs#L550)
public static FirestoreClient Create(grpccore::Channel channel, FirestoreSettings settings = null)
{
gax::GaxPreconditions.CheckNotNull(channel, nameof(channel));
return Create(new grpccore::DefaultCallInvoker(channel), settings);
}
Also, have a good look at the SslCredentialsTest code - https://github.com/grpc/grpc/blob/master/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
Note: Usually a channel will be created with the s_channelPool. If you create it manually it might not use the pool - there might not be a way to do that. That may or may not be a problem for you.
You may be able to extend this to manually read the public certificate information from the Windows Certificate Store at runtime by name, selecting the most recent. This would mean that you can use app.config to assign the CN of the certificate to get from the Windows Certificate Store, then you won't need to manually send updated versions of your software.
This link seems to indicate the by default "Publically Trusted Roots" are trusted: https://forum.predix.io/questions/30875/event-hub-c-client-how-to-deal-with-the-tls-certif.html
var channel = new Channel("greeter.googleapis.com", new SslCredentials()); // Use publicly trusted roots.
This means that although there are no "hardcoded" specific certificates for firestore, it doesn't normally integrate with Windows Certificate Store, and therefore any custom installed Root CAs are not trusted.
Looking more closely at the library source code [https://github.com/googleapis/gax-dotnet/blob/master/Google.Api.Gax.Grpc/ChannelPool.cs]:
public Channel GetChannel(ServiceEndpoint endpoint, IEnumerable<ChannelOption> channelOptions)
{
GaxPreconditions.CheckNotNull(endpoint, nameof(endpoint));
var credentials = _lazyScopedDefaultChannelCredentials.Value.ResultWithUnwrappedExceptions();
return GetChannel(endpoint, channelOptions, credentials);
}
The credentials parameter is filled automatically, and following _lazyScopedDefaultChannelCredentials
, GoogleCredential.GetApplicationDefaultAsync()
, we end up at CreateDefaultCredentialAsync
which refers to your Google Services file which you likely downloaded from firebase somewhere.
Note:
There a good chance this answer only leads to custom client-side credentials, rather than the ability to have hardcoded credentials for a custom server-side Root CA.