Skip to content

Commit

Permalink
(chocolatey#3503, chocolatey#3513) Better handle argument decryption
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
corbob committed Nov 4, 2024
1 parent 5d0e42d commit 7793d7a
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 34 deletions.
71 changes: 43 additions & 28 deletions src/chocolatey/infrastructure.app/services/NugetService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ChocolateyPackageInformation>();

foreach (var pkg in NugetList.GetPackages(config, _nugetLogger, _fileSystem))
{
var package = pkg; // for lamda access
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<string>();
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);
Expand Down
43 changes: 37 additions & 6 deletions src/chocolatey/infrastructure.app/utility/ArgumentsUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ public static bool SensitiveArgumentsProvided(string commandArguments)
}

public static IEnumerable<string> DecryptPackageArgumentsFile(IFileSystem fileSystem, string id, string version)
{
return DecryptPackageArgumentsFile(fileSystem, id, version, true, false);
}

public static IEnumerable<string> 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");
Expand All @@ -79,11 +89,32 @@ public static IEnumerable<string> 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.
Expand All @@ -102,7 +133,7 @@ public static IEnumerable<string> 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]";
}
Expand Down

0 comments on commit 7793d7a

Please sign in to comment.