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

Discover plugins installed using Net tools #5990

Open
wants to merge 21 commits into
base: dev-feature-dot-net-tool-plugin-support
Choose a base branch
from

Conversation

Nigusu-Allehu
Copy link
Contributor

@Nigusu-Allehu Nigusu-Allehu commented Aug 24, 2024

Bug

Fixes: NuGet/Home#13740

Description

This PR makes sure NuGet discovers credential providers installed using .NET tools.
dotnet tool install --global should install the tool and add its path to the PATH environmental varibale. Iterate through all the paths in PATH and all the files in the path to find the plugins. By design, we expect these plugins to be named nuget-plugin*.

PR Checklist

  • Meaningful title, helpful description and a linked NuGet/Home issue
  • Added tests
  • Link to an issue or pull request to update docs if this PR changes settings, environment variables, new feature, etc.

@Nigusu-Allehu Nigusu-Allehu requested a review from a team as a code owner August 24, 2024 19:20
@Nigusu-Allehu Nigusu-Allehu marked this pull request as draft August 24, 2024 19:20
@Nigusu-Allehu Nigusu-Allehu self-assigned this Aug 26, 2024
@dotnet-policy-service dotnet-policy-service bot added the Status:No recent activity PRs that have not had any recent activity and will be closed if the label is not removed label Sep 3, 2024
Copy link
Contributor

This PR has been automatically marked as stale because it has no activity for 7 days. It will be closed if no further activity occurs within another 7 days of this comment. If it is closed, you may reopen it anytime when you're ready again, as long as you don't delete the branch.

