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

Add an ENV Variable for Package Signing Verification on .NET 5+ Linux/MAC #3986

Merged
merged 19 commits into from
Apr 9, 2021

Conversation

erdembayar
Copy link
Contributor

@erdembayar erdembayar commented Apr 5, 2021

Bug

Fixes: NuGet/Home#10742 https://github.com/NuGet/Client.Engineering/issues/882

Regression? Last working version:

Description

Add an environment variable to opt-in to the package signing verification on .NET 5.
This is to provide an opt-in / troubleshooting experience for Mac/Linux based on the reversion of:
#3979
We intend this ENV for testing and don't guarantee compatibility.

PR Checklist

  • PR has a meaningful title

  • PR has a linked issue.

  • Described changes

  • Tests

    • Automated tests added
    • OR
    • Test exception
    • OR
    • N/A
  • Documentation

    • Documentation PR or issue filled
    • OR
    • N/A

@erdembayar erdembayar requested a review from a team as a code owner April 5, 2021 18:11
@erdembayar erdembayar changed the title Dev eryondon sign config env variable Add an ENV Variable for Package Signing Verification on .NET 5 Linux/MAC Apr 5, 2021
return false;
// Conditionally enable back package sign verification temporary disabled due to Mozilla drop Symantec as CA on Linux/MAC.
// Please note: Linux/MAC case sensitive for env var.
string signVerifyEnvVariable = Environment.GetEnvironmentVariable("DOTNET_OPT_IN_SECURE_PACKAGE_VERIFICATION");
Copy link
Member

Choose a reason for hiding this comment

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

Use the https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Common/EnvironmentVariableWrapper.cs so you can add a unit test.

This is an API that's easily unit testable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I already checked that one. I don't see any actual benefit.
Does it prevent from cross test env var value set contamination? If yes then we're going to have more flaky unit tests?
Specially it's problem if parallel tests are running same time.

Copy link
Contributor

@loic-sharma loic-sharma Apr 5, 2021

Choose a reason for hiding this comment

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

Yes it prevents issues with concurrent unit test as you can provide a mock to each test with values you control. For example:

// Arrange
var settings = Mock.Of<ISettings>();
var environment = new Mock<IEnvironmentVariableReader>(MockBehavior.Strict);
environment.Setup(s => s.GetEnvironmentVariable("http_proxy")).Returns(proxyValue);
var proxyCache = new ProxyCache(settings, environment.Object);
// Act
var proxy = proxyCache.GetUserConfiguredProxy();
// Assert
Assert.Null(proxy);

Copy link
Member

Choose a reason for hiding this comment

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

Does it prevent from cross test env var value set contamination? If yes then we're going to have more flaky unit tests?

Yes, because as @loic-sharma showed, you can mock the specific instance of the wrapper.

Copy link
Contributor Author

@erdembayar erdembayar Apr 6, 2021

Choose a reason for hiding this comment

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

I just realized problem with this approach. I have to make change to existing public api PackageExtractor.ExtractPackageAsync in order to pass IEnvironmentVariableReader as parameter.
Is it really good justification to make change to public api, just sake of additional unit testing working?

Copy link
Member

Choose a reason for hiding this comment

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

You can add an internal constructor.
We set internalsvisibleto to test projects.

