Merge pull request #8230 from obsidiansystems/daemon-trust-override

Experimentally allow forcing `nix-daemon` trust; use this to test
This commit is contained in:
Robert Hensing 2023-04-17 19:43:41 +02:00 committed by GitHub
commit 64ee02890c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 145 additions and 17 deletions

View file

@ -1067,6 +1067,8 @@ void processConnection(
opCount++; opCount++;
debug("performing daemon worker op: %d", op);
try { try {
performOp(tunnelLogger, store, trusted, recursive, clientVersion, from, to, op); performOp(tunnelLogger, store, trusted, recursive, clientVersion, from, to, op);
} catch (Error & e) { } catch (Error & e) {

View file

@ -12,7 +12,7 @@ struct ExperimentalFeatureDetails
std::string_view description; std::string_view description;
}; };
constexpr std::array<ExperimentalFeatureDetails, 11> xpFeatureDetails = {{ constexpr std::array<ExperimentalFeatureDetails, 12> xpFeatureDetails = {{
{ {
.tag = Xp::CaDerivations, .tag = Xp::CaDerivations,
.name = "ca-derivations", .name = "ca-derivations",
@ -189,6 +189,16 @@ constexpr std::array<ExperimentalFeatureDetails, 11> xpFeatureDetails = {{
runtime dependencies. runtime dependencies.
)", )",
}, },
{
.tag = Xp::DaemonTrustOverride,
.name = "daemon-trust-override",
.description = R"(
Allow forcing trusting or not trusting clients with
`nix-daemon`. This is useful for testing, but possibly also
useful for various experiments with `nix-daemon --stdio`
networking.
)",
},
}}; }};
static_assert( static_assert(

View file

@ -28,6 +28,7 @@ enum struct ExperimentalFeature
AutoAllocateUids, AutoAllocateUids,
Cgroups, Cgroups,
DiscardReferences, DiscardReferences,
DaemonTrustOverride,
}; };
/** /**

View file

@ -273,8 +273,12 @@ static std::pair<TrustedFlag, std::string> authPeer(const PeerInfo & peer)
/** /**
* Run a server. The loop opens a socket and accepts new connections from that * Run a server. The loop opens a socket and accepts new connections from that
* socket. * socket.
*
* @param forceTrustClientOpt If present, force trusting or not trusted
* the client. Otherwise, decide based on the authentication settings
* and user credentials (from the unix domain socket).
*/ */
static void daemonLoop() static void daemonLoop(std::optional<TrustedFlag> forceTrustClientOpt)
{ {
if (chdir("/") == -1) if (chdir("/") == -1)
throw SysError("cannot change current directory"); throw SysError("cannot change current directory");
@ -317,9 +321,18 @@ static void daemonLoop()
closeOnExec(remote.get()); closeOnExec(remote.get());
PeerInfo peer = getPeerInfo(remote.get()); PeerInfo peer { .pidKnown = false };
auto [_trusted, user] = authPeer(peer); TrustedFlag trusted;
auto trusted = _trusted; std::string user;
if (forceTrustClientOpt)
trusted = *forceTrustClientOpt;
else {
peer = getPeerInfo(remote.get());
auto [_trusted, _user] = authPeer(peer);
trusted = _trusted;
user = _user;
};
printInfo((std::string) "accepted connection from pid %1%, user %2%" + (trusted ? " (trusted)" : ""), printInfo((std::string) "accepted connection from pid %1%, user %2%" + (trusted ? " (trusted)" : ""),
peer.pidKnown ? std::to_string(peer.pid) : "<unknown>", peer.pidKnown ? std::to_string(peer.pid) : "<unknown>",
@ -410,38 +423,47 @@ static void forwardStdioConnection(RemoteStore & store) {
* Unlike `forwardStdioConnection()` we do process commands ourselves in * Unlike `forwardStdioConnection()` we do process commands ourselves in
* this case, not delegating to another daemon. * this case, not delegating to another daemon.
* *
* @note `Trusted` is unconditionally passed because in this mode we * @param trustClient Whether to trust the client. Forwarded directly to
* blindly trust the standard streams. Limiting access to those is * `processConnection()`.
* explicitly not `nix-daemon`'s responsibility.
*/ */
static void processStdioConnection(ref<Store> store) static void processStdioConnection(ref<Store> store, TrustedFlag trustClient)
{ {
FdSource from(STDIN_FILENO); FdSource from(STDIN_FILENO);
FdSink to(STDOUT_FILENO); FdSink to(STDOUT_FILENO);
processConnection(store, from, to, Trusted, NotRecursive); processConnection(store, from, to, trustClient, NotRecursive);
} }
/** /**
* Entry point shared between the new CLI `nix daemon` and old CLI * Entry point shared between the new CLI `nix daemon` and old CLI
* `nix-daemon`. * `nix-daemon`.
*
* @param forceTrustClientOpt See `daemonLoop()` and the parameter with
* the same name over there for details.
*/ */
static void runDaemon(bool stdio) static void runDaemon(bool stdio, std::optional<TrustedFlag> forceTrustClientOpt)
{ {
if (stdio) { if (stdio) {
auto store = openUncachedStore(); auto store = openUncachedStore();
if (auto remoteStore = store.dynamic_pointer_cast<RemoteStore>()) // If --force-untrusted is passed, we cannot forward the connection and
// must process it ourselves (before delegating to the next store) to
// force untrusting the client.
if (auto remoteStore = store.dynamic_pointer_cast<RemoteStore>(); remoteStore && (!forceTrustClientOpt || *forceTrustClientOpt != NotTrusted))
forwardStdioConnection(*remoteStore); forwardStdioConnection(*remoteStore);
else else
processStdioConnection(store); // `Trusted` is passed in the auto (no override case) because we
// cannot see who is on the other side of a plain pipe. Limiting
// access to those is explicitly not `nix-daemon`'s responsibility.
processStdioConnection(store, forceTrustClientOpt.value_or(Trusted));
} else } else
daemonLoop(); daemonLoop(forceTrustClientOpt);
} }
static int main_nix_daemon(int argc, char * * argv) static int main_nix_daemon(int argc, char * * argv)
{ {
{ {
auto stdio = false; auto stdio = false;
std::optional<TrustedFlag> isTrustedOpt = std::nullopt;
parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) { parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) {
if (*arg == "--daemon") if (*arg == "--daemon")
@ -452,11 +474,20 @@ static int main_nix_daemon(int argc, char * * argv)
printVersion("nix-daemon"); printVersion("nix-daemon");
else if (*arg == "--stdio") else if (*arg == "--stdio")
stdio = true; stdio = true;
else return false; else if (*arg == "--force-trusted") {
experimentalFeatureSettings.require(Xp::DaemonTrustOverride);
isTrustedOpt = Trusted;
} else if (*arg == "--force-untrusted") {
experimentalFeatureSettings.require(Xp::DaemonTrustOverride);
isTrustedOpt = NotTrusted;
} else if (*arg == "--default-trust") {
experimentalFeatureSettings.require(Xp::DaemonTrustOverride);
isTrustedOpt = std::nullopt;
} else return false;
return true; return true;
}); });
runDaemon(stdio); runDaemon(stdio, isTrustedOpt);
return 0; return 0;
} }
@ -482,7 +513,7 @@ struct CmdDaemon : StoreCommand
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
runDaemon(false); runDaemon(false, std::nullopt);
} }
}; };

