Skip to content

Commit

Permalink
Enable vulnerability checking for packages.config based projects in C…
Browse files Browse the repository at this point in the history
…LI scenarios
  • Loading branch information
nkolev92 committed Feb 22, 2024
1 parent 5502baa commit 35a7083
Show file tree
Hide file tree
Showing 18 changed files with 407 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ private async Task PerformV2RestoreAsync(string packagesConfigFilePath, string i
packageRestoreFailedEvent: (sender, args) => { failedEvents.Enqueue(args); },
sourceRepositories: packageSources.Select(sourceRepositoryProvider.CreateRepository),
maxNumberOfParallelTasks: DisableParallelProcessing ? 1 : PackageManagementConstants.DefaultMaxDegreeOfParallelism,
disableNuGetAudit: false,
restoreAuditProperties: new(),
logger: Console);

var packageSaveMode = Packaging.PackageSaveMode.Defaultv2;
Expand Down
41 changes: 34 additions & 7 deletions src/NuGet.Clients/NuGet.CommandLine/Commands/RestoreCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Remoting;
using System.Threading;
using System.Threading.Tasks;
using NuGet.Commands;
Expand Down Expand Up @@ -273,6 +272,7 @@ private async Task<IReadOnlyList<RestoreSummary>> PerformNuGetV2RestoreAsync(Pac

List<PackageRestoreData> packageRestoreData = new();
bool areAnyPackagesMissing = false;
Dictionary<string, RestoreAuditProperties> restoreAuditProperties = null;

if (packageRestoreInputs.RestoringWithSolutionFile)
{
Expand Down Expand Up @@ -303,6 +303,7 @@ private async Task<IReadOnlyList<RestoreSummary>> PerformNuGetV2RestoreAsync(Pac
packageRestoreData.Add(new PackageRestoreData(package.Key, package.Value, !exists));
areAnyPackagesMissing |= !exists;
}
restoreAuditProperties = GetRestoreAuditProperties(packageRestoreInputs);

}
else if (packageRestoreInputs.PackagesConfigFiles.Count > 0)
Expand Down Expand Up @@ -331,8 +332,15 @@ private async Task<IReadOnlyList<RestoreSummary>> PerformNuGetV2RestoreAsync(Pac
packageRestoreData.Add(new PackageRestoreData(packageReference, [packageReferenceFile], !exists));
areAnyPackagesMissing |= !exists;
}
restoreAuditProperties = new();
}

var packageSources = GetPackageSources(Settings);

var repositories = packageSources
.Select(sourceRepositoryProvider.CreateRepository)
.ToList();

