libstore: support searching for roots from an external daemon
This comes in two parts: a `nix store roots-daemon` command that can run as root and list runtime roots, and client logic to find runtime roots for a `LocalStore` by connecting to that daemon. This may be useful with an unprivileged nix daemon, as it would otherwise be unable to find runtime roots from process open files and maps.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
#include "nix/store/local-gc.hh"
|
||||
#include "nix/store/local-store.hh"
|
||||
#include "nix/store/path.hh"
|
||||
#include "nix/util/configuration.hh"
|
||||
#include "nix/util/finally.hh"
|
||||
#include "nix/util/unix-domain-socket.hh"
|
||||
#include "nix/util/signals.hh"
|
||||
@@ -306,9 +307,33 @@ Roots LocalStore::findRoots(bool censor)
|
||||
return roots;
|
||||
}
|
||||
|
||||
static Roots requestRuntimeRoots(const LocalStoreConfig & config, const std::filesystem::path & socketPath)
|
||||
{
|
||||
Roots roots;
|
||||
|
||||
auto socket = connect(socketPath);
|
||||
auto socketSource = FdSource(socket.get());
|
||||
|
||||
while (1) {
|
||||
auto line = socketSource.readLine(true, '\0');
|
||||
if (line == "")
|
||||
break;
|
||||
roots[config.parseStorePath(line)].insert(censored);
|
||||
};
|
||||
|
||||
return roots;
|
||||
}
|
||||
|
||||
void LocalStore::findRuntimeRoots(Roots & roots, bool censor)
|
||||
{
|
||||
auto unchecked = findRuntimeRootsUnchecked(*config);
|
||||
Roots unchecked;
|
||||
|
||||
if (config->useRootsDaemon) {
|
||||
experimentalFeatureSettings.require(Xp::LocalOverlayStore);
|
||||
unchecked = requestRuntimeRoots(*config, config->getRootsSocketPath());
|
||||
} else {
|
||||
unchecked = findRuntimeRootsUnchecked(*config);
|
||||
}
|
||||
|
||||
for (auto & [path, links] : unchecked) {
|
||||
if (!isValidPath(path))
|
||||
|
||||
@@ -133,6 +133,27 @@ public:
|
||||
Xp::LocalOverlayStore,
|
||||
};
|
||||
|
||||
Setting<bool> useRootsDaemon{
|
||||
this,
|
||||
false,
|
||||
"use-roots-daemon",
|
||||
R"(
|
||||
Whether to request garbage collector roots from an external daemon.
|
||||
|
||||
When enabled, the garbage collector connects to a Unix domain socket
|
||||
at [`<state-dir>`](@docroot@/store/types/local-store.md#store-option-state)`/gc-roots-socket/socket` to discover additional roots
|
||||
that should not be collected. This is useful when the Nix daemon runs
|
||||
without root privileges and cannot scan `/proc` for runtime roots.
|
||||
|
||||
The daemon can be started with [`nix store roots-daemon`](@docroot@/command-ref/new-cli/nix3-store-roots-daemon.md).
|
||||
)",
|
||||
{},
|
||||
true,
|
||||
Xp::LocalOverlayStore,
|
||||
};
|
||||
|
||||
std::filesystem::path getRootsSocketPath() const;
|
||||
|
||||
static const std::string name()
|
||||
{
|
||||
return "Local Store";
|
||||
|
||||
@@ -457,6 +457,11 @@ LocalStore::~LocalStore()
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path LocalStoreConfig::getRootsSocketPath() const
|
||||
{
|
||||
return std::filesystem::path(stateDir.get()) / "gc-roots-socket" / "socket";
|
||||
}
|
||||
|
||||
StoreReference LocalStoreConfig::getReference() const
|
||||
{
|
||||
auto params = getQueryParams();
|
||||
|
||||
@@ -113,6 +113,7 @@ nix_sources = [ config_priv_h ] + files(
|
||||
if host_machine.system() != 'windows'
|
||||
nix_sources += files(
|
||||
'unix/daemon.cc',
|
||||
'unix/store-roots-daemon.cc',
|
||||
)
|
||||
endif
|
||||
|
||||
|
||||
@@ -52,8 +52,8 @@ sockets:
|
||||
|
||||
```
|
||||
[Socket]
|
||||
ListenStream=/nix/var/nix/unix/daemon-socket/socket
|
||||
ListenStream=/nix/var/nix/unix/daemon-socket/socket-2
|
||||
ListenStream=/nix/var/nix/daemon-socket/socket
|
||||
ListenStream=/nix/var/nix/daemon-socket/socket-2
|
||||
```
|
||||
|
||||
)""
|
||||
|
||||
66
src/nix/unix/store-roots-daemon.cc
Normal file
66
src/nix/unix/store-roots-daemon.cc
Normal file
@@ -0,0 +1,66 @@
|
||||
#include "nix/cmd/command.hh"
|
||||
#include "nix/cmd/unix-socket-server.hh"
|
||||
#include "nix/store/local-store.hh"
|
||||
#include "nix/store/store-api.hh"
|
||||
#include "nix/store/local-gc.hh"
|
||||
#include "nix/util/file-descriptor.hh"
|
||||
|
||||
#include <thread>
|
||||
|
||||
using namespace nix;
|
||||
|
||||
struct CmdRootsDaemon : StoreConfigCommand
|
||||
{
|
||||
CmdRootsDaemon() {}
|
||||
|
||||
std::string description() override
|
||||
{
|
||||
return "run a daemon that returns garbage collector roots on request";
|
||||
}
|
||||
|
||||
std::string doc() override
|
||||
{
|
||||
return
|
||||
#include "store-roots-daemon.md"
|
||||
;
|
||||
}
|
||||
|
||||
std::optional<ExperimentalFeature> experimentalFeature() override
|
||||
{
|
||||
return Xp::LocalOverlayStore;
|
||||
}
|
||||
|
||||
void run(ref<StoreConfig> storeConfig) override
|
||||
{
|
||||
auto localStoreConfig = dynamic_cast<LocalStoreConfig *>(&*storeConfig);
|
||||
if (!localStoreConfig) {
|
||||
throw UsageError(
|
||||
"Roots daemon only functions with a local store, not '%s'", storeConfig->getHumanReadableURI());
|
||||
}
|
||||
|
||||
auto gcSocketPath = localStoreConfig->getRootsSocketPath();
|
||||
|
||||
unix::serveUnixSocket(
|
||||
{
|
||||
.socketPath = gcSocketPath,
|
||||
.socketMode = 0666,
|
||||
},
|
||||
[&](AutoCloseFD remote, std::function<void()> closeListeners) {
|
||||
std::thread([&, remote = std::move(remote)]() mutable {
|
||||
auto roots = findRuntimeRootsUnchecked(*localStoreConfig);
|
||||
|
||||
FdSink sink(remote.get());
|
||||
|
||||
for (auto & [key, _] : roots) {
|
||||
sink(localStoreConfig->printStorePath(key));
|
||||
sink(std::string_view("\0", 1));
|
||||
}
|
||||
|
||||
sink.flush();
|
||||
remote.close();
|
||||
}).detach();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
static auto rCmdStoreRootsDaemon = registerCommand2<CmdRootsDaemon>({"store", "roots-daemon"});
|
||||
46
src/nix/unix/store-roots-daemon.md
Normal file
46
src/nix/unix/store-roots-daemon.md
Normal file
@@ -0,0 +1,46 @@
|
||||
R""(
|
||||
|
||||
# Examples
|
||||
|
||||
* Run the daemon:
|
||||
|
||||
```console
|
||||
# nix store roots-daemon
|
||||
```
|
||||
|
||||
# Description
|
||||
|
||||
This command runs a daemon that serves garbage collector roots from a Unix domain socket.
|
||||
It is not required in all Nix installations, but is useful when the main Nix daemon
|
||||
is not running as root and therefore cannot find runtime roots by scanning `/proc`.
|
||||
|
||||
When the garbage collector runs with [`use-roots-daemon`](@docroot@/store/types/local-store.md#store-experimental-option-use-roots-daemon)
|
||||
enabled, it connects to this daemon to discover additional roots that should not be collected.
|
||||
|
||||
The daemon listens on [`<state-dir>`](@docroot@/store/types/local-store.md#store-option-state)`/gc-roots-socket/socket` (typically `/nix/var/nix/gc-roots-socket/socket`).
|
||||
|
||||
# Protocol
|
||||
|
||||
The protocol is simple.
|
||||
For each client-initiated Unix socket connection, the server:
|
||||
|
||||
1. Sends zero or more [store paths](@docroot@/store/store-path.md) as NUL-terminated (`\0`) strings.
|
||||
2. Closes the connection.
|
||||
|
||||
Example (with `\0` shown as newlines for clarity):
|
||||
```
|
||||
/nix/store/s66mzxpvicwk07gjbjfw9izjfa797vsw-hello-2.12.1
|
||||
/nix/store/fvpr7x8l3illdnziggvkhdpf6vikg65w-git-2.44.0
|
||||
```
|
||||
|
||||
# Security
|
||||
|
||||
No information is provided as to which processes are opening which store paths.
|
||||
While only the main Nix daemon needs to use this daemon, any user able to talk to the main Nix daemon can already obtain the same information with [`nix-store --gc --print-roots`](@docroot@/command-ref/nix-store/gc.md).
|
||||
|
||||
Therefore, restricting this daemon to only accept the Nix daemon as a client is, while recommended for defense-in-depth reasons, strictly speaking not reducing what information can be extracted versus merely restricting this daemon to accept connections from any [allowed user](@docroot@/command-ref/conf-file.md#conf-allowed-users).
|
||||
|
||||
# Systemd socket activation
|
||||
|
||||
`nix store roots-daemon` supports systemd socket-based activation, [just like `nix-daemon`](@docroot@/command-ref/nix-daemon.md#systemd-socket-activation).
|
||||
)""
|
||||
Reference in New Issue
Block a user