Compare commits

...

8 Commits

Author SHA1 Message Date
Michael B. Gale
cffb3e1717 Use proxy for feed reachability check 2024-11-04 14:29:12 +00:00
Michael B. Gale
50b7257e02 C#: Fix log message 2024-11-04 14:19:52 +00:00
Michael B. Gale
61d1ba3d37 C#: Write user-level nuget.config 2024-11-04 14:19:13 +00:00
Michael B. Gale
fe317469c6 C#: Add http_proxy config to nuget.config 2024-11-04 13:32:24 +00:00
Michael B. Gale
974b981b81 Log exception 2024-11-04 13:23:28 +00:00
Michael B. Gale
2924a895c8 Log when TLS validation is disabled in certificate check 2024-11-04 13:23:27 +00:00
Michael B. Gale
d8f8f86fad C#: Propagate NugetFeed values, disable certificate validation in reachability check 2024-11-04 13:23:22 +00:00
Michael B. Gale
2f83dc626c C#: Add NugetConfig class 2024-11-04 13:22:46 +00:00
2 changed files with 137 additions and 25 deletions

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Semmle.Util;
namespace Semmle.Extraction.CSharp.DependencyFetching
{
public class NugetConfig
{
internal class NugetFeed : IComparable<NugetFeed>
{
internal string Value { get; }
internal bool DisableTlsCertificateValidation { get; set; }
internal NugetFeed(string value)
{
this.Value = value;
this.DisableTlsCertificateValidation = true;
}
public override string ToString()
{
return this.Value;
}
public int CompareTo(NugetFeed? other)
{
return this.Value.CompareTo(other?.Value);
}
}
internal IEnumerable<NugetFeed> Feeds { get; }
public NugetConfig()
{
this.Feeds = new List<NugetFeed>();
}
/// <summary>
/// Writes this configuration to a file located at <paramref name="nugetConfigPath"/>.
/// </summary>
/// <param name="nugetConfigPath">The path of the file to which the configuration should be written to.</param>
public void Write(string nugetConfigPath)
{
var config = "";
var proxyHost = Environment.GetEnvironmentVariable("CODEQL_PROXY_HOST");
var proxyPort = Environment.GetEnvironmentVariable("CODEQL_PROXY_PORT");
if (!string.IsNullOrWhiteSpace(proxyHost) && !string.IsNullOrWhiteSpace(proxyPort))
{
var proxyAddress = $"http://{proxyHost}:{proxyPort}";
config = $"""<add key="http_proxy" value="{proxyAddress}" />""";
}
var sb = new StringBuilder();
this.Feeds.ForEach((feed, index) => sb.AppendLine($"<add key=\"feed{index}\" value=\"{feed.Value}\" disableTLSCertificateValidation=\"{feed.DisableTlsCertificateValidation}\" />"));
File.WriteAllText(nugetConfigPath,
$"""
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<config>
{config}
</config>
<packageSources>
<clear />
{sb}
</packageSources>
</configuration>
""");
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
@@ -93,12 +94,25 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
public HashSet<AssemblyLookupLocation> Restore() public HashSet<AssemblyLookupLocation> Restore()
{ {
try
{
var userConfigPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "NuGet");
Directory.CreateDirectory(userConfigPath);
var userConfig = new NugetConfig();
userConfig.Write(Path.Join(userConfigPath, "NuGet.Config"));
}
catch (Exception ex)
{
logger.LogError($"Failed to write user nuget.config: {ex}");
}
var assemblyLookupLocations = new HashSet<AssemblyLookupLocation>(); var assemblyLookupLocations = new HashSet<AssemblyLookupLocation>();
var checkNugetFeedResponsiveness = EnvironmentVariables.GetBooleanOptOut(EnvironmentVariableNames.CheckNugetFeedResponsiveness); var checkNugetFeedResponsiveness = EnvironmentVariables.GetBooleanOptOut(EnvironmentVariableNames.CheckNugetFeedResponsiveness);
logger.LogInfo($"Checking NuGet feed responsiveness: {checkNugetFeedResponsiveness}"); logger.LogInfo($"Checking NuGet feed responsiveness: {checkNugetFeedResponsiveness}");
compilationInfoContainer.CompilationInfos.Add(("NuGet feed responsiveness checked", checkNugetFeedResponsiveness ? "1" : "0")); compilationInfoContainer.CompilationInfos.Add(("NuGet feed responsiveness checked", checkNugetFeedResponsiveness ? "1" : "0"));
HashSet<string>? explicitFeeds = null; HashSet<NugetConfig.NugetFeed>? explicitFeeds = null;
try try
{ {
@@ -175,12 +189,12 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
return assemblyLookupLocations; return assemblyLookupLocations;
} }
private List<string> GetReachableFallbackNugetFeeds(HashSet<string>? feedsFromNugetConfigs) private List<NugetConfig.NugetFeed> GetReachableFallbackNugetFeeds(HashSet<NugetConfig.NugetFeed>? feedsFromNugetConfigs)
{ {
var fallbackFeeds = EnvironmentVariables.GetURLs(EnvironmentVariableNames.FallbackNugetFeeds).ToHashSet(); var fallbackFeeds = EnvironmentVariables.GetURLs(EnvironmentVariableNames.FallbackNugetFeeds).Select(feed => new NugetConfig.NugetFeed(feed)).ToHashSet();
if (fallbackFeeds.Count == 0) if (fallbackFeeds.Count == 0)
{ {
fallbackFeeds.Add(PublicNugetOrgFeed); fallbackFeeds.Add(new NugetConfig.NugetFeed(PublicNugetOrgFeed));
logger.LogInfo($"No fallback Nuget feeds specified. Adding default feed: {PublicNugetOrgFeed}"); logger.LogInfo($"No fallback Nuget feeds specified. Adding default feed: {PublicNugetOrgFeed}");
var shouldAddNugetConfigFeeds = EnvironmentVariables.GetBooleanOptOut(EnvironmentVariableNames.AddNugetConfigFeedsToFallback); var shouldAddNugetConfigFeeds = EnvironmentVariables.GetBooleanOptOut(EnvironmentVariableNames.AddNugetConfigFeedsToFallback);
@@ -293,7 +307,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
compilationInfoContainer.CompilationInfos.Add(("Failed project restore with package source error", nugetSourceFailures.ToString())); compilationInfoContainer.CompilationInfos.Add(("Failed project restore with package source error", nugetSourceFailures.ToString()));
} }
private AssemblyLookupLocation? DownloadMissingPackagesFromSpecificFeeds(HashSet<string>? feedsFromNugetConfigs) private AssemblyLookupLocation? DownloadMissingPackagesFromSpecificFeeds(HashSet<NugetConfig.NugetFeed>? feedsFromNugetConfigs)
{ {
var reachableFallbackFeeds = GetReachableFallbackNugetFeeds(feedsFromNugetConfigs); var reachableFallbackFeeds = GetReachableFallbackNugetFeeds(feedsFromNugetConfigs);
if (reachableFallbackFeeds.Count > 0) if (reachableFallbackFeeds.Count > 0)
@@ -305,7 +319,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
return null; return null;
} }
private AssemblyLookupLocation? DownloadMissingPackages(IEnumerable<string>? fallbackNugetFeeds = null) private AssemblyLookupLocation? DownloadMissingPackages(IEnumerable<NugetConfig.NugetFeed>? fallbackNugetFeeds = null)
{ {
var alreadyDownloadedPackages = GetRestoredPackageDirectoryNames(PackageDirectory.DirInfo); var alreadyDownloadedPackages = GetRestoredPackageDirectoryNames(PackageDirectory.DirInfo);
var alreadyDownloadedLegacyPackages = GetRestoredLegacyPackageNames(); var alreadyDownloadedLegacyPackages = GetRestoredLegacyPackageNames();
@@ -367,23 +381,14 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
return missingPackageDirectory.DirInfo.FullName; return missingPackageDirectory.DirInfo.FullName;
} }
private string? CreateFallbackNugetConfig(IEnumerable<string> fallbackNugetFeeds, string folderPath) private string? CreateFallbackNugetConfig(IEnumerable<NugetConfig.NugetFeed> fallbackNugetFeeds, string folderPath)
{ {
var sb = new StringBuilder(); NugetConfig config = new NugetConfig();
fallbackNugetFeeds.ForEach((feed, index) => sb.AppendLine($"<add key=\"feed{index}\" value=\"{feed}\" />")); fallbackNugetFeeds.ForEach((feed, index) => config.Feeds.Append(feed));
var nugetConfigPath = Path.Combine(folderPath, "nuget.config"); var nugetConfigPath = Path.Combine(folderPath, "nuget.config");
logger.LogInfo($"Creating fallback nuget.config file {nugetConfigPath}."); logger.LogInfo($"Creating fallback nuget.config file {nugetConfigPath}.");
File.WriteAllText(nugetConfigPath, config.Write(nugetConfigPath);
$"""
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
{sb}
</packageSources>
</configuration>
""");
return nugetConfigPath; return nugetConfigPath;
} }
@@ -586,10 +591,34 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
} }
} }
private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, bool allowExceptions = true) private bool IsFeedReachable(NugetConfig.NugetFeed feed, int timeoutMilliSeconds, int tryCount, bool allowExceptions = true)
{ {
logger.LogInfo($"Checking if Nuget feed '{feed}' is reachable..."); logger.LogInfo($"Checking if Nuget feed '{feed}' is reachable...");
using HttpClient client = new();
// Configure the handler for this check. If `DisableTlsCertificateValidation` is `true` for this feed,
// we disable certificate validation.
var handler = new HttpClientHandler();
var proxyHost = Environment.GetEnvironmentVariable("CODEQL_PROXY_HOST");
var proxyPort = Environment.GetEnvironmentVariable("CODEQL_PROXY_PORT");
if (!string.IsNullOrWhiteSpace(proxyHost) && !string.IsNullOrWhiteSpace(proxyPort))
{
var proxyAddress = new Uri($"http://{proxyHost}:{proxyPort}");
handler.Proxy = new WebProxy(proxyAddress);
handler.Proxy.Credentials = new NetworkCredential(Environment.GetEnvironmentVariable("CODEQL_PROXY_USER"), Environment.GetEnvironmentVariable("CODEQL_PROXY_PASSWORD"));
logger.LogInfo($"Using proxy at {proxyAddress}...");
}
if (feed.DisableTlsCertificateValidation)
{
logger.LogInfo($"Disabling TLS certificate validation for '{feed}'...");
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, certChain, policyErrors) => { return true; };
}
using HttpClient client = new(handler);
for (var i = 0; i < tryCount; i++) for (var i = 0; i < tryCount; i++)
{ {
@@ -597,7 +626,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
cts.CancelAfter(timeoutMilliSeconds); cts.CancelAfter(timeoutMilliSeconds);
try try
{ {
ExecuteGetRequest(feed, client, cts.Token).GetAwaiter().GetResult(); ExecuteGetRequest(feed.Value, client, cts.Token).GetAwaiter().GetResult();
logger.LogInfo($"Querying Nuget feed '{feed}' succeeded."); logger.LogInfo($"Querying Nuget feed '{feed}' succeeded.");
return true; return true;
} }
@@ -615,6 +644,13 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
// We're only interested in timeouts. // We're only interested in timeouts.
var start = allowExceptions ? "Considering" : "Not considering"; var start = allowExceptions ? "Considering" : "Not considering";
logger.LogInfo($"Querying Nuget feed '{feed}' failed in a timely manner. {start} the feed for use. The reason for the failure: {exc.Message}"); logger.LogInfo($"Querying Nuget feed '{feed}' failed in a timely manner. {start} the feed for use. The reason for the failure: {exc.Message}");
logger.LogError($"Exception: {exc}");
if (exc.InnerException != null)
{
logger.LogError($"Inner exception: {exc.InnerException}");
}
return allowExceptions; return allowExceptions;
} }
} }
@@ -642,7 +678,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
return (timeoutMilliSeconds, tryCount); return (timeoutMilliSeconds, tryCount);
} }
private bool CheckFeeds(out HashSet<string> explicitFeeds) private bool CheckFeeds(out HashSet<NugetConfig.NugetFeed> explicitFeeds)
{ {
logger.LogInfo("Checking Nuget feeds..."); logger.LogInfo("Checking Nuget feeds...");
(explicitFeeds, var allFeeds) = GetAllFeeds(); (explicitFeeds, var allFeeds) = GetAllFeeds();
@@ -657,7 +693,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback: false); var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback: false);
var allFeedsReachable = explicitFeeds.All(feed => excludedFeeds.Contains(feed) || IsFeedReachable(feed, initialTimeout, tryCount)); var allFeedsReachable = explicitFeeds.All(feed => excludedFeeds.Contains(feed.Value) || IsFeedReachable(feed, initialTimeout, tryCount));
if (!allFeedsReachable) if (!allFeedsReachable)
{ {
logger.LogWarning("Found unreachable Nuget feed in C# analysis with build-mode 'none'. This may cause missing dependencies in the analysis."); logger.LogWarning("Found unreachable Nuget feed in C# analysis with build-mode 'none'. This may cause missing dependencies in the analysis.");
@@ -711,11 +747,12 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
} }
} }
private (HashSet<string> explicitFeeds, HashSet<string> allFeeds) GetAllFeeds() private (HashSet<NugetConfig.NugetFeed> explicitFeeds, HashSet<NugetConfig.NugetFeed> allFeeds) GetAllFeeds()
{ {
var nugetConfigs = fileProvider.NugetConfigs; var nugetConfigs = fileProvider.NugetConfigs;
var explicitFeeds = nugetConfigs var explicitFeeds = nugetConfigs
.SelectMany(config => GetFeeds(() => dotnet.GetNugetFeeds(config))) .SelectMany(config => GetFeeds(() => dotnet.GetNugetFeeds(config)))
.Select(feed => new NugetConfig.NugetFeed(feed))
.ToHashSet(); .ToHashSet();
if (explicitFeeds.Count > 0) if (explicitFeeds.Count > 0)
@@ -744,6 +781,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
}) })
.Where(folder => folder != null) .Where(folder => folder != null)
.SelectMany(folder => GetFeeds(() => dotnet.GetNugetFeedsFromFolder(folder!))) .SelectMany(folder => GetFeeds(() => dotnet.GetNugetFeedsFromFolder(folder!)))
.Select(feed => new NugetConfig.NugetFeed(feed))
.ToHashSet(); .ToHashSet();
logger.LogInfo($"Found {allFeeds.Count} Nuget feeds (with inherited ones) in nuget.config files: {string.Join(", ", allFeeds.OrderBy(f => f))}"); logger.LogInfo($"Found {allFeeds.Count} Nuget feeds (with inherited ones) in nuget.config files: {string.Join(", ", allFeeds.OrderBy(f => f))}");