Grpc request throw exception on .net core 5 - c#

I've standart project HelloWorld in .net 5 to send grpc request and show in console the result.
This is my GrpcHelloWorldServer :
Program.cs :
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseKestrel(options =>
{
options.ListenLocalhost(5001, o =>
{
o.Protocols = HttpProtocols.Http2;
o.UseHttps(Get());
});
options.ListenLocalhost(5000, o =>
{
o.Protocols = HttpProtocols.Http2;
});
});
webBuilder.UseStartup<Startup>();
});
My method Get Get certificate :
public static X509Certificate2 Get()
{
var assembly = typeof(Program).GetTypeInfo().Assembly;
using (var stream = assembly.GetManifestResourceStream("GrpcHelloWorldServer.Certificate.translator.pfx"))
{
return new X509Certificate2(ReadStream(stream), "secret", X509KeyStorageFlags.MachineKeySet);
}
}
In startup in ConfigureSerices method I use services.AddGrpc(); and in Configure method I've
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<TestMyService>();
});
On client side main method is simple :
static async Task Main(string[] args)
{
try
{
// wait for grpc server is running
Console.WriteLine("Waiting for server is running");
Thread.Sleep(2000);
var httpClientHandler = new HttpClientHandler();
httpClientHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
var pem = File.ReadAllText(#"my certificate.pem");
var certData = GetBytesFromPem(pem, "CERTIFICATE");
var cert = new X509Certificate2(certData);
httpClientHandler.ClientCertificates.Add(cert);
var httpClient = new HttpClient(httpClientHandler);
var grpcChannel = GrpcChannel.ForAddress("https:myserverName:5001",
new GrpcChannelOptions { HttpClient = httpClient });
var client = new TestService.TestServiceClient(grpcChannel);
var response = await client.TestHelloAsync(new TestRequest { Name = "Julien" });
Console.WriteLine("Press any key to exit...");
Console.WriteLine(response.Message);
Console.ReadKey();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
On local on my machine it's works perfectly. But on my Windows Server 2019 grpc request throw 2 exceptions :
For http request :
quote
Status(StatusCode="Internal", Detail="Error starting gRPC call. HttpRequestException: An error occurred while sending the request. Http2ConnectionException: The HTTP/2 server sent invalid data on the connection. HTTP/2 error code 'PROTOCOL_ERROR' (0x1)."
quote
And for https request :
quote
Status(StatusCode="Internal", Detail="Error starting gRPC call. HttpRequestException: The SSL connection could not be established, see inner exception. AuthenticationException: The remote certificate is invalid according to the validation procedure: RemoteCertificateNameMismatch", DebugException="System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure: RemoteCertificateNameMismatch
quote
My IIS configuration is :
So I don't understand why it's working fine on local and not on IIS ?
UPDATE
I add certificate on server and client side, but on my server error is :
Exception Info: System.InvalidOperationException: Application is
running inside IIS process but is not configured to use IIS server.
So if I remove UseKestrel in Program.cs and add
webBuilder.UseStartup<Startup>().ConfigureKestrel((context, options) =>
{
options.ListenLocalhost(5001, o =>
{
o.Protocols = HttpProtocols.Http2;
o.UseHttps(Get());
});
options.ListenLocalhost(5000, o =>
{
o.Protocols = HttpProtocols.Http2;
});
});
, IIS throw another exception :
System.InvalidOperationException: Trailers are not supported for this
response. The server may not support gRPC.

Related

Detected a TLS handshake to an endpoint that does not have TLS enabled

I have an ASP.NET Core Web API running in a Linux Docker container orchestrated using Docker compose. When I try to access the Swagger endpoint of the Web API on HTTPS, the browser cannot reach the endpoint, and I see the following error in the Web API logs.
Detected a TLS handshake to an endpoint that does not have TLS enabled
After reading this topic, I "fixed" the issue by adding the following to my configuration. However, I am not sure why this is needed. It seems overkill and has too much complexity to enable HTTPS, so I am wondering if I am missing something.
builder.WebHost.ConfigureKestrel(options =>
{
options.Listen(
address: IPAddress.Any,
port: 443,
configure: listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
listenOptions.UseHttps(new TlsHandshakeCallbackOptions
{
OnConnection = context =>
{
if (string.Equals(context.ClientHelloInfo.ServerName, "localhost", StringComparison.OrdinalIgnoreCase) ||
string.Equals(context.ClientHelloInfo.ServerName, "host.docker.internal", StringComparison.OrdinalIgnoreCase))
{
// Different TLS requirements for this host
context.AllowDelayedClientCertificateNegotation = true;
return new ValueTask<SslServerAuthenticationOptions>(
new SslServerAuthenticationOptions
{
ServerCertificate = new X509Certificate2("/root/.aspnet/https/webapi.pfx", "password")
});
}
return new ValueTask<SslServerAuthenticationOptions>(
new SslServerAuthenticationOptions
{
ServerCertificate = new X509Certificate2("/root/.aspnet/https/webapi.pfx", "password")
});
}
});
});
});

SignalR js client is not able to start connection, even if logs are showing that connection is being made (only LongPooling works)

I am struggling with configuring and using signalR with .net core mvc 6. The purpose for the signalR hub is to send messages to js clients after invoking method in C# controller (js client is React application configured in MVC as ClientApp).
I enabled debugging for both client signalR instance and asp.net
here are the logs from ASP.NET:
SPA proxy is ready. Redirecting to https://localhost:44440.
dbug: Microsoft.AspNetCore.Http.Connections.Internal.HttpConnectionManager[1]
New connection ctA6QHwS4fvVGcufYvtlAA created.
dbug: Microsoft.AspNetCore.Http.Connections.Internal.HttpConnectionDispatcher[10]
Sending negotiation response.
dbug: Microsoft.AspNetCore.Http.Connections.Internal.HttpConnectionDispatcher[4]
Establishing new connection.
dbug: Microsoft.AspNetCore.SignalR.HubConnectionHandler[5]
OnConnectedAsync started.
dbug: Microsoft.AspNetCore.SignalR.Internal.DefaultHubProtocolResolver[2]
Found protocol implementation for requested protocol: json.
dbug: Microsoft.AspNetCore.SignalR.HubConnectionContext[1]
Completed connection handshake. Using HubProtocol 'json'.
connected!! ctA6QHwS4fvVGcufYvtlAA
and corresponding to them logs with js client:
Utils.ts:194 [2022-02-03T18:40:17.568Z] Debug: Starting HubConnection.
Utils.ts:194 [2022-02-03T18:40:17.568Z] Debug: Starting connection with transfer format 'Text'.
Utils.ts:194 [2022-02-03T18:40:17.576Z] Debug: Sending negotiation request: https://localhost:44440/hubs/order/negotiate?negotiateVersion=1.
Utils.ts:194 [2022-02-03T18:40:21.741Z] Debug: Skipping transport 'WebSockets' because it was disabled by the client.
Utils.ts:194 [2022-02-03T18:40:21.742Z] Debug: Selecting transport 'ServerSentEvents'.
Utils.ts:194 [2022-02-03T18:40:21.742Z] Trace: (SSE transport) Connecting.
Utils.ts:190 [2022-02-03T18:40:25.857Z] Information: SSE connected to https://localhost:44440/hubs/order?id=fxqgKpJnF5Dq5MX-RCfXcg
Utils.ts:194 [2022-02-03T18:40:25.857Z] Debug: The HttpConnection connected successfully.
Utils.ts:194 [2022-02-03T18:40:25.857Z] Debug: Sending handshake request.
Utils.ts:194 [2022-02-03T18:40:25.858Z] Trace: (SSE transport) sending data. String data of length 32.
Utils.ts:194 [2022-02-03T18:40:29.969Z] Trace: (SSE transport) request complete. Response status: 200.
Utils.ts:190 [2022-02-03T18:40:29.978Z] Information: Using HubProtocol 'json'.
Utils.ts:194 [2022-02-03T18:40:59.997Z] Debug: HttpConnection.stopConnection(undefined) called while in state Disconnecting.
index.js:1 [2022-02-03T18:40:59.997Z] Error: Connection disconnected with error 'Error: Server timeout elapsed without receiving a message from the server.'.
console.<computed> # index.js:1
Utils.ts:194 [2022-02-03T18:40:59.997Z] Debug: HubConnection.connectionClosed(Error: Server timeout elapsed without receiving a message from the server.) called while in state Connecting.
Utils.ts:194 [2022-02-03T18:40:59.997Z] Debug: Hub handshake failed with error 'Error: Server timeout elapsed without receiving a message from the server.' during start(). Stopping HubConnection.
Utils.ts:194 [2022-02-03T18:40:59.997Z] Debug: Call to HttpConnection.stop(Error: Server timeout elapsed without receiving a message from the server.) ignored because the connection is already in the disconnected state.
Utils.ts:194 [2022-02-03T18:40:59.997Z] Debug: HubConnection failed to start successfully because of error 'Error: Server timeout elapsed without receiving a message from the server.'.
here is sample code from js client application:
console.log("hub attached");
const hubConnection = new HubConnectionBuilder()
.withUrl(OrderHubUrl, {
transport: HttpTransportType.ServerSentEvents,
accessTokenFactory: () => user.accessToken ?? "",
})
.withAutomaticReconnect()
.configureLogging(LogLevel.Trace)
.build();
this.dispatcher.state.saveState("hubConnection", hubConnection);
const startConnection = async () => {
try {
await hubConnection.start();
console.log("connected");
} catch (e) {
this.dispatcher.dispatch(Event.ShowModal, {
actionName: "OK",
header: "Error",
content: e,
});
}
};
hubConnection.on("ReceiveMessage", (user, message) => {
console.log("message received");
console.log(user);
console.log(message);
});
hubConnection.onreconnecting((e) => console.log("reconnecting", e));
hubConnection.onreconnected((e) => console.log("reconnected", e));
startConnection();
}
as you can see from logs on the top, signalR js client is not able to pass through start method. Instead after some time it throws an error message.
below is my Program.cs file, where i configured signalR (i am suspecting that maybe something wrong is here)
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using OrderMaker.Authentication.Helpers;
using OrderMaker.Authentication.Services;
using OrderMaker.Entities;
using OrderMaker.Modules.Common.Services;
using OrderMaker.Modules.Order.Hubs;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddSignalR(c =>
{
c.EnableDetailedErrors = true;
c.ClientTimeoutInterval = TimeSpan.MaxValue;
c.KeepAliveInterval = TimeSpan.MaxValue;
});
ConfigureConfiguration(builder.Configuration);
ConfigureServices(builder.Services, builder.Configuration);
var app = builder.Build();
app.UseAuthentication();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCors(
x => x
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
app.MapFallbackToFile("index.html");
app.MapHub<OrderHub>("/hubs/order");
app.Run();
void ConfigureConfiguration(ConfigurationManager configuration)
{
using var client = new OrderMakerContext();
client.Database.EnsureCreated();
}
void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
services.AddControllers();
// configure strongly typed settings objects
var appSettingsSection = configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingsSection);
// configure jwt authentication
var appSettings = appSettingsSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
x.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
context.Token = context.Request.Cookies["order_maker_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
var accessToken = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hubs/")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
}
);
services.AddScoped<IUserService, UserService>();
services.AddSingleton<ILogService, LogService>();
}
definition of my hub:
public class OrderHub : Hub
{
public async Task SendNotification(List<OrderModel> message)
{
await Clients.All.SendAsync("ReceiveMessage", message);
}
public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
Console.WriteLine("connected!! " + Context.ConnectionId);
}
public override async Task OnDisconnectedAsync(Exception exception)
{
await base.OnDisconnectedAsync(exception);
Console.WriteLine("disconnected!!");
}
}
And sample controller from where i want to send message to clients:
private readonly IHubContext<OrderHub> _orderHubContext;
public OrdersController(IHubContext<OrderHub> orderHubContext)
{
_orderHubContext = orderHubContext;
}
[Route("api/[controller]/")]
[HttpPost]
//[Authorize(Roles = $"{Role.Admin},{Role.Manager},{Role.Employee}")]
public async Task<IActionResult> Post([FromBody] List<OrderModel> model)
{
/// some code for creating entities etc
db.SaveChanges();
/// notification to all clients that something new was added
Console.Write(_orderHubContext.Clients.All.ToString());
await _orderHubContext.Clients.All.SendAsync("ReceiveMessage", "hi there");
}
return Ok(new MessageResponse("Order added succesfully."));
}
basically i am lost, i spend two days already figuring out what may cause the issues, but i just can't make this thing working. I would really appreciate any sugestions or help. I tried to disable firewall, use different browser, etc without any success. Connection to hub is being made, c# app see that new connection but js client simply stuck in start() method for a while and throws an error message 'Error: Server timeout elapsed without receiving a message from the server.'.
Update: when i explicitly set type of transport in js clint to LongPolling hub is working as intended, but this is not ideal solution.
--- Update ---
All of those issues are happening only on local machine. I tried to check my luck and deploy to production app with transport fixed to SSE and it works without any issuess, as well as WebSocket transport. The only clue i have is that on localhost app is using kestrel and on server when i am hosting my app is using IIS.
You could try to set the serverTimeout and KeepAliveInterval in your program.cs:
builder.Services.AddSignalR(c =>
{
c.EnableDetailedErrors = true;
c.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
c.KeepAliveInterval = TimeSpan.FromSeconds(15);
});
Reason:
If the server hasn't sent a message within this interval, a ping message is sent automatically to keep the connection open. When changing KeepAliveInterval, change the ServerTimeout or serverTimeoutInMilliseconds setting on the client. The recommended ServerTimeout or serverTimeoutInMilliseconds value is double the KeepAliveInterval value.
So,you'd better not set the value of KeepAliveInterval as max value.
This is related to the SPA Proxy that .NET is setting up.
The message I get is as follows:
Utils.ts:193
[2022-09-29T17:09:19.866Z] Error: Failed to complete negotiation with the server: Error: <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot POST /hub/negotiate</pre>
</body>
</html>
: Status code '404' Either this is not a SignalR endpoint or there is a proxy blocking the connection.
What happens, likely, is that the proxy routes traffic from your spa to the port specified here in your csproj file
eg.
https://localhost:44423
You should not initiate a websocket to this port this is -not- proxied.
In my case, the dotnet served port is in reality https://localhost:7088 (properties/launchsettings.json)
So modify your Cors settings... like below, if the spaproxy e.g. is at 44423 and the .net service port at 7088.
services.AddCors(options =>
{
options.AddPolicy("ClientPermission", policy =>
{
policy.AllowAnyHeader()
.AllowAnyMethod()
.WithOrigins("https://localhost:44423", "https://localhost:7088")
.AllowCredentials();
});
});
Next is your js code looks like this e.g.:
const newConnection = new HubConnectionBuilder()
.withUrl('https://localhost:7088/hub/myhub')
.withAutomaticReconnect()
.build();
For me this solved it.

