Skip to content

Commit 44ed67e

Browse files
committed
fix httpsys cert bindings and fail fast
1 parent 95365f5 commit 44ed67e

File tree

4 files changed

+69
-27
lines changed

4 files changed

+69
-27
lines changed

scenarios/tls.benchmarks.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@ jobs:
2020
tlsRenegotiation: false # enables client cert validation
2121
# debug settings
2222
certValidationConsoleEnabled: false
23-
httpSysLogs: false
2423
statsEnabled: false
2524
logRequestDetails: false
26-
arguments: "--urls https://{{serverAddress}}:{{serverPort}} --mTLS {{mTLS}} --certValidationConsoleEnabled {{certValidationConsoleEnabled}} --statsEnabled {{statsEnabled}} --tlsRenegotiation {{tlsRenegotiation}} --httpSysLogs {{httpSysLogs}} --logRequestDetails {{logRequestDetails}}"
25+
arguments: "--urls https://{{serverAddress}}:{{serverPort}} --mTLS {{mTLS}} --certValidationConsoleEnabled {{certValidationConsoleEnabled}} --statsEnabled {{statsEnabled}} --tlsRenegotiation {{tlsRenegotiation}} --logRequestDetails {{logRequestDetails}}"
2726

2827
kestrelServer:
2928
source:
@@ -81,7 +80,6 @@ scenarios:
8180
variables:
8281
mTLS: true # enables settings on http.sys to negotiate client cert on connections
8382
tlsRenegotiation: true # enables client cert validation
84-
httpSysLogs: false # only for debug purposes
8583
certValidationConsoleEnabled: false # only for debug purposes
8684
serverPort: 8080 # IMPORTANT: not to intersect with other tests in case http.sys configuration impacts other benchmarks
8785
load:
@@ -102,7 +100,6 @@ scenarios:
102100
variables:
103101
mTLS: false
104102
tlsRenegotiation: true
105-
httpSysLogs: false # only for debug purposes
106103
certValidationConsoleEnabled: false # only for debug purposes
107104
load:
108105
job: httpclient

src/BenchmarksApps/TLS/HttpSys/NetShWrapper.cs

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
using System.Diagnostics;
22
using System.Security.Cryptography.X509Certificates;
3+
using System.Text.RegularExpressions;
34

