forked from lix-project/lix
Experimentally allow forcing nix-daemon
trust; use this to test
We finally test the status quo of remote build trust in a number of ways. We create a new experimental feature on `nix-daemon` to do so. PR #3921, which improves the situation with trustless remote building, will build upon these changes. This code / tests was pull out of there to make this, so everything is easier to review, and in particular we test before and after so the new behavior in that PR is readily apparent from the testsuite diff alone.
This commit is contained in:
parent
3f9589f17e
commit
d41e1bed5e
|
@ -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) {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -28,6 +28,7 @@ enum struct ExperimentalFeature
|
||||||
AutoAllocateUids,
|
AutoAllocateUids,
|
||||||
Cgroups,
|
Cgroups,
|
||||||
DiscardReferences,
|
DiscardReferences,
|
||||||
|
DaemonTrustOverride,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
2
tests/build-remote-trustless-after.sh
Normal file
2
tests/build-remote-trustless-after.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
outPath=$(readlink -f $TEST_ROOT/result)
|
||||||
|
grep 'FOO BAR BAZ' ${remoteDir}/${outPath}
|
29
tests/build-remote-trustless-should-fail-0.sh
Normal file
29
tests/build-remote-trustless-should-fail-0.sh
Normal 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"
|
9
tests/build-remote-trustless-should-pass-0.sh
Normal file
9
tests/build-remote-trustless-should-pass-0.sh
Normal 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
|
9
tests/build-remote-trustless-should-pass-1.sh
Normal file
9
tests/build-remote-trustless-should-pass-1.sh
Normal 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
|
14
tests/build-remote-trustless-should-pass-3.sh
Normal file
14
tests/build-remote-trustless-should-pass-3.sh
Normal 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
|
14
tests/build-remote-trustless.sh
Normal file
14
tests/build-remote-trustless.sh
Normal 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"
|
|
@ -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
3
tests/nix-daemon-untrusting.sh
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
exec nix-daemon --force-untrusted "$@"
|
Loading…
Reference in a new issue