From 7793d7ab7093abdfc9ca9cdc48263a4f667f829f Mon Sep 17 00:00:00 2001 From: Cory Knox Date: Fri, 25 Oct 2024 10:52:46 -0700 Subject: [PATCH] (#3503, #3513) Better handle argument decryption Currently we are decrypting the arguments file every time a list is run, even though we only care about them in scenarios where we might output them to the user (when Verbose is specified on the list command or with the info --local-only command). This updates the handling of the remembered arguments to be consistent between the List method and the scenario in an Upgrade where we want to use the remembered arguments. We now will only throw an error in a scenario where we can't decrypt the contents and we need them for the scenario to succeed. We are also stating what file we couldn't decrypt so the user can troubleshoot. --- .../services/NugetService.cs | 71 +++++++++++-------- .../utility/ArgumentsUtility.cs | 43 +++++++++-- 2 files changed, 80 insertions(+), 34 deletions(-) diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index 608d3a90be..b03f4ab3ee 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -189,6 +189,8 @@ it is possible that incomplete package lists are returned from a command this.Log().Debug(ChocolateyLoggers.Verbose, () => "--- Start of List ---"); } + var decryptionFailures = new List(); + foreach (var pkg in NugetList.GetPackages(config, _nugetLogger, _fileSystem)) { var package = pkg; // for lamda access @@ -217,9 +219,24 @@ it is possible that incomplete package lists are returned from a command } } - if (!string.IsNullOrWhiteSpace(packageInfo.Arguments)) + // The main scenario where we desire the decrypted arguments during a list sequence is to display them when verbose output is selected. + // This is done by default on the `info` command, and by request on the `list` command. As such, we are going to validate it's that scenario + // to avoid needlessly decrypting the arguments file. + var shouldDecryptArguments = ( + config.CommandName.Equals("info", StringComparison.InvariantCultureIgnoreCase) || + config.CommandName.Equals("list", StringComparison.InvariantCultureIgnoreCase) + ) && + config.Verbose && + !string.IsNullOrWhiteSpace(packageInfo.Arguments); + + if (shouldDecryptArguments) { - var decryptedArguments = ArgumentsUtility.DecryptPackageArgumentsFile(_fileSystem, packageInfo.Package.Id, packageInfo.Package.Version.ToNormalizedStringChecked()); + var decryptedArguments = ArgumentsUtility.DecryptPackageArgumentsFile(_fileSystem, packageInfo.Package.Id, packageInfo.Package.Version.ToNormalizedStringChecked()).ToList(); + + if (decryptedArguments.Count <= 0) + { + decryptionFailures.Add(packageInfo); + } packageArgumentsUnencrypted = "\n Remembered Package Arguments: \n {0}".FormatWith(string.Join(Environment.NewLine + " ", decryptedArguments)); } @@ -336,6 +353,23 @@ Package url{6} this.Log().Warn(logType, "Over {0:N0} packages, or package versions, was found per source, but there may be more packages available that were filtered out. Please refine your search, or specify a page number to retrieve more results.".FormatWith(NugetList.LastPackageLimitUsed * 0.9)); } } + + if (decryptionFailures.Count > 0) + { + var failedPackages = string.Join(", ", decryptionFailures.Select(f => "{0} - {1}".FormatWith(f.Package.Id, f.Package.Version))); + var failureMessage = "There were some failures decrypting package arguments."; + var failedPackagesMessage = "Failed packages: {0}".FormatWith(failedPackages); + if (config.RegularOutput) + { + this.Log().Warn(failureMessage); + this.Log().Warn(failedPackagesMessage); + } + else + { + this.Log().Debug(failureMessage); + this.Log().Debug(failedPackagesMessage); + } + } } public void PackDryRun(ChocolateyConfiguration config) @@ -1915,38 +1949,19 @@ protected virtual ChocolateyConfiguration SetConfigFromRememberedArguments(Choco return config; } - var packageArgumentsUnencrypted = packageInfo.Arguments.ContainsSafe(" --") && packageInfo.Arguments.ToStringSafe().Length > 4 ? packageInfo.Arguments : NugetEncryptionUtility.DecryptString(packageInfo.Arguments); + var packageArguments = ArgumentsUtility.DecryptPackageArgumentsFile( + _fileSystem, + packageInfo.Package.Id, + packageInfo.Package.Version.ToNormalizedStringChecked(), + redactSensitiveArguments: false, + throwOnFailure: true).ToList(); + var packageArgumentsUnencrypted = string.Join(" ", packageArguments); - var sensitiveArgs = true; if (!ArgumentsUtility.SensitiveArgumentsProvided(packageArgumentsUnencrypted)) { - sensitiveArgs = false; this.Log().Debug(ChocolateyLoggers.Verbose, "{0} - Adding remembered arguments for upgrade: {1}".FormatWith(packageInfo.Package.Id, packageArgumentsUnencrypted.EscapeCurlyBraces())); } - var packageArgumentsSplit = packageArgumentsUnencrypted.Split(new[] { " --" }, StringSplitOptions.RemoveEmptyEntries); - var packageArguments = new List(); - foreach (var packageArgument in packageArgumentsSplit.OrEmpty()) - { - var packageArgumentSplit = packageArgument.Split(new[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries); - var optionName = packageArgumentSplit[0].ToStringSafe(); - var optionValue = string.Empty; - if (packageArgumentSplit.Length == 2) - { - optionValue = packageArgumentSplit[1].ToStringSafe().UnquoteSafe(); - if (optionValue.StartsWith("'")) - { - optionValue.UnquoteSafe(); - } - } - - if (sensitiveArgs) - { - this.Log().Debug(ChocolateyLoggers.Verbose, "{0} - Adding '{1}' to upgrade arguments. Values not shown due to detected sensitive arguments".FormatWith(packageInfo.Package.Id, optionName.EscapeCurlyBraces())); - } - packageArguments.Add("--{0}{1}".FormatWith(optionName, string.IsNullOrWhiteSpace(optionValue) ? string.Empty : "=" + optionValue)); - } - var originalConfig = config.DeepCopy(); // this changes config globally ConfigurationOptions.OptionSet.Parse(packageArguments); diff --git a/src/chocolatey/infrastructure.app/utility/ArgumentsUtility.cs b/src/chocolatey/infrastructure.app/utility/ArgumentsUtility.cs index 99ea0a5d75..845e079432 100644 --- a/src/chocolatey/infrastructure.app/utility/ArgumentsUtility.cs +++ b/src/chocolatey/infrastructure.app/utility/ArgumentsUtility.cs @@ -54,6 +54,16 @@ public static bool SensitiveArgumentsProvided(string commandArguments) } public static IEnumerable DecryptPackageArgumentsFile(IFileSystem fileSystem, string id, string version) + { + return DecryptPackageArgumentsFile(fileSystem, id, version, true, false); + } + + public static IEnumerable DecryptPackageArgumentsFile( + IFileSystem fileSystem, + string id, + string version, + bool redactSensitiveArguments, + bool throwOnFailure) { var argumentsPath = fileSystem.CombinePaths(ApplicationParameters.InstallLocation, ".chocolatey", "{0}.{1}".FormatWith(id, version)); var argumentsFile = fileSystem.CombinePaths(argumentsPath, ".arguments"); @@ -79,11 +89,32 @@ public static IEnumerable DecryptPackageArgumentsFile(IFileSystem fileSy yield break; } - // The following code is borrowed from the Chocolatey codebase, should - // be extracted to a separate location in choco executable so we can re-use it. - var packageArgumentsUnencrypted = arguments.Contains(" --") && arguments.ToStringSafe().Length > 4 - ? arguments - : NugetEncryptionUtility.DecryptString(arguments); + string packageArgumentsUnencrypted = string.Empty; + + try + { + // The following code is borrowed from the Chocolatey codebase, should + // be extracted to a separate location in choco executable so we can re-use it. + packageArgumentsUnencrypted = arguments.Contains(" --") && arguments.ToStringSafe().Length > 4 + ? arguments + : NugetEncryptionUtility.DecryptString(arguments); + + } + catch (Exception ex) + { + var firstMessage = "There was an error attempting to decrypt the contents of the .arguments file for version '{0}' of package '{1}'. See log file for more information.".FormatWith(version, id); + var secondMessage = "We failed to decrypt {0}. Error from decryption: {1}".FormatWith(argumentsFile, ex.Message); + + if (throwOnFailure) + { + "chocolatey".Log().Error(firstMessage); + "chocolatey".Log().Error(secondMessage); + throw; + } + + "chocolatey".Log().Debug(firstMessage); + "chocolatey".Log().Debug(secondMessage); + } // Lets do a global check first to see if there are any sensitive arguments // before we filter out the values used later. @@ -102,7 +133,7 @@ public static IEnumerable DecryptPackageArgumentsFile(IFileSystem fileSy var optionName = packageArgumentSplit[0].ToStringSafe(); var optionValue = string.Empty; - if (packageArgumentSplit.Length == 2 && isSensitiveArgument) + if (packageArgumentSplit.Length == 2 && isSensitiveArgument && redactSensitiveArguments) { optionValue = "[REDACTED ARGUMENT]"; }