@Nigusu-Allehu Nigusu-Allehu removed the Status:No recent activity PRs that have not had any recent activity and will be closed if the label is not removed label Sep 3, 2024
@Nigusu-Allehu Nigusu-Allehu marked this pull request as ready for review September 3, 2024 19:21
@Nigusu-Allehu Nigusu-Allehu added this to the 6.12 milestone Sep 5, 2024
@Nigusu-Allehu Nigusu-Allehu changed the title Discover Net tools installed plugins Discover plugins installed using Net tools Sep 9, 2024
{
if (IsValidPluginFile(file))
{
var state = new Lazy<PluginFileState>(() => _verifier.IsValid(file.FullName) ? PluginFileState.Valid : PluginFileState.InvalidEmbeddedSignature);
Copy link
Contributor

@kartheekp-ms kartheekp-ms Sep 12, 2024

Choose a reason for hiding this comment

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

.NET SDK recently enabled package signature verification for .NET tools. Hence, I think we can skip the _verifier.IsValid check because .NET tools are published as NuGet packages, which can be signed by the package author.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is the suggestion to not check the validity of the package and just assume it is valid?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, that is correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From my understanding, isValid checks whether the file has a valid embedded signature or not. Even though we do expect the files that come from .Net tools to have a valid signature. Isn't possible some file that was not installed by .Net tools exists in the path and it does not have a valid signature. Or it could be a .Net tools package that has been tempered with and does not have a valid signature anymore.

Copy link
Member

Choose a reason for hiding this comment

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

The "embedded signature" is an authenticode signature, which is not related to signed packages.

It typically costs hundreds of dollars a year to buy a code signing certificate, putting it out of reach for most non-corporate NuGet plugin developers. Even when a developer does have a certificate available for the production code, it's typically not available during development and debugging. I hit this today while trying to develop a new plugin, and I had to hardcode Valid state just to be able to develop the plugin.

At the very least we should add an environment variable to allow skipping the authenticode checks, so that plugin developers can actually develop. However, I'm in favour of removing the authenticode checks altogether. If someone wants to allow only signed executables to run on a Windows machine, they can configure it via group policy.

string[] paths = Array.Empty<string>();

// The path to the plugins installed using dotnet tools, should be specified in the NUGET_PLUGIN_PATHS environment variable.
paths = _environmentVariableReader.GetEnvironmentVariable("NUGET_PLUGIN_PATHS")?.Split(Path.PathSeparator) ?? Array.Empty<string>();
Copy link
Contributor

Choose a reason for hiding this comment

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

If the NUGET_PLUGIN_PATHS environment variable is already set, the _rawpluginpaths parameter passed to the constructor will contain a list of paths to plugin executables that are installed as .NET tools going forward.

if (string.IsNullOrEmpty(_rawPluginPaths))
{
_rawPluginPaths = reader.GetEnvironmentVariable(EnvironmentVariableConstants.PluginPaths);
}

I think we should remove the logic in PluginManager for discovering .NET tool plugins because they have different verification logic. For example, the file name should start with nuget-plugin-*, the executable bit should be set for non-Windows platforms, and the file should have a .exe or .bat extension on Windows.

Copy link
Contributor Author

@Nigusu-Allehu Nigusu-Allehu Sep 13, 2024

Choose a reason for hiding this comment

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

I removed it and now the logic is

  • If NUGET_NETFX_PLUGIN_PATHS or NUGET_NETCORE_PLUGIN_PATHS are set, use them.
  • Otherwise if NUGET_PLUGIN_PATHS is set, assume it is pointing to the directory that contains the dotnet tools installed plugins. If none of the env variables are set, go a head and scan for the plugins from the directories listed in PATH.

This limits NUGET_PLUGIN_PATHS to be used for .NET tools plugins only. Would that not be a problem for users who use it for non .NET tools plugins previously?

(fileMode & UnixFileMode.GroupExecute) != 0 ||
(fileMode & UnixFileMode.OtherExecute) != 0);
#else
return fileInfo.Exists && (fileInfo.Attributes & FileAttributes.ReparsePoint) == 0 && IsExecutable(fileInfo);
Copy link
Contributor

@kartheekp-ms kartheekp-ms Sep 13, 2024

Choose a reason for hiding this comment

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

Please help me understand the reasoning behind the (fileInfo.Attributes & FileAttributes.ReparsePoint == 0) check. My internet search suggested it is related to symbolic links, etc., but I happy to learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right. I wanted to ensure the file both exists and is a real file (not a symbolic link) to minimize the risk of running malicious code. For example, imagine a symbolic link located in one of the directories listed in the user's PATH environment variable. The link has a name like nuget-plugin-*, making it appear like a normal plugin, but it actually points to a malicious file in a different directory. If we run that symlink, we unintentionally execute the malicious file from the other location. We could say it is up to the user to ensure there is no malicious files in the PATH folders. However, I don't see a reason why we should leave that vulnerability open.

Comment on lines +100 to +103
if (string.IsNullOrEmpty(_rawPluginPaths))
{
// Nuget plugin configuration environmental variables were not used to discover the configuration files
_pluginFiles.AddRange(GetNetToolsPluginFiles());
Copy link
Member

Choose a reason for hiding this comment

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

The XMLdoc for rawPluginPaths says:

The raw semicolon-delimited list of supposed plugin file paths

But I don't understand when the value will be null or not null, or where the value comes from, and therefore I'm having a hard time understanding why we look for .NET tool plugins only when it's empty.

Is rawPluginPaths the value of the NUGET_PLUGIN_PATHS or NUGET_NETFX_PLUGIN_PATHS environment variables when they're set?

Comment on lines 98 to +103
_pluginFiles = GetPluginFiles(cancellationToken);

if (string.IsNullOrEmpty(_rawPluginPaths))
{
// Nuget plugin configuration environmental variables were not used to discover the configuration files
_pluginFiles.AddRange(GetNetToolsPluginFiles());
Copy link
Member

Choose a reason for hiding this comment

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

Today, I've been trying to create a cred provider. I used the nuget-plugin-*.exe file convention, but setting NUGET_NETFX_PLUGIN_PATHS or NUGET_PLUGIN_PATHS to the bin directory of my project, or setting it to the full path of the exe itself, I couldn't get NuGet to use my plugin, without adding the project's bin directory to the path (and using your test branch for this feature that does execution, in addition to discovery). However, when doing this, it also discovered other cred providers I have installed, which was making debuggign my plugin much more difficult.

It can be done in a follow up, but I think there should be a way to disable normal plugin discovery, and only discover plugins I want. The NUGET_PLUGIN_PATHS environment variable appears to do that for the older discovery method, so there should be a way for this new one as well.

{
if (IsValidPluginFile(file))
{
var state = new Lazy<PluginFileState>(() => _verifier.IsValid(file.FullName) ? PluginFileState.Valid : PluginFileState.InvalidEmbeddedSignature);
Copy link
Member

Choose a reason for hiding this comment

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

The "embedded signature" is an authenticode signature, which is not related to signed packages.

It typically costs hundreds of dollars a year to buy a code signing certificate, putting it out of reach for most non-corporate NuGet plugin developers. Even when a developer does have a certificate available for the production code, it's typically not available during development and debugging. I hit this today while trying to develop a new plugin, and I had to hardcode Valid state just to be able to develop the plugin.

At the very least we should add an environment variable to allow skipping the authenticode checks, so that plugin developers can actually develop. However, I'm in favour of removing the authenticode checks altogether. If someone wants to allow only signed executables to run on a Windows machine, they can configure it via group policy.

@nkolev92 nkolev92 added the Merge next release PRs that should not be merged until the dev branch targets the next release label Sep 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Merge next release PRs that should not be merged until the dev branch targets the next release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants