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

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")
});
}
});
});
});

Related

Problem with ssl while using wsl and identity server 4

I have developed the simple identity server application with entity framework storage for user credentials and the client app asp.net core MVC with OpenId authentication. It worked properly on the local machine but when I am trying to debug it on a wsl with ubuntu 20 04 installed I get the following error.
AuthenticationException: The remote certificate is invalid according to the validation procedure.
I simply use wsl as a debug target in Visual Studio. Installed .net on a wsl machine, generated the developer certificates, and simply ran 2 projects simultanuosly. Sorry but I don't know what code I should provide to debug the problem here is my client configuration on an Identity server side:
new Client
{
ClientId = "mvc_client",
ClientSecrets = { new Secret("mvc_client_secret".ToSha256()) },
AllowedGrantTypes = GrantTypes.Code,
RequireConsent = false,
AllowedScopes =
{
"dummy_api",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
},
RedirectUris = { "https://localhost:5000/signin-oidc" }
},
And the open id on a client-side:
services.AddAuthentication(config =>
{
config.DefaultScheme = "Cookie";
config.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookie")
.AddOpenIdConnect("oidc", config =>
{
config.Authority = "https://localhost:5001/";
//config.Authority = "http://192.168.1.11:5004/";
//config.RequireHttpsMetadata = false;
config.ClientId = "mvc_client";
config.ClientSecret = "mvc_client_secret";
config.SaveTokens = true; // persist tokens in the cookie
config.ResponseType = "code";
});
I am getting this error while trying to login with client app. If I try just to login with Identity server everythin works.
If you can access the oidc config address (in your case it should be: https:/localhost:5001/.well-known/openid-configuration) in Postman or your browser and you are just testing you can set the BackchannelHttpHandler to always return true on certificate validation.
Also set SslProtocols to allow different versions. These should be avoided in production environment for security reasons:
.
.
.AddOpenIdConnect("oidc", config =>
{
config.BackchannelHttpHandler = new HttpClientHandler
{
SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13,
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
};
});

ASP.NET Core get http version from WebApplication

I need to start a webserver without a specific IP and Port with HTTP 1.1 and HTTP 2.
After start, i need to provide the webservers IP, Port and HTTP version of each url.
var builder = WebApplication.CreateBuilder();
...
builder_.WebHost.UseKestrel(options =>
{
options.ListenAnyIP(0, (listenOptions) =>
{
listenOptions.Protocols = HttpProtocols.Http1;
});
options.ListenAnyIP(0, (listenOptions) =>
{
listenOptions.Protocols = HttpProtocols.Http2;
});
});
...
var app = builder.Build();
// Start application
_ = app.RunAsync();
// Does only provide IP and Port but not HTTP version
var urls = app.Urls;
ASP.NET Core .net6.0
So far i was unable to retrieve the HTTP Version of the webservers endpoints from the "WebApplication" class.

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?

Bind gRPC services to specific port in aspnetcore

Using aspnetcore 3.1 and the Grpc.AspNetCore nuget package, I have managed to get gRPC services running successfully alongside standard asp.net controllers as described in this tutorial.
However I would like to bind the gRPC services to a specific port (e.g. 5001), preferably through configuration instead of code if possible. This is because I would like to limit how my gRPC services are exposed.
The closest I have come has been using RequireHost when mapping the endpoints:
// Startup.cs
public void Configure(IApplicationBuilder app)
{
// ...
app.useEndpoints(endpoints =>
{
endpoints.MapGrpcService<MyService>()
.RequireHost("0.0.0.0:5001");
});
}
This seems to do what I want but I can't find any documentation about it, and it requires configuration in code per service. Perhaps there is a better way?
This works (server side) with Kestrel:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(options =>
{
options.Listen(IPAddress.Loopback, 5000);
options.Listen(IPAddress.Loopback, 5005, configure => configure.UseHttps());
});
webBuilder.UseStartup<Startup>();
});
client side:
var httpHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
using var channel = GrpcChannel.ForAddress("https://localhost:5005", new GrpcChannelOptions { HttpHandler = httpHandler } );
var client = new Greeter.GreeterClient(channel);
Note:
var httpHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
when you have a self-signed certificate without a trust chain (mostly when developing).
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
Is for support of http.
You need to configure the middleware
app.UseRouting();
app.MapWhen(context => {
return context.Connection.LocalPort == 1000
}, newApp => {
newApp.UseRouting();
newApp.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<Service1>();
}
});
app.MapWhen(context => {
return context.Connection.LocalPort == 2000
}, newApp => {
newApp.UseRouting();
newApp.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<Service2>();
}
});
In the ASP.NET Core 6.0 ports can be changed in the Properties > launchSettings.json file. But this file is considered only if you run the server from the Visual Studio or VS Code.
I was trying to run the server directly using the .exe file for testing. The server was running with the default ports: "http://localhost:5000;https://localhost:5001".
Finally, I changed it from the appsettings.json for the .exe file:
"AllowedHosts": "*",
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "https://localhost:7005",
"Protocols": "Http1AndHttp2"
},
"gRPC": {
"Url": "http://localhost:5005",
"Protocols": "Http2"
}
}
As far as I know, there is no other way to set a specific port for the GRPC service.
The grpc service is also running on the asp.net core kestrel server, the server will listen the port not the service.
If your asp.net core application just has GRPC service, you could just set the kestrel server's listen port to 5001.
If you have multiple service like MVC web api or else, RequireHost is the best workaround to allow only specific port access the grpc service.
If you want to prompt the routing system for GRPC service to require the specified port, you could use below port:
routes.MapGrpcService<MyService>().RequireHost("*:5001");
You can try to use the UseWhen method to use the MapGrpcService endpoints only when the request uses the port you defined.
var grpcPort = 5001;
app.UseWhen(context => context.Connection.LocalPort == grpcPort,
builder =>
{
builder.UseRouting();
builder.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<MyService>();
});
});
This has the benefit of not repeating .RequireHost("*:5001"); for every single service, although repeating UseRouting twice may induce weird behaviour: for instance, authentication may not be working unless you put in builder.UseAuthentication() after builder.UseRouting().
However, this behaviour be useful if you want to have a distinct request pipeline for REST and gRPC.

