1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-25 19:01:16 +02:00

Merge pull request #12658 from obsidiansystems/local-derivation-goal-hide-and-split

Move `RestrictedStore` into its own file+header
This commit is contained in:
mergify[bot] 2025-03-19 10:00:34 +00:00 committed by GitHub
commit d10f9488fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 413 additions and 281 deletions

View file

@ -234,6 +234,7 @@ sources = files(
'realisation.cc', 'realisation.cc',
'remote-fs-accessor.cc', 'remote-fs-accessor.cc',
'remote-store.cc', 'remote-store.cc',
'restricted-store.cc',
's3-binary-cache-store.cc', 's3-binary-cache-store.cc',
'serve-protocol-connection.cc', 'serve-protocol-connection.cc',
'serve-protocol.cc', 'serve-protocol.cc',
@ -305,6 +306,7 @@ headers = [config_h] + files(
'remote-fs-accessor.hh', 'remote-fs-accessor.hh',
'remote-store-connection.hh', 'remote-store-connection.hh',
'remote-store.hh', 'remote-store.hh',
'restricted-store.hh',
's3-binary-cache-store.hh', 's3-binary-cache-store.hh',
's3.hh', 's3.hh',
'ssh-store.hh', 'ssh-store.hh',

View file

@ -0,0 +1,341 @@
#include "restricted-store.hh"
#include "build-result.hh"
#include "callback.hh"
#include "realisation.hh"
namespace nix {
static StorePath pathPartOfReq(const SingleDerivedPath & req)
{
return std::visit(
overloaded{
[&](const SingleDerivedPath::Opaque & bo) { return bo.path; },
[&](const SingleDerivedPath::Built & bfd) { return pathPartOfReq(*bfd.drvPath); },
},
req.raw());
}
static StorePath pathPartOfReq(const DerivedPath & req)
{
return std::visit(
overloaded{
[&](const DerivedPath::Opaque & bo) { return bo.path; },
[&](const DerivedPath::Built & bfd) { return pathPartOfReq(*bfd.drvPath); },
},
req.raw());
}
bool RestrictionContext::isAllowed(const DerivedPath & req)
{
return isAllowed(pathPartOfReq(req));
}
struct RestrictedStoreConfig : virtual LocalFSStoreConfig
{
using LocalFSStoreConfig::LocalFSStoreConfig;
const std::string name() override
{
return "Restricted Store";
}
};
/**
* A wrapper around LocalStore that only allows building/querying of
* paths that are in the input closures of the build or were added via
* recursive Nix calls.
*/
struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual IndirectRootStore, public virtual GcStore
{
ref<LocalStore> next;
RestrictionContext & goal;
RestrictedStore(const Params & params, ref<LocalStore> next, RestrictionContext & goal)
: StoreConfig(params)
, LocalFSStoreConfig(params)
, RestrictedStoreConfig(params)
, Store(params)
, LocalFSStore(params)
, next(next)
, goal(goal)
{
}
Path getRealStoreDir() override
{
return next->realStoreDir;
}
std::string getUri() override
{
return next->getUri();
}
StorePathSet queryAllValidPaths() override;
void queryPathInfoUncached(
const StorePath & path, Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept override;
void queryReferrers(const StorePath & path, StorePathSet & referrers) override;
std::map<std::string, std::optional<StorePath>>
queryPartialDerivationOutputMap(const StorePath & path, Store * evalStore = nullptr) override;
std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override
{
throw Error("queryPathFromHashPart");
}
StorePath addToStore(
std::string_view name,
const SourcePath & srcPath,
ContentAddressMethod method,
HashAlgorithm hashAlgo,
const StorePathSet & references,
PathFilter & filter,
RepairFlag repair) override
{
throw Error("addToStore");
}
void addToStore(
const ValidPathInfo & info,
Source & narSource,
RepairFlag repair = NoRepair,
CheckSigsFlag checkSigs = CheckSigs) override;
StorePath addToStoreFromDump(
Source & dump,
std::string_view name,
FileSerialisationMethod dumpMethod,
ContentAddressMethod hashMethod,
HashAlgorithm hashAlgo,
const StorePathSet & references,
RepairFlag repair) override;
void narFromPath(const StorePath & path, Sink & sink) override;
void ensurePath(const StorePath & path) override;
void registerDrvOutput(const Realisation & info) override;
void queryRealisationUncached(
const DrvOutput & id, Callback<std::shared_ptr<const Realisation>> callback) noexcept override;
void
buildPaths(const std::vector<DerivedPath> & paths, BuildMode buildMode, std::shared_ptr<Store> evalStore) override;
std::vector<KeyedBuildResult> buildPathsWithResults(
const std::vector<DerivedPath> & paths,
BuildMode buildMode = bmNormal,
std::shared_ptr<Store> evalStore = nullptr) override;
BuildResult
buildDerivation(const StorePath & drvPath, const BasicDerivation & drv, BuildMode buildMode = bmNormal) override
{
unsupported("buildDerivation");
}
void addTempRoot(const StorePath & path) override {}
void addIndirectRoot(const Path & path) override {}
Roots findRoots(bool censor) override
{
return Roots();
}
void collectGarbage(const GCOptions & options, GCResults & results) override {}
void addSignatures(const StorePath & storePath, const StringSet & sigs) override
{
unsupported("addSignatures");
}
void queryMissing(
const std::vector<DerivedPath> & targets,
StorePathSet & willBuild,
StorePathSet & willSubstitute,
StorePathSet & unknown,
uint64_t & downloadSize,
uint64_t & narSize) override;
virtual std::optional<std::string> getBuildLogExact(const StorePath & path) override
{
return std::nullopt;
}
virtual void addBuildLog(const StorePath & path, std::string_view log) override
{
unsupported("addBuildLog");
}
std::optional<TrustedFlag> isTrustedClient() override
{
return NotTrusted;
}
};
ref<Store> makeRestrictedStore(const Store::Params & params, ref<LocalStore> next, RestrictionContext & context)
{
return make_ref<RestrictedStore>(params, next, context);
}
StorePathSet RestrictedStore::queryAllValidPaths()
{
StorePathSet paths;
for (auto & p : goal.originalPaths())
paths.insert(p);
for (auto & p : goal.addedPaths)
paths.insert(p);
return paths;
}
void RestrictedStore::queryPathInfoUncached(
const StorePath & path, Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept
{
if (goal.isAllowed(path)) {
try {
/* Censor impure information. */
auto info = std::make_shared<ValidPathInfo>(*next->queryPathInfo(path));
info->deriver.reset();
info->registrationTime = 0;
info->ultimate = false;
info->sigs.clear();
callback(info);
} catch (InvalidPath &) {
callback(nullptr);
}
} else
callback(nullptr);
};
void RestrictedStore::queryReferrers(const StorePath & path, StorePathSet & referrers) {}
std::map<std::string, std::optional<StorePath>>
RestrictedStore::queryPartialDerivationOutputMap(const StorePath & path, Store * evalStore)
{
if (!goal.isAllowed(path))
throw InvalidPath("cannot query output map for unknown path '%s' in recursive Nix", printStorePath(path));
return next->queryPartialDerivationOutputMap(path, evalStore);
}
void RestrictedStore::addToStore(
const ValidPathInfo & info, Source & narSource, RepairFlag repair, CheckSigsFlag checkSigs)
{
next->addToStore(info, narSource, repair, checkSigs);
goal.addDependency(info.path);
}
StorePath RestrictedStore::addToStoreFromDump(
Source & dump,
std::string_view name,
FileSerialisationMethod dumpMethod,
ContentAddressMethod hashMethod,
HashAlgorithm hashAlgo,
const StorePathSet & references,
RepairFlag repair)
{
auto path = next->addToStoreFromDump(dump, name, dumpMethod, hashMethod, hashAlgo, references, repair);
goal.addDependency(path);
return path;
}
void RestrictedStore::narFromPath(const StorePath & path, Sink & sink)
{
if (!goal.isAllowed(path))
throw InvalidPath("cannot dump unknown path '%s' in recursive Nix", printStorePath(path));
LocalFSStore::narFromPath(path, sink);
}
void RestrictedStore::ensurePath(const StorePath & path)
{
if (!goal.isAllowed(path))
throw InvalidPath("cannot substitute unknown path '%s' in recursive Nix", printStorePath(path));
/* Nothing to be done; 'path' must already be valid. */
}
void RestrictedStore::registerDrvOutput(const Realisation & info)
// XXX: This should probably be allowed as a no-op if the realisation
// corresponds to an allowed derivation
{
throw Error("registerDrvOutput");
}
void RestrictedStore::queryRealisationUncached(
const DrvOutput & id, Callback<std::shared_ptr<const Realisation>> callback) noexcept
// XXX: This should probably be allowed if the realisation corresponds to
// an allowed derivation
{
if (!goal.isAllowed(id))
callback(nullptr);
next->queryRealisation(id, std::move(callback));
}
void RestrictedStore::buildPaths(
const std::vector<DerivedPath> & paths, BuildMode buildMode, std::shared_ptr<Store> evalStore)
{
for (auto & result : buildPathsWithResults(paths, buildMode, evalStore))
if (!result.success())
result.rethrow();
}
std::vector<KeyedBuildResult> RestrictedStore::buildPathsWithResults(
const std::vector<DerivedPath> & paths, BuildMode buildMode, std::shared_ptr<Store> evalStore)
{
assert(!evalStore);
if (buildMode != bmNormal)
throw Error("unsupported build mode");
StorePathSet newPaths;
std::set<Realisation> newRealisations;
for (auto & req : paths) {
if (!goal.isAllowed(req))
throw InvalidPath("cannot build '%s' in recursive Nix because path is unknown", req.to_string(*next));
}
auto results = next->buildPathsWithResults(paths, buildMode);
for (auto & result : results) {
for (auto & [outputName, output] : result.builtOutputs) {
newPaths.insert(output.outPath);
newRealisations.insert(output);
}
}
StorePathSet closure;
next->computeFSClosure(newPaths, closure);
for (auto & path : closure)
goal.addDependency(path);
for (auto & real : Realisation::closure(*next, newRealisations))
goal.addedDrvOutputs.insert(real.id);
return results;
}
void RestrictedStore::queryMissing(
const std::vector<DerivedPath> & targets,
StorePathSet & willBuild,
StorePathSet & willSubstitute,
StorePathSet & unknown,
uint64_t & downloadSize,
uint64_t & narSize)
{
/* This is slightly impure since it leaks information to the
client about what paths will be built/substituted or are
already present. Probably not a big deal. */
std::vector<DerivedPath> allowed;
for (auto & req : targets) {
if (goal.isAllowed(req))
allowed.emplace_back(req);
else
unknown.insert(pathPartOfReq(req));
}
next->queryMissing(allowed, willBuild, willSubstitute, unknown, downloadSize, narSize);
}
}

View file

@ -0,0 +1,60 @@
#pragma once
///@file
#include "local-store.hh"
namespace nix {
/**
* A restricted store has a pointer to one of these, which manages the
* restrictions that are in place.
*
* This is a separate data type so the whitelists can be mutated before
* the restricted store is created: put differently, someones we don't
* know whether we will in fact create a restricted store, but we need
* to prepare the whitelists just in case.
*
* It is possible there are other ways to solve this problem. This was
* just the easiest place to begin, when this was extracted from
* `LocalDerivationGoal`.
*/
struct RestrictionContext
{
/**
* Paths that are already allowed to begin with
*/
virtual const StorePathSet & originalPaths() = 0;
/**
* Paths that were added via recursive Nix calls.
*/
StorePathSet addedPaths;
/**
* Realisations that were added via recursive Nix calls.
*/
std::set<DrvOutput> addedDrvOutputs;
/**
* Recursive Nix calls are only allowed to build or realize paths
* in the original input closure or added via a recursive Nix call
* (so e.g. you can't do 'nix-store -r /nix/store/<bla>' where
* /nix/store/<bla> is some arbitrary path in a binary cache).
*/
virtual bool isAllowed(const StorePath &) = 0;
virtual bool isAllowed(const DrvOutput & id) = 0;
bool isAllowed(const DerivedPath & id);
/**
* Add 'path' to the set of paths that may be referenced by the
* outputs, and make it appear in the sandbox.
*/
virtual void addDependency(const StorePath & path) = 0;
};
/**
* Create a shared pointer to a restricted store.
*/
ref<Store> makeRestrictedStore(const Store::Params & params, ref<LocalStore> next, RestrictionContext & context);
}

View file

@ -21,6 +21,7 @@
#include "unix-domain-socket.hh" #include "unix-domain-socket.hh"
#include "posix-fs-canonicalise.hh" #include "posix-fs-canonicalise.hh"
#include "posix-source-accessor.hh" #include "posix-source-accessor.hh"
#include "restricted-store.hh"
#include <regex> #include <regex>
#include <queue> #include <queue>
@ -75,7 +76,7 @@ extern "C" int sandbox_init_with_parameters(const char *profile, uint64_t flags,
namespace nix { namespace nix {
struct LocalDerivationGoal : public DerivationGoal struct LocalDerivationGoal : DerivationGoal, RestrictionContext
{ {
LocalStore & getLocalStore(); LocalStore & getLocalStore();
@ -208,27 +209,16 @@ struct LocalDerivationGoal : public DerivationGoal
*/ */
std::vector<std::thread> daemonWorkerThreads; std::vector<std::thread> daemonWorkerThreads;
/** const StorePathSet & originalPaths() override
* Paths that were added via recursive Nix calls. {
*/ return inputPaths;
StorePathSet addedPaths; }
/** bool isAllowed(const StorePath & path) override
* Realisations that were added via recursive Nix calls.
*/
std::set<DrvOutput> addedDrvOutputs;
/**
* Recursive Nix calls are only allowed to build or realize paths
* in the original input closure or added via a recursive Nix call
* (so e.g. you can't do 'nix-store -r /nix/store/<bla>' where
* /nix/store/<bla> is some arbitrary path in a binary cache).
*/
bool isAllowed(const StorePath & path)
{ {
return inputPaths.count(path) || addedPaths.count(path); return inputPaths.count(path) || addedPaths.count(path);
} }
bool isAllowed(const DrvOutput & id) bool isAllowed(const DrvOutput & id) override
{ {
return addedDrvOutputs.count(id); return addedDrvOutputs.count(id);
} }
@ -287,11 +277,7 @@ struct LocalDerivationGoal : public DerivationGoal
*/ */
void stopDaemon(); void stopDaemon();
/** void addDependency(const StorePath & path) override;
* Add 'path' to the set of paths that may be referenced by the
* outputs, and make it appear in the sandbox.
*/
void addDependency(const StorePath & path);
/** /**
* Make a file owned by the builder. * Make a file owned by the builder.
@ -1618,263 +1604,6 @@ void LocalDerivationGoal::writeStructuredAttrs()
} }
static StorePath pathPartOfReq(const SingleDerivedPath & req)
{
return std::visit(overloaded {
[&](const SingleDerivedPath::Opaque & bo) {
return bo.path;
},
[&](const SingleDerivedPath::Built & bfd) {
return pathPartOfReq(*bfd.drvPath);
},
}, req.raw());
}
static StorePath pathPartOfReq(const DerivedPath & req)
{
return std::visit(overloaded {
[&](const DerivedPath::Opaque & bo) {
return bo.path;
},
[&](const DerivedPath::Built & bfd) {
return pathPartOfReq(*bfd.drvPath);
},
}, req.raw());
}
bool LocalDerivationGoal::isAllowed(const DerivedPath & req)
{
return this->isAllowed(pathPartOfReq(req));
}
struct RestrictedStoreConfig : virtual LocalFSStoreConfig
{
using LocalFSStoreConfig::LocalFSStoreConfig;
const std::string name() override { return "Restricted Store"; }
};
/* A wrapper around LocalStore that only allows building/querying of
paths that are in the input closures of the build or were added via
recursive Nix calls. */
struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual IndirectRootStore, public virtual GcStore
{
ref<LocalStore> next;
LocalDerivationGoal & goal;
RestrictedStore(const Params & params, ref<LocalStore> next, LocalDerivationGoal & goal)
: StoreConfig(params)
, LocalFSStoreConfig(params)
, RestrictedStoreConfig(params)
, Store(params)
, LocalFSStore(params)
, next(next), goal(goal)
{ }
Path getRealStoreDir() override
{ return next->realStoreDir; }
std::string getUri() override
{ return next->getUri(); }
StorePathSet queryAllValidPaths() override
{
StorePathSet paths;
for (auto & p : goal.inputPaths) paths.insert(p);
for (auto & p : goal.addedPaths) paths.insert(p);
return paths;
}
void queryPathInfoUncached(const StorePath & path,
Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept override
{
if (goal.isAllowed(path)) {
try {
/* Censor impure information. */
auto info = std::make_shared<ValidPathInfo>(*next->queryPathInfo(path));
info->deriver.reset();
info->registrationTime = 0;
info->ultimate = false;
info->sigs.clear();
callback(info);
} catch (InvalidPath &) {
callback(nullptr);
}
} else
callback(nullptr);
};
void queryReferrers(const StorePath & path, StorePathSet & referrers) override
{ }
std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(
const StorePath & path,
Store * evalStore = nullptr) override
{
if (!goal.isAllowed(path))
throw InvalidPath("cannot query output map for unknown path '%s' in recursive Nix", printStorePath(path));
return next->queryPartialDerivationOutputMap(path, evalStore);
}
std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override
{ throw Error("queryPathFromHashPart"); }
StorePath addToStore(
std::string_view name,
const SourcePath & srcPath,
ContentAddressMethod method,
HashAlgorithm hashAlgo,
const StorePathSet & references,
PathFilter & filter,
RepairFlag repair) override
{ throw Error("addToStore"); }
void addToStore(const ValidPathInfo & info, Source & narSource,
RepairFlag repair = NoRepair, CheckSigsFlag checkSigs = CheckSigs) override
{
next->addToStore(info, narSource, repair, checkSigs);
goal.addDependency(info.path);
}
StorePath addToStoreFromDump(
Source & dump,
std::string_view name,
FileSerialisationMethod dumpMethod,
ContentAddressMethod hashMethod,
HashAlgorithm hashAlgo,
const StorePathSet & references,
RepairFlag repair) override
{
auto path = next->addToStoreFromDump(dump, name, dumpMethod, hashMethod, hashAlgo, references, repair);
goal.addDependency(path);
return path;
}
void narFromPath(const StorePath & path, Sink & sink) override
{
if (!goal.isAllowed(path))
throw InvalidPath("cannot dump unknown path '%s' in recursive Nix", printStorePath(path));
LocalFSStore::narFromPath(path, sink);
}
void ensurePath(const StorePath & path) override
{
if (!goal.isAllowed(path))
throw InvalidPath("cannot substitute unknown path '%s' in recursive Nix", printStorePath(path));
/* Nothing to be done; 'path' must already be valid. */
}
void registerDrvOutput(const Realisation & info) override
// XXX: This should probably be allowed as a no-op if the realisation
// corresponds to an allowed derivation
{ throw Error("registerDrvOutput"); }
void queryRealisationUncached(const DrvOutput & id,
Callback<std::shared_ptr<const Realisation>> callback) noexcept override
// XXX: This should probably be allowed if the realisation corresponds to
// an allowed derivation
{
if (!goal.isAllowed(id))
callback(nullptr);
next->queryRealisation(id, std::move(callback));
}
void buildPaths(const std::vector<DerivedPath> & paths, BuildMode buildMode, std::shared_ptr<Store> evalStore) override
{
for (auto & result : buildPathsWithResults(paths, buildMode, evalStore))
if (!result.success())
result.rethrow();
}
std::vector<KeyedBuildResult> buildPathsWithResults(
const std::vector<DerivedPath> & paths,
BuildMode buildMode = bmNormal,
std::shared_ptr<Store> evalStore = nullptr) override
{
assert(!evalStore);
if (buildMode != bmNormal) throw Error("unsupported build mode");
StorePathSet newPaths;
std::set<Realisation> newRealisations;
for (auto & req : paths) {
if (!goal.isAllowed(req))
throw InvalidPath("cannot build '%s' in recursive Nix because path is unknown", req.to_string(*next));
}
auto results = next->buildPathsWithResults(paths, buildMode);
for (auto & result : results) {
for (auto & [outputName, output] : result.builtOutputs) {
newPaths.insert(output.outPath);
newRealisations.insert(output);
}
}
StorePathSet closure;
next->computeFSClosure(newPaths, closure);
for (auto & path : closure)
goal.addDependency(path);
for (auto & real : Realisation::closure(*next, newRealisations))
goal.addedDrvOutputs.insert(real.id);
return results;
}
BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
BuildMode buildMode = bmNormal) override
{ unsupported("buildDerivation"); }
void addTempRoot(const StorePath & path) override
{ }
void addIndirectRoot(const Path & path) override
{ }
Roots findRoots(bool censor) override
{ return Roots(); }
void collectGarbage(const GCOptions & options, GCResults & results) override
{ }
void addSignatures(const StorePath & storePath, const StringSet & sigs) override
{ unsupported("addSignatures"); }
void queryMissing(const std::vector<DerivedPath> & targets,
StorePathSet & willBuild, StorePathSet & willSubstitute, StorePathSet & unknown,
uint64_t & downloadSize, uint64_t & narSize) override
{
/* This is slightly impure since it leaks information to the
client about what paths will be built/substituted or are
already present. Probably not a big deal. */
std::vector<DerivedPath> allowed;
for (auto & req : targets) {
if (goal.isAllowed(req))
allowed.emplace_back(req);
else
unknown.insert(pathPartOfReq(req));
}
next->queryMissing(allowed, willBuild, willSubstitute,
unknown, downloadSize, narSize);
}
virtual std::optional<std::string> getBuildLogExact(const StorePath & path) override
{ return std::nullopt; }
virtual void addBuildLog(const StorePath & path, std::string_view log) override
{ unsupported("addBuildLog"); }
std::optional<TrustedFlag> isTrustedClient() override
{ return NotTrusted; }
};
void LocalDerivationGoal::startDaemon() void LocalDerivationGoal::startDaemon()
{ {
experimentalFeatureSettings.require(Xp::RecursiveNix); experimentalFeatureSettings.require(Xp::RecursiveNix);
@ -1886,7 +1615,7 @@ void LocalDerivationGoal::startDaemon()
params["root"] = *optRoot; params["root"] = *optRoot;
params["state"] = "/no-such-path"; params["state"] = "/no-such-path";
params["log"] = "/no-such-path"; params["log"] = "/no-such-path";
auto store = make_ref<RestrictedStore>(params, auto store = makeRestrictedStore(params,
ref<LocalStore>(std::dynamic_pointer_cast<LocalStore>(worker.store.shared_from_this())), ref<LocalStore>(std::dynamic_pointer_cast<LocalStore>(worker.store.shared_from_this())),
*this); *this);