diff --git a/doc/manual/rl-next/batch-request-for-untrusted-options.md b/doc/manual/rl-next/batch-request-for-untrusted-options.md new file mode 100644 index 000000000..0b35062cc --- /dev/null +++ b/doc/manual/rl-next/batch-request-for-untrusted-options.md @@ -0,0 +1,88 @@ +--- +synopsis: "Collect Flakes untrusted settings into one prompt" +cls: [2921] +issues: [fj#682] +category: "Improvements" +credits: [isabelroses, raito, horrors] +--- + +When working with Flakes containing untrusted settings, a prompt is shown for each setting, asking whether to vet or approve it. This looks like: + +``` +❯ nix flake lock +warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page) +warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page) +The following settings require your decision: +- allow-dirty = false +- sandbox = false +Do you want to allow configuration settings to be applied? +This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all) +``` + +In Flakes with a large number of settings to approve or reject, this process can become tedious as each option must be handled individually. + +To address this, all untrusted settings are now consolidated into a single prompt: allowing for bulk acceptance permanently or not, rejection, or detailed review. For example: + +### Scrutiny scenario + +```console +❯ nix flake lock +warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page) +warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page) +The following settings require your decision: +- allow-dirty = false +- sandbox = false +Do you want to allow configuration settings to be applied? +This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all) n +warning: you can set 'accept-flake-config' to 'false' to automatically reject configuration options supplied by flakes +Do you want to allow setting 'allow-dirty = false'? (yes for now/Allow always/no for now) y +Do you want to allow setting 'sandbox = false'? (yes for now/Allow always/no for now) n +``` + +### Reject everything scenario + +```console +❯ nix flake lock +warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page) +warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page) +The following settings require your decision: +- allow-dirty = false +- sandbox = false +Do you want to allow configuration settings to be applied? +This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all) N +Rejecting all untrusted nix.conf entries +warning: you can set 'accept-flake-config' to 'false' to automatically reject configuration options supplied by flakes +``` + +### Accept everything scenario + +```console +❯ nix flake lock +warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page) +warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page) +The following settings require your decision: +- allow-dirty = false +- sandbox = false +Do you want to allow configuration settings to be applied? +This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all) y +``` + +### Accept everything PERMANENTLY scenario + +Note that accepting everything permanently will authorize these options for any +further operations. + +The file containing this trust information is usually located in +`~/.local/share/nix/trusted-settings.json` and can be edited manually to revoke +this permission until Lix provides a first-class command for this manipulation. + +```console +❯ nix flake lock +warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page) +warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page) +The following settings require your decision: +- allow-dirty = false +- sandbox = false +Do you want to allow configuration settings to be applied? +This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all) A +``` diff --git a/lix/libexpr/flake/config.cc b/lix/libexpr/flake/config.cc index 6e72e8514..fa32c747a 100644 --- a/lix/libexpr/flake/config.cc +++ b/lix/libexpr/flake/config.cc @@ -1,4 +1,5 @@ #include "lix/libexpr/flake/flake.hh" +#include "lix/libutil/fmt.hh" #include "lix/libutil/logging.hh" #include "lix/libutil/json.hh" #include "lix/libutil/users.hh" @@ -29,19 +30,28 @@ static void writeTrustedList(const TrustedList & trustedList) writeFile(path, JSON(trustedList).dump()); } -static bool askForSetting( +static bool batchAskForSetting( bool & negativeTrustOverride, TrustedList & trustedList, - const std::string & name, - const std::string & valueS) + std::map & untrustedSettings) { - bool trusted = false; + printWarning("The following settings require your decision:"); + for (const auto & [name, valueS] : untrustedSettings) { + // FIXME: filter ANSI escapes, newlines, \r, etc. + logger->cout("- %s = %s", name, valueS); + } - // FIXME: filter ANSI escapes, newlines, \r, etc. - auto reply = logger->ask(fmt("Do you want to allow configuration setting '%s' to be set to '" ANSI_RED "%s" ANSI_NORMAL "'?\nThis may allow the flake to gain root, see the nix.conf manual page (" ANSI_BOLD "y" ANSI_NORMAL "es/" ANSI_BOLD "n" ANSI_NORMAL "o/" ANSI_BOLD "N" ANSI_NORMAL "o to all) ", name, valueS)).value_or('n'); + auto reply = logger + ->ask( + fmt("Do you want to allow configuration settings to be applied?\nThis may allow the " + "flake to gain root, see the nix.conf manual page (" ANSI_BOLD "y" ANSI_NORMAL + "es for now/" ANSI_BOLD "A" ANSI_NORMAL "llow always/" ANSI_BOLD "n" ANSI_NORMAL + "o/" ANSI_BOLD "N" ANSI_NORMAL "o to all) ") + ) + .value_or('n'); if (reply == 'N') { - printTaggedWarning("Rejecting all untrusted nix.conf entries"); + printWarning("Rejecting all untrusted nix.conf entries"); printTaggedWarning( "you can set '%s' to '%b' to automatically reject configuration options supplied by " "flakes", @@ -49,25 +59,59 @@ static bool askForSetting( false ); negativeTrustOverride = true; - } else { - if (std::tolower(reply) == 'y') { - trusted = true; - } else { - printTaggedWarning( - "you can set '%s' to '%b' to automatically reject configuration options supplied " - "by flakes", - "accept-flake-config", - false - ); + return false; + } + + if (reply == 'y' || reply == 'A') { + auto alwaysAllow = reply == 'A'; + for (const auto & [name, valueS] : untrustedSettings) { + if (alwaysAllow) { + trustedList[name][valueS] = true; + } + globalConfig.set(name, valueS); } - if (std::tolower(logger->ask(fmt("do you want to permanently (in %s) mark this value as %s? (y/N) ", trustedListPath(), trusted ? "trusted": "untrusted" )).value_or('n')) == 'y') { - trustedList[name][valueS] = trusted; + if (alwaysAllow) { writeTrustedList(trustedList); } + + return true; + } else { + printTaggedWarning( + "you can set '%s' to '%b' to automatically reject configuration options supplied " + "by flakes", + "accept-flake-config", + false + ); + } + + auto didTrustedListChange = false; + for (const auto & [name, valueS] : untrustedSettings) { + auto individualReply = logger + ->ask( + fmt("Do you want to allow setting '%s = %s'? (" ANSI_BOLD + "y" ANSI_NORMAL "es for now/" ANSI_BOLD "A" ANSI_NORMAL + "llow always/" ANSI_BOLD "n" ANSI_NORMAL "o for now) ", + name, + valueS) + ) + .value_or('n'); + + if (individualReply == 'y' || individualReply == 'A') { + if (individualReply == 'A') { + trustedList[name][valueS] = true; + didTrustedListChange = true; + } + + globalConfig.set(name, valueS); + } } - return trusted; + if (didTrustedListChange) { + writeTrustedList(trustedList); + } + + return false; } void ConfigFile::apply() @@ -77,8 +121,11 @@ void ConfigFile::apply() // Allows to ignore all subsequent settings from this file. bool negativeTrustOverride = false; - for (auto & [name, value] : settings) { + std::map untrustedSettings; + TrustedList trustedList = readTrustedList(); + + for (auto & [name, value] : settings) { auto baseName = name.starts_with("extra-") ? std::string(name, 6) : name; // FIXME: Move into libutil/config.cc. @@ -90,11 +137,12 @@ void ConfigFile::apply() else if (auto* b = std::get_if>(&value)) valueS = b->t ? "true" : "false"; else if (auto ss = std::get_if>(&value)) - valueS = concatStringsSep(" ", *ss); // FIXME: evil + valueS = concatStringsSep(" ", *ss); // FIXME: evil else assert(false); bool trusted = whitelist.count(baseName); + if (!trusted) { switch (nix::fetchSettings.acceptFlakeConfig.get()) { case AcceptFlakeConfig::True: { @@ -102,21 +150,16 @@ void ConfigFile::apply() break; } case AcceptFlakeConfig::Ask: { - auto trustedList = readTrustedList(); auto tlname = get(trustedList, name); if (auto saved = tlname ? get(*tlname, valueS) : nullptr) { trusted = *saved; printInfo("Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name, valueS); } else { - if (negativeTrustOverride) { - trusted = false; - } else { - trusted = askForSetting(negativeTrustOverride, trustedList, name, valueS); - } + untrustedSettings[name] = valueS; } break; } - case nix::AcceptFlakeConfig::False: { + case AcceptFlakeConfig::False: { trusted = false; break; }; @@ -135,6 +178,10 @@ void ConfigFile::apply() ); } } + + if (!untrustedSettings.empty()) { + batchAskForSetting(negativeTrustOverride, trustedList, untrustedSettings); + } } }