Skip to content

Commit 72b5069

Browse files
fix: wire mTLS client certs into VssHttpMessageHandler and RawHttpMessageHandler
The runner's primary HTTP paths (VssConnection/RawConnection) create bare HttpClientHandler instances without loading client certificates. RunnerWebProxy reads HTTPS_PROXY_CLIENT_CERT/KEY env vars but only HttpClientHandlerFactory (secondary path) wires them into the handler. Changes: - VssHttpMessageHandler: add ConfigureClientCertificates callback, invoke it in ApplySettings after proxy is set - RawHttpMessageHandler: same callback pattern - VssUtil: set RawHttpMessageHandler.DefaultWebProxy (was missing), wire cert loading callback into both handlers This enables mTLS proxy authentication for all runner communication: job pickup, token refresh, broker connections, action downloads. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 42dd104 commit 72b5069

3 files changed

Lines changed: 42 additions & 0 deletions

File tree

src/Runner.Sdk/Util/VssUtil.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Globalization;
4+
using System.IO;
45
using System.Net.Http;
6+
using System.Security.Cryptography.X509Certificates;
57
using GitHub.DistributedTask.WebApi;
68
using GitHub.Services.Common;
79
using GitHub.Services.WebApi;
@@ -34,6 +36,38 @@ public static void InitializeVssClientSettings(List<ProductInfoHeaderValue> addi
3436

3537
VssClientHttpRequestSettings.Default.UserAgent = headerValues;
3638
VssHttpMessageHandler.DefaultWebProxy = proxy;
39+
RawHttpMessageHandler.DefaultWebProxy = proxy;
40+
41+
// Wire mTLS client cert loading into both HTTP message handlers
42+
var certPath = Environment.GetEnvironmentVariable("HTTPS_PROXY_CLIENT_CERT")
43+
?? Environment.GetEnvironmentVariable("https_proxy_client_cert");
44+
var keyPath = Environment.GetEnvironmentVariable("HTTPS_PROXY_CLIENT_KEY")
45+
?? Environment.GetEnvironmentVariable("https_proxy_client_key");
46+
if (!string.IsNullOrEmpty(certPath) && File.Exists(certPath))
47+
{
48+
Action<HttpClientHandler> configureCerts = (HttpClientHandler handler) =>
49+
{
50+
try
51+
{
52+
var certPem = File.ReadAllText(certPath);
53+
var keyPem = !string.IsNullOrEmpty(keyPath) && File.Exists(keyPath) ? File.ReadAllText(keyPath) : null;
54+
X509Certificate2 cert;
55+
if (keyPem != null)
56+
{
57+
cert = X509Certificate2.CreateFromPem(certPem, keyPem);
58+
cert = new X509Certificate2(cert.Export(X509ContentType.Pfx));
59+
}
60+
else
61+
{
62+
cert = new X509Certificate2(certPath);
63+
}
64+
handler.ClientCertificates.Add(cert);
65+
}
66+
catch { }
67+
};
68+
VssHttpMessageHandler.ConfigureClientCertificates = configureCerts;
69+
RawHttpMessageHandler.ConfigureClientCertificates = configureCerts;
70+
}
3771

3872
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY")))
3973
{

src/Sdk/Common/Common/RawHttpMessageHandler.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ public RawClientHttpRequestSettings Settings
7777
// This needs to be investigated further.
7878
private static IWebProxy s_defaultWebProxy = null;
7979

80+
public static Action<HttpClientHandler> ConfigureClientCertificates { get; set; }
81+
8082
/// <summary>
8183
/// Allows you to set a proxy to be used by all RawHttpMessageHandler requests without affecting the global WebRequest.DefaultWebProxy. If not set it returns the WebRequest.DefaultWebProxy.
8284
/// </summary>
@@ -300,6 +302,8 @@ private static void ApplySettings(
300302
httpClientHandler.Proxy = DefaultWebProxy;
301303
httpClientHandler.UseCookies = false;
302304
httpClientHandler.UseProxy = true;
305+
306+
ConfigureClientCertificates?.Invoke(httpClientHandler);
303307
}
304308
}
305309

src/Sdk/Common/Common/VssHttpMessageHandler.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,9 +503,13 @@ private static void ApplySettings(
503503
{
504504
httpClientHandler.AutomaticDecompression = DecompressionMethods.GZip;
505505
}
506+
507+
ConfigureClientCertificates?.Invoke(httpClientHandler);
506508
}
507509
}
508510

511+
public static Action<HttpClientHandler> ConfigureClientCertificates { get; set; }
512+
509513
// setting this to WebRequest.DefaultWebProxy in NETSTANDARD is causing a System.PlatformNotSupportedException
510514
//.in System.Net.SystemWebProxy.IsBypassed. Comment in IsBypassed method indicates ".NET Core and .NET Native
511515
// code will handle this exception and call into WinInet/WinHttp as appropriate to use the system proxy."

0 commit comments

Comments
 (0)