diff options
-rw-r--r-- | src/build-remote/build-remote.cc | 43 | ||||
-rw-r--r-- | src/libstore/daemon.cc | 2 | ||||
-rw-r--r-- | src/libstore/store-api.cc | 43 | ||||
-rw-r--r-- | src/libstore/store-api.hh | 27 | ||||
-rw-r--r-- | src/libutil/experimental-features.cc | 1 | ||||
-rw-r--r-- | src/libutil/experimental-features.hh | 8 | ||||
-rw-r--r-- | src/nix/daemon.cc | 26 | ||||
-rw-r--r-- | tests/build-remote-trustless-should-fail-0.sh | 11 | ||||
-rw-r--r-- | tests/build-remote-trustless-should-pass-0.sh | 9 | ||||
-rw-r--r-- | tests/build-remote-trustless-should-pass-1.sh | 9 | ||||
-rw-r--r-- | tests/build-remote-trustless-should-pass-2.sh | 9 | ||||
-rw-r--r-- | tests/build-remote-trustless-should-pass-3.sh | 10 | ||||
-rw-r--r-- | tests/build-remote-trustless.sh | 16 | ||||
-rw-r--r-- | tests/init.sh | 2 | ||||
-rw-r--r-- | tests/local.mk | 5 | ||||
-rwxr-xr-x | tests/nix-daemon-untrusting.sh | 3 |
16 files changed, 194 insertions, 30 deletions
diff --git a/src/build-remote/build-remote.cc b/src/build-remote/build-remote.cc index 9d2eacb54..d9e59d865 100644 --- a/src/build-remote/build-remote.cc +++ b/src/build-remote/build-remote.cc @@ -252,6 +252,8 @@ static int main_build_remote(int argc, char * * argv) connected: close(5); + assert(sshStore); + std::cerr << "# accept\n" << storeUri << "\n"; auto inputs = readStrings<PathSet>(source); @@ -272,31 +274,39 @@ connected: auto substitute = settings.buildersUseSubstitutes ? Substitute : NoSubstitute; + auto copyStorePathImpl = sshStore->isTrusting ? copyStorePathAdapter : copyOrBuildStorePath; + { Activity act(*logger, lvlTalkative, actUnknown, fmt("copying dependencies to '%s'", storeUri)); - copyPaths(*store, *sshStore, store->parseStorePathSet(inputs), NoRepair, NoCheckSigs, substitute); + copyPaths(*store, *sshStore, store->parseStorePathSet(inputs), NoRepair, NoCheckSigs, substitute, copyStorePathImpl); } uploadLock = -1; auto drv = store->readDerivation(*drvPath); - auto outputHashes = staticOutputHashes(*store, drv); - - // Hijack the inputs paths of the derivation to include all the paths - // that come from the `inputDrvs` set. - // We don’t do that for the derivations whose `inputDrvs` is empty - // because - // 1. It’s not needed - // 2. Changing the `inputSrcs` set changes the associated output ids, - // which break CA derivations - if (!drv.inputDrvs.empty()) - drv.inputSrcs = store->parseStorePathSet(inputs); - auto result = sshStore->buildDerivation(*drvPath, drv); + std::optional<BuildResult> optResult; + if (sshStore->isTrusting || derivationIsCA(drv.type())) { + // Hijack the inputs paths of the derivation to include all the paths + // that come from the `inputDrvs` set. + // We don’t do that for the derivations whose `inputDrvs` is empty + // because + // 1. It’s not needed + // 2. Changing the `inputSrcs` set changes the associated output ids, + // which break CA derivations + if (!drv.inputDrvs.empty()) + drv.inputSrcs = store->parseStorePathSet(inputs); + optResult = sshStore->buildDerivation(*drvPath, (const BasicDerivation &) drv); + auto & result = *optResult; + if (!result.success()) + throw Error("build of '%s' on '%s' failed: %s", store->printStorePath(*drvPath), storeUri, result.errorMsg); + } else { + copyPaths(*store, *sshStore, StorePathSet {*drvPath}, NoRepair, NoCheckSigs, substitute, copyStorePathImpl); + sshStore->buildPaths({ DerivedPath::Built { *drvPath, {} } }); + } - if (!result.success()) - throw Error("build of '%s' on '%s' failed: %s", store->printStorePath(*drvPath), storeUri, result.errorMsg); + auto outputHashes = staticOutputHashes(*store, drv); std::set<Realisation> missingRealisations; StorePathSet missingPaths; if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && !derivationHasKnownOutputPaths(drv.type())) { @@ -305,6 +315,8 @@ connected: auto thisOutputId = DrvOutput{ thisOutputHash, outputName }; if (!store->queryRealisation(thisOutputId)) { debug("missing output %s", outputName); + assert(optResult); + auto & result = *optResult; assert(result.builtOutputs.count(thisOutputId)); auto newRealisation = result.builtOutputs.at(thisOutputId); missingRealisations.insert(newRealisation); @@ -325,6 +337,7 @@ connected: if (auto localStore = store.dynamic_pointer_cast<LocalStore>()) for (auto & path : missingPaths) localStore->locksHeld.insert(store->printStorePath(path)); /* FIXME: ugly */ + /* No `copyStorePathImpl` because we always trust ourselves. */ copyPaths(*sshStore, *store, missingPaths, NoRepair, NoCheckSigs, NoSubstitute); } // XXX: Should be done as part of `copyPaths` diff --git a/src/libstore/daemon.cc b/src/libstore/daemon.cc index 2ba03f0dd..971adef51 100644 --- a/src/libstore/daemon.cc +++ b/src/libstore/daemon.cc @@ -1014,6 +1014,8 @@ void processConnection( opCount++; + debug("performing daemon worker op: %d", op); + try { performOp(tunnelLogger, store, trusted, recursive, clientVersion, from, to, op); } catch (Error & e) { diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index 86fa6a211..51485bd43 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -923,13 +923,47 @@ void copyStorePath( } +void copyStorePathAdapter(Store & srcStore, Store & dstStore, + const ValidPathInfo & info, RepairFlag repair, CheckSigsFlag checkSigs) +{ + copyStorePath(srcStore, dstStore, info.path, repair, checkSigs); +} + +void copyOrBuildStorePath(Store & srcStore, Store & dstStore, + const ValidPathInfo & info, RepairFlag repair, CheckSigsFlag checkSigs) +{ + auto storePath = info.path; + if (dstStore.isTrusting || info.ca) { + copyStorePath(srcStore, dstStore, storePath, repair, checkSigs); + } else if (info.deriver && dstStore.storeDir == srcStore.storeDir) { + auto drvPath = *info.deriver; + auto outputMap = srcStore.queryDerivationOutputMap(drvPath); + auto p = std::find_if(outputMap.begin(), outputMap.end(), [&](auto & i) { + return i.second == storePath; + }); + // drv file is always CA + srcStore.ensurePath(drvPath); + copyStorePath(srcStore, dstStore, drvPath, repair, checkSigs); + dstStore.buildPaths({ + DerivedPath::Built { + .drvPath = drvPath, + .outputs = p != outputMap.end() ? StringSet { p->first } : StringSet {}, + }, + }); + } else { + dstStore.ensurePath(storePath); + } +} + + std::map<StorePath, StorePath> copyPaths( Store & srcStore, Store & dstStore, const RealisedPath::Set & paths, RepairFlag repair, CheckSigsFlag checkSigs, - SubstituteFlag substitute) + SubstituteFlag substitute, + std::function<void(Store &, Store &, const ValidPathInfo &, RepairFlag, CheckSigsFlag)> copyStorePathImpl) { StorePathSet storePaths; std::set<Realisation> toplevelRealisations; @@ -940,7 +974,7 @@ std::map<StorePath, StorePath> copyPaths( toplevelRealisations.insert(*realisation); } } - auto pathsMap = copyPaths(srcStore, dstStore, storePaths, repair, checkSigs, substitute); + auto pathsMap = copyPaths(srcStore, dstStore, storePaths, repair, checkSigs, substitute, copyStorePathImpl); ThreadPool pool; @@ -983,7 +1017,8 @@ std::map<StorePath, StorePath> copyPaths( const StorePathSet & storePaths, RepairFlag repair, CheckSigsFlag checkSigs, - SubstituteFlag substitute) + SubstituteFlag substitute, + std::function<void(Store &, Store &, const ValidPathInfo &, RepairFlag, CheckSigsFlag)> copyStorePathImpl) { auto valid = dstStore.queryValidPaths(storePaths, substitute); @@ -1083,7 +1118,7 @@ std::map<StorePath, StorePath> copyPaths( MaintainCount<decltype(nrRunning)> mc(nrRunning); showProgress(); try { - copyStorePath(srcStore, dstStore, storePath, repair, checkSigs); + copyStorePathImpl(srcStore, dstStore, *info, repair, checkSigs); } catch (Error &e) { nrFailed++; if (!settings.keepGoing) diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh index e4fb1f1fd..6b2ad4b68 100644 --- a/src/libstore/store-api.hh +++ b/src/libstore/store-api.hh @@ -216,7 +216,9 @@ struct StoreConfig : public Config const Setting<int> pathInfoCacheSize{this, 65536, "path-info-cache-size", "size of the in-memory store path information cache"}; - const Setting<bool> isTrusted{this, false, "trusted", "whether paths from this store can be used as substitutes even when they lack trusted signatures"}; + const Setting<bool> isTrusted{this, false, "trusted", "whether paths from this store can be used as substitutes even when they lack trusted signatures. Compare \"trusting\""}; + + Setting<bool> isTrusting{this, true, "trusting", "whether (we think) paths can be added to this store even when they lack trusted signatures. Compare \"trusted\""}; Setting<int> priority{this, 0, "priority", "priority of this substituter (lower value means higher priority)"}; @@ -800,25 +802,42 @@ void copyStorePath( CheckSigsFlag checkSigs = CheckSigs); +/* copyStorePath wrapped to be used with `copyPaths`. */ +void copyStorePathAdapter(Store & srcStore, Store & dstStore, + const ValidPathInfo & info, RepairFlag repair = NoRepair, CheckSigsFlag checkSigs = CheckSigs); + +/* The more liberal alternative to `copyStorePathAdapter`, useful for remote + stores that do not trust us. */ +void copyOrBuildStorePath(Store & srcStore, Store & dstStore, + const ValidPathInfo & info, RepairFlag repair = NoRepair, CheckSigsFlag checkSigs = CheckSigs); + /* Copy store paths from one store to another. The paths may be copied in parallel. They are copied in a topologically sorted order (i.e. if A is a reference of B, then A is copied before B), but the set of store paths is not automatically closed; use copyClosure() for that. Returns a map of what each path was copied to the dstStore - as. */ + as. + + The `copyStorePathImpl` parameter allows doing something other than just + copying. For example, this is used with the build hook to allow the other + side to build dependencies we don't have permission to copy. This behavior + isn't just the default that way `nix copy` etc. still can be relied upon to + not build anything. */ std::map<StorePath, StorePath> copyPaths( Store & srcStore, Store & dstStore, const RealisedPath::Set &, RepairFlag repair = NoRepair, CheckSigsFlag checkSigs = CheckSigs, - SubstituteFlag substitute = NoSubstitute); + SubstituteFlag substitute = NoSubstitute, + std::function<void(Store &, Store &, const ValidPathInfo &, RepairFlag, CheckSigsFlag)> copyStorePathImpl = copyStorePathAdapter); std::map<StorePath, StorePath> copyPaths( Store & srcStore, Store & dstStore, const StorePathSet & paths, RepairFlag repair = NoRepair, CheckSigsFlag checkSigs = CheckSigs, - SubstituteFlag substitute = NoSubstitute); + SubstituteFlag substitute = NoSubstitute, + std::function<void(Store &, Store &, const ValidPathInfo &, RepairFlag, CheckSigsFlag)> copyStorePathImpl = copyStorePathAdapter); /* Copy the closure of `paths` from `srcStore` to `dstStore`. */ void copyClosure( diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc index b49f47e1d..687857869 100644 --- a/src/libutil/experimental-features.cc +++ b/src/libutil/experimental-features.cc @@ -11,6 +11,7 @@ std::map<ExperimentalFeature, std::string> stringifiedXpFeatures = { { Xp::NixCommand, "nix-command" }, { Xp::RecursiveNix, "recursive-nix" }, { Xp::NoUrlLiterals, "no-url-literals" }, + { Xp::NixTesting, "nix-testing" }, }; const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name) diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh index 291a58e32..6c5f55bd6 100644 --- a/src/libutil/experimental-features.hh +++ b/src/libutil/experimental-features.hh @@ -19,7 +19,13 @@ enum struct ExperimentalFeature Flakes, NixCommand, RecursiveNix, - NoUrlLiterals + NoUrlLiterals, + + /** + * A "permanent" experimental feature for extra features we just + * need for testing. + **/ + NixTesting, }; /** diff --git a/src/nix/daemon.cc b/src/nix/daemon.cc index 940923d3b..b592de5f7 100644 --- a/src/nix/daemon.cc +++ b/src/nix/daemon.cc @@ -265,10 +265,16 @@ static void daemonLoop() } } -static void runDaemon(bool stdio) +static void runDaemon(bool stdio, std::optional<TrustedFlag> isTrustedOpt = {}) { + auto ensureNoTrustedFlag = [&]() { + if (isTrustedOpt) + throw Error("--trust and --no-trust flags are only for use with --stdio when this nix-daemon process is not proxying another"); + }; + if (stdio) { if (auto store = openUncachedStore().dynamic_pointer_cast<RemoteStore>()) { + ensureNoTrustedFlag(); auto conn = store->openConnectionWrapper(); int from = conn->from.fd; int to = conn->to.fd; @@ -302,16 +308,20 @@ static void runDaemon(bool stdio) /* Auth hook is empty because in this mode we blindly trust the standard streams. Limiting access to those is explicitly not `nix-daemon`'s responsibility. */ - processConnection(openUncachedStore(), from, to, Trusted, NotRecursive, [&](Store & _){}); + auto isTrusted = isTrustedOpt.value_or(Trusted); + processConnection(openUncachedStore(), from, to, isTrusted, NotRecursive, [&](Store & _){}); } - } else + } else { + ensureNoTrustedFlag(); daemonLoop(); + } } static int main_nix_daemon(int argc, char * * argv) { { auto stdio = false; + std::optional<TrustedFlag> isTrustedOpt; parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) { if (*arg == "--daemon") @@ -322,11 +332,17 @@ static int main_nix_daemon(int argc, char * * argv) printVersion("nix-daemon"); else if (*arg == "--stdio") stdio = true; - else return false; + else if (*arg == "--trust") { + settings.requireExperimentalFeature(Xp::NixTesting); + isTrustedOpt = Trusted; + } else if (*arg == "--no-trust") { + settings.requireExperimentalFeature(Xp::NixTesting); + isTrustedOpt = NotTrusted; + } else return false; return true; }); - runDaemon(stdio); + runDaemon(stdio, isTrustedOpt); return 0; } diff --git a/tests/build-remote-trustless-should-fail-0.sh b/tests/build-remote-trustless-should-fail-0.sh new file mode 100644 index 000000000..b72c134e7 --- /dev/null +++ b/tests/build-remote-trustless-should-fail-0.sh @@ -0,0 +1,11 @@ +source common.sh + +# We act as if remote trusts us, but it doesn't. This fails since we are +# building input-addressed derivations with `buildDerivation`, which +# depends on trust. +file=build-hook.nix +prog=$(readlink -e ./nix-daemon-untrusting.sh) +proto=ssh-ng +trusting=true + +! source build-remote-trustless.sh diff --git a/tests/build-remote-trustless-should-pass-0.sh b/tests/build-remote-trustless-should-pass-0.sh new file mode 100644 index 000000000..82c3f4520 --- /dev/null +++ b/tests/build-remote-trustless-should-pass-0.sh @@ -0,0 +1,9 @@ +source common.sh + +# Remote trusts us but we pretend it doesn't. +file=build-hook.nix +prog=nix-store +proto=ssh +trusting=false + +source build-remote-trustless.sh diff --git a/tests/build-remote-trustless-should-pass-1.sh b/tests/build-remote-trustless-should-pass-1.sh new file mode 100644 index 000000000..22c304bc2 --- /dev/null +++ b/tests/build-remote-trustless-should-pass-1.sh @@ -0,0 +1,9 @@ +source common.sh + +# Remote trusts us but we pretend it doesn't. +file=build-hook.nix +prog=nix-daemon +proto=ssh-ng +trusting=false + +source build-remote-trustless.sh diff --git a/tests/build-remote-trustless-should-pass-2.sh b/tests/build-remote-trustless-should-pass-2.sh new file mode 100644 index 000000000..941ddca05 --- /dev/null +++ b/tests/build-remote-trustless-should-pass-2.sh @@ -0,0 +1,9 @@ +source common.sh + +# Remote doesn't trust us nor do we think it does +file=build-hook.nix +prog=$(readlink -e ./nix-daemon-untrusting.sh) +proto=ssh-ng +trusting=false + +source build-remote-trustless.sh diff --git a/tests/build-remote-trustless-should-pass-3.sh b/tests/build-remote-trustless-should-pass-3.sh new file mode 100644 index 000000000..0c4e191fc --- /dev/null +++ b/tests/build-remote-trustless-should-pass-3.sh @@ -0,0 +1,10 @@ +source common.sh + +# We act as if remote trusts us, but it doesn't. This is fine because we +# are only building (fixed) CA derivations. +file=build-hook-ca-fixed.nix +prog=$(readlink -e ./nix-daemon-untrusting.sh) +proto=ssh-ng +trusting=true + +source build-remote-trustless.sh diff --git a/tests/build-remote-trustless.sh b/tests/build-remote-trustless.sh new file mode 100644 index 000000000..8260c4dcd --- /dev/null +++ b/tests/build-remote-trustless.sh @@ -0,0 +1,16 @@ +if ! canUseSandbox; then exit; fi +if ! [[ $busybox =~ busybox ]]; then exit; fi + +unset NIX_STORE_DIR +unset NIX_STATE_DIR + +# Note: ssh{-ng}://localhost bypasses ssh. See tests/build-remote.sh for +# more details. +nix build -L -v -f $file -o $TEST_ROOT/result --max-jobs 0 \ + --arg busybox $busybox \ + --store $TEST_ROOT/local \ + --builders "$proto://localhost?remote-program=$prog&trusting=$trusting&remote-store=$TEST_ROOT/remote%3Fsystem-features=foo%20bar%20baz - - 1 1 foo,bar,baz" + +outPath=$(readlink -f $TEST_ROOT/result) + +grep 'FOO BAR BAZ' $TEST_ROOT/${subDir}/local${outPath} diff --git a/tests/init.sh b/tests/init.sh index 3c6d5917d..909b50f63 100644 --- a/tests/init.sh +++ b/tests/init.sh @@ -17,7 +17,7 @@ cat > "$NIX_CONF_DIR"/nix.conf <<EOF build-users-group = keep-derivations = false sandbox = false -experimental-features = nix-command flakes +experimental-features = nix-command flakes nix-testing gc-reserved-space = 0 substituters = flake-registry = $TEST_ROOT/registry.json diff --git a/tests/local.mk b/tests/local.mk index 53a9179a3..7074579c9 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -50,6 +50,11 @@ nix_tests = \ check-reqs.sh \ build-remote-content-addressed-fixed.sh \ build-remote-content-addressed-floating.sh \ + build-remote-trustless-should-pass-0.sh \ + build-remote-trustless-should-pass-1.sh \ + build-remote-trustless-should-pass-2.sh \ + build-remote-trustless-should-pass-3.sh \ + build-remote-trustless-should-fail-0.sh \ nar-access.sh \ pure-eval.sh \ ca/post-hook.sh \ diff --git a/tests/nix-daemon-untrusting.sh b/tests/nix-daemon-untrusting.sh new file mode 100755 index 000000000..a3a420147 --- /dev/null +++ b/tests/nix-daemon-untrusting.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +exec nix-daemon --no-trust "$@" |