FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

C#: Automatically use configured private registry feeds by mbg · Pull Request #18850 · github/codeql · GitHub

/ codeql Public

C#: Automatically use configured private registry feeds - #18850

Merged
mbg merged 21 commits into
mainfrom
mbg/csharp/inject-proxy-urls
Mar 27, 2025
Merged

mbg merged 21 commits into
mainfrom
mbg/csharp/inject-proxy-urls

Conversation

mbg commented Feb 24, 2025
edited
Loading

Copy link
Copy Markdown
Member

When private package registries are configured for Default Setup, we initialise the Dependabot proxy to provide an authenticated proxy for those registries. As of #18029, the C# extractor is able to make use of the authenticated Dependabot proxy to connect to those private package registries.

However, in some scenarios, the address of the private package registries is not actually stored in the nuget.config files for a project. Since we only restore dependencies from feeds that are configured in nuget.config files, we are unable to restore dependencies from private registries in this case. Ordinary CI workflows for such projects rely on injecting the feed addresses into the configuration.

This PR modifies the NugetPackageRestorer to "inject" any private package registry feeds that are configured through the UI into the package restore process by passing them to dotnet on the command-line. This allows private package registries to be used without needing the address to be stored in the repository.

The CodeQL Action that initialises the Dependabot proxy provides the private registry feed URLs as an output as of github/codeql-action#2652 (and annotates them with their type as of github/codeql-action#2672). A future version of the Default Setup workflow will propagate that output to the analyze step / CodeQL extractors.

Approach

The approach I chose for this implementation is to detect when the CODEQL_PROXY_URLS environment is set, extract nuget feeds from the JSON array that is expected to be stored in it, and then pass those feed URLs, along with others that we discovered in the nuget.config files, to dotnet on the command-line.

Using the command-line has the advantage that we do not need to modify (and later clean-up) configuration files on disk, but the disadvantage that dotnet will only use feeds given to it on the command-line and ignore those in configuration files entirely. This means that we must provide all feeds that we want to use on the command-line.

To minimise the risk of breaking what was already working before, nuget feeds are only passed to dotnet on the command-line when the Dependabot proxy is configured (i.e. when private package registries are being used). If not, then the old behaviour of implicitly using nuget.config files continues to be used.

mbg self-assigned this Feb 24, 2025
github-actions Bot added the C# label Feb 24, 2025
mbg force-pushed the mbg/csharp/inject-proxy-urls branch from 379f635 to a8dde15 Compare March 14, 2025 13:47
mbg marked this pull request as ready for review March 14, 2025 14:37
mbg requested a review from a team as a code owner March 14, 2025 14:37
/// <summary>
/// Represents configurations for package registries.
/// </summary>
public struct RegistryConfig

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Is there any particular reason/benefit to declare this as a struct?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

No particular reason. I thought I was mirroring a similar setup elsewhere in the codebase, but happy to change it.

// The value of the environment variable should be a JSON array of objects, such as:
// [ { "type": "nuget_feed", "url": "https://nuget.pkg.github.com/org/index.json" } ]
var array = JsonConvert.DeserializeObject<List<RegistryConfig>>(registryURLs);
if (array != null)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Do we expect the array to be null? Should we log if it is null?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

The main reason for the check is just to make sure we don't work with a null value. We could log it, but I have no strong view on it either way.

michaelnebel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Thank you @mbg !
I really appreciate the PR explanation and inline code comments - it makes it much easier to review.
This adds an extra layer of configuration; Is it intended to work in conjunction with the other environment variables feeds (for instance excluding a feed from a reachability check)?

// we have discovered from analysing `nuget.config` files.
sources = configuredSources ?? new();
sources.Add(PublicNugetOrgFeed);
this.dependabotProxy?.RegistryURLs.ForEach(url => sources.Add(url));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Consider constructing the "source" string at this location instead. Otherwise it will be constructed once for each project that is restored in the parallel restore loop.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Done in d564529

// we have discovered from analysing `nuget.config` files.
sources = configuredSources ?? new();
sources.Add(PublicNugetOrgFeed);
this.dependabotProxy?.RegistryURLs.ForEach(url => sources.Add(url));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

To avoid timeouts, maybe there should be a feed validation check similar to:

var allFeedsReachable = explicitFeeds.All(feed => excludedFeeds.Contains(feed) || IsFeedReachable(feed, initialTimeout, tryCount));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Done in 4448369

mbg and others added 5 commits March 17, 2025 14:24
Co-authored-by: Michael Nebel <michaelnebel@github.com>
This allows the string of package feeds to be constructed once and used repeatedly in the parallel restore loop as well.
mbg requested review from michaelnebel and tamasvajk March 24, 2025 17:56

michaelnebel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Thank you for looking into the comments and thank you for doing this!!! 👍

// in addition to the ones that are configured in `nuget.config` files.
this.dependabotProxy?.RegistryURLs.ForEach(url => feedsToCheck.Add(url));

var allFeedsReachable = this.CheckFeeds(feedsToCheck);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

