Merge pull request #8230 from obsidiansystems/daemon-trust-override
Experimentally allow forcing `nix-daemon` trust; use this to test
This commit is contained in:
commit
64ee02890c
|
@ -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