Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: integrate mutual TLS scenario with underlying http.sys server #2044

Merged
merged 18 commits into from
Jan 15, 2025
Merged
3 changes: 3 additions & 0 deletions build/trend-scenarios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ parameters:
- displayName: "HttpSys Windows: mTLS Handshakes"
arguments: --scenario mTls-handshakes-httpsys $(tlsJobs) --property scenario=HttpSysMutualTLSHandshakes --application.options.requiredOperatingSystem windows

- displayName: "HttpSys Windows: TLS Renegotiation"
arguments: --scenario tls-renegotiation-httpsys $(tlsJobs) --property scenario=HttpSysTLSRenegotiation --application.options.requiredOperatingSystem windows

- displayName: "Kestrel Linux: TLS Handshakes"
arguments: --scenario tls-handshakes-kestrel $(tlsJobs) --property scenario=KestrelTLSHandshakes --application.options.requiredOperatingSystem linux

Expand Down
30 changes: 27 additions & 3 deletions scenarios/tls.benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ jobs:
project: src/BenchmarksApps/TLS/HttpSys/HttpSys.csproj
readyStateText: Application started.
variables:
mTLS: false
mTLS: false # enables settings on http.sys to negotiate client cert on connections
tlsRenegotiation: false # enables client cert validation
certValidationConsoleEnabled: false
httpSysLogs: false
statsEnabled: false
arguments: "--urls https://{{serverAddress}}:{{serverPort}} --mTLS {{mTLS}} --certValidationConsoleEnabled {{certValidationConsoleEnabled}} --statsEnabled {{statsEnabled}}"
arguments: "--urls https://{{serverAddress}}:{{serverPort}} --mTLS {{mTLS}} --certValidationConsoleEnabled {{certValidationConsoleEnabled}} --statsEnabled {{statsEnabled}} --tlsRenegotiation {{tlsRenegotiation}} --httpSysLogs {{httpSysLogs}}"

kestrelServer:
source:
Expand Down Expand Up @@ -52,7 +54,29 @@ scenarios:
application:
job: httpSysServer
variables:
mTLS: true
mTLS: true # enables settings on http.sys to negotiate client cert on connections
tlsRenegotiation: true # enables client cert validation
httpSysLogs: false # only for debug purposes
certValidationConsoleEnabled: false # only for debug purposes
serverPort: 8080 # IMPORTANT: not to intersect with other tests in case http.sys configuration impacts other benchmarks
load:
job: httpclient
variables:
serverPort: 8080 # in sync with server
path: /hello-world
presetHeaders: connectionclose
connections: 32
serverScheme: https
certPath: https://raw.githubusercontent.com/aspnet/Benchmarks/refs/heads/main/src/BenchmarksApps/TLS/HttpSys/testCert.pfx
certPwd: testPassword

tls-renegotiation-httpsys:
application:
job: httpSysServer
variables:
mTLS: false
tlsRenegotiation: true
httpSysLogs: false # only for debug purposes
certValidationConsoleEnabled: false # only for debug purposes
load:
job: httpclient
Expand Down
6 changes: 6 additions & 0 deletions src/BenchmarksApps/TLS/HttpSys/HttpSys.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,10 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<None Update="testCert.pfx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
89 changes: 89 additions & 0 deletions src/BenchmarksApps/TLS/HttpSys/NetShWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System.Diagnostics;
using System.Security.Cryptography.X509Certificates;

namespace HttpSys
{
public static class NetShWrapper
{
public static void DisableHttpSysMutualTlsIfExists(string ipPort)
{
try
{
DisableHttpSysMutualTls(ipPort);
}
catch
{
// ignore
}
}

public static void DisableHttpSysMutualTls(string ipPort)
{
Console.WriteLine("Disabling mTLS for http.sys");

string command = $"http delete sslcert ipport={ipPort}";
ExecuteNetShCommand(command);

Console.WriteLine("Disabled http.sys settings for mTLS");
}

public static void Show()
{
ExecuteNetShCommand("http show sslcert", alwaysLogOutput: true);
}

public static void EnableHttpSysMutualTls(string ipPort)
{
Console.WriteLine("Setting up mTLS for http.sys");

var certificate = LoadCertificate();
Console.WriteLine("Loaded `testCert.pfx` from local file system");
using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadWrite);
store.Add(certificate);
Console.WriteLine("Added `testCert.pfx` to localMachine cert store");
store.Close();
}

string certThumbprint = certificate.Thumbprint;
string appId = Guid.NewGuid().ToString();

string command = $"http add sslcert ipport={ipPort} certstorename=MY certhash={certThumbprint} appid={{{appId}}} clientcertnegotiation=enable";
ExecuteNetShCommand(command);

Console.WriteLine("Configured http.sys settings for mTLS");
}

