2016-03-29 15:29:50 +03:00
|
|
|
#include "command.hh"
|
|
|
|
#include "shared.hh"
|
|
|
|
#include "store-api.hh"
|
|
|
|
#include "sync.hh"
|
|
|
|
#include "thread-pool.hh"
|
2018-03-30 01:56:13 +03:00
|
|
|
#include "references.hh"
|
2016-03-29 15:29:50 +03:00
|
|
|
|
|
|
|
#include <atomic>
|
|
|
|
|
|
|
|
using namespace nix;
|
|
|
|
|
2016-04-14 22:14:29 +03:00
|
|
|
struct CmdVerify : StorePathsCommand
|
2016-03-29 15:29:50 +03:00
|
|
|
{
|
|
|
|
bool noContents = false;
|
2016-04-07 16:14:12 +03:00
|
|
|
bool noTrust = false;
|
2016-03-30 12:18:54 +03:00
|
|
|
Strings substituterUris;
|
2019-10-10 16:03:01 +03:00
|
|
|
size_t sigsNeeded = 0;
|
2016-03-29 15:29:50 +03:00
|
|
|
|
2016-04-14 22:14:29 +03:00
|
|
|
CmdVerify()
|
2016-03-29 15:29:50 +03:00
|
|
|
{
|
2021-01-27 13:06:03 +02:00
|
|
|
addFlag({
|
|
|
|
.longName = "no-contents",
|
|
|
|
.description = "Do not verify the contents of each store path.",
|
|
|
|
.handler = {&noContents, true},
|
|
|
|
});
|
|
|
|
|
|
|
|
addFlag({
|
|
|
|
.longName = "no-trust",
|
|
|
|
.description = "Do not verify whether each store path is trusted.",
|
|
|
|
.handler = {&noTrust, true},
|
|
|
|
});
|
2021-01-08 11:44:55 +02:00
|
|
|
|
2020-05-04 23:40:19 +03:00
|
|
|
addFlag({
|
|
|
|
.longName = "substituter",
|
|
|
|
.shortName = 's',
|
2021-01-13 15:18:04 +02:00
|
|
|
.description = "Use signatures from the specified store.",
|
2020-05-04 23:40:19 +03:00
|
|
|
.labels = {"store-uri"},
|
|
|
|
.handler = {[&](std::string s) { substituterUris.push_back(s); }}
|
|
|
|
});
|
2021-01-08 11:44:55 +02:00
|
|
|
|
|
|
|
addFlag({
|
|
|
|
.longName = "sigs-needed",
|
|
|
|
.shortName = 'n',
|
2022-09-23 20:57:57 +03:00
|
|
|
.description = "Require that each path is signed by at least *n* different keys.",
|
2021-01-08 11:44:55 +02:00
|
|
|
.labels = {"n"},
|
|
|
|
.handler = {&sigsNeeded}
|
|
|
|
});
|
2016-03-29 15:29:50 +03:00
|
|
|
}
|
|
|
|
|
2016-04-14 22:14:29 +03:00
|
|
|
std::string description() override
|
|
|
|
{
|
|
|
|
return "verify the integrity of store paths";
|
|
|
|
}
|
|
|
|
|
2020-12-10 00:45:06 +02:00
|
|
|
std::string doc() override
|
2016-04-21 15:58:32 +03:00
|
|
|
{
|
2020-12-10 00:45:06 +02:00
|
|
|
return
|
|
|
|
#include "verify.md"
|
|
|
|
;
|
2016-04-21 15:58:32 +03:00
|
|
|
}
|
|
|
|
|
2021-09-27 11:53:09 +03:00
|
|
|
void run(ref<Store> store, StorePaths && storePaths) override
|
2016-03-29 15:29:50 +03:00
|
|
|
{
|
2016-03-30 12:18:54 +03:00
|
|
|
std::vector<ref<Store>> substituters;
|
|
|
|
for (auto & s : substituterUris)
|
2016-09-02 13:35:48 +03:00
|
|
|
substituters.push_back(openStore(s));
|
2016-03-30 12:18:54 +03:00
|
|
|
|
2016-03-29 15:29:50 +03:00
|
|
|
auto publicKeys = getDefaultPublicKeys();
|
|
|
|
|
2020-06-19 01:09:22 +03:00
|
|
|
Activity act(*logger, actVerifyPaths);
|
2017-08-16 20:23:46 +03:00
|
|
|
|
2016-04-25 16:26:07 +03:00
|
|
|
std::atomic<size_t> done{0};
|
2016-03-29 15:29:50 +03:00
|
|
|
std::atomic<size_t> untrusted{0};
|
|
|
|
std::atomic<size_t> corrupted{0};
|
|
|
|
std::atomic<size_t> failed{0};
|
2017-08-16 20:23:46 +03:00
|
|
|
std::atomic<size_t> active{0};
|
2016-03-29 15:29:50 +03:00
|
|
|
|
2017-08-16 20:23:46 +03:00
|
|
|
auto update = [&]() {
|
|
|
|
act.progress(done, storePaths.size(), active, failed);
|
|
|
|
};
|
2016-03-29 15:29:50 +03:00
|
|
|
|
|
|
|
ThreadPool pool;
|
|
|
|
|
2023-01-30 16:35:25 +02:00
|
|
|
auto doPath = [&](const StorePath & storePath) {
|
2016-03-29 15:29:50 +03:00
|
|
|
try {
|
2016-03-30 12:18:54 +03:00
|
|
|
checkInterrupt();
|
|
|
|
|
2017-08-16 20:23:46 +03:00
|
|
|
MaintainCount<std::atomic<size_t>> mcActive(active);
|
|
|
|
update();
|
2016-03-29 15:29:50 +03:00
|
|
|
|
2023-01-30 16:35:25 +02:00
|
|
|
auto info = store->queryPathInfo(storePath);
|
2016-03-29 15:29:50 +03:00
|
|
|
|
2020-07-13 21:12:44 +03:00
|
|
|
// Note: info->path can be different from storePath
|
|
|
|
// for binary cache stores when using --all (since we
|
|
|
|
// can't enumerate names efficiently).
|
|
|
|
Activity act2(*logger, lvlInfo, actUnknown, fmt("checking '%s'", store->printStorePath(info->path)));
|
|
|
|
|
2016-03-29 15:29:50 +03:00
|
|
|
if (!noContents) {
|
|
|
|
|
2021-05-26 10:39:29 +03:00
|
|
|
auto hashSink = HashSink(info->narHash.type);
|
2016-03-29 15:29:50 +03:00
|
|
|
|
2021-05-26 10:39:29 +03:00
|
|
|
store->narFromPath(info->path, hashSink);
|
2018-03-30 01:56:13 +03:00
|
|
|
|
2021-05-26 10:39:29 +03:00
|
|
|
auto hash = hashSink.finish();
|
2016-03-29 15:29:50 +03:00
|
|
|
|
2020-08-05 21:42:48 +03:00
|
|
|
if (hash.first != info->narHash) {
|
2017-08-16 20:23:46 +03:00
|
|
|
corrupted++;
|
2020-06-19 01:09:22 +03:00
|
|
|
act2.result(resCorruptedPath, store->printStorePath(info->path));
|
2021-01-21 01:27:36 +02:00
|
|
|
printError("path '%s' was modified! expected hash '%s', got '%s'",
|
|
|
|
store->printStorePath(info->path),
|
2023-10-13 04:48:15 +03:00
|
|
|
info->narHash.to_string(HashFormat::Base32, true),
|
|
|
|
hash.first.to_string(HashFormat::Base32, true));
|
2016-03-29 15:29:50 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-07 16:14:12 +03:00
|
|
|
if (!noTrust) {
|
2016-03-29 15:29:50 +03:00
|
|
|
|
2016-03-30 12:18:54 +03:00
|
|
|
bool good = false;
|
|
|
|
|
2016-04-19 19:50:15 +03:00
|
|
|
if (info->ultimate && !sigsNeeded)
|
2016-03-30 12:18:54 +03:00
|
|
|
good = true;
|
|
|
|
|
2016-04-07 16:14:12 +03:00
|
|
|
else {
|
|
|
|
|
|
|
|
StringSet sigsSeen;
|
2019-10-10 16:03:01 +03:00
|
|
|
size_t actualSigsNeeded = std::max(sigsNeeded, (size_t) 1);
|
2016-04-07 16:14:12 +03:00
|
|
|
size_t validSigs = 0;
|
|
|
|
|
|
|
|
auto doSigs = [&](StringSet sigs) {
|
|
|
|
for (auto sig : sigs) {
|
2019-10-09 16:51:52 +03:00
|
|
|
if (!sigsSeen.insert(sig).second) continue;
|
2019-12-05 20:11:09 +02:00
|
|
|
if (validSigs < ValidPathInfo::maxSigs && info->checkSignature(*store, publicKeys, sig))
|
2016-04-07 16:14:12 +03:00
|
|
|
validSigs++;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-08-03 14:17:11 +03:00
|
|
|
if (info->isContentAddressed(*store)) validSigs = ValidPathInfo::maxSigs;
|
|
|
|
|
2016-04-19 19:50:15 +03:00
|
|
|
doSigs(info->sigs);
|
2016-03-30 12:18:54 +03:00
|
|
|
|
|
|
|
for (auto & store2 : substituters) {
|
2016-04-07 16:14:12 +03:00
|
|
|
if (validSigs >= actualSigsNeeded) break;
|
|
|
|
try {
|
2016-08-03 14:17:11 +03:00
|
|
|
auto info2 = store2->queryPathInfo(info->path);
|
|
|
|
if (info2->isContentAddressed(*store)) validSigs = ValidPathInfo::maxSigs;
|
|
|
|
doSigs(info2->sigs);
|
2016-04-19 19:50:15 +03:00
|
|
|
} catch (InvalidPath &) {
|
2016-04-07 16:14:12 +03:00
|
|
|
} catch (Error & e) {
|
2020-05-01 01:31:47 +03:00
|
|
|
logError(e.info());
|
2016-03-30 12:18:54 +03:00
|
|
|
}
|
|
|
|
}
|
2016-04-07 16:14:12 +03:00
|
|
|
|
|
|
|
if (validSigs >= actualSigsNeeded)
|
|
|
|
good = true;
|
2016-03-30 12:18:54 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!good) {
|
2016-03-29 15:29:50 +03:00
|
|
|
untrusted++;
|
2020-06-19 01:09:22 +03:00
|
|
|
act2.result(resUntrustedPath, store->printStorePath(info->path));
|
2021-01-21 01:27:36 +02:00
|
|
|
printError("path '%s' is untrusted", store->printStorePath(info->path));
|
2016-03-29 15:29:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
done++;
|
|
|
|
|
|
|
|
} catch (Error & e) {
|
2020-05-01 01:31:47 +03:00
|
|
|
logError(e.info());
|
2016-03-29 15:29:50 +03:00
|
|
|
failed++;
|
|
|
|
}
|
2017-08-16 20:23:46 +03:00
|
|
|
|
|
|
|
update();
|
2016-03-29 15:29:50 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
for (auto & storePath : storePaths)
|
2023-01-30 16:35:25 +02:00
|
|
|
pool.enqueue(std::bind(doPath, storePath));
|
2016-03-29 15:29:50 +03:00
|
|
|
|
|
|
|
pool.process();
|
|
|
|
|
|
|
|
throw Exit(
|
|
|
|
(corrupted ? 1 : 0) |
|
|
|
|
(untrusted ? 2 : 0) |
|
|
|
|
(failed ? 4 : 0));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-07-24 21:38:56 +03:00
|
|
|
static auto rCmdVerify = registerCommand2<CmdVerify>({"store", "verify"});
|