if (!areAnyPackagesMissing)
{
var message = string.Format(
Expand All @@ -350,6 +358,15 @@ private async Task<IReadOnlyList<RestoreSummary>> PerformNuGetV2RestoreAsync(Pac
packagesFolderPath,
restoreSummaries);

using SourceCacheContext cacheContext = new();

var auditUtility = new AuditChecker(
repositories,
cacheContext,
Console);

await auditUtility.CheckPackageVulnerabilitiesAsync(packageRestoreData, restoreAuditProperties, CancellationToken.None);

if (restoreSummaries.Count == 0)
{
restoreSummaries.Add(new RestoreSummary(success: true));
Expand All @@ -358,12 +375,6 @@ private async Task<IReadOnlyList<RestoreSummary>> PerformNuGetV2RestoreAsync(Pac
return restoreSummaries;
}

var packageSources = GetPackageSources(Settings);

var repositories = packageSources
.Select(sourceRepositoryProvider.CreateRepository)
.ToArray();

var installCount = 0;
var failedEvents = new ConcurrentQueue<PackageRestoreFailedEventArgs>();
var collectorLogger = new RestoreCollectorLogger(Console);
Expand All @@ -378,6 +389,8 @@ private async Task<IReadOnlyList<RestoreSummary>> PerformNuGetV2RestoreAsync(Pac
maxNumberOfParallelTasks: DisableParallelProcessing
? 1
: PackageManagementConstants.DefaultMaxDegreeOfParallelism,
disableNuGetAudit: false,
restoreAuditProperties,
logger: collectorLogger);

CheckRequireConsent();
Expand Down Expand Up @@ -458,6 +471,20 @@ private static Dictionary<string, string> GetPackagesConfigToProjectPath(Package
return configToProjectPath;
}

private static Dictionary<string, RestoreAuditProperties> GetRestoreAuditProperties(PackageRestoreInputs packageRestoreInputs)
{
Dictionary<string, RestoreAuditProperties> restoreAuditProperties = new(PathUtility.GetStringComparerBasedOnOS());
foreach (PackageSpec project in packageRestoreInputs.ProjectReferenceLookup.Projects)
{
if (project.RestoreMetadata?.ProjectStyle == ProjectStyle.PackagesConfig)
{
restoreAuditProperties.Add(project.FilePath, project.RestoreMetadata.RestoreAuditProperties);
}
}

return restoreAuditProperties;
}

/// <summary>
/// Processes List of PackageRestoreFailedEventArgs into a List of RestoreLogMessages.
/// </summary>
Expand Down
17 changes: 16 additions & 1 deletion src/NuGet.Core/NuGet.Build.Tasks/BuildTasksUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
using NuGet.Shared;
using static NuGet.Shared.XmlUtility;
using System.Globalization;
using System.Collections;
#endif

namespace NuGet.Build.Tasks
Expand Down Expand Up @@ -422,6 +423,7 @@ private static async Task<RestoreSummary> PerformNuGetV2RestoreAsync(Common.ILog
ISettings settings = null;

Dictionary<PackageReference, List<string>> packageReferenceToProjects = new(PackageReferenceComparer.Instance);
Dictionary<string, RestoreAuditProperties> restoreAuditProperties = new(PathUtility.GetStringComparerBasedOnOS());

foreach (PackageSpec packageSpec in dgFile.Projects.Where(i => i.RestoreMetadata.ProjectStyle == ProjectStyle.PackagesConfig))
{
Expand Down Expand Up @@ -456,6 +458,7 @@ private static async Task<RestoreSummary> PerformNuGetV2RestoreAsync(Common.ILog
}
value.Add(packagesConfigPath);
}
restoreAuditProperties.Add(packageSpec.FilePath, packageSpec.RestoreMetadata.RestoreAuditProperties);
}

if (string.IsNullOrEmpty(repositoryPath))
Expand All @@ -482,12 +485,22 @@ private static async Task<RestoreSummary> PerformNuGetV2RestoreAsync(Common.ILog
areAnyPackagesMissing |= !exists;
}

var repositories = sourceRepositoryProvider.GetRepositories().ToList();

if (!areAnyPackagesMissing)
{
using SourceCacheContext cacheContext = new();

var auditUtility = new AuditChecker(
repositories,
cacheContext,
log);

await auditUtility.CheckPackageVulnerabilitiesAsync(packageRestoreData, restoreAuditProperties, CancellationToken.None);

return new RestoreSummary(true);
}

var repositories = sourceRepositoryProvider.GetRepositories().ToArray();

var installCount = 0;
var failedEvents = new ConcurrentQueue<PackageRestoreFailedEventArgs>();
Expand All @@ -503,6 +516,8 @@ private static async Task<RestoreSummary> PerformNuGetV2RestoreAsync(Common.ILog
maxNumberOfParallelTasks: disableParallel
? 1
: PackageManagementConstants.DefaultMaxDegreeOfParallelism,
disableNuGetAudit: false,
restoreAuditProperties,
logger: collectorLogger);

// TODO: Check require consent?
Expand Down
2 changes: 2 additions & 0 deletions src/NuGet.Core/NuGet.Build.Tasks/NuGet.targets
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,8 @@ Copyright (c) .NET Foundation. All rights reserved.
<ConfigFilePaths>$(_OutputConfigFilePaths)</ConfigFilePaths>
<PackagesPath>$(_OutputPackagesPath)</PackagesPath>
<TargetFrameworks>@(_RestoreTargetFrameworksOutputFiltered)</TargetFrameworks>
<NuGetAudit>$(NuGetAudit)</NuGetAudit>
<NuGetAuditLevel>$(NuGetAuditLevel)</NuGetAuditLevel>
</_RestoreGraphEntry>
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ await _logger.LogAsync(RestoreLogMessage.CreateWarning(NuGetLogCode.NU1803,
}

bool auditEnabled = AuditUtility.ParseEnableValue(
_request.Project.RestoreMetadata?.RestoreAuditProperties?.EnableAudit,
_request.Project.RestoreMetadata?.RestoreAuditProperties,
_request.Project.FilePath,
_logger);
telemetry.TelemetryEvent[AuditEnabled] = auditEnabled ? "enabled" : "disabled";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using NuGet.DependencyResolver;
using NuGet.LibraryModel;
using NuGet.Packaging.Core;
using NuGet.ProjectModel;
using NuGet.Protocol;
using NuGet.Protocol.Model;
using NuGet.Versioning;
Expand Down Expand Up @@ -363,27 +364,15 @@ private PackageVulnerabilitySeverity ParseAuditLevel()
return PackageVulnerabilitySeverity.Low;
}

if (string.Equals("low", auditLevel, StringComparison.OrdinalIgnoreCase))
if (_restoreAuditProperties!.TryParseAuditLevel(out PackageVulnerabilitySeverity result))
{
return PackageVulnerabilitySeverity.Low;
}
if (string.Equals("moderate", auditLevel, StringComparison.OrdinalIgnoreCase))
{
return PackageVulnerabilitySeverity.Moderate;
}
if (string.Equals("high", auditLevel, StringComparison.OrdinalIgnoreCase))
{
return PackageVulnerabilitySeverity.High;
}
if (string.Equals("critical", auditLevel, StringComparison.OrdinalIgnoreCase))
{
return PackageVulnerabilitySeverity.Critical;
return result;
}

string messageText = string.Format(Strings.Error_InvalidNuGetAuditLevelValue, auditLevel, "low, moderate, high, critical");
RestoreLogMessage message = RestoreLogMessage.CreateError(NuGetLogCode.NU1014, messageText);
_logger.Log(message);
return 0;
return PackageVulnerabilitySeverity.Low;
}

internal enum NuGetAuditMode { Unknown, Direct, All }
Expand Down Expand Up @@ -414,27 +403,23 @@ private NuGetAuditMode ParseAuditMode()
}

// Enum parsing and ToString are a magnitude of times slower than a naive implementation.
public static bool ParseEnableValue(string? value, string projectFullPath, ILogger logger)
public static bool ParseEnableValue(RestoreAuditProperties? value, string projectFullPath, ILogger logger)
{
// Earlier versions allowed "enable" and "default" to opt-in
if (string.IsNullOrEmpty(value)
|| string.Equals(value, bool.TrueString, StringComparison.OrdinalIgnoreCase)
|| string.Equals(value, "enable", StringComparison.OrdinalIgnoreCase)
|| string.Equals(value, "default", StringComparison.OrdinalIgnoreCase))
if (value != null)
{
return true;

}
if (string.Equals(value, bool.FalseString, StringComparison.OrdinalIgnoreCase)
|| string.Equals(value, "disable", StringComparison.OrdinalIgnoreCase))

if (!value!.TryParseEnableAudit(out bool result))
{
return false;
string messageText = string.Format(Strings.Error_InvalidNuGetAuditValue, value, "true, false");
RestoreLogMessage message = RestoreLogMessage.CreateError(NuGetLogCode.NU1014, messageText);
message.ProjectPath = projectFullPath;
logger.Log(message);
}

string messageText = string.Format(Strings.Error_InvalidNuGetAuditValue, value, "true, false");
RestoreLogMessage message = RestoreLogMessage.CreateError(NuGetLogCode.NU1014, messageText);
message.ProjectPath = projectFullPath;
logger.Log(message);
return true;
return result;
}

// Enum parsing and ToString are a magnitude of times slower than a naive implementation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ public static PackageSpec GetPackageSpec(IEnumerable<IMSBuildItem> items)
);
}
pcRestoreMetadata.RestoreLockProperties = GetRestoreLockProperties(specItem);