45
namespace HttpSys
56
{
67
public static class NetShWrapper
78
{
8-
public static void DisableHttpSysMutualTlsIfExists(string ipPort)
9+
public static void DeleteBindingIfExists(string ipPort)
910
{
1011
try
1112
{
12-
DisableHttpSysMutualTls(ipPort);
13+
DeleteBinding(ipPort);
1314
}
1415
catch
1516
{
1617
// ignore
1718
}
1819
}
1920

20-
public static void DisableHttpSysMutualTls(string ipPort)
21+
public static void DeleteBinding(string ipPort)
2122
{
2223
Console.WriteLine("Disabling mTLS for http.sys");
2324

@@ -27,14 +28,42 @@ public static void DisableHttpSysMutualTls(string ipPort)
2728
Console.WriteLine("Disabled http.sys settings for mTLS");
2829
}
2930

31+
public static bool BindingExists(string ipPort, out string certThumbprint, out string appId)
32+
{
33+
certThumbprint = string.Empty;
34+
appId = string.Empty;
35+
36+
var bindings = ExecuteNetShCommand("http show sslcert");
37+
if (string.IsNullOrEmpty(bindings) || !bindings.Contains(ipPort))
38+
{
39+
return false;
40+
}
41+
42+
// Extract the certificate thumbprint
43+
var thumbprintMatch = Regex.Match(bindings, @"Certificate Hash\s+:\s+([a-fA-F0-9]+)");
44+
if (thumbprintMatch.Success)
45+
{
46+
certThumbprint = thumbprintMatch.Groups[1].Value;
47+
}
48+
49+
// Extract the application ID
50+
var appIdMatch = Regex.Match(bindings, @"Application ID\s+:\s+{([a-fA-F0-9-]+)}");
51+
if (appIdMatch.Success)
52+
{
53+
appId = appIdMatch.Groups[1].Value;
54+
}
55+
56+
return true;
57+
}
58+
3059
public static void Show()
3160
{
3261
ExecuteNetShCommand("http show sslcert", alwaysLogOutput: true);
3362
}
3463

35-
public static void EnableHttpSysMutualTls(string ipPort)
64+
public static void SetTestCertBinding(string ipPort, bool enableClientCertNegotiation)
3665
{
37-
Console.WriteLine("Setting up mTLS for http.sys");
66+
Console.WriteLine("Setting up binding for testCert for http.sys");
3867

3968
var certificate = LoadCertificate();
4069
Console.WriteLine("Loaded `testCert.pfx` from local file system");
@@ -47,25 +76,36 @@ public static void EnableHttpSysMutualTls(string ipPort)
4776
}
4877

4978
string certThumbprint = certificate.Thumbprint;
50-
string appId = Guid.NewGuid().ToString();
79+
SetCertBinding(ipPort, certThumbprint, enableClientCertNegotiation: enableClientCertNegotiation);
5180

52-
string command = $"http add sslcert ipport={ipPort} certstorename=MY certhash={certThumbprint} appid={{{appId}}} clientcertnegotiation=enable";
53-
ExecuteNetShCommand(command);
81+
Console.WriteLine("Configured binding for testCert for http.sys");
82+
}
5483

55-
Console.WriteLine("Configured http.sys settings for mTLS");
84+
public static void SetCertBinding(string ipPort, string certThumbprint, string appId = null, bool enableClientCertNegotiation = false)
85+
{
86+
var negotiateClientCert = enableClientCertNegotiation ? "enable" : "disable";
87+
if (string.IsNullOrEmpty(appId))
88+
{
89+
appId = "00000000-0000-0000-0000-000000000000";
90+
}
91+
string command = $"http add sslcert ipport={ipPort} certstorename=MY certhash={certThumbprint} appid={{{appId}}} clientcertnegotiation={negotiateClientCert}";
92+
ExecuteNetShCommand(command);
5693
}
5794

58-
private static void ExecuteNetShCommand(string command, bool alwaysLogOutput = false)
95+
private static string ExecuteNetShCommand(string command, bool alwaysLogOutput = false)
96+
=> ExecuteCommand("netsh", command, alwaysLogOutput);
97+
98+
private static string ExecuteCommand(string fileName, string command, bool alwaysLogOutput = false)
5999
{
60-
ProcessStartInfo processInfo = new ProcessStartInfo("netsh", command)
100+
ProcessStartInfo processInfo = new ProcessStartInfo(fileName, command)
61101
{
62102
RedirectStandardOutput = true,
63103
RedirectStandardError = true,
64104
UseShellExecute = false,
65105
CreateNoWindow = true
66106
};
67107

68-
Console.WriteLine($"Executing command: `netsh {command}`");
108+
Console.WriteLine($"Executing command: `{fileName} {command}`");
69109
using Process process = Process.Start(processInfo)!;
70110
string output = process.StandardOutput.ReadToEnd();
71111
process.WaitForExit();
@@ -77,8 +117,10 @@ private static void ExecuteNetShCommand(string command, bool alwaysLogOutput = f
77117

78118
if (process.ExitCode != 0)
79119
{
80-
throw new InvalidOperationException($"netsh command execution failure: {output}");
120+
throw new InvalidOperationException($"{fileName} command execution failure: {output}");
81121
}
122+
123+
return output;
82124
}
83125

84126
private static X509Certificate2 LoadCertificate()

src/BenchmarksApps/TLS/HttpSys/Program.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
builder.Logging.ClearProviders();
88

99
// behavioral
10-
var httpSysLoggingEnabled = bool.TryParse(builder.Configuration["httpSysLogs"], out var httpSysLogsEnabled) && httpSysLogsEnabled;
1110
var mTlsEnabled = bool.TryParse(builder.Configuration["mTLS"], out var mTlsEnabledConfig) && mTlsEnabledConfig;
1211
var tlsRenegotiationEnabled = bool.TryParse(builder.Configuration["tlsRenegotiation"], out var tlsRenegotiationEnabledConfig) && tlsRenegotiationEnabledConfig;
1312
var listeningEndpoints = builder.Configuration["urls"] ?? "https://localhost:5000/";
@@ -18,6 +17,13 @@
1817
var statsEnabled = bool.TryParse(builder.Configuration["statsEnabled"], out var connectionStatsEnabledConfig) && connectionStatsEnabledConfig;
1918
var logRequestDetails = bool.TryParse(builder.Configuration["logRequestDetails"], out var logRequestDetailsConfig) && logRequestDetailsConfig;
2019

20+
// existing netsh bindings to restore after the benchmark run
21+
if (!NetShWrapper.BindingExists(httpsIpPort, out var originalCertThumbprint, out var originalAppId))
22+
{
23+
Console.WriteLine("WARNING: no binding existed, performing a new self-signed certificate generation and binding...");
24+
throw new ApplicationException($"SslCert binding should exist for '{httpsIpPort}' before. Infrastructure error.");
25+
}
26+
2127
#pragma warning disable CA1416 // Can be launched only on Windows (HttpSys)
2228
builder.WebHost.UseHttpSys(options =>
2329
{
@@ -81,7 +87,8 @@ void OnShutdown()
8187

8288
try
8389
{
84-
NetShWrapper.DisableHttpSysMutualTls(ipPort: httpsIpPort);
90+
NetShWrapper.DeleteBinding(ipPort: httpsIpPort);
91+
NetShWrapper.SetCertBinding(ipPort: httpsIpPort, certThumbprint: originalCertThumbprint, appId: originalAppId);
8592
}
8693
catch
8794
{
@@ -93,9 +100,8 @@ void OnShutdown()
93100
try
94101
{
95102
// if not executed, following command (enable http.sys mutual tls) will fail because binding exists
96-
NetShWrapper.DisableHttpSysMutualTlsIfExists(ipPort: httpsIpPort);
97-
98-
NetShWrapper.EnableHttpSysMutualTls(ipPort: httpsIpPort);
103+
NetShWrapper.DeleteBindingIfExists(ipPort: httpsIpPort);
104+
NetShWrapper.SetTestCertBinding(ipPort: httpsIpPort, enableClientCertNegotiation: true);
99105
}
100106
catch
101107
{
@@ -138,10 +144,7 @@ void OnShutdown()
138144

139145
await app.StartAsync();
140146

141-
if (httpSysLoggingEnabled)
142-
{
143-
NetShWrapper.Show();
144-
}
147+
NetShWrapper.Show();
145148

146149
Console.WriteLine("Application Info:");
147150
if (mTlsEnabled)

src/BenchmarksApps/TLS/HttpSys/Properties/launchSettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"dotnetRunMessages": true,
77
"launchBrowser": true,
88
"launchUrl": "hello-world",
9-
"applicationUrl": "https://localhost:5000;http://localhost:5001",
9+
"applicationUrl": "https://127.0.0.1:5000;http://localhost:5001",
1010
"environmentVariables": {
1111
"ASPNETCORE_ENVIRONMENT": "Development"
1212
}

0 commit comments

Comments
 (0)