Kestrel isn't "using" pfx file for https

My local development is wanting to install a localhost ssl certificate even though I have specified a pfx I want it to use.
core API .NET 5.0
My understanding was that kestrel was used by default so this is what I have done.
In my hosts file I setup a FQDN.
127.0.0.1 myapi.mysite.com
In my program.cs I added ConfigureKestrel
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.UseStartup<Startup>()
.ConfigureKestrel(options =>
{
options.Listen(IPAddress.Loopback, 44394, listenOptions =>
{
var serverCertificate = LoadCertificate();
listenOptions.UseHttps(serverCertificate); // <- Configures SSL
});
});
});
private static X509Certificate2 LoadCertificate()
{
var assembly = typeof(Startup).GetTypeInfo().Assembly;
var embeddedFileProvider = new EmbeddedFileProvider(assembly, "My.API");
var certificateFileInfo = embeddedFileProvider.GetFileInfo("wildcard_mydomain_com.pfx");
using (var certificateStream = certificateFileInfo.CreateReadStream())
{
byte[] certificatePayload;
using (var memoryStream = new MemoryStream())
{
certificateStream.CopyTo(memoryStream);
certificatePayload = memoryStream.ToArray();
}
return new X509Certificate2(certificatePayload, "password");
}
}
I do not get any errors but I don't get the custom domain certificate presented to the browser either. The browser tell me I am unsecure and wants me to accept the localhost cert....which I don't want to accept. I want my custom domain cert to be used.
Am I not understanding how the certs work in a browser?
Doesn't the web server present the cert to the browser and the browser checks to see if it is issued by a legit CA?
Am I loading my custom cert but not "attaching" it to the development server to use?

