Merge pull request #3921 from obsidiansystems/trustless-remote-builder-simple

Trustless remote building for input-addressed drvs
This commit is contained in:
John Ericson 2023-05-08 10:43:37 -04:00 committed by GitHub
commit b5d9ef0a4c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 63 additions and 20 deletions

View file

@ -258,6 +258,8 @@ static int main_build_remote(int argc, char * * argv)
connected: connected:
close(5); close(5);
assert(sshStore);
std::cerr << "# accept\n" << storeUri << "\n"; std::cerr << "# accept\n" << storeUri << "\n";
auto inputs = readStrings<PathSet>(source); auto inputs = readStrings<PathSet>(source);
@ -286,23 +288,48 @@ connected:
uploadLock = -1; uploadLock = -1;
auto drv = store->readDerivation(*drvPath); auto drv = store->readDerivation(*drvPath);
std::optional<BuildResult> optResult;
// If we don't know whether we are trusted (e.g. `ssh://`
// stores), we assume we are. This is necessary for backwards
// compat.
bool trustedOrLegacy = ({
std::optional trusted = sshStore->isTrustedClient();
!trusted || *trusted;
});
// See the very large comment in `case wopBuildDerivation:` in
// `src/libstore/daemon.cc` that explains the trust model here.
//
// This condition mirrors that: that code enforces the "rules" outlined there;
// we do the best we can given those "rules".
if (trustedOrLegacy || drv.type().isCA()) {
// Hijack the inputs paths of the derivation to include all
// the paths that come from the `inputDrvs` set. We dont do
// that for the derivations whose `inputDrvs` is empty
// because:
//
// 1. Its 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 {
copyClosure(*store, *sshStore, StorePathSet {*drvPath}, NoRepair, NoCheckSigs, substitute);
auto res = sshStore->buildPathsWithResults({ DerivedPath::Built { *drvPath, OutputsSpec::All {} } });
// One path to build should produce exactly one build result
assert(res.size() == 1);
optResult = std::move(res[0]);
}
auto outputHashes = staticOutputHashes(*store, drv); auto outputHashes = staticOutputHashes(*store, drv);
// Hijack the inputs paths of the derivation to include all the paths
// that come from the `inputDrvs` set.
// We dont do that for the derivations whose `inputDrvs` is empty
// because
// 1. Its 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);
if (!result.success())
throw Error("build of '%s' on '%s' failed: %s", store->printStorePath(*drvPath), storeUri, result.errorMsg);
std::set<Realisation> missingRealisations; std::set<Realisation> missingRealisations;
StorePathSet missingPaths; StorePathSet missingPaths;
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations) && !drv.type().hasKnownOutputPaths()) { if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations) && !drv.type().hasKnownOutputPaths()) {
@ -311,6 +338,8 @@ connected:
auto thisOutputId = DrvOutput{ thisOutputHash, outputName }; auto thisOutputId = DrvOutput{ thisOutputHash, outputName };
if (!store->queryRealisation(thisOutputId)) { if (!store->queryRealisation(thisOutputId)) {
debug("missing output %s", outputName); debug("missing output %s", outputName);
assert(optResult);
auto & result = *optResult;
auto i = result.builtOutputs.find(outputName); auto i = result.builtOutputs.find(outputName);
assert(i != result.builtOutputs.end()); assert(i != result.builtOutputs.end());
auto & newRealisation = i->second; auto & newRealisation = i->second;

View file

@ -17,13 +17,13 @@ nix-build build-hook.nix -A passthru.input2 \
--store "$TEST_ROOT/local" \ --store "$TEST_ROOT/local" \
--option system-features bar --option system-features bar
# Now when we go to build that downstream derivation, Nix will fail # Now when we go to build that downstream derivation, Nix will try to
# because we cannot trustlessly build input-addressed derivations with # copy our already-build `input2` to the remote store. That store object
# `inputDrv` dependencies. # is input-addressed, so this will fail.
file=build-hook.nix file=build-hook.nix
prog=$(readlink -e ./nix-daemon-untrusting.sh) prog=$(readlink -e ./nix-daemon-untrusting.sh)
proto=ssh-ng proto=ssh-ng
expectStderr 1 source build-remote-trustless.sh \ expectStderr 1 source build-remote-trustless.sh \
| grepQuiet "you are not privileged to build input-addressed derivations" | grepQuiet "cannot add path '[^ ]*' because it lacks a signature by a trusted key"

View file

@ -0,0 +1,13 @@
source common.sh
enableFeatures "daemon-trust-override"
restartDaemon
# Remote doesn't trust us
file=build-hook.nix
prog=$(readlink -e ./nix-daemon-untrusting.sh)
proto=ssh-ng
source build-remote-trustless.sh
source build-remote-trustless-after.sh

View file

@ -72,6 +72,7 @@ nix_tests = \
build-remote-content-addressed-floating.sh \ build-remote-content-addressed-floating.sh \
build-remote-trustless-should-pass-0.sh \ build-remote-trustless-should-pass-0.sh \
build-remote-trustless-should-pass-1.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-pass-3.sh \
build-remote-trustless-should-fail-0.sh \ build-remote-trustless-should-fail-0.sh \
nar-access.sh \ nar-access.sh \