aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/test.yml34
-rw-r--r--src/libfetchers/git.cc10
-rw-r--r--src/libstore/build/derivation-goal.cc33
-rw-r--r--src/libstore/build/derivation-goal.hh3
-rw-r--r--src/libstore/build/drv-output-substitution-goal.cc95
-rw-r--r--src/libstore/build/drv-output-substitution-goal.hh50
-rw-r--r--src/libstore/build/entry-points.cc8
-rw-r--r--src/libstore/build/local-derivation-goal.cc17
-rw-r--r--src/libstore/build/local-derivation-goal.hh2
-rw-r--r--src/libstore/build/substitution-goal.cc34
-rw-r--r--src/libstore/build/substitution-goal.hh9
-rw-r--r--src/libstore/build/worker.cc34
-rw-r--r--src/libstore/build/worker.hh17
-rw-r--r--src/libstore/ca-specific-schema.sql1
-rw-r--r--src/libstore/local-store.cc41
-rw-r--r--src/libstore/local-store.hh9
-rw-r--r--src/libstore/realisation.cc46
-rw-r--r--src/libstore/realisation.hh8
-rw-r--r--src/libstore/store-api.cc2
-rw-r--r--src/libstore/store-api.hh9
-rw-r--r--tests/ca/signatures.sh39
-rw-r--r--tests/ca/substitute.sh21
-rw-r--r--tests/flakes.sh6
-rw-r--r--tests/local.mk2
-rwxr-xr-xtests/push-to-store.sh6
25 files changed, 449 insertions, 87 deletions
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index bde6106e0..2531a7d35 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -8,52 +8,62 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
- env:
- CACHIX_NAME: nix-ci
+
steps:
- uses: actions/checkout@v2.3.4
with:
fetch-depth: 0
- uses: cachix/install-nix-action@v12
+ - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
- uses: cachix/cachix-action@v8
with:
name: '${{ env.CACHIX_NAME }}'
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
+ authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
#- run: nix flake check
- run: nix-build -A checks.$(if [[ `uname` = Linux ]]; then echo x86_64-linux; else echo x86_64-darwin; fi)
+ check_cachix:
+ name: Cachix secret present for installer tests
+ runs-on: ubuntu-latest
+ outputs:
+ secret: ${{ steps.secret.outputs.secret }}
+ steps:
+ - name: Check for Cachix secret
+ id: secret
+ env:
+ _CACHIX_SECRETS: ${{ secrets.CACHIX_SIGNING_KEY }}${{ secrets.CACHIX_AUTH_TOKEN }}
+ run: echo "::set-output name=secret::${{ env._CACHIX_SECRETS != '' }}"
installer:
- if: github.event_name == 'push'
- needs: tests
+ needs: [tests, check_cachix]
+ if: github.event_name == 'push' && needs.check_cachix.outputs.secret == 'true'
runs-on: ubuntu-latest
- env:
- CACHIX_NAME: nix-ci
outputs:
installerURL: ${{ steps.prepare-installer.outputs.installerURL }}
steps:
- uses: actions/checkout@v2.3.4
with:
fetch-depth: 0
+ - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
- uses: cachix/install-nix-action@v12
- uses: cachix/cachix-action@v8
with:
name: '${{ env.CACHIX_NAME }}'
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
+ authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- id: prepare-installer
run: scripts/prepare-installer-for-github-actions
installer_test:
- if: github.event_name == 'push'
- needs: installer
+ needs: [installer, check_cachix]
+ if: github.event_name == 'push' && needs.check_cachix.outputs.secret == 'true'
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
- env:
- CACHIX_NAME: nix-ci
steps:
- uses: actions/checkout@v2.3.4
+ - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
- uses: cachix/install-nix-action@master
with:
install_url: '${{needs.installer.outputs.installerURL}}'
- install_options: '--tarball-url-prefix https://${{ env.CACHIX_NAME }}.cachix.org/serve'
+ install_options: "--tarball-url-prefix https://${{ env.CACHIX_NAME }}.cachix.org/serve"
- run: nix-instantiate -E 'builtins.currentTime' --eval
- \ No newline at end of file
diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc
index 81c647f89..4f9db1bcd 100644
--- a/src/libfetchers/git.cc
+++ b/src/libfetchers/git.cc
@@ -153,12 +153,14 @@ struct GitInputScheme : InputScheme
std::pair<bool, std::string> getActualUrl(const Input & input) const
{
- // Don't clone file:// URIs (but otherwise treat them the
- // same as remote URIs, i.e. don't use the working tree or
- // HEAD).
+ // file:// URIs are normally not cloned (but otherwise treated the
+ // same as remote URIs, i.e. we don't use the working tree or
+ // HEAD). Exception: If _NIX_FORCE_HTTP is set, or the repo is a bare git
+ // repo, treat as a remote URI to force a clone.
static bool forceHttp = getEnv("_NIX_FORCE_HTTP") == "1"; // for testing
auto url = parseURL(getStrAttr(input.attrs, "url"));
- bool isLocal = url.scheme == "file" && !forceHttp;
+ bool isBareRepository = url.scheme == "file" && !pathExists(url.path + "/.git");
+ bool isLocal = url.scheme == "file" && !forceHttp && !isBareRepository;
return {isLocal, isLocal ? url.path : url.base};
}
diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc
index c29237f5c..d624e58b9 100644
--- a/src/libstore/build/derivation-goal.cc
+++ b/src/libstore/build/derivation-goal.cc
@@ -170,7 +170,7 @@ void DerivationGoal::getDerivation()
return;
}
- addWaitee(upcast_goal(worker.makeSubstitutionGoal(drvPath)));
+ addWaitee(upcast_goal(worker.makePathSubstitutionGoal(drvPath)));
state = &DerivationGoal::loadDerivation;
}
@@ -246,17 +246,22 @@ void DerivationGoal::haveDerivation()
through substitutes. If that doesn't work, we'll build
them. */
if (settings.useSubstitutes && parsedDrv->substitutesAllowed())
- for (auto & [_, status] : initialOutputs) {
+ for (auto & [outputName, status] : initialOutputs) {
if (!status.wanted) continue;
- if (!status.known) {
- warn("do not know how to query for unknown floating content-addressed derivation output yet");
- /* Nothing to wait for; tail call */
- return DerivationGoal::gaveUpOnSubstitution();
- }
- addWaitee(upcast_goal(worker.makeSubstitutionGoal(
- status.known->path,
- buildMode == bmRepair ? Repair : NoRepair,
- getDerivationCA(*drv))));
+ if (!status.known)
+ addWaitee(
+ upcast_goal(
+ worker.makeDrvOutputSubstitutionGoal(
+ DrvOutput{status.outputHash, outputName},
+ buildMode == bmRepair ? Repair : NoRepair
+ )
+ )
+ );
+ else
+ addWaitee(upcast_goal(worker.makePathSubstitutionGoal(
+ status.known->path,
+ buildMode == bmRepair ? Repair : NoRepair,
+ getDerivationCA(*drv))));
}
if (waitees.empty()) /* to prevent hang (no wake-up event) */
@@ -337,7 +342,7 @@ void DerivationGoal::gaveUpOnSubstitution()
if (!settings.useSubstitutes)
throw Error("dependency '%s' of '%s' does not exist, and substitution is disabled",
worker.store.printStorePath(i), worker.store.printStorePath(drvPath));
- addWaitee(upcast_goal(worker.makeSubstitutionGoal(i)));
+ addWaitee(upcast_goal(worker.makePathSubstitutionGoal(i)));
}
if (waitees.empty()) /* to prevent hang (no wake-up event) */
@@ -388,7 +393,7 @@ void DerivationGoal::repairClosure()
worker.store.printStorePath(i), worker.store.printStorePath(drvPath));
auto drvPath2 = outputsToDrv.find(i);
if (drvPath2 == outputsToDrv.end())
- addWaitee(upcast_goal(worker.makeSubstitutionGoal(i, Repair)));
+ addWaitee(upcast_goal(worker.makePathSubstitutionGoal(i, Repair)));
else
addWaitee(worker.makeDerivationGoal(drvPath2->second, StringSet(), bmRepair));
}
@@ -920,6 +925,8 @@ void DerivationGoal::resolvedFinished() {
if (realisation) {
auto newRealisation = *realisation;
newRealisation.id = DrvOutput{initialOutputs.at(wantedOutput).outputHash, wantedOutput};
+ newRealisation.signatures.clear();
+ signRealisation(newRealisation);
worker.store.registerDrvOutput(newRealisation);
} else {
// If we don't have a realisation, then it must mean that something
diff --git a/src/libstore/build/derivation-goal.hh b/src/libstore/build/derivation-goal.hh
index c85bcd84f..704b77caf 100644
--- a/src/libstore/build/derivation-goal.hh
+++ b/src/libstore/build/derivation-goal.hh
@@ -180,6 +180,9 @@ struct DerivationGoal : public Goal
/* Open a log file and a pipe to it. */
Path openLogFile();
+ /* Sign the newly built realisation if the store allows it */
+ virtual void signRealisation(Realisation&) {}
+
/* Close the log file. */
void closeLogFile();
diff --git a/src/libstore/build/drv-output-substitution-goal.cc b/src/libstore/build/drv-output-substitution-goal.cc
new file mode 100644
index 000000000..a5ac4c49d
--- /dev/null
+++ b/src/libstore/build/drv-output-substitution-goal.cc
@@ -0,0 +1,95 @@
+#include "drv-output-substitution-goal.hh"
+#include "worker.hh"
+#include "substitution-goal.hh"
+
+namespace nix {
+
+DrvOutputSubstitutionGoal::DrvOutputSubstitutionGoal(const DrvOutput& id, Worker & worker, RepairFlag repair, std::optional<ContentAddress> ca)
+ : Goal(worker)
+ , id(id)
+{
+ state = &DrvOutputSubstitutionGoal::init;
+ name = fmt("substitution of '%s'", id.to_string());
+ trace("created");
+}
+
+
+void DrvOutputSubstitutionGoal::init()
+{
+ trace("init");
+ subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
+ tryNext();
+}
+
+void DrvOutputSubstitutionGoal::tryNext()
+{
+ trace("Trying next substituter");
+
+ if (subs.size() == 0) {
+ /* None left. Terminate this goal and let someone else deal
+ with it. */
+ debug("drv output '%s' is required, but there is no substituter that can provide it", id.to_string());
+
+ /* Hack: don't indicate failure if there were no substituters.
+ In that case the calling derivation should just do a
+ build. */
+ amDone(substituterFailed ? ecFailed : ecNoSubstituters);
+
+ if (substituterFailed) {
+ worker.failedSubstitutions++;
+ worker.updateProgress();
+ }
+
+ return;
+ }
+
+ auto sub = subs.front();
+ subs.pop_front();
+
+ // FIXME: Make async
+ outputInfo = sub->queryRealisation(id);
+ if (!outputInfo) {
+ tryNext();
+ return;
+ }
+
+ addWaitee(worker.makePathSubstitutionGoal(outputInfo->outPath));
+
+ if (waitees.empty()) outPathValid();
+ else state = &DrvOutputSubstitutionGoal::outPathValid;
+}
+
+void DrvOutputSubstitutionGoal::outPathValid()
+{
+ assert(outputInfo);
+ trace("Output path substituted");
+
+ if (nrFailed > 0) {
+ debug("The output path of the derivation output '%s' could not be substituted", id.to_string());
+ amDone(nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed);
+ return;
+ }
+
+ worker.store.registerDrvOutput(*outputInfo);
+ finished();
+}
+
+void DrvOutputSubstitutionGoal::finished()
+{
+ trace("finished");
+ amDone(ecSuccess);
+}
+
+string DrvOutputSubstitutionGoal::key()
+{
+ /* "a$" ensures substitution goals happen before derivation
+ goals. */
+ return "a$" + std::string(id.to_string());
+}
+
+void DrvOutputSubstitutionGoal::work()
+{
+ (this->*state)();
+}
+
+}
diff --git a/src/libstore/build/drv-output-substitution-goal.hh b/src/libstore/build/drv-output-substitution-goal.hh
new file mode 100644
index 000000000..63ab53d89
--- /dev/null
+++ b/src/libstore/build/drv-output-substitution-goal.hh
@@ -0,0 +1,50 @@
+#pragma once
+
+#include "store-api.hh"
+#include "goal.hh"
+#include "realisation.hh"
+
+namespace nix {
+
+class Worker;
+
+// Substitution of a derivation output.
+// This is done in three steps:
+// 1. Fetch the output info from a substituter
+// 2. Substitute the corresponding output path
+// 3. Register the output info
+class DrvOutputSubstitutionGoal : public Goal {
+private:
+ // The drv output we're trying to substitue
+ DrvOutput id;
+
+ // The realisation corresponding to the given output id.
+ // Will be filled once we can get it.
+ std::optional<Realisation> outputInfo;
+
+ /* The remaining substituters. */
+ std::list<ref<Store>> subs;
+
+ /* Whether a substituter failed. */
+ bool substituterFailed = false;
+
+public:
+ DrvOutputSubstitutionGoal(const DrvOutput& id, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
+
+ typedef void (DrvOutputSubstitutionGoal::*GoalState)();
+ GoalState state;
+
+ void init();
+ void tryNext();
+ void outPathValid();
+ void finished();
+
+ void timedOut(Error && ex) override { abort(); };
+
+ string key() override;
+
+ void work() override;
+
+};
+
+}
diff --git a/src/libstore/build/entry-points.cc b/src/libstore/build/entry-points.cc
index 01a564aba..686364440 100644
--- a/src/libstore/build/entry-points.cc
+++ b/src/libstore/build/entry-points.cc
@@ -15,7 +15,7 @@ void Store::buildPaths(const std::vector<StorePathWithOutputs> & drvPaths, Build
if (path.path.isDerivation())
goals.insert(worker.makeDerivationGoal(path.path, path.outputs, buildMode));
else
- goals.insert(worker.makeSubstitutionGoal(path.path, buildMode == bmRepair ? Repair : NoRepair));
+ goals.insert(worker.makePathSubstitutionGoal(path.path, buildMode == bmRepair ? Repair : NoRepair));
}
worker.run(goals);
@@ -31,7 +31,7 @@ void Store::buildPaths(const std::vector<StorePathWithOutputs> & drvPaths, Build
}
if (i->exitCode != Goal::ecSuccess) {
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get())) failed.insert(i2->drvPath);
- else if (auto i2 = dynamic_cast<SubstitutionGoal *>(i.get())) failed.insert(i2->storePath);
+ else if (auto i2 = dynamic_cast<PathSubstitutionGoal *>(i.get())) failed.insert(i2->storePath);
}
}
@@ -90,7 +90,7 @@ void Store::ensurePath(const StorePath & path)
if (isValidPath(path)) return;
Worker worker(*this);
- GoalPtr goal = worker.makeSubstitutionGoal(path);
+ GoalPtr goal = worker.makePathSubstitutionGoal(path);
Goals goals = {goal};
worker.run(goals);
@@ -108,7 +108,7 @@ void Store::ensurePath(const StorePath & path)
void LocalStore::repairPath(const StorePath & path)
{
Worker worker(*this);
- GoalPtr goal = worker.makeSubstitutionGoal(path, Repair);
+ GoalPtr goal = worker.makePathSubstitutionGoal(path, Repair);
Goals goals = {goal};
worker.run(goals);
diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
index 9c2f1dda6..2966bb565 100644
--- a/src/libstore/build/local-derivation-goal.cc
+++ b/src/libstore/build/local-derivation-goal.cc
@@ -2615,13 +2615,22 @@ void LocalDerivationGoal::registerOutputs()
but it's fine to do in all cases. */
if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
- for (auto& [outputName, newInfo] : infos)
- worker.store.registerDrvOutput(Realisation{
- .id = DrvOutput{initialOutputs.at(outputName).outputHash, outputName},
- .outPath = newInfo.path});
+ for (auto& [outputName, newInfo] : infos) {
+ auto thisRealisation = Realisation{
+ .id = DrvOutput{initialOutputs.at(outputName).outputHash,
+ outputName},
+ .outPath = newInfo.path};
+ signRealisation(thisRealisation);
+ worker.store.registerDrvOutput(thisRealisation);
+ }
}
}
+void LocalDerivationGoal::signRealisation(Realisation & realisation)
+{
+ getLocalStore().signRealisation(realisation);
+}
+
void LocalDerivationGoal::checkOutputs(const std::map<Path, ValidPathInfo> & outputs)
{
diff --git a/src/libstore/build/local-derivation-goal.hh b/src/libstore/build/local-derivation-goal.hh
index 4bbf27a1b..47b818a8b 100644
--- a/src/libstore/build/local-derivation-goal.hh
+++ b/src/libstore/build/local-derivation-goal.hh
@@ -161,6 +161,8 @@ struct LocalDerivationGoal : public DerivationGoal
as valid. */
void registerOutputs() override;
+ void signRealisation(Realisation &) override;
+
/* Check that an output meets the requirements specified by the
'outputChecks' attribute (or the legacy
'{allowed,disallowed}{References,Requisites}' attributes). */
diff --git a/src/libstore/build/substitution-goal.cc b/src/libstore/build/substitution-goal.cc
index c4b0de78d..7b1ac126e 100644
--- a/src/libstore/build/substitution-goal.cc
+++ b/src/libstore/build/substitution-goal.cc
@@ -5,20 +5,20 @@
namespace nix {
-SubstitutionGoal::SubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair, std::optional<ContentAddress> ca)
+PathSubstitutionGoal::PathSubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair, std::optional<ContentAddress> ca)
: Goal(worker)
, storePath(storePath)
, repair(repair)
, ca(ca)
{
- state = &SubstitutionGoal::init;
+ state = &PathSubstitutionGoal::init;
name = fmt("substitution of '%s'", worker.store.printStorePath(this->storePath));
trace("created");
maintainExpectedSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.expectedSubstitutions);
}
-SubstitutionGoal::~SubstitutionGoal()
+PathSubstitutionGoal::~PathSubstitutionGoal()
{
try {
if (thr.joinable()) {
@@ -32,13 +32,13 @@ SubstitutionGoal::~SubstitutionGoal()
}
-void SubstitutionGoal::work()
+void PathSubstitutionGoal::work()
{
(this->*state)();
}
-void SubstitutionGoal::init()
+void PathSubstitutionGoal::init()
{
trace("init");
@@ -59,7 +59,7 @@ void SubstitutionGoal::init()
}
-void SubstitutionGoal::tryNext()
+void PathSubstitutionGoal::tryNext()
{
trace("trying next substituter");
@@ -142,7 +142,7 @@ void SubstitutionGoal::tryNext()
/* Bail out early if this substituter lacks a valid
signature. LocalStore::addToStore() also checks for this, but
only after we've downloaded the path. */
- if (!sub->isTrusted && worker.store.pathInfoIsTrusted(*info))
+ if (!sub->isTrusted && worker.store.pathInfoIsUntrusted(*info))
{
warn("substituter '%s' does not have a valid signature for path '%s'",
sub->getUri(), worker.store.printStorePath(storePath));
@@ -154,16 +154,16 @@ void SubstitutionGoal::tryNext()
paths referenced by this one. */
for (auto & i : info->references)
if (i != storePath) /* ignore self-references */
- addWaitee(worker.makeSubstitutionGoal(i));
+ addWaitee(worker.makePathSubstitutionGoal(i));
if (waitees.empty()) /* to prevent hang (no wake-up event) */
referencesValid();
else
- state = &SubstitutionGoal::referencesValid;
+ state = &PathSubstitutionGoal::referencesValid;
}
-void SubstitutionGoal::referencesValid()
+void PathSubstitutionGoal::referencesValid()
{
trace("all references realised");
@@ -177,12 +177,12 @@ void SubstitutionGoal::referencesValid()
if (i != storePath) /* ignore self-references */
assert(worker.store.isValidPath(i));
- state = &SubstitutionGoal::tryToRun;
+ state = &PathSubstitutionGoal::tryToRun;
worker.wakeUp(shared_from_this());
}
-void SubstitutionGoal::tryToRun()
+void PathSubstitutionGoal::tryToRun()
{
trace("trying to run");
@@ -221,11 +221,11 @@ void SubstitutionGoal::tryToRun()
worker.childStarted(shared_from_this(), {outPipe.readSide.get()}, true, false);
- state = &SubstitutionGoal::finished;
+ state = &PathSubstitutionGoal::finished;
}
-void SubstitutionGoal::finished()
+void PathSubstitutionGoal::finished()
{
trace("substitute finished");
@@ -249,7 +249,7 @@ void SubstitutionGoal::finished()
}
/* Try the next substitute. */
- state = &SubstitutionGoal::tryNext;
+ state = &PathSubstitutionGoal::tryNext;
worker.wakeUp(shared_from_this());
return;
}
@@ -278,12 +278,12 @@ void SubstitutionGoal::finished()
}
-void SubstitutionGoal::handleChildOutput(int fd, const string & data)
+void PathSubstitutionGoal::handleChildOutput(int fd, const string & data)
{
}
-void SubstitutionGoal::handleEOF(int fd)
+void PathSubstitutionGoal::handleEOF(int fd)
{
if (fd == outPipe.readSide.get()) worker.wakeUp(shared_from_this());
}
diff --git a/src/libstore/build/substitution-goal.hh b/src/libstore/build/substitution-goal.hh
index dee2cecbf..3b3cb7e32 100644
--- a/src/libstore/build/substitution-goal.hh
+++ b/src/libstore/build/substitution-goal.hh
@@ -8,7 +8,7 @@ namespace nix {
class Worker;
-struct SubstitutionGoal : public Goal
+struct PathSubstitutionGoal : public Goal
{
/* The store path that should be realised through a substitute. */
StorePath storePath;
@@ -47,14 +47,15 @@ struct SubstitutionGoal : public Goal
std::unique_ptr<MaintainCount<uint64_t>> maintainExpectedSubstitutions,
maintainRunningSubstitutions, maintainExpectedNar, maintainExpectedDownload;
- typedef void (SubstitutionGoal::*GoalState)();
+ typedef void (PathSubstitutionGoal::*GoalState)();
GoalState state;
/* Content address for recomputing store path */
std::optional<ContentAddress> ca;
- SubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
- ~SubstitutionGoal();
+public:
+ PathSubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
+ ~PathSubstitutionGoal();
void timedOut(Error && ex) override { abort(); };
diff --git a/src/libstore/build/worker.cc b/src/libstore/build/worker.cc
index b2223c3b6..616b17e61 100644
--- a/src/libstore/build/worker.cc
+++ b/src/libstore/build/worker.cc
@@ -1,6 +1,7 @@
#include "machines.hh"
#include "worker.hh"
#include "substitution-goal.hh"
+#include "drv-output-substitution-goal.hh"
#include "local-derivation-goal.hh"
#include "hook-instance.hh"
@@ -78,20 +79,32 @@ std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const StorePath
}
-std::shared_ptr<SubstitutionGoal> Worker::makeSubstitutionGoal(const StorePath & path, RepairFlag repair, std::optional<ContentAddress> ca)
+std::shared_ptr<PathSubstitutionGoal> Worker::makePathSubstitutionGoal(const StorePath & path, RepairFlag repair, std::optional<ContentAddress> ca)
{
- std::weak_ptr<SubstitutionGoal> & goal_weak = substitutionGoals[path];
+ std::weak_ptr<PathSubstitutionGoal> & goal_weak = substitutionGoals[path];
auto goal = goal_weak.lock(); // FIXME
if (!goal) {
- goal = std::make_shared<SubstitutionGoal>(path, *this, repair, ca);
+ goal = std::make_shared<PathSubstitutionGoal>(path, *this, repair, ca);
goal_weak = goal;
wakeUp(goal);
}
return goal;
}
-template<typename G>
-static void removeGoal(std::shared_ptr<G> goal, std::map<StorePath, std::weak_ptr<G>> & goalMap)
+std::shared_ptr<DrvOutputSubstitutionGoal> Worker::makeDrvOutputSubstitutionGoal(const DrvOutput& id, RepairFlag repair, std::optional<ContentAddress> ca)
+{
+ std::weak_ptr<DrvOutputSubstitutionGoal> & goal_weak = drvOutputSubstitutionGoals[id];
+ auto goal = goal_weak.lock(); // FIXME
+ if (!goal) {
+ goal = std::make_shared<DrvOutputSubstitutionGoal>(id, *this, repair, ca);
+ goal_weak = goal;
+ wakeUp(goal);
+ }
+ return goal;
+}
+
+template<typename K, typename G>
+static void removeGoal(std::shared_ptr<G> goal, std::map<K, std::weak_ptr<G>> & goalMap)
{
/* !!! inefficient */
for (auto i = goalMap.begin();
@@ -109,8 +122,10 @@ void Worker::removeGoal(GoalPtr goal)
{
if (auto drvGoal = std::dynamic_pointer_cast<DerivationGoal>(goal))
nix::removeGoal(drvGoal, derivationGoals);
- else if (auto subGoal = std::dynamic_pointer_cast<SubstitutionGoal>(goal))
+ else if (auto subGoal = std::dynamic_pointer_cast<PathSubstitutionGoal>(goal))
nix::removeGoal(subGoal, substitutionGoals);
+ else if (auto subGoal = std::dynamic_pointer_cast<DrvOutputSubstitutionGoal>(goal))
+ nix::removeGoal(subGoal, drvOutputSubstitutionGoals);
else
assert(false);
if (topGoals.find(goal) != topGoals.end()) {
@@ -217,7 +232,7 @@ void Worker::run(const Goals & _topGoals)
topGoals.insert(i);
if (auto goal = dynamic_cast<DerivationGoal *>(i.get())) {
topPaths.push_back({goal->drvPath, goal->wantedOutputs});
- } else if (auto goal = dynamic_cast<SubstitutionGoal *>(i.get())) {
+ } else if (auto goal = dynamic_cast<PathSubstitutionGoal *>(i.get())) {
topPaths.push_back({goal->storePath});
}
}
@@ -471,7 +486,10 @@ void Worker::markContentsGood(const StorePath & path)
}
-GoalPtr upcast_goal(std::shared_ptr<SubstitutionGoal> subGoal) {
+GoalPtr upcast_goal(std::shared_ptr<PathSubstitutionGoal> subGoal) {
+ return subGoal;
+}
+GoalPtr upcast_goal(std::shared_ptr<DrvOutputSubstitutionGoal> subGoal) {
return subGoal;
}
diff --git a/src/libstore/build/worker.hh b/src/libstore/build/worker.hh
index 82e711191..918de35f6 100644
--- a/src/libstore/build/worker.hh
+++ b/src/libstore/build/worker.hh
@@ -4,6 +4,7 @@
#include "lock.hh"
#include "store-api.hh"
#include "goal.hh"
+#include "realisation.hh"
#include <future>
#include <thread>
@@ -12,18 +13,20 @@ namespace nix {
/* Forward definition. */
struct DerivationGoal;
-struct SubstitutionGoal;
+struct PathSubstitutionGoal;
+class DrvOutputSubstitutionGoal;
/* Workaround for not being able to declare a something like
- class SubstitutionGoal : public Goal;
+ class PathSubstitutionGoal : public Goal;
even when Goal is a complete type.
This is still a static cast. The purpose of exporting it is to define it in
- a place where `SubstitutionGoal` is concrete, and use it in a place where it
+ a place where `PathSubstitutionGoal` is concrete, and use it in a place where it
is opaque. */
-GoalPtr upcast_goal(std::shared_ptr<SubstitutionGoal> subGoal);
+GoalPtr upcast_goal(std::shared_ptr<PathSubstitutionGoal> subGoal);
+GoalPtr upcast_goal(std::shared_ptr<DrvOutputSubstitutionGoal> subGoal);
typedef std::chrono::time_point<std::chrono::steady_clock> steady_time_point;
@@ -72,7 +75,8 @@ private:
/* Maps used to prevent multiple instantiations of a goal for the
same derivation / path. */
std::map<StorePath, std::weak_ptr<DerivationGoal>> derivationGoals;
- std::map<StorePath, std::weak_ptr<SubstitutionGoal>> substitutionGoals;
+ std::map<StorePath, std::weak_ptr<PathSubstitutionGoal>> substitutionGoals;
+ std::map<DrvOutput, std::weak_ptr<DrvOutputSubstitutionGoal>> drvOutputSubstitutionGoals;
/* Goals waiting for busy paths to be unlocked. */
WeakGoals waitingForAnyGoal;
@@ -146,7 +150,8 @@ public:
const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
/* substitution goal */
- std::shared_ptr<SubstitutionGoal> makeSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
+ std::shared_ptr<PathSubstitutionGoal> makePathSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
+ std::shared_ptr<DrvOutputSubstitutionGoal> makeDrvOutputSubstitutionGoal(const DrvOutput & id, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
/* Remove a dead goal. */
void removeGoal(GoalPtr goal);
diff --git a/src/libstore/ca-specific-schema.sql b/src/libstore/ca-specific-schema.sql
index 93c442826..20ee046a1 100644
--- a/src/libstore/ca-specific-schema.sql
+++ b/src/libstore/ca-specific-schema.sql
@@ -6,6 +6,7 @@ create table if not exists Realisations (
drvPath text not null,
outputName text not null, -- symbolic output id, usually "out"
outputPath integer not null,
+ signatures text, -- space-separated list
primary key (drvPath, outputName),
foreign key (outputPath) references ValidPaths(id) on delete cascade
);
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index 90fb4a4bd..83daa7506 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -310,13 +310,13 @@ LocalStore::LocalStore(const Params & params)
if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
state->stmts->RegisterRealisedOutput.create(state->db,
R"(
- insert or replace into Realisations (drvPath, outputName, outputPath)
- values (?, ?, (select id from ValidPaths where path = ?))
+ insert or replace into Realisations (drvPath, outputName, outputPath, signatures)
+ values (?, ?, (select id from ValidPaths where path = ?), ?)
;
)");
state->stmts->QueryRealisedOutput.create(state->db,
R"(
- select Output.path from Realisations
+ select Output.path, Realisations.signatures from Realisations
inner join ValidPaths as Output on Output.id = Realisations.outputPath
where drvPath = ? and outputName = ?
;
@@ -652,6 +652,14 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat
}
}
+void LocalStore::registerDrvOutput(const Realisation & info, CheckSigsFlag checkSigs)
+{
+ settings.requireExperimentalFeature("ca-derivations");
+ if (checkSigs == NoCheckSigs || !realisationIsUntrusted(info))
+ registerDrvOutput(info);
+ else
+ throw Error("cannot register realisation '%s' because it lacks a valid signature", info.outPath.to_string());
+}
void LocalStore::registerDrvOutput(const Realisation & info)
{
@@ -662,6 +670,7 @@ void LocalStore::registerDrvOutput(const Realisation & info)
(info.id.strHash())
(info.id.outputName)
(printStorePath(info.outPath))
+ (concatStringsSep(" ", info.signatures))
.exec();
});
}
@@ -1102,15 +1111,20 @@ const PublicKeys & LocalStore::getPublicKeys()
return *state->publicKeys;
}
-bool LocalStore::pathInfoIsTrusted(const ValidPathInfo & info)
+bool LocalStore::pathInfoIsUntrusted(const ValidPathInfo & info)
{
return requireSigs && !info.checkSignatures(*this, getPublicKeys());
}
+bool LocalStore::realisationIsUntrusted(const Realisation & realisation)
+{
+ return requireSigs && !realisation.checkSignatures(getPublicKeys());
+}
+
void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
RepairFlag repair, CheckSigsFlag checkSigs)
{
- if (checkSigs && pathInfoIsTrusted(info))
+ if (checkSigs && pathInfoIsUntrusted(info))
throw Error("cannot add path '%s' because it lacks a valid signature", printStorePath(info.path));
addTempRoot(info.path);
@@ -1612,6 +1626,18 @@ void LocalStore::addSignatures(const StorePath & storePath, const StringSet & si
}
+void LocalStore::signRealisation(Realisation & realisation)
+{
+ // FIXME: keep secret keys in memory.
+
+ auto secretKeyFiles = settings.secretKeyFiles;
+
+ for (auto & secretKeyFile : secretKeyFiles.get()) {
+ SecretKey secretKey(readFile(secretKeyFile));
+ realisation.sign(secretKey);
+ }
+}
+
void LocalStore::signPathInfo(ValidPathInfo & info)
{
// FIXME: keep secret keys in memory.
@@ -1649,8 +1675,9 @@ std::optional<const Realisation> LocalStore::queryRealisation(
if (!use.next())
return std::nullopt;
auto outputPath = parseStorePath(use.getStr(0));
- return Ret{
- Realisation{.id = id, .outPath = outputPath}};
+ auto signatures = tokenizeString<StringSet>(use.getStr(1));
+ return Ret{Realisation{
+ .id = id, .outPath = outputPath, .signatures = signatures}};
});
}
} // namespace nix
diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh
index 03bb0218d..26e034a82 100644
--- a/src/libstore/local-store.hh
+++ b/src/libstore/local-store.hh
@@ -136,7 +136,8 @@ public:
void querySubstitutablePathInfos(const StorePathCAMap & paths,
SubstitutablePathInfos & infos) override;
- bool pathInfoIsTrusted(const ValidPathInfo &) override;
+ bool pathInfoIsUntrusted(const ValidPathInfo &) override;
+ bool realisationIsUntrusted(const Realisation & ) override;
void addToStore(const ValidPathInfo & info, Source & source,
RepairFlag repair, CheckSigsFlag checkSigs) override;
@@ -202,6 +203,7 @@ public:
/* Register the store path 'output' as the output named 'outputName' of
derivation 'deriver'. */
void registerDrvOutput(const Realisation & info) override;
+ void registerDrvOutput(const Realisation & info, CheckSigsFlag checkSigs) override;
void cacheDrvOutputMapping(State & state, const uint64_t deriver, const string & outputName, const StorePath & output);
std::optional<const Realisation> queryRealisation(const DrvOutput&) override;
@@ -272,16 +274,19 @@ private:
bool isValidPath_(State & state, const StorePath & path);
void queryReferrers(State & state, const StorePath & path, StorePathSet & referrers);
- /* Add signatures to a ValidPathInfo using the secret keys
+ /* Add signatures to a ValidPathInfo or Realisation using the secret keys
specified by the ‘secret-key-files’ option. */
void signPathInfo(ValidPathInfo & info);
+ void signRealisation(Realisation &);
Path getRealStoreDir() override { return realStoreDir; }
void createUser(const std::string & userName, uid_t userId) override;
friend struct LocalDerivationGoal;
+ friend struct PathSubstitutionGoal;
friend struct SubstitutionGoal;
+ friend struct DerivationGoal;
};
diff --git a/src/libstore/realisation.cc b/src/libstore/realisation.cc
index cd74af4ee..638065547 100644
--- a/src/libstore/realisation.cc
+++ b/src/libstore/realisation.cc
@@ -25,27 +25,69 @@ nlohmann::json Realisation::toJSON() const {
return nlohmann::json{
{"id", id.to_string()},
{"outPath", outPath.to_string()},
+ {"signatures", signatures},
};
}
Realisation Realisation::fromJSON(
const nlohmann::json& json,
const std::string& whence) {
- auto getField = [&](std::string fieldName) -> std::string {
+ auto getOptionalField = [&](std::string fieldName) -> std::optional<std::string> {
auto fieldIterator = json.find(fieldName);
if (fieldIterator == json.end())
+ return std::nullopt;
+ return *fieldIterator;
+ };
+ auto getField = [&](std::string fieldName) -> std::string {
+ if (auto field = getOptionalField(fieldName))
+ return *field;
+ else
throw Error(
"Drv output info file '%1%' is corrupt, missing field %2%",
whence, fieldName);
- return *fieldIterator;
};
+ StringSet signatures;
+ if (auto signaturesIterator = json.find("signatures"); signaturesIterator != json.end())
+ signatures.insert(signaturesIterator->begin(), signaturesIterator->end());
+
return Realisation{
.id = DrvOutput::parse(getField("id")),
.outPath = StorePath(getField("outPath")),
+ .signatures = signatures,
};
}
+std::string Realisation::fingerprint() const
+{
+ auto serialized = toJSON();
+ serialized.erase("signatures");
+ return serialized.dump();
+}
+
+void Realisation::sign(const SecretKey & secretKey)
+{
+ signatures.insert(secretKey.signDetached(fingerprint()));
+}
+
+bool Realisation::checkSignature(const PublicKeys & publicKeys, const std::string & sig) const
+{
+ return verifyDetached(fingerprint(), sig, publicKeys);
+}
+
+size_t Realisation::checkSignatures(const PublicKeys & publicKeys) const
+{
+ // FIXME: Maybe we should return `maxSigs` if the realisation corresponds to
+ // an input-addressed one − because in that case the drv is enough to check
+ // it − but we can't know that here.
+
+ size_t good = 0;
+ for (auto & sig : signatures)
+ if (checkSignature(publicKeys, sig))
+ good++;
+ return good;
+}
+
StorePath RealisedPath::path() const {
return std::visit([](auto && arg) { return arg.getPath(); }, raw);
}
diff --git a/src/libstore/realisation.hh b/src/libstore/realisation.hh
index fc92d3c17..f5049c9e9 100644
--- a/src/libstore/realisation.hh
+++ b/src/libstore/realisation.hh
@@ -3,6 +3,7 @@
#include "path.hh"
#include <nlohmann/json_fwd.hpp>
#include "comparator.hh"
+#include "crypto.hh"
namespace nix {
@@ -25,9 +26,16 @@ struct Realisation {
DrvOutput id;
StorePath outPath;
+ StringSet signatures;
+
nlohmann::json toJSON() const;
static Realisation fromJSON(const nlohmann::json& json, const std::string& whence);
+ std::string fingerprint() const;
+ void sign(const SecretKey &);
+ bool checkSignature(const PublicKeys & publicKeys, const std::string & sig) const;
+ size_t checkSignatures(const PublicKeys & publicKeys) const;
+
StorePath getPath() const { return outPath; }
GENERATE_CMP(Realisation, me->id, me->outPath);
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index 77c310988..5e321cedf 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -798,7 +798,7 @@ std::map<StorePath, StorePath> copyPaths(ref<Store> srcStore, ref<Store> dstStor
auto pathsMap = copyPaths(srcStore, dstStore, storePaths, repair, checkSigs, substitute);
try {
for (auto & realisation : realisations) {
- dstStore->registerDrvOutput(realisation);
+ dstStore->registerDrvOutput(realisation, checkSigs);
}
} catch (MissingExperimentalFeature & e) {
// Don't fail if the remote doesn't support CA derivations is it might
diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh
index 71a28eeb8..5d19e8949 100644
--- a/src/libstore/store-api.hh
+++ b/src/libstore/store-api.hh
@@ -384,7 +384,12 @@ public:
we don't really want to add the dependencies listed in a nar info we
don't trust anyyways.
*/
- virtual bool pathInfoIsTrusted(const ValidPathInfo &)
+ virtual bool pathInfoIsUntrusted(const ValidPathInfo &)
+ {
+ return true;
+ }
+
+ virtual bool realisationIsUntrusted(const Realisation & )
{
return true;
}
@@ -480,6 +485,8 @@ public:
*/
virtual void registerDrvOutput(const Realisation & output)
{ unsupported("registerDrvOutput"); }
+ virtual void registerDrvOutput(const Realisation & output, CheckSigsFlag checkSigs)
+ { return registerDrvOutput(output); }
/* Write a NAR dump of a store path. */
virtual void narFromPath(const StorePath & path, Sink & sink) = 0;
diff --git a/tests/ca/signatures.sh b/tests/ca/signatures.sh
new file mode 100644
index 000000000..4b4e468f7
--- /dev/null
+++ b/tests/ca/signatures.sh
@@ -0,0 +1,39 @@
+source common.sh
+
+# Globally enable the ca derivations experimental flag
+sed -i 's/experimental-features = .*/& ca-derivations ca-references/' "$NIX_CONF_DIR/nix.conf"
+
+clearStore
+clearCache
+
+nix-store --generate-binary-cache-key cache1.example.org $TEST_ROOT/sk1 $TEST_ROOT/pk1
+pk1=$(cat $TEST_ROOT/pk1)
+
+export REMOTE_STORE_DIR="$TEST_ROOT/remote_store"
+export REMOTE_STORE="file://$REMOTE_STORE_DIR"
+
+ensureCorrectlyCopied () {
+ attrPath="$1"
+ nix build --store "$REMOTE_STORE" --file ./content-addressed.nix "$attrPath"
+}
+
+testOneCopy () {
+ clearStore
+ rm -rf "$REMOTE_STORE_DIR"
+
+ attrPath="$1"
+ nix copy --to $REMOTE_STORE "$attrPath" --file ./content-addressed.nix \
+ --secret-key-files "$TEST_ROOT/sk1"
+
+ ensureCorrectlyCopied "$attrPath"
+
+ # Ensure that we can copy back what we put in the store
+ clearStore
+ nix copy --from $REMOTE_STORE \
+ --file ./content-addressed.nix "$attrPath" \
+ --trusted-public-keys $pk1
+}
+
+for attrPath in rootCA dependentCA transitivelyDependentCA dependentNonCA dependentFixedOutput; do
+ testOneCopy "$attrPath"
+done
diff --git a/tests/ca/substitute.sh b/tests/ca/substitute.sh
new file mode 100644
index 000000000..79a6ef8b1
--- /dev/null
+++ b/tests/ca/substitute.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+# Ensure that binary substitution works properly with ca derivations
+
+source common.sh
+
+sed -i 's/experimental-features .*/& ca-derivations ca-references/' "$NIX_CONF_DIR"/nix.conf
+
+export REMOTE_STORE=file://$TEST_ROOT/binary_cache
+
+buildDrvs () {
+ nix build --file ./content-addressed.nix -L --no-link "$@"
+}
+
+# Populate the remote cache
+buildDrvs --post-build-hook ../push-to-store.sh
+
+# Restart the build on an empty store, ensuring that we don't build
+clearStore
+buildDrvs --substitute --substituters $REMOTE_STORE --no-require-sigs -j0
+
diff --git a/tests/flakes.sh b/tests/flakes.sh
index 25ba2ac43..9747aba7a 100644
--- a/tests/flakes.sh
+++ b/tests/flakes.sh
@@ -25,6 +25,7 @@ templatesDir=$TEST_ROOT/templates
nonFlakeDir=$TEST_ROOT/nonFlake
flakeA=$TEST_ROOT/flakeA
flakeB=$TEST_ROOT/flakeB
+flakeGitBare=$TEST_ROOT/flakeGitBare
for repo in $flake1Dir $flake2Dir $flake3Dir $flake7Dir $templatesDir $nonFlakeDir $flakeA $flakeB; do
rm -rf $repo $repo.tmp
@@ -604,6 +605,11 @@ nix flake update $flake3Dir
[[ $(jq -c .nodes.flake2.inputs.flake1 $flake3Dir/flake.lock) =~ '["foo"]' ]]
[[ $(jq .nodes.foo.locked.url $flake3Dir/flake.lock) =~ flake7 ]]
+# Test git+file with bare repo.
+rm -rf $flakeGitBare
+git clone --bare $flake1Dir $flakeGitBare
+nix build -o $TEST_ROOT/result git+file://$flakeGitBare
+
# Test Mercurial flakes.
rm -rf $flake5Dir
hg init $flake5Dir
diff --git a/tests/local.mk b/tests/local.mk
index 07cfd7a50..9a227bec5 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -41,6 +41,8 @@ nix_tests = \
build.sh \
compute-levels.sh \
ca/build.sh \
+ ca/substitute.sh
+ ca/signatures.sh \
ca/nix-copy.sh
# parallel.sh
diff --git a/tests/push-to-store.sh b/tests/push-to-store.sh
index 6aadb916b..25352c751 100755
--- a/tests/push-to-store.sh
+++ b/tests/push-to-store.sh
@@ -1,4 +1,6 @@
#!/bin/sh
-echo Pushing "$@" to "$REMOTE_STORE"
-printf "%s" "$OUT_PATHS" | xargs -d: nix copy --to "$REMOTE_STORE" --no-require-sigs
+set -x
+
+echo Pushing "$OUT_PATHS" to "$REMOTE_STORE"
+printf "%s" "$DRV_PATH" | xargs nix copy --to "$REMOTE_STORE" --no-require-sigs