Skip to content

Commit e16adda

Browse files
authored
Merge pull request #18029 from github/mbg/csharp/set-proxy-cert-file
2 parents 3ba87de + c8ccfe4 commit e16adda

File tree

6 files changed

+148
-6
lines changed

6 files changed

+148
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.IO;
4+
using System.Security.Cryptography.X509Certificates;
5+
using Semmle.Util;
6+
using Semmle.Util.Logging;
7+
8+
namespace Semmle.Extraction.CSharp.DependencyFetching
9+
{
10+
public class DependabotProxy : IDisposable
11+
{
12+
private readonly string host;
13+
private readonly string port;
14+
15+
/// <summary>
16+
/// The full address of the Dependabot proxy, if available.
17+
/// </summary>
18+
internal string Address { get; }
19+
/// <summary>
20+
/// The path to the temporary file where the certificate is stored.
21+
/// </summary>
22+
internal string? CertificatePath { get; private set; }
23+
/// <summary>
24+
/// The certificate used for the Dependabot proxy.
25+
/// </summary>
26+
internal X509Certificate2? Certificate { get; private set; }
27+
28+
internal static DependabotProxy? GetDependabotProxy(ILogger logger, TemporaryDirectory tempWorkingDirectory)
29+
{
30+
// Setting HTTP(S)_PROXY and SSL_CERT_FILE have no effect on Windows or macOS,
31+
// but we would still end up using the Dependabot proxy to check for feed reachability.
32+
// This would result in us discovering that the feeds are reachable, but `dotnet` would
33+
// fail to connect to them. To prevent this from happening, we do not initialise an
34+
// instance of `DependabotProxy` on those platforms.
35+
if (SystemBuildActions.Instance.IsWindows() || SystemBuildActions.Instance.IsMacOs()) return null;
36+
37+
// Obtain and store the address of the Dependabot proxy, if available.
38+
var host = Environment.GetEnvironmentVariable(EnvironmentVariableNames.ProxyHost);
39+
var port = Environment.GetEnvironmentVariable(EnvironmentVariableNames.ProxyPort);
40+
41+
if (string.IsNullOrWhiteSpace(host) || string.IsNullOrWhiteSpace(port))
42+
{
43+
logger.LogInfo("No Dependabot proxy credentials are configured.");
44+
return null;
45+
}
46+
47+
var result = new DependabotProxy(host, port);
48+
logger.LogInfo($"Dependabot proxy configured at {result.Address}");
49+
50+
// Obtain and store the proxy's certificate, if available.
51+
var cert = Environment.GetEnvironmentVariable(EnvironmentVariableNames.ProxyCertificate);
52+
53+
if (!string.IsNullOrWhiteSpace(cert))
54+
{
55+
logger.LogInfo("No certificate configured for Dependabot proxy.");
56+
57+
var certDirPath = new DirectoryInfo(Path.Join(tempWorkingDirectory.DirInfo.FullName, ".dependabot-proxy"));
58+
Directory.CreateDirectory(certDirPath.FullName);
59+
60+
result.CertificatePath = Path.Join(certDirPath.FullName, "proxy.crt");
61+
var certFile = new FileInfo(result.CertificatePath);
62+
63+
using var writer = certFile.CreateText();
64+
writer.Write(cert);
65+
writer.Close();
66+
67+
logger.LogInfo($"Stored Dependabot proxy certificate at {result.CertificatePath}");
68+
69+
result.Certificate = X509Certificate2.CreateFromPem(cert);
70+
}
71+
72+
return result;
73+
}
74+
75+
private DependabotProxy(string host, string port)
76+
{
77+
this.host = host;
78+
this.port = port;
79+
this.Address = $"http://{this.host}:{this.port}";
80+
}
81+
82+
public void Dispose()
83+
{
84+
this.Certificate?.Dispose();
85+
}
86+
}
87+
}

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DependencyManager.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public sealed partial class DependencyManager : IDisposable, ICompilationInfoCon
2727
private readonly ILogger logger;
2828
private readonly IDiagnosticsWriter diagnosticsWriter;
2929
private readonly NugetPackageRestorer nugetPackageRestorer;
30+
private readonly DependabotProxy? dependabotProxy;
3031
private readonly IDotNet dotnet;
3132
private readonly FileContent fileContent;
3233
private readonly FileProvider fileProvider;
@@ -106,9 +107,11 @@ void exitCallback(int ret, string msg, bool silent)
106107
return BuildScript.Success;
107108
}).Run(SystemBuildActions.Instance, startCallback, exitCallback);
108109