If the dependabot proxy is set, we also explicitly add the public nuget org feed as a source - so we should probably check whether that feed is reachable as well.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I have changed this so that the public feed is not manually added when private registries are configured in 4d3b024, since explicitFeeds should already contain it. I checked and dotnet nuget list source does return it unless there is a <clear /> entry in the list of packageSources in nuget.config.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Actually, there's more subtly to this. GetNugetFeeds doesn't include it, because dotnet nuget list source only includes the feeds that are explicitly configured in a given configuration file if it is given an --configfile argument. GetNugetFeedsFromFolder does include the public feed (unless there's a <clear /> element), but we don't check whether these "inherited" feeds are reachable already.

So, we didn't previously check that the public feed is reachable anyway. @tamasvajk left a comment saying that we could check that they are reachable, but don't because of authentication requirements(?).

I think the real lesson for my changes here is that I should really use allFeeds as argument for RestoreProjects rather than just explicitFeeds.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

OK that also still doesn't quite work with the current logic, because GetAllFeeds only invokes GetNugetFeedsFromFolder in folders that contain nuget.config files to begin with. In our case, we may not have a nuget.config file at all, so GetNugetFeedsFromFolder never gets called, even though it would give us the public feed as expected.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

If the reachability of the public nuget feed wasn't checked before, then maybe there is no need to check it now either.
However, I think its availability is checked when it is added as a fallback feed (not as an inherited feed).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

If the reachability of the public nuget feed wasn't checked before, then maybe there is no need to check it now either.

In any case, it's probably a separate discussion to the changes in this PR. I have also now removed that part of the code that manually added the public feed in be95d33.

However, I think its availability is checked when it is added as a fallback feed (not as an inherited feed).

That's true.

/// </summary>
/// <param name="explicitFeeds">Outputs the set of explicit feeds.</param>
/// <returns>True if all feeds are reachable or false otherwise.</returns>
private bool CheckFeeds(out HashSet<string> explicitFeeds)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Maybe rename one of the CheckFeeds methods. The naming is a bit confusing when their type signatures are so similar.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Done in d2b88ae

Comment on lines +831 to +834
.Where(folder => folder != null)
.SelectMany(folder => GetFeeds(() => dotnet.GetNugetFeedsFromFolder(folder!)))
.ToHashSet();
}

Check notice

Code scanning / CodeQL

Generic catch clause

Generic catch clause.
Show autofix suggestion Hide autofix suggestion

Copilot Autofix

AI over 1 year ago

To fix the problem, we should catch specific exceptions that are likely to be thrown by the operation being performed. In this case, FileInfo(config).Directory?.FullName can throw exceptions such as ArgumentException, PathTooLongException, NotSupportedException, UnauthorizedAccessException, and DirectoryNotFoundException. We should catch these specific exceptions and log them accordingly.

Suggested changeset 1
csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs
--- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs
@@ -830,5 +830,21 @@
                         }
-                        catch (Exception exc)
+                        catch (ArgumentException exc)
                         {
-                            logger.LogWarning($"Failed to get directory of '{config}': {exc}");
+                            logger.LogWarning($"Failed to get directory of '{config}' due to an argument exception: {exc}");
+                        }
+                        catch (PathTooLongException exc)
+                        {
+                            logger.LogWarning($"Failed to get directory of '{config}' due to a path too long exception: {exc}");
+                        }
+                        catch (NotSupportedException exc)
+                        {
+                            logger.LogWarning($"Failed to get directory of '{config}' due to a not supported exception: {exc}");
+                        }
+                        catch (UnauthorizedAccessException exc)
+                        {
+                            logger.LogWarning($"Failed to get directory of '{config}' due to an unauthorized access exception: {exc}");
+                        }
+                        catch (DirectoryNotFoundException exc)
+                        {
+                            logger.LogWarning($"Failed to get directory of '{config}' due to a directory not found exception: {exc}");
                         }
EOF
@@ -830,5 +830,21 @@
}
catch (Exception exc)
catch (ArgumentException exc)
{
logger.LogWarning($"Failed to get directory of '{config}': {exc}");
logger.LogWarning($"Failed to get directory of '{config}' due to an argument exception: {exc}");
}
catch (PathTooLongException exc)
{
logger.LogWarning($"Failed to get directory of '{config}' due to a path too long exception: {exc}");
}
catch (NotSupportedException exc)
{
logger.LogWarning($"Failed to get directory of '{config}' due to a not supported exception: {exc}");
}
catch (UnauthorizedAccessException exc)
{
logger.LogWarning($"Failed to get directory of '{config}' due to an unauthorized access exception: {exc}");
}
catch (DirectoryNotFoundException exc)
{
logger.LogWarning($"Failed to get directory of '{config}' due to a directory not found exception: {exc}");
}
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
mbg requested a review from michaelnebel March 25, 2025 14:41

michaelnebel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Great work! Thank you!

else
{
// If we haven't found any `nuget.config` files, then obtain a list of feeds from the root source directory.
allFeeds = GetFeeds(() => dotnet.GetNugetFeedsFromFolder(this.fileProvider.SourceDir.FullName)).ToHashSet();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Nice improvement!

Copy link
Copy Markdown
Contributor

@mbg : We should also run DCA before merging.

mbg commented Mar 27, 2025

Copy link
Copy Markdown
Member Author

DCA looks good, I think. So I will go ahead and merge this.

mbg merged commit 2aee47b into main Mar 27, 2025
mbg deleted the mbg/csharp/inject-proxy-urls branch March 27, 2025 10:11

michaelnebel commented Mar 27, 2025
edited
Loading

Copy link
Copy Markdown
Contributor

DCA looks good, I think. So I will go ahead and merge this.

It looks like the changes was not tested by the DCA run.
When testing extractor changes database caching needs to be disabled and it also needs to be specified that the extractor should be built based on the branch. 😃
In the wizard this looks like

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants


Back | FazBrowse Home | New Git URL