SignalR behind Yarp.ReverseProxy leads to timeout cause server not answering

I have implemented an yarp.reverse proxy server with the code below:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpProxy();
services.AddCors(options =>
{
options.AddPolicy("customPolicy", builder =>
{
builder.AllowAnyOrigin();
});
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHttpProxy httpProxy)
{
// Configure our own HttpMessageInvoker for outbound calls for proxy operations
var httpClient = new HttpMessageInvoker(new SocketsHttpHandler()
{
UseProxy = false,
AllowAutoRedirect = false,
AutomaticDecompression = DecompressionMethods.None,
UseCookies = false
});
// Setup our own request transform class
var transformer = new CustomTransformer(); // or HttpTransformer.Default;
var requestOptions = new RequestProxyOptions { Timeout = TimeSpan.FromSeconds(100) };
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.Map("/{**catch-all}", async httpContext =>
{
httpContext.Request.Headers["Connection"] = "upgrade";
await httpProxy.ProxyAsync(httpContext, "http://192.168.178.80:5000", httpClient, requestOptions, transformer);
var errorFeature = httpContext.Features.Get<IProxyErrorFeature>();
save_log(httpContext.Request.Path, "/", "http://192.168.178.80:5000" + httpContext.Request.Path, "3");
// Check if the proxy operation was successful
if (errorFeature != null)
{
var error = errorFeature.Error;
var exception = errorFeature.Exception;
}
});
});
}
And in another app a SignalR server following this example: https://learn.microsoft.com/en-GB/aspnet/core/tutorials/signalr?view=aspnetcore-5.0&tabs=visual-studio
The proxy server works and forwards the request to the signalR server. But the signalR Client is not able to connect to the signalR Server. I always get a Connection disconnected with error
Error: Server timeout elapsed without receiving a message from the server.
in the Java Script console.
But the SSE is connected as you can see in the following browser status report:
signalr.js:2156 [2021-03-25T13:19:29.970Z] Information: SSE connected to https://localhost:44318/chatHub?id=IqKD6P0NsUY9Is6OSrMusQ
The problem seems to be the Proxy Server because if I call the site directly it works. Has somebody any idea what's wrong with my Proxy and how I can solve it?

