forked from lix-project/lix
0abb3ad537
This adds a command 'nix make-content-addressable' that rewrites the specified store paths into content-addressable paths. The advantage of such paths is that 1) they can be imported without signatures; 2) they can enable deduplication in cases where derivation changes do not cause output changes (apart from store path hashes). For example, $ nix make-content-addressable -r nixpkgs.cowsay rewrote '/nix/store/g1g31ah55xdia1jdqabv1imf6mcw0nb1-glibc-2.25-49' to '/nix/store/48jfj7bg78a8n4f2nhg269rgw1936vj4-glibc-2.25-49' ... rewrote '/nix/store/qbi6rzpk0bxjw8lw6azn2mc7ynnn455q-cowsay-3.03+dfsg1-16' to '/nix/store/iq6g2x4q62xp7y7493bibx0qn5w7xz67-cowsay-3.03+dfsg1-16' We can then copy the resulting closure to another store without signatures: $ nix copy --trusted-public-keys '' ---to ~/my-nix /nix/store/iq6g2x4q62xp7y7493bibx0qn5w7xz67-cowsay-3.03+dfsg1-16 In order to support self-references in content-addressable paths, these paths are hashed "modulo" self-references, meaning that self-references are zeroed out during hashing. Somewhat annoyingly, this means that the NAR hash stored in the Nix database is no longer necessarily equal to the output of "nix hash-path"; for content-addressable paths, you need to pass the --modulo flag: $ nix path-info --json /nix/store/iq6g2x4q62xp7y7493bibx0qn5w7xz67-cowsay-3.03+dfsg1-16 | jq -r .[].narHash sha256:0ri611gdilz2c9rsibqhsipbfs9vwcqvs811a52i2bnkhv7w9mgw $ nix hash-path --type sha256 --base32 /nix/store/iq6g2x4q62xp7y7493bibx0qn5w7xz67-cowsay-3.03+dfsg1-16 1ggznh07khq0hz6id09pqws3a8q9pn03ya3c03nwck1kwq8rclzs $ nix hash-path --type sha256 --base32 /nix/store/iq6g2x4q62xp7y7493bibx0qn5w7xz67-cowsay-3.03+dfsg1-16 --modulo iq6g2x4q62xp7y7493bibx0qn5w7xz67 0ri611gdilz2c9rsibqhsipbfs9vwcqvs811a52i2bnkhv7w9mgw
184 lines
5.8 KiB
C++
184 lines
5.8 KiB
C++
#include "command.hh"
|
|
#include "shared.hh"
|
|
#include "store-api.hh"
|
|
#include "sync.hh"
|
|
#include "thread-pool.hh"
|
|
#include "references.hh"
|
|
|
|
#include <atomic>
|
|
|
|
using namespace nix;
|
|
|
|
struct CmdVerify : StorePathsCommand
|
|
{
|
|
bool noContents = false;
|
|
bool noTrust = false;
|
|
Strings substituterUris;
|
|
size_t sigsNeeded = 0;
|
|
|
|
CmdVerify()
|
|
{
|
|
mkFlag(0, "no-contents", "do not verify the contents of each store path", &noContents);
|
|
mkFlag(0, "no-trust", "do not verify whether each store path is trusted", &noTrust);
|
|
mkFlag()
|
|
.longName("substituter")
|
|
.shortName('s')
|
|
.labels({"store-uri"})
|
|
.description("use signatures from specified store")
|
|
.arity(1)
|
|
.handler([&](std::vector<std::string> ss) { substituterUris.push_back(ss[0]); });
|
|
mkIntFlag('n', "sigs-needed", "require that each path has at least N valid signatures", &sigsNeeded);
|
|
}
|
|
|
|
std::string name() override
|
|
{
|
|
return "verify";
|
|
}
|
|
|
|
std::string description() override
|
|
{
|
|
return "verify the integrity of store paths";
|
|
}
|
|
|
|
Examples examples() override
|
|
{
|
|
return {
|
|
Example{
|
|
"To verify the entire Nix store:",
|
|
"nix verify --all"
|
|
},
|
|
Example{
|
|
"To check whether each path in the closure of Firefox has at least 2 signatures:",
|
|
"nix verify -r -n2 --no-contents $(type -p firefox)"
|
|
},
|
|
};
|
|
}
|
|
|
|
void run(ref<Store> store, Paths storePaths) override
|
|
{
|
|
std::vector<ref<Store>> substituters;
|
|
for (auto & s : substituterUris)
|
|
substituters.push_back(openStore(s));
|
|
|
|
auto publicKeys = getDefaultPublicKeys();
|
|
|
|
Activity act(*logger, actVerifyPaths);
|
|
|
|
std::atomic<size_t> done{0};
|
|
std::atomic<size_t> untrusted{0};
|
|
std::atomic<size_t> corrupted{0};
|
|
std::atomic<size_t> failed{0};
|
|
std::atomic<size_t> active{0};
|
|
|
|
auto update = [&]() {
|
|
act.progress(done, storePaths.size(), active, failed);
|
|
};
|
|
|
|
ThreadPool pool;
|
|
|
|
auto doPath = [&](const Path & storePath) {
|
|
try {
|
|
checkInterrupt();
|
|
|
|
Activity act2(*logger, lvlInfo, actUnknown, fmt("checking '%s'", storePath));
|
|
|
|
MaintainCount<std::atomic<size_t>> mcActive(active);
|
|
update();
|
|
|
|
auto info = store->queryPathInfo(storePath);
|
|
|
|
if (!noContents) {
|
|
|
|
std::unique_ptr<AbstractHashSink> hashSink;
|
|
if (info->ca == "")
|
|
hashSink = std::make_unique<HashSink>(info->narHash.type);
|
|
else
|
|
hashSink = std::make_unique<HashModuloSink>(info->narHash.type, storePathToHash(info->path));
|
|
|
|
store->narFromPath(info->path, *hashSink);
|
|
|
|
auto hash = hashSink->finish();
|
|
|
|
if (hash.first != info->narHash) {
|
|
corrupted++;
|
|
act2.result(resCorruptedPath, info->path);
|
|
printError(
|
|
format("path '%s' was modified! expected hash '%s', got '%s'")
|
|
% info->path % info->narHash.to_string() % hash.first.to_string());
|
|
}
|
|
|
|
}
|
|
|
|
if (!noTrust) {
|
|
|
|
bool good = false;
|
|
|
|
if (info->ultimate && !sigsNeeded)
|
|
good = true;
|
|
|
|
else {
|
|
|
|
StringSet sigsSeen;
|
|
size_t actualSigsNeeded = std::max(sigsNeeded, (size_t) 1);
|
|
size_t validSigs = 0;
|
|
|
|
auto doSigs = [&](StringSet sigs) {
|
|
for (auto sig : sigs) {
|
|
if (!sigsSeen.insert(sig).second) continue;
|
|
if (validSigs < ValidPathInfo::maxSigs && info->checkSignature(publicKeys, sig))
|
|
validSigs++;
|
|
}
|
|
};
|
|
|
|
if (info->isContentAddressed(*store)) validSigs = ValidPathInfo::maxSigs;
|
|
|
|
doSigs(info->sigs);
|
|
|
|
for (auto & store2 : substituters) {
|
|
if (validSigs >= actualSigsNeeded) break;
|
|
try {
|
|
auto info2 = store2->queryPathInfo(info->path);
|
|
if (info2->isContentAddressed(*store)) validSigs = ValidPathInfo::maxSigs;
|
|
doSigs(info2->sigs);
|
|
} catch (InvalidPath &) {
|
|
} catch (Error & e) {
|
|
printError(format(ANSI_RED "error:" ANSI_NORMAL " %s") % e.what());
|
|
}
|
|
}
|
|
|
|
if (validSigs >= actualSigsNeeded)
|
|
good = true;
|
|
}
|
|
|
|
if (!good) {
|
|
untrusted++;
|
|
act2.result(resUntrustedPath, info->path);
|
|
printError(format("path '%s' is untrusted") % info->path);
|
|
}
|
|
|
|
}
|
|
|
|
done++;
|
|
|
|
} catch (Error & e) {
|
|
printError(format(ANSI_RED "error:" ANSI_NORMAL " %s") % e.what());
|
|
failed++;
|
|
}
|
|
|
|
update();
|
|
};
|
|
|
|
for (auto & storePath : storePaths)
|
|
pool.enqueue(std::bind(doPath, storePath));
|
|
|
|
pool.process();
|
|
|
|
throw Exit(
|
|
(corrupted ? 1 : 0) |
|
|
(untrusted ? 2 : 0) |
|
|
(failed ? 4 : 0));
|
|
}
|
|
};
|
|
|
|
static RegisterCommand r1(make_ref<CmdVerify>());
|