pcRestoreMetadata.RestoreAuditProperties = GetRestoreAuditProperties(specItem);
}

if (restoreType == ProjectStyle.ProjectJson)
Expand Down
85 changes: 85 additions & 0 deletions src/NuGet.Core/NuGet.PackageManagement/Audit/AuditCheckResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

#nullable enable

using System;
using System.Collections.Generic;
using NuGet.Common;
using NuGet.Packaging.Core;

namespace NuGet.PackageManagement
{
public record AuditCheckResult
{
public IReadOnlyList<ILogMessage> Warnings { get; }
internal bool IsAuditEnabled { get; set; } = true;

internal int Severity0VulnerabilitiesFound { get; set; }
internal int Severity1VulnerabilitiesFound { get; set; }
internal int Severity2VulnerabilitiesFound { get; set; }
internal int Severity3VulnerabilitiesFound { get; set; }
internal int InvalidSeverityVulnerabilitiesFound { get; set; }
internal List<PackageIdentity>? Packages { get; set; }
internal double? DownloadDurationInSeconds { get; set; }
internal double? CheckPackagesDurationInSeconds { get; set; }
internal int SourcesWithVulnerabilities { get; set; }

private const string AuditVulnerabilitiesStatus = "PackagesConfig.Audit.Enabled";
private const string AuditVulnerabilitiesCount = "PackagesConfig.Audit.Vulnerability.Count";
private const string AuditVulnerabilitiesSev0Count = "PackagesConfig.Audit.Vulnerability.Severity0.Count";
private const string AuditVulnerabilitiesSev1Count = "PackagesConfig.Audit.Vulnerability.Severity1.Count";
private const string AuditVulnerabilitiesSev2Count = "PackagesConfig.Audit.Vulnerability.Severity2.Count";
private const string AuditVulnerabilitiesSev3Count = "PackagesConfig.Audit.Vulnerability.Severity3.Count";
private const string AuditVulnerabilitiesInvalidSeverityCount = "PackagesConfig.Audit.Vulnerability.SeverityInvalid.Count";
private const string AuditDurationDownload = "PackagesConfig.Audit.Duration.Download";
private const string AuditDurationCheck = "PackagesConfig.Audit.Duration.Check";
private const string SourcesWithVulnerabilitiesCount = "PackagesConfig.Audit.DataSources.Count";
private const string AuditVulnerabilitiesPackages = "PackagesConfig.Audit.Vulnerability.Packages";


public AuditCheckResult(IReadOnlyList<ILogMessage> warnings)
{
if (warnings is null)
{
throw new ArgumentNullException(nameof(warnings));
}

Warnings = warnings;
}

public void AddMetricsToTelemetry(TelemetryEvent telemetryEvent)
{
telemetryEvent[AuditVulnerabilitiesStatus] = IsAuditEnabled;
telemetryEvent[AuditVulnerabilitiesSev0Count] = Severity0VulnerabilitiesFound;
telemetryEvent[AuditVulnerabilitiesSev1Count] = Severity1VulnerabilitiesFound;
telemetryEvent[AuditVulnerabilitiesSev2Count] = Severity2VulnerabilitiesFound;
telemetryEvent[AuditVulnerabilitiesSev3Count] = Severity3VulnerabilitiesFound;
telemetryEvent[AuditVulnerabilitiesInvalidSeverityCount] = InvalidSeverityVulnerabilitiesFound;
telemetryEvent[SourcesWithVulnerabilitiesCount] = SourcesWithVulnerabilities;
telemetryEvent[AuditVulnerabilitiesCount] = Packages?.Count ?? 0;

if (DownloadDurationInSeconds.HasValue)
{
telemetryEvent[AuditDurationDownload] = DownloadDurationInSeconds;
}
if (CheckPackagesDurationInSeconds.HasValue)
{
telemetryEvent[AuditDurationCheck] = CheckPackagesDurationInSeconds;
}

if (Packages is not null)
{
List<TelemetryEvent> result = new List<TelemetryEvent>(Packages.Count);
foreach (var package in Packages)
{
TelemetryEvent packageData = new TelemetryEvent(eventName: string.Empty);
packageData.AddPiiData("id", package.Id.ToLowerInvariant());
packageData["version"] = package.Version;
result.Add(packageData);
}
telemetryEvent.ComplexData[AuditVulnerabilitiesPackages] = result;
}
}
}
}
Loading

0 comments on commit 35a7083

Please sign in to comment.