Azure web app service to call onpremise WEB API using HttpClient using hybrid connection manager

We have onpremise web service (asp.net core mvc) deployed on local network machine. We are trying to call these WEB API using App Service deployed on Azure. But it is giving time out error or Task was cancelled error in case when we try to connect it using "HTTP" Protocol. In case of "HTTPS" it is giving "security error occurred error".
We have created Hybrid connection on Azure App Service to connect to onpremise web api service which shows online for both 80 and 443 port. We have setup Hybrid Connection Manager on local network machine too.
Below is the code snippet for calling code which is deployed on Azure App Service (e.g. https://xyz.azurewebsite.com)
try
{
var httpClient = new HttpClient();
httpClient.Timeout = TimeSpan.FromMinutes(60);
httpClient.BaseAddress = new Uri("https://onpremise");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
//Simple Get Request to test on-premise service
var response = await httpClient.GetStringAsync("api/OnPremiseData/GetData");
ViewBag.ResponseText = response;
}
Above code works fine in case I debug application from localhost. So there is no issue with code I assume.
Below is web api code snippet:
[Route("api/[controller]/[action]")]
public class OnPremiseDataController : Controller
{
[HttpGet]
public string GetData()
{
return "Success";
}
}
and below is startup.cs file
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors(options => options.WithOrigins("https://xyz.azurewebsite.com", "https://localhost:44310").AllowAnyMethod().AllowAnyHeader());
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
We had similar scenario (WebApp and SQL on-premise) and the trick was to use fully qualified domain name of the endpoint in Hybrid Connection Manager on the on-premise machine.
Below is the solution for above question. Basically I have solved 2 problem.
First is using FQDN (fully qualified domain name) I am able to connect to on-premise services. Please make sure you are using FQDN while configuring endpoint in Hybrid connection on Azure.
Second, using below line of code I am able to make secure HTTPS request ot on-premise server:
HttpMessageHandler handler = new HttpClientHandler
{
SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls,
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
};
Below is complete solution of above problem :
HttpMessageHandler handler = new HttpClientHandler
{
SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls,
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
};
var httpClient = new HttpClient(handler);
httpClient.Timeout = TimeSpan.FromMinutes(60);
httpClient.BaseAddress = new Uri("https://onpremise");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Simple Get Request to test on-premise service
var response = await httpClient.GetStringAsync("api/OnPremiseData/GetData");
ViewBag.ResponseText = response;

Categories

Resources