how to use ssl certificates with gRPC and ASP Net Core 3.0?

I am rtying to configure the service to use a SSL certificate. I have read this post:
How to enable server side SSL for gRPC?
I guess this is the main code:
var cacert = File.ReadAllText(#"ca.crt");
var servercert = File.ReadAllText(#"server.crt");
var serverkey = File.ReadAllText(#"server.key");
var keypair = new KeyCertificatePair(servercert, serverkey);
var sslCredentials = new SslServerCredentials(new List<KeyCertificatePair>() { keypair }, cacert, false);
var server = new Server
{
Services = { GrpcTest.BindService(new GrpcTestImpl(writeToDisk)) },
Ports = { new ServerPort("0.0.0.0", 555, sslCredentials) }
};
server.Start();
The problem is that in my case, I don't start the service in this way, I am using kestrel, and the code is this:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(options =>
{
System.Net.IPAddress miAddress = System.Net.IPAddress.Parse("x.x.x.x");
//options.Listen(miAddress, 5001, o => o.Protocols = HttpProtocols.Http2);
options.Listen(miAddress, 5001, l =>
{
l.Protocols = HttpProtocols.Http2;
l.UseHttps();
});
});
webBuilder.UseStartup<Startup>();
});
In this case, I don't have access to SslCredentials, so I can't create a new one.
How could I configure my ssl certificate using kestrel?
Thanks.
The post you linked to is for Grpc.Core, the grpc-dotnet implementation is configured differently.
This documentation and example should help:
https://github.com/grpc/grpc-dotnet/blob/dd72d6a38ab2984fd224aa8ed53686dc0153b9da/testassets/InteropTestsWebsite/Program.cs#L55
https://learn.microsoft.com/en-us/aspnet/core/grpc/authn-and-authz?view=aspnetcore-3.1
(in another words, you can configure the certificates on the server side exactly the same way as you would for any other HTTP/2 server - there's nothing grpc specific in configuring the secure connections in ASP.NET Core).
It looks like you mistake authentication by certificates for SSL-data-encryption. In case you just want to encrypt the data channel, good practice is to use Kestrel:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(builder =>
{
builder.ConfigureKestrel(options =>
{
options.Listen(IPAddress.Loopback, 5005, configure => { configure.UseHttps(); configure.Protocols = HttpProtocols.Http2; });
});
});
The call to UseHttps() uses the internal ASP.NET Core’s trusted development certificate.
If you want to provide one yourself, use i.e. (or the other overloads):
public static ListenOptions UseHttps(this ListenOptions listenOptions, X509Certificate2 serverCertificate)

Categories

Resources