diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNetCliInvoker.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNetCliInvoker.cs index 384404fbc6ce..467a90ac7288 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNetCliInvoker.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNetCliInvoker.cs @@ -44,14 +44,7 @@ private ProcessStartInfo MakeDotnetStartInfo(List args, string? workingD } // Configure the proxy settings, if applicable. - if (this.proxy != null) - { - logger.LogDebug($"Configuring environment variables for the registry proxy at {this.proxy.Address}"); - - startInfo.EnvironmentVariables["HTTP_PROXY"] = this.proxy.Address; - startInfo.EnvironmentVariables["HTTPS_PROXY"] = this.proxy.Address; - startInfo.EnvironmentVariables["SSL_CERT_FILE"] = this.proxy.CertificatePath; - } + proxy?.SetProcessEnvironment(startInfo); return startInfo; } diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IRegistryProxy.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IRegistryProxy.cs index 2537ac0b4054..a7f7c1567152 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IRegistryProxy.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IRegistryProxy.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Immutable; +using System.Diagnostics; using System.Security.Cryptography.X509Certificates; namespace Semmle.Extraction.CSharp.DependencyFetching @@ -30,5 +31,11 @@ public interface IRegistryProxy : IDisposable /// The certificate used for the registry proxy. /// X509Certificate2? Certificate { get; } + + /// + /// Configures the environment variables for a process to use the registry proxy. + /// + /// The process start info to configure. + void SetProcessEnvironment(ProcessStartInfo pi); } } diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index fee72034781f..9257edfe2c19 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -18,6 +18,7 @@ internal sealed partial class NugetPackageRestorer : IDisposable private readonly IFileProvider fileProvider; private readonly FileContent fileContent; private readonly IDotNet dotnet; + private readonly IRegistryProxy? registryProxy; private readonly IDiagnosticsWriter diagnosticsWriter; private readonly DependencyDirectory legacyPackageDirectory; private readonly DependencyDirectory missingPackageDirectory; @@ -40,6 +41,7 @@ public NugetPackageRestorer( this.fileProvider = fileProvider; this.fileContent = fileContent; this.dotnet = dotnet; + this.registryProxy = registryProxy; this.diagnosticsWriter = diagnosticsWriter; this.logger = logger; this.compilationInfoContainer = compilationInfoContainer; @@ -133,7 +135,7 @@ public HashSet Restore() try { - var packagesConfigRestore = PackagesConfigRestoreFactory.Create(fileProvider, legacyPackageDirectory, logger, feedManager); + var packagesConfigRestore = PackagesConfigRestoreFactory.Create(fileProvider, legacyPackageDirectory, logger, feedManager, registryProxy); var count = packagesConfigRestore.InstallPackages(); if (packagesConfigRestore.PackageCount > 0) { diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs index 861622ca4c02..29276d72a82c 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs @@ -33,11 +33,11 @@ internal interface IPackagesConfigRestore /// internal class PackagesConfigRestoreFactory { - public static IPackagesConfigRestore Create(IFileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger, FeedManager feedManager) + public static IPackagesConfigRestore Create(IFileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger, FeedManager feedManager, IRegistryProxy? registryProxy) { if (SystemBuildActions.Instance.IsWindows() || SystemBuildActions.Instance.IsMonoInstalled()) { - return new NugetExeWrapper(fileProvider, packageDirectory, logger, feedManager); + return new NugetExeWrapper(fileProvider, packageDirectory, logger, feedManager, registryProxy); } return new NoOpPackagesConfig(fileProvider.PackagesConfigs, logger); @@ -52,6 +52,7 @@ private class NugetExeWrapper : IPackagesConfigRestore { private readonly string? nugetExe; private readonly Semmle.Util.Logging.ILogger logger; + private readonly IRegistryProxy? registryProxy; public int PackageCount => fileProvider.PackagesConfigs.Count; @@ -70,12 +71,13 @@ private class NugetExeWrapper : IPackagesConfigRestore /// /// Create the package manager for a specified source tree. /// - public NugetExeWrapper(IFileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger, FeedManager feedManager) + public NugetExeWrapper(IFileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger, FeedManager feedManager, IRegistryProxy? registryProxy) { this.fileProvider = fileProvider; this.packageDirectory = packageDirectory; this.logger = logger; this.feedManager = feedManager; + this.registryProxy = registryProxy; if (fileProvider.PackagesConfigs.Count > 0) { @@ -209,9 +211,13 @@ private bool TryRestoreNugetPackage(string packagesConfig) UseShellExecute = false }; + // Configure the proxy settings, if applicable. + registryProxy?.SetProcessEnvironment(pi); + var threadId = Environment.CurrentManagedThreadId; void onOut(string s) => logger.LogDebug(s, threadId); void onError(string s) => logger.LogError(s, threadId); + logger.LogInfo($"Running '{pi.FileName} {string.Join(" ", pi.ArgumentList)}'"); var exitCode = pi.ReadOutput(out _, onOut, onError); if (exitCode != 0) { diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/RegistryProxy.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/RegistryProxy.cs index e16ea66725c7..d49d08510a22 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/RegistryProxy.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/RegistryProxy.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Immutable; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Security.Cryptography.X509Certificates; using Semmle.Util; @@ -36,6 +37,8 @@ public class RegistryConfig public string Address { get; } + private readonly ILogger logger; + /// /// A dictionary mapping registry URLs to a boolean indicating whether they replace the base registry. /// @@ -65,8 +68,9 @@ public class RegistryConfig public X509Certificate2? Certificate { get; private set; } - private RegistryProxy(IRegistryProxyConfiguration config, ILogger logger, TemporaryDirectory tempWorkingDirectory) + private RegistryProxy(IRegistryProxyConfiguration config, ILogger l, TemporaryDirectory tempWorkingDirectory) { + logger = l; Address = $"http://{config.Host}:{config.Port}"; if (!string.IsNullOrWhiteSpace(config.Certificate)) @@ -179,6 +183,25 @@ private RegistryProxy(IRegistryProxyConfiguration config, ILogger logger, Tempor return result; } + public void SetProcessEnvironment(ProcessStartInfo pi) + { + logger.LogDebug($"Configuring environment variables for the registry proxy at {Address}"); + + pi.EnvironmentVariables["HTTP_PROXY"] = Address; + pi.EnvironmentVariables["HTTPS_PROXY"] = Address; + + // Also set the lower case variants of the environment variables + // This might be needed on Linux systems. + pi.EnvironmentVariables["http_proxy"] = Address; + pi.EnvironmentVariables["https_proxy"] = Address; + + if (CertificatePath != null) + { + logger.LogDebug("Setting the SSL certificate path for the registry proxy."); + pi.EnvironmentVariables["SSL_CERT_FILE"] = CertificatePath; + } + } + public void Dispose() { Certificate?.Dispose(); diff --git a/csharp/extractor/Semmle.Extraction.Tests/FeedManager.cs b/csharp/extractor/Semmle.Extraction.Tests/FeedManager.cs index d812f008c43b..ae97d2e7e346 100644 --- a/csharp/extractor/Semmle.Extraction.Tests/FeedManager.cs +++ b/csharp/extractor/Semmle.Extraction.Tests/FeedManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.IO; using System.Linq; using System.Security.Cryptography.X509Certificates; @@ -17,6 +18,8 @@ public class RegistryProxyStub : IRegistryProxy public string? CertificatePath { get; } = null; public X509Certificate2? Certificate { get; } = null; + public void SetProcessEnvironment(ProcessStartInfo pi) { } + public void Dispose() { } } @@ -28,6 +31,7 @@ public class RegistryProxyStubWithBaseUrls : IRegistryProxy public string? CertificatePath { get; } = null; public X509Certificate2? Certificate { get; } = null; + public void SetProcessEnvironment(ProcessStartInfo pi) { } public void Dispose() { } } diff --git a/csharp/extractor/Semmle.Extraction.Tests/RegistryProxy.cs b/csharp/extractor/Semmle.Extraction.Tests/RegistryProxy.cs index 5705077c0219..f978736416fd 100644 --- a/csharp/extractor/Semmle.Extraction.Tests/RegistryProxy.cs +++ b/csharp/extractor/Semmle.Extraction.Tests/RegistryProxy.cs @@ -1,5 +1,6 @@ using Xunit; using System; +using System.Diagnostics; using System.IO; using Semmle.Extraction.CSharp.DependencyFetching; using Semmle.Util; @@ -245,5 +246,40 @@ public void TestRegistryProxyUrlsReplacesBase() "https://example.com/org/index.json", ], proxy.RegistryBaseURLs); } + + /// + /// Verifies that the registry proxy correctly sets the environment variables needed for a .NET process to use the proxy. + /// In this case, the environment variables for the HTTP and HTTPS proxies, as well as the SSL certificate file, should be correctly set. + /// The http proxies should be set to the proxy address, and the SSL certificate file should point to the certificate path. + /// The latter is tested by verifying that the SSL_CERT_FILE environment variable ends with "proxy.crt" as we can't check the absolute path + /// due to temporary directories. + /// + [Fact] + public void TestRegistryProxyProcessEnvironment() + { + // Setup + var config = new RegistryConfigurationStub + { + Port = "8080", + Host = "localhost", + Certificate = ExampleCertificate + }; + + // Execute + using var tempWorkingDirectory = MakeTemporaryDirectory(); + using var proxy = RegistryProxy.Make(config, new LoggerStub(), new DiagnosticsWriterStub(), tempWorkingDirectory); + + var pi = new ProcessStartInfo("nuget"); + proxy?.SetProcessEnvironment(pi); + + // Verify + Assert.NotNull(proxy); + Assert.Equal("http://localhost:8080", proxy.Address); + Assert.Equal("http://localhost:8080", pi.EnvironmentVariables["HTTP_PROXY"]); + Assert.Equal("http://localhost:8080", pi.EnvironmentVariables["HTTPS_PROXY"]); + Assert.Equal("http://localhost:8080", pi.EnvironmentVariables["http_proxy"]); + Assert.Equal("http://localhost:8080", pi.EnvironmentVariables["https_proxy"]); + Assert.EndsWith("proxy.crt", pi.EnvironmentVariables["SSL_CERT_FILE"]); + } } } diff --git a/csharp/ql/lib/change-notes/2026-10-05-nugetcli-proxy-config.md b/csharp/ql/lib/change-notes/2026-10-05-nugetcli-proxy-config.md new file mode 100644 index 000000000000..3f2ed8cddb56 --- /dev/null +++ b/csharp/ql/lib/change-notes/2026-10-05-nugetcli-proxy-config.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The subprocess for the NuGet CLI is now provided with the proxy and certificate environment variables needed to access private registries if any are configured.