Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable AuditSource during dotnet list package command #6206

Open
wants to merge 8 commits into
base: dev
Choose a base branch
from

Conversation

Nigusu-Allehu
Copy link
Contributor

@Nigusu-Allehu Nigusu-Allehu commented Dec 21, 2024

Bug

Fixes: NuGet/Home#13767

Description

This PR updates dotnet list package --vulnerable to use user configured <AuditSources>.

Currently, the command only looks into <PackageSources> to load vulnerability data. However, with the introduction of NuGet Audit, other commands now support <AuditSources> to specify vulnerability data sources. This PR makes sure dotnet list package --vulnerable is also up to date and supports <AuditSources>

In order to do a manual test, I specified a package that has only one vulnerability data source. That source is only specified as an Audit source. This is what running dotnet list package --vulnerable results in before and after this PR

Before

image

After

image

PR Checklist

@Nigusu-Allehu Nigusu-Allehu self-assigned this Dec 21, 2024
@Nigusu-Allehu Nigusu-Allehu marked this pull request as ready for review December 24, 2024 18:01
@Nigusu-Allehu Nigusu-Allehu requested a review from a team as a code owner December 24, 2024 18:01
Copy link
Member

@zivkan zivkan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work!

I particularly like test calling GetReportDataAsync and asserting on the object, rather than a test that tries to parse console output.

There are some changes needed to make dothet list package work more similarly to restore with regards to audit sources. And considering audit sources introduces multiple behaviours, it means multiple tests are needed to test all the scenarios.

I also made some suggestions about code readability/maintainability, which while subjective, I hope that I've justified sufficiently well to convince you that it's changes we should make, not just an opinion I'm trying to force onto others.

@@ -17,6 +17,7 @@ internal class ListPackageArgs
public ILogger Logger { get; }
public string Path { get; }
public List<PackageSource> PackageSources { get; }
public List<PackageSource> AuditSources { get; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern that most NuGet.CommandLine and NuGet.CommandLine.XPlat commands use is that these *Args classes hold the inputs, and business logic goes in the *Runner classes.

Since no CLI argument/option was added to dotnet list package for audit sources (should there be?), I feel like this AuditSources property doesn't belong here.

I had a quick look, and I see that ListCommand reads nuget.config and gets the sources at the same time as it's parsing the other CLI arguments and options into the ListPakcageArgs class. But I think this is not good software design. Following the Single Responsibility Principal, I think easier to understand code if ListPackageArgs is only parses CLI commands and arguments and puts them in the ListPackageArgs class, and then ListCommandRunner has all business logic regarding whether CLI sources are added to nuget.config sources, or used as a replacement instead, as well all the other work that list package does.

Logger = logger ?? throw new ArgumentNullException(nameof(logger));
CancellationToken = cancellationToken;
ArgumentText = GetReportParameters();
}

public ListPackageArgs(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My comment isn't as relevant given my other suggestion that AuditSources shouldnt be defined in this class (unless a CLI option is added), but this class isn't public, so it's not part of the public API, and therefore we can modify the existing constructor rather than adding a new overload.

If this was already understood, and this overload was added instead to avoid needing to modify existing tests that call the existing constructor, a trade-off needs to be made between ease of finishing your current work (this PR) vs ongoing tech debt in having more methods, risks of bugs because tests are using the production class differently to how production code is being used.

There will be times where adding an overload is the better choice, and other times when making the effort to update tests is the better choice to reduce risk. I haven't looked in detail at how many tests create instances of this class, but my gut feeling is that I would expect it not to be too difficult to update all tests, so we probably don't need a constructor overload.

@@ -176,6 +179,18 @@ private static void WarnForHttpSources(ListPackageArgs listPackageArgs, ListPack
}
}

foreach (PackageSource packageSource in listPackageArgs.AuditSources)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have liked the PR description to have addressed my question, but from https://learn.microsoft.com/en-us/nuget/concepts/auditing-packages#audit-sources

