Unable to connect to the greeter grpc service mentioned in this link - https://learn.microsoft.com/en-us/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-3.0 from a greeter client which is written from a .net framework app using grpc.core libraries(Grpc.Core.2.24.0
and Grpc.Core.Api.2.24.0
).
Below is my client code. It works with non SSL but not with SSL
Client code with non SSL(this works)
var channel = new Channel("localhost:5000", ChannelCredentials.Insecure);
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" });
channel.ShutdownAsync().Wait();
Client code with SSL(this fails to connect )
SslCredentials secureChannel = new SslCredentials();
var channel = new Channel("localhost", 5001, secureChannel);
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" });
channel.ShutdownAsync().Wait();
The error I get with SSL is:
Grpc.Core.RpcException: 'Status(StatusCode=Unavailable, Detail="failed to connect to all addresses")'
I tried with .net core app client mentioned in the same link(https://learn.microsoft.com/en-us/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-3.0) that works with SSL and non SSL but not by using the grp libraries directly. My client is a .Net framework client that is the reason I can't use .net libraries for connecting to grpc service. .Net grpc libraries are supported only from .net core app.
SslCredentials secureChannel = new SslCredentials();
var channel = new Channel("localhost", 5001, secureChannel);
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" });
channel.ShutdownAsync().Wait();
Expected result - response from the server
Actual result - Grpc.Core.RpcException: 'Status(StatusCode=Unavailable, Detail="failed to connect to all addresses")'
I made a working client on the .NET Framework c with a server on .NET Core on localhost:
static async Task Main(string[] args)
{
string s = GetRootCertificates();
var channel_creds = new SslCredentials(s);
var channel = new Channel("localhost",50051, channel_creds);
var client = new Informer.InformerClient(channel);
await GetPing(client);
}
public static string GetRootCertificates()
{
StringBuilder builder = new StringBuilder();
X509Store store = new X509Store(StoreName.Root);
store.Open(OpenFlags.ReadOnly);
foreach (X509Certificate2 mCert in store.Certificates)
{
builder.AppendLine(
"# Issuer: " + mCert.Issuer.ToString() + "\n" +
"# Subject: " + mCert.Subject.ToString() + "\n" +
"# Label: " + mCert.FriendlyName.ToString() + "\n" +
"# Serial: " + mCert.SerialNumber.ToString() + "\n" +
"# SHA1 Fingerprint: " + mCert.GetCertHashString().ToString() + "\n" +
ExportToPEM(mCert) + "\n");
}
return builder.ToString();
}
/// <summary>
/// Export a certificate to a PEM format string
/// </summary>
/// <param name="cert">The certificate to export</param>
/// <returns>A PEM encoded string</returns>
public static string ExportToPEM(X509Certificate cert)
{
StringBuilder builder = new StringBuilder();
builder.AppendLine("-----BEGIN CERTIFICATE-----");
builder.AppendLine(Convert.ToBase64String(cert.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks));
builder.AppendLine("-----END CERTIFICATE-----");
return builder.ToString();
}
private static async Task GetPing(Informer.InformerClient client)
{
Console.WriteLine("Getting ping...");
try
{
Metadata headers = null;
var response = await client.GetServerPingAsync(new Empty(), headers);
string result = "Nan";
if (response.PingResponse_ == 1)
result = "Ok!";
Console.WriteLine($"Ping say: {result }");
}
catch (Exception ex)
{
Console.WriteLine("Error get server ping." + Environment.NewLine + ex.ToString());
}
}
But I have not yet succeeded in making this work on remote machines (for example, where ip 192.168.1.7 is the server address and the client address is 192.168.1.2)
I got it working with SSL port by using the Server's certificate in pem format in the client.
SslCredentials secureCredentials = new SslCredentials(File.ReadAllText("certificate.pem"));
var channel = new Channel("localhost", 5001, secureCredentials);
A bit of explanation, Asp.NETCore template in VS 2019 uses a development certificate with pfx file at %AppData%\ASP.NET\Https\ProjectName.pfx
and
password = %AppData%\Microsoft\UserSecrets\{UserSecretsId}\secrets.json {:Kestrel:Certificates:Development:Password} Value
You can get the UserSecretsId
id from the ProjectName.csproj
. This will be different for each ASP.NET Core Project.
We just need the public key of the certificate as a certificate.pem
file to communicate securely over gRPC. Use the command below to extract publickey from pfx
openssl pkcs12 -in "<DiskLocationOfPfx>\ProjectName.pfx" -nokeys -out "<TargetLocation>\certifcate.pem"
Copy this cerificate.pem for the gRPC .NET Framework client to use.
SslCredentials secureCredentials = new SslCredentials(File.ReadAllText("<DiskLocationTo the Folder>/certificate.pem"))
var channel = new Channel("localhost", 5001, secureCredentials);
Note that port 5001 I used is the SSL port of my ASP.NET Core application.
For Production Scenarios
Use a valid certificate from certificate signing authority and use same certificate in ASP.NET Core Server and .NET Framework client as pfx and pem respectively.
Or Using Self signed certificate
Using Self signed certificates are a valid option for most microservices that communicate between our own microservices. We may not need an authority signed certificate. One problem we may face with using self signed certificate is that the certificate may be issued to some target DNS name and our gRPC server may be running somewhere else and secure connection cannot be established.
Use gRPC Target Name override keys to override the ssl target name validation.
List<ChannelOption> channelOptions = new List<ChannelOption>()
{
new ChannelOption("grpc.ssl_target_name_override", <DNS to which our certificate is issued to>),
};
SslCredentials secureCredentials = new SslCredentials(File.ReadAllText("certificate.pem"));
var channel = new Channel("localhost", 5001, secureCredentials, channelOptions);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With