[assembly: InternalsVisibleTo("NuGet.Packaging.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nkolev92
PackageExtractor is static class, so it doesn't have constructor.
If we introduce new field into static class then it'll cause problem in concurrent unit test context.

Copy link
Member

Choose a reason for hiding this comment

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

PackageExtractor is static class, so it doesn't have constructor.

The change is in PackageArchiveReader not PackageExtractor.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok. I create new internal constructors for PackageArchiveReader and created new tests in most places by creating duplicate of existing test with environmental variable change.

But there're 2 places I can't use it and I don't think it it's really important for now.

  1. RestoreCommandTests.cs >> RestoreCommand_InvalidSignedPackageAsync_SuccessAsync()
  2. PackageExtractorTests.cs>>InstallFromSourceAsyncByStream_InvalidSignPackageWithoutUnzipAsync

Please review again.

@erdembayar erdembayar changed the title Add an ENV Variable for Package Signing Verification on .NET 5 Linux/MAC Add an ENV Variable for Package Signing Verification on .NET 5+ Linux/MAC Apr 5, 2021
@erdembayar erdembayar force-pushed the dev-eryondon-SignConfigEnvVariable branch from 93f5e0e to dfc6c98 Compare April 6, 2021 04:38
Comment on lines 335 to 336
result.Success.Should().BeFalse();
result.ExitCode.Should().Be(1, because: "error text should be displayed as restore failed");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. 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.

Yes, technically they're same. I just copied from another already existing integration test.
2nd one actually tells you what went wrong in text.

result.Success.Should().BeFalse();
result.ExitCode.Should().Be(1, because: "error text should be displayed as restore failed");

Copy link
Member

Choose a reason for hiding this comment

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

result.Success.Should().BeFalse(because: "error text should be displayed as restore failed");

The following works too.
Existing code is not always perfect.

@erdembayar erdembayar requested a review from kartheekp-ms April 6, 2021 23:28
@erdembayar
Copy link
Contributor Author

@NuGet/nuget-client
Ready to review.

Comment on lines 485 to 487
string signVerifyEnvVariable = _environmentVariableReader == null ?
EnvironmentVariableWrapper.Instance.GetEnvironmentVariable(envVarName) :
_environmentVariableReader.GetEnvironmentVariable(envVarName);
Copy link
Member

Choose a reason for hiding this comment

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

If you made the old constructors call the new constructor, passing in EnvironmentVariableWrapper.Instance to the new parameter, then _environmentVariableReader will never be null. (well, assuming you implement null checking and throw ArgumentNullException).

Copy link
Contributor Author

@erdembayar erdembayar Apr 7, 2021

Choose a reason for hiding this comment

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

My intention was not to change existing code. Also there're 6 public constructors vs 2 internal constructors for unit tests.
Currently _environmentVariableReader is used only for prevent from flaky unit tests when many concurrent tests run same time and I'm expecting whole Linux/MAC specific changes are temporary (including this environmental var checking + all new unit tests) will be removed once we get long term solution resistant any 3rd party changes(like Mozilla did), so checking it and throwing ArgumentNullException is not really important. Since I'm expecting this changes are temporary I prefer to make less changes to existing code, so it would be easy to revert in the future.

Copy link
Member

Choose a reason for hiding this comment

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

You can have the public constructors chain into a private constructor.

I couldn't find any blog posts or stack overflow answers suggesting best practices, but a pattern I've often seen, especially in the NuGet repo, is that only a single constructor has a non-empty body and all other constructor overloads chain into this "one true constructor". This makes it easy to validate that all fields/properties that must not be null are checked and assigned a value. Otherwise it's very difficult reading the code to ensure that fields/properties that should be set are correctly assigned a value in all different constructors.

This would have the side-effect of _environmentVariableHelper never being null, and having slightly less complex code in the methods that use the field.

Currently _environmentVariableReader is used only for prevent from flaky unit tests

If it's "only for tests", then we need to consider whether Arcade need an opt-in mechanism to keep doing signature validation on Linux and Mac, and do API design, including a new public API if necessary, to allow them to keep doing so.

This is the problem with NuGet being both a tool and an API. Given that the .NET platform is built with the arcade SDK, if we disable package signature validation, we might risk breaking every github.com/dotnet/ repo since they're using these APIs (maybe we will "already have" when Arcade upgrades to the version of NuGet that includes the PR we merged a week or two ago).

Maybe we need to talk to DncEng to find out what exactly they're using our package validation APIs for, and test that it will still do what they need with all these changes.

Copy link
Member

Choose a reason for hiding this comment

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

My intention was not to change existing code. Also there're 6 public constructors vs 2 internal constructors for unit tests.

What's motivation behind not changing existing code?
Is there a risk inherent with that?
Having a single root constructor helps avoid many of the argument checking pitfalls.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My intention was not to change existing code. Also there're 6 public constructors vs 2 internal constructors for unit tests.

What's motivation behind not changing existing code?
Is there a risk inherent with that?
Having a single root constructor helps avoid many of the argument checking pitfalls.

Since I'm expecting this changes are temporary I prefer to make less changes to existing code, so it would be easy to revert in the future.

Copy link
Member

Choose a reason for hiding this comment

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

We have git for that.
I think we should strive for quality code, regardless whether we think the change is temp or not temp.

There's no public API changes here, so no future risk there.

Copy link
Contributor Author

@erdembayar erdembayar Apr 7, 2021

Choose a reason for hiding this comment

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

Maybe we need to talk to DncEng to find out what exactly they're using our package validation APIs for, and test that it will still do what they need with all these changes.

I don't know if DncEng/Arcade would be only users of this env var, intent for now only for testing. There're many cases temporary things become permanent thing so I'll address this for better code quality.

@erdembayar erdembayar force-pushed the dev-eryondon-SignConfigEnvVariable branch from 97a9715 to fb609f6 Compare April 7, 2021 21:48
Comment on lines 335 to 336
result.Success.Should().BeFalse();
result.ExitCode.Should().Be(1, because: "error text should be displayed as restore failed");
Copy link
Member

Choose a reason for hiding this comment

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

result.Success.Should().BeFalse(because: "error text should be displayed as restore failed");

The following works too.
Existing code is not always perfect.

@erdembayar
Copy link
Contributor Author

@JonDouglas @nkolev92 @aortiz-msft @dtivel
Renamed to DOTNET_NUGET_SIGNATURE_VERIFICATION. I'll merge the PR today evening.

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.

Add an ENV Variable for Package Signing Verification on .NET 5+ Linux/MAC
7 participants