If auditSources is not defined or is cleared without adding any sources, then packageSources will be used and warning NU1905 is suppressed.

So, for restore, when nuget.config defines one or more audit sources, then vulnerability information is not retrieved from the package sources, only from the audit sources.

This PR does not appear to follow this behaviour for dotnet list package.

In theory, if a package source lists a package as vulnerable, but the audit source does not, this will cause dotnet list package --vulnerable to have different results to dotnet restore.

If it's intentional that dotnet list package --vulnerable will work differently, I'm curious why

@@ -176,6 +179,18 @@ private static void WarnForHttpSources(ListPackageArgs listPackageArgs, ListPack
}
}

foreach (PackageSource packageSource in listPackageArgs.AuditSources)
{
if (packageSource.IsHttp && !packageSource.IsHttps && !packageSource.AllowInsecureConnections)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no else block, so if any sources defined in nuget.config are not used because it's using insecure http, it will be silently ignored. I believe all or other commands report an error, so this should as well.

Comment on lines +298 to +302
foreach (var source in listPackageArgs.AuditSources)
{
var repository = Repository.Factory.GetCoreV3(source);
var vulnerabilityProvider = new VulnerabilityInfoResourceV3Provider();
var result = await vulnerabilityProvider.TryCreate(repository, listPackageArgs.CancellationToken);
Copy link
Member

@zivkan zivkan Dec 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Package sources can provide the VulnerabilityInfoResource as well as audit sources, and that's actually how audit during restore works. An issue was created for this before auditSources was implemented, so maybe some people would consider it scope creep because the issue to add support for auditSources didn't explicitly call it out, but it's very related to the work currently being done, so I feel like it's a good time to implement it at the same time:

Implementing this will give dotnet list package --vulnerable a significant performance boost when there are many packages to check, because NuGet can download the database once and then do a whole lot of lookups, instead of making an HTTP request to every source for every package id.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing test: when a package source has vulnerabilitiy data in addition to an audit source

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing test: when an audit source has a server-side error. For example, the vulnerability index lists two files, one of which does not exist.

Note that I designed IVulnerabilityInfoResource to be easily mockable, see some of the restore tests. In theory it's not necessary to use a mock HTTP server to test, you can create a SourceRepository that overrides the default IVulnerabilityInfoResource, where you return a hard-coded result with an exception and/or vulnerability data. You just need to be able to inject the mock SourceRepository into the production code. For example, make it possible for tests to inject their own _sourceRepositoryCache

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically this is probably a missing test in list package's original implementation, but I can't find a test that validates expected behaviour when two package/audit sources list the same vulnerability. Does it get de-duplicated, or does the same advisory get reported to the customer multiple times?

}";
if (!Directory.Exists(projectFolder))
{
Directory.CreateDirectory(projectFolder);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Directory.CreateDirectory does not throw if the path already exists, and considering SimpleTestPathContext creates a random directory for the test to run in, we know that this directory does not exist.

This test is very long, making it hard to review for correctness, so little things like removing unnecessary if blocks can help shorten it a bit.

Assert.Equal(1, result.Item2.Projects.First().TargetFrameworkPackages.First().TopLevelPackages.Count);
Assert.Equal(1, result.Item2.Projects.First().TargetFrameworkPackages.First().TopLevelPackages.First().Vulnerabilities.Count);
Assert.Equal(severity, result.Item2.Projects[0].TargetFrameworkPackages[0].TopLevelPackages.First().Vulnerabilities.First().Severity);
Assert.Equal(advisoryUrl, result.Item2.Projects[0].TargetFrameworkPackages[0].TopLevelPackages.First().Vulnerabilities.First().AdvisoryUrl.ToString());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider using FluentAssertions when writing new tests. Personally, I think their exception messages are better than XUnit's Assert methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

dotnet list package --vulerable should support auditSources
2 participants