My issue was how to configure a gRPC server to use TLS. Using OpenSSL, there are a myriad of ways to create certificate files, but you have to know exactly which set of commands to use to get the type of certificate you need. In my case, I am using Ubuntu 20.04 Server, Visual Studio 2022, C#, ASP.NET Core 6, and gRPC.
The base issue was how to configure Kestrel to use a certificate and listen to a set of specific ports. The Microsoft documentation is pitiful in that it will give you the signature of an API call, but almost NEVER will show even a minute code snippet. I could find no examples in this or any other Google search. I basically brute forced my way to success.
I have two profiles in launchSettings.json:
{
"profiles": {
"OperationsServicesDev": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:30051",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_PREVENTHOSTINGSTARTUP": "true"
}
},
"OperationsServicesProd": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://some.node.com:30051",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production",
"ASPNETCORE_PREVENTHOSTINGSTARTUP": "true"
}
}
}
}
I use the various profiles to control things that are different in the Linux versus Windows worlds such as log folders, ports, etc. I also make use of appsettigns.Development.json and appsettings.Production.json to further control the differences in environments. The development profile always worked as I was testing in a development environment using a dev cert. When I deployed the published works to my server, the wheels stopped turning. I would get a denial of service, or a timeout, or a vague error about the certificate not passing the parsing stage.
I have a valid set of certs from Godaddy.com. I will come back to this in a bit.
Since projects in .NET 6 have had their templates changed, this added more time brute-forcing stuff, but I figured that out. Startup.cs is no longer used (I guess you can still create one and use it, but all of the configuration is now done in Program.cs).
I start off getting access to the various builders:
// Start the app configuration. Enlist the help of the various
// .Net Core builders.
var builder = WebApplication.CreateBuilder (args);
var webHostBuilder = builder.WebHost;
// What environment are we running? It could be either Development
// or Production. This is used to get the correct configuration from
// the appsettings.Development.json or appsettings.Production.json
// files.
var environment = builder.Environment.EnvironmentName;
Then, using NLog, I configure the loggers:
// Configure logging.
_ = webHostBuilder.ConfigureLogging (options => {
// Get the correct appsettigns.environment.json file.
var config = new ConfigurationBuilder ()
.SetBasePath (Directory.GetCurrentDirectory ())
.AddJsonFile ($"appsettings.{environment}.json", optional: true, reloadOnChange: true).Build ();
_ = options.AddNLog (config);
// Get the logger.
LogManager.Configuration = new NLogLoggingConfiguration (config.GetSection ("NLog"));
logger = NLogBuilder.ConfigureNLog (LogManager.Configuration).GetCurrentClassLogger () as NLog.ILogger;
});
Next it's time to configure Kestrel:
// Configure Kestrel, the .NET Core web server.
var hostBuilder = webHostBuilder.ConfigureKestrel (kestrelServerOptions => {
kestrelServerOptions.ConfigureHttpsDefaults (httpsConnectionAdapterOptions => httpsConnectionAdapterOptions.SslProtocols = SslProtocols.Tls12);
// Read in the X.509 certificate file.
var certPath = Path.Combine (builder.Environment.ContentRootPath, "Certs", $"xxx-{environment}.pfx");
kestrelServerOptions.ConfigureEndpointDefaults (listenOptions => {
_ = listenOptions.UseHttps (certPath, password);
logger.Debug ($"Using {certPath} as the cert file.");
logger.Debug ("Configuring host to use HTTP/2 protocol.");
listenOptions.Protocols = HttpProtocols.Http2;
});
logger.Debug ("Reading config values for the server name and port.");
// Get the host name and port number to bind the service to.
var port = builder.Configuration.GetValue<int> ("AppSettings:OperationsServerPort");
var address = IPAddress.Parse ("0.0.0.0");
if (address != null) {
logger.Debug ($"Host will listen at https://{address}:{port}");
kestrelServerOptions.Listen (address, port);
} else {
logger.Error ("DNS address for service host cannot be determined! Exiting...");
Environment.Exit (-1);
}
});
When errors occur on the server side, it is helpful for the gRPC pipeline to give some clues on exceptions thrown:
// Configure gRPC exception handling.
_ = builder.Services.AddGrpc (grpcServiceOptions => {
grpcServiceOptions.Interceptors.Add<ServerLoggerInterceptor> ();
_ = grpcServiceOptions.EnableDetailedErrors = true;
});
The rest is boilerplate.
Now, the crucial thing is in creating the .pfx cert file. I had originally been including the Godaddy CA bundle file, typically named gd_bundle-g2-g1.crt. When I added several certs to the Ubuntu trust store (and this Godaddy bundle was one of the files I loaded into the trust store) I noticed an error when I did a
sudo update-ca-certificates --fresh
I got this extremely helpful clue:
Clearing symlinks in /etc/ssl/certs... done. Updating certificates in
/etc/ssl/certs... rehash: warning: skipping gd_bundle-g2-g1.pem,it
does not contain exactly one certificate or CRL 131 added, 0 removed;
done. Running hooks in /etc/ca-certificates/update.d... done.
This was the OpenSSL command I used to create the bad certificate file:
openssl pkcs12 -export -out certificate.pfx -inkey server.key -in 1baa5781b0db93d3.crt -certfile gd_bundle-g2-g1.crt
The last bit, the -certfile, was the issue. When I regenerated the certificate using the following command:
openssl pkcs12 -export -out certificate.pfx -inkey server.key -in 1baa5781b0db93d3.crt
everything started to work like a charm.
Key takeaways:
- Be tenacious
- Don't listen to the naysayers
- Read the documentation and then read it again and then read it again
- Download the source and read that, too.
Somewhere along the way, you will find a way to be successful.