private static void ExecuteNetShCommand(string command, bool alwaysLogOutput = false)
{
ProcessStartInfo processInfo = new ProcessStartInfo("netsh", command)
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};

Console.WriteLine($"Executing command: `netsh {command}`");
using Process process = Process.Start(processInfo)!;
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();

if (alwaysLogOutput)
{
Console.WriteLine(output);
}

if (process.ExitCode != 0)
{
throw new InvalidOperationException($"netsh command execution failure: {output}");
}
}

private static X509Certificate2 LoadCertificate()
=> File.Exists("testCert.pfx")
? X509CertificateLoader.LoadPkcs12FromFile("testCert.pfx", "testPassword", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable)
: X509CertificateLoader.LoadPkcs12FromFile("../testCert.pfx", "testPassword", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
}
}
47 changes: 45 additions & 2 deletions src/BenchmarksApps/TLS/HttpSys/Program.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
using Microsoft.AspNetCore.Mvc;
using HttpSys;
using Microsoft.AspNetCore.Server.HttpSys;

var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();

var writeCertValidationEventsToConsole = bool.TryParse(builder.Configuration["certValidationConsoleEnabled"], out var certValidationConsoleEnabled) && certValidationConsoleEnabled;
var httpSysLoggingEnabled = bool.TryParse(builder.Configuration["httpSysLogs"], out var httpSysLogsEnabled) && httpSysLogsEnabled;
var statsEnabled = bool.TryParse(builder.Configuration["statsEnabled"], out var connectionStatsEnabledConfig) && connectionStatsEnabledConfig;
var mTlsEnabled = bool.TryParse(builder.Configuration["mTLS"], out var mTlsEnabledConfig) && mTlsEnabledConfig;
var tlsRenegotiationEnabled = bool.TryParse(builder.Configuration["tlsRenegotiation"], out var tlsRenegotiationEnabledConfig) && tlsRenegotiationEnabledConfig;
var listeningEndpoints = builder.Configuration["urls"] ?? "https://localhost:5000/";
var httpsIpPort = listeningEndpoints.Split(";").First(x => x.Contains("https")).Replace("https://", "");

#pragma warning disable CA1416 // Can be launched only on Windows (HttpSys)
builder.WebHost.UseHttpSys(options =>
Expand All @@ -17,7 +20,7 @@
});
#pragma warning restore CA1416 // Can be launched only on Windows (HttpSys)

var app = builder.Build();
var app = builder.Build();

app.MapGet("/hello-world", () =>
{
Expand All @@ -40,6 +43,40 @@
}

if (mTlsEnabled)
{
var hostAppLifetime = app.Services.GetRequiredService<IHostApplicationLifetime>();
hostAppLifetime!.ApplicationStopping.Register(OnShutdown);

void OnShutdown()
{
Console.WriteLine("Application shutdown started.");

try
{
NetShWrapper.DisableHttpSysMutualTls(ipPort: httpsIpPort);
}
catch
{
Console.WriteLine("Failed to disable HTTP.SYS mTLS settings");
throw;
}
}

try
{
// if not executed, following command (enable http.sys mutual tls) will fail because binding exists
NetShWrapper.DisableHttpSysMutualTlsIfExists(ipPort: httpsIpPort);

NetShWrapper.EnableHttpSysMutualTls(ipPort: httpsIpPort);
}
catch
{
Console.WriteLine($"Http.Sys configuration for mTLS failed");
throw;
}
}

if (tlsRenegotiationEnabled)
{
// this is an http.sys middleware to get a cert
Console.WriteLine("Registered client cert validation middleware");
Expand Down Expand Up @@ -72,6 +109,12 @@
}

await app.StartAsync();

if (httpSysLoggingEnabled)
{
NetShWrapper.Show();
}

Console.WriteLine("Application Info:");
if (mTlsEnabled)
{
Expand Down
4 changes: 3 additions & 1 deletion src/BenchmarksApps/TLS/HttpSys/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"Microsoft.AspNetCore": "Warning"
}
},
"mTLS": "true",
"mTLS": "false",
"httpSysLogs": "true",
"tlsRenegotiation": "true",
"certValidationConsoleEnabled": "true"
}
Loading