110+
dependabotProxy = DependabotProxy.GetDependabotProxy(logger, tempWorkingDirectory);
111+
109112
try
110113
{
111-
this.dotnet = DotNet.Make(logger, dotnetPath, tempWorkingDirectory);
114+
this.dotnet = DotNet.Make(logger, dotnetPath, tempWorkingDirectory, dependabotProxy);
112115
runtimeLazy = new Lazy<Runtime>(() => new Runtime(dotnet));
113116
}
114117
catch
@@ -117,7 +120,7 @@ void exitCallback(int ret, string msg, bool silent)
117120
throw;
118121
}
119122

120-
nugetPackageRestorer = new NugetPackageRestorer(fileProvider, fileContent, dotnet, diagnosticsWriter, logger, this);
123+
nugetPackageRestorer = new NugetPackageRestorer(fileProvider, fileContent, dotnet, dependabotProxy, diagnosticsWriter, logger, this);
121124

122125
var dllLocations = fileProvider.Dlls.Select(x => new AssemblyLookupLocation(x)).ToHashSet();
123126
dllLocations.UnionWith(nugetPackageRestorer.Restore());
@@ -542,6 +545,7 @@ private void AnalyseProject(FileInfo project)
542545
public void Dispose()
543546
{
544547
nugetPackageRestorer?.Dispose();
548+
dependabotProxy?.Dispose();
545549
if (cleanupTempWorkingDirectory)
546550
{
547551
tempWorkingDirectory?.Dispose();

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNet.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ private DotNet(IDotNetCliInvoker dotnetCliInvoker, ILogger logger, TemporaryDire
2727
Info();
2828
}
2929

30-
private DotNet(ILogger logger, string? dotNetPath, TemporaryDirectory tempWorkingDirectory) : this(new DotNetCliInvoker(logger, Path.Combine(dotNetPath ?? string.Empty, "dotnet")), logger, tempWorkingDirectory) { }
30+
private DotNet(ILogger logger, string? dotNetPath, TemporaryDirectory tempWorkingDirectory, DependabotProxy? dependabotProxy) : this(new DotNetCliInvoker(logger, Path.Combine(dotNetPath ?? string.Empty, "dotnet"), dependabotProxy), logger, tempWorkingDirectory) { }
3131

3232
internal static IDotNet Make(IDotNetCliInvoker dotnetCliInvoker, ILogger logger) => new DotNet(dotnetCliInvoker, logger);
3333

34-
public static IDotNet Make(ILogger logger, string? dotNetPath, TemporaryDirectory tempWorkingDirectory) => new DotNet(logger, dotNetPath, tempWorkingDirectory);
34+
public static IDotNet Make(ILogger logger, string? dotNetPath, TemporaryDirectory tempWorkingDirectory, DependabotProxy? dependabotProxy) => new DotNet(logger, dotNetPath, tempWorkingDirectory, dependabotProxy);
3535

3636
private void Info()
3737
{

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNetCliInvoker.cs

+14-1
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
1212
internal sealed class DotNetCliInvoker : IDotNetCliInvoker
1313
{
1414
private readonly ILogger logger;
15+
private readonly DependabotProxy? proxy;
1516

1617
public string Exec { get; }
1718

18-
public DotNetCliInvoker(ILogger logger, string exec)
19+
public DotNetCliInvoker(ILogger logger, string exec, DependabotProxy? dependabotProxy)
1920
{
2021
this.logger = logger;
22+
this.proxy = dependabotProxy;
2123
this.Exec = exec;
2224
logger.LogInfo($"Using .NET CLI executable: '{Exec}'");
2325
}
@@ -38,6 +40,17 @@ private ProcessStartInfo MakeDotnetStartInfo(string args, string? workingDirecto
3840
startInfo.EnvironmentVariables["DOTNET_CLI_UI_LANGUAGE"] = "en";
3941
startInfo.EnvironmentVariables["MSBUILDDISABLENODEREUSE"] = "1";
4042
startInfo.EnvironmentVariables["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "true";
43+
44+
// Configure the proxy settings, if applicable.
45+
if (this.proxy != null)
46+
{
47+
logger.LogInfo($"Setting up Dependabot proxy at {this.proxy.Address}");
48+
49+
startInfo.EnvironmentVariables.Add("HTTP_PROXY", this.proxy.Address);
50+
startInfo.EnvironmentVariables.Add("HTTPS_PROXY", this.proxy.Address);
51+
startInfo.EnvironmentVariables.Add("SSL_CERT_FILE", this.proxy.CertificatePath);
52+
}
53+
4154
return startInfo;
4255
}
4356

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/EnvironmentVariableNames.cs

+15
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,20 @@ internal static class EnvironmentVariableNames
7474
/// Specifies the location of the diagnostic directory.
7575
/// </summary>
7676
public const string DiagnosticDir = "CODEQL_EXTRACTOR_CSHARP_DIAGNOSTIC_DIR";
77+
78+
/// <summary>
79+
/// Specifies the hostname of the Dependabot proxy.
80+
/// </summary>
81+
public const string ProxyHost = "CODEQL_PROXY_HOST";
82+
83+
/// <summary>
84+
/// Specifies the hostname of the Dependabot proxy.
85+
/// </summary>
86+
public const string ProxyPort = "CODEQL_PROXY_PORT";
87+
88+
/// <summary>
89+
/// Contains the certificate used by the Dependabot proxy.
90+
/// </summary>
91+
public const string ProxyCertificate = "CODEQL_PROXY_CA_CERTIFICATE";
7792
}
7893
}

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs

+24-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
using System.Collections.Generic;
44
using System.IO;
55
using System.Linq;
6+
using System.Net;
67
using System.Net.Http;
8+
using System.Security.Cryptography.X509Certificates;
79
using System.Text;
810
using System.Text.RegularExpressions;
911
using System.Threading;
@@ -20,6 +22,7 @@ internal sealed partial class NugetPackageRestorer : IDisposable
2022
private readonly FileProvider fileProvider;
2123
private readonly FileContent fileContent;
2224
private readonly IDotNet dotnet;
25+
private readonly DependabotProxy? dependabotProxy;
2326
private readonly IDiagnosticsWriter diagnosticsWriter;
2427
private readonly TemporaryDirectory legacyPackageDirectory;
2528
private readonly TemporaryDirectory missingPackageDirectory;
@@ -32,13 +35,15 @@ public NugetPackageRestorer(
3235
FileProvider fileProvider,
3336
FileContent fileContent,
3437
IDotNet dotnet,
38+
DependabotProxy? dependabotProxy,
3539
IDiagnosticsWriter diagnosticsWriter,
3640
ILogger logger,
3741
ICompilationInfoContainer compilationInfoContainer)
3842
{
3943
this.fileProvider = fileProvider;
4044
this.fileContent = fileContent;
4145
this.dotnet = dotnet;
46+
this.dependabotProxy = dependabotProxy;
4247
this.diagnosticsWriter = diagnosticsWriter;
4348
this.logger = logger;
4449
this.compilationInfoContainer = compilationInfoContainer;
@@ -588,7 +593,25 @@ private static async Task ExecuteGetRequest(string address, HttpClient httpClien
588593
private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, bool allowExceptions = true)
589594
{
590595
logger.LogInfo($"Checking if Nuget feed '{feed}' is reachable...");
591-
using HttpClient client = new();
596+
597+
// Configure the HttpClient to be aware of the Dependabot Proxy, if used.
598+
HttpClientHandler httpClientHandler = new();
599+
if (this.dependabotProxy != null)
600+
{
601+
httpClientHandler.Proxy = new WebProxy(this.dependabotProxy.Address);
602+
603+
if (this.dependabotProxy.Certificate != null)
604+
{
605+
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, _) =>
606+
{
607+
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
608+
chain.ChainPolicy.CustomTrustStore.Add(this.dependabotProxy.Certificate);
609+
return chain.Build(cert);
610+
};
611+
}
612+
}
613+
614+
using HttpClient client = new(httpClientHandler);
592615

593616
for (var i = 0; i < tryCount; i++)
594617
{

0 commit comments

Comments
 (0)