View file

@ -0,0 +1,2 @@
outPath=$(readlink -f $TEST_ROOT/result)
grep 'FOO BAR BAZ' ${remoteDir}/${outPath}

View file

@ -0,0 +1,29 @@
source common.sh
enableFeatures "daemon-trust-override"
restartDaemon
[[ $busybox =~ busybox ]] || skipTest "no busybox"
unset NIX_STORE_DIR
unset NIX_STATE_DIR
# We first build a dependency of the derivation we eventually want to
# build.
nix-build build-hook.nix -A passthru.input2 \
-o "$TEST_ROOT/input2" \
--arg busybox "$busybox" \
--store "$TEST_ROOT/local" \
--option system-features bar
# Now when we go to build that downstream derivation, Nix will fail
# because we cannot trustlessly build input-addressed derivations with
# `inputDrv` dependencies.
file=build-hook.nix
prog=$(readlink -e ./nix-daemon-untrusting.sh)
proto=ssh-ng
expectStderr 1 source build-remote-trustless.sh \
| grepQuiet "you are not privileged to build input-addressed derivations"

View file

@ -0,0 +1,9 @@
source common.sh
# Remote trusts us
file=build-hook.nix
prog=nix-store
proto=ssh
source build-remote-trustless.sh
source build-remote-trustless-after.sh

View file

@ -0,0 +1,9 @@
source common.sh
# Remote trusts us
file=build-hook.nix
prog=nix-daemon
proto=ssh-ng
source build-remote-trustless.sh
source build-remote-trustless-after.sh

View file

@ -0,0 +1,14 @@
source common.sh
enableFeatures "daemon-trust-override"
restartDaemon
# Remote doesn't trusts us, but this is fine because we are only
# building (fixed) CA derivations.
file=build-hook-ca-fixed.nix
prog=$(readlink -e ./nix-daemon-untrusting.sh)
proto=ssh-ng
source build-remote-trustless.sh
source build-remote-trustless-after.sh

View file

@ -0,0 +1,14 @@
requireSandboxSupport
[[ $busybox =~ busybox ]] || skipTest "no busybox"
unset NIX_STORE_DIR
unset NIX_STATE_DIR
remoteDir=$TEST_ROOT/remote
# Note: ssh{-ng}://localhost bypasses ssh. See tests/build-remote.sh for
# more details.
nix-build $file -o $TEST_ROOT/result --max-jobs 0 \
--arg busybox $busybox \
--store $TEST_ROOT/local \
--builders "$proto://localhost?remote-program=$prog&remote-store=${remoteDir}%3Fsystem-features=foo%20bar%20baz - - 1 1 foo,bar,baz"

View file

@ -70,6 +70,10 @@ nix_tests = \
check-reqs.sh \ check-reqs.sh \
build-remote-content-addressed-fixed.sh \ build-remote-content-addressed-fixed.sh \
build-remote-content-addressed-floating.sh \ build-remote-content-addressed-floating.sh \
build-remote-trustless-should-pass-0.sh \
build-remote-trustless-should-pass-1.sh \
build-remote-trustless-should-pass-3.sh \
build-remote-trustless-should-fail-0.sh \
nar-access.sh \ nar-access.sh \
pure-eval.sh \ pure-eval.sh \
eval.sh \ eval.sh \

3
tests/nix-daemon-untrusting.sh Executable file
View file

@ -0,0 +1,3 @@
#!/bin/sh
exec nix-daemon --force-untrusted "$@"