Merge pull request #7515 from MatthewCroughan/mc/check-trusted-user
Add Store::isTrustedClient()
This commit is contained in:
commit
81dfc2b012
|
@ -39,3 +39,9 @@
|
||||||
|
|
||||||
* `man nix-store-<operation>` and `man nix-env-<operation>`
|
* `man nix-store-<operation>` and `man nix-env-<operation>`
|
||||||
* `nix-store --help --<operation>` and `nix-env --help --<operation>`.
|
* `nix-store --help --<operation>` and `nix-env --help --<operation>`.
|
||||||
|
|
||||||
|
* Nix when used as a client now checks whether the store (the server) trusts the client.
|
||||||
|
(The store always had to check whether it trusts the client, but now the client is informed of the store's decision.)
|
||||||
|
This is useful for scripting interactions with (non-legacy-ssh) remote Nix stores.
|
||||||
|
|
||||||
|
`nix store ping` and `nix doctor` now display this information.
|
||||||
|
|
|
@ -1415,6 +1415,9 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
|
||||||
|
|
||||||
virtual void addBuildLog(const StorePath & path, std::string_view log) override
|
virtual void addBuildLog(const StorePath & path, std::string_view log) override
|
||||||
{ unsupported("addBuildLog"); }
|
{ unsupported("addBuildLog"); }
|
||||||
|
|
||||||
|
std::optional<TrustedFlag> isTrustedClient() override
|
||||||
|
{ return NotTrusted; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -1467,7 +1470,7 @@ void LocalDerivationGoal::startDaemon()
|
||||||
FdSink to(remote.get());
|
FdSink to(remote.get());
|
||||||
try {
|
try {
|
||||||
daemon::processConnection(store, from, to,
|
daemon::processConnection(store, from, to,
|
||||||
daemon::NotTrusted, daemon::Recursive);
|
NotTrusted, daemon::Recursive);
|
||||||
debug("terminated daemon connection");
|
debug("terminated daemon connection");
|
||||||
} catch (SysError &) {
|
} catch (SysError &) {
|
||||||
ignoreException();
|
ignoreException();
|
||||||
|
|
|
@ -1032,6 +1032,15 @@ void processConnection(
|
||||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 33)
|
if (GET_PROTOCOL_MINOR(clientVersion) >= 33)
|
||||||
to << nixVersion;
|
to << nixVersion;
|
||||||
|
|
||||||
|
if (GET_PROTOCOL_MINOR(clientVersion) >= 35) {
|
||||||
|
// We and the underlying store both need to trust the client for
|
||||||
|
// it to be trusted.
|
||||||
|
auto temp = trusted
|
||||||
|
? store->isTrustedClient()
|
||||||
|
: std::optional { NotTrusted };
|
||||||
|
worker_proto::write(*store, to, temp);
|
||||||
|
}
|
||||||
|
|
||||||
/* Send startup error messages to the client. */
|
/* Send startup error messages to the client. */
|
||||||
tunnelLogger->startWork();
|
tunnelLogger->startWork();
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
namespace nix::daemon {
|
namespace nix::daemon {
|
||||||
|
|
||||||
enum TrustedFlag : bool { NotTrusted = false, Trusted = true };
|
|
||||||
enum RecursiveFlag : bool { NotRecursive = false, Recursive = true };
|
enum RecursiveFlag : bool { NotRecursive = false, Recursive = true };
|
||||||
|
|
||||||
void processConnection(
|
void processConnection(
|
||||||
|
|
|
@ -39,6 +39,14 @@ struct DummyStore : public virtual DummyStoreConfig, public virtual Store
|
||||||
callback(nullptr);
|
callback(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The dummy store is incapable of *not* trusting! :)
|
||||||
|
*/
|
||||||
|
virtual std::optional<TrustedFlag> isTrustedClient() override
|
||||||
|
{
|
||||||
|
return Trusted;
|
||||||
|
}
|
||||||
|
|
||||||
static std::set<std::string> uriSchemes() {
|
static std::set<std::string> uriSchemes() {
|
||||||
return {"dummy"};
|
return {"dummy"};
|
||||||
}
|
}
|
||||||
|
|
|
@ -194,6 +194,18 @@ protected:
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This isn't actually necessary read only. We support "upsert" now, so we
|
||||||
|
* have a notion of authentication via HTTP POST/PUT.
|
||||||
|
*
|
||||||
|
* For now, we conservatively say we don't know.
|
||||||
|
*
|
||||||
|
* \todo try to expose our HTTP authentication status.
|
||||||
|
*/
|
||||||
|
std::optional<TrustedFlag> isTrustedClient() override
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static RegisterStoreImplementation<HttpBinaryCacheStore, HttpBinaryCacheStoreConfig> regHttpBinaryCacheStore;
|
static RegisterStoreImplementation<HttpBinaryCacheStore, HttpBinaryCacheStoreConfig> regHttpBinaryCacheStore;
|
||||||
|
|
|
@ -389,6 +389,15 @@ public:
|
||||||
return conn->remoteVersion;
|
return conn->remoteVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The legacy ssh protocol doesn't support checking for trusted-user.
|
||||||
|
* Try using ssh-ng:// instead if you want to know.
|
||||||
|
*/
|
||||||
|
std::optional<TrustedFlag> isTrustedClient() override
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
void queryRealisationUncached(const DrvOutput &,
|
void queryRealisationUncached(const DrvOutput &,
|
||||||
Callback<std::shared_ptr<const Realisation>> callback) noexcept override
|
Callback<std::shared_ptr<const Realisation>> callback) noexcept override
|
||||||
// TODO: Implement
|
// TODO: Implement
|
||||||
|
|
|
@ -95,6 +95,10 @@ protected:
|
||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<TrustedFlag> isTrustedClient() override
|
||||||
|
{
|
||||||
|
return Trusted;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void LocalBinaryCacheStore::init()
|
void LocalBinaryCacheStore::init()
|
||||||
|
|
|
@ -1685,6 +1685,11 @@ unsigned int LocalStore::getProtocol()
|
||||||
return PROTOCOL_VERSION;
|
return PROTOCOL_VERSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<TrustedFlag> LocalStore::isTrustedClient()
|
||||||
|
{
|
||||||
|
return Trusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#if defined(FS_IOC_SETFLAGS) && defined(FS_IOC_GETFLAGS) && defined(FS_IMMUTABLE_FL)
|
#if defined(FS_IOC_SETFLAGS) && defined(FS_IOC_GETFLAGS) && defined(FS_IMMUTABLE_FL)
|
||||||
|
|
||||||
|
|
|
@ -204,6 +204,8 @@ public:
|
||||||
|
|
||||||
unsigned int getProtocol() override;
|
unsigned int getProtocol() override;
|
||||||
|
|
||||||
|
std::optional<TrustedFlag> isTrustedClient() override;
|
||||||
|
|
||||||
void vacuumDB();
|
void vacuumDB();
|
||||||
|
|
||||||
void repairPath(const StorePath & path) override;
|
void repairPath(const StorePath & path) override;
|
||||||
|
|
|
@ -42,6 +42,40 @@ void write(const Store & store, Sink & out, const StorePath & storePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::optional<TrustedFlag> read(const Store & store, Source & from, Phantom<std::optional<TrustedFlag>> _)
|
||||||
|
{
|
||||||
|
auto temp = readNum<uint8_t>(from);
|
||||||
|
switch (temp) {
|
||||||
|
case 0:
|
||||||
|
return std::nullopt;
|
||||||
|
case 1:
|
||||||
|
return { Trusted };
|
||||||
|
case 2:
|
||||||
|
return { NotTrusted };
|
||||||
|
default:
|
||||||
|
throw Error("Invalid trusted status from remote");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(const Store & store, Sink & out, const std::optional<TrustedFlag> & optTrusted)
|
||||||
|
{
|
||||||
|
if (!optTrusted)
|
||||||
|
out << (uint8_t)0;
|
||||||
|
else {
|
||||||
|
switch (*optTrusted) {
|
||||||
|
case Trusted:
|
||||||
|
out << (uint8_t)1;
|
||||||
|
break;
|
||||||
|
case NotTrusted:
|
||||||
|
out << (uint8_t)2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ContentAddress read(const Store & store, Source & from, Phantom<ContentAddress> _)
|
ContentAddress read(const Store & store, Source & from, Phantom<ContentAddress> _)
|
||||||
{
|
{
|
||||||
return parseContentAddress(readString(from));
|
return parseContentAddress(readString(from));
|
||||||
|
@ -226,6 +260,13 @@ void RemoteStore::initConnection(Connection & conn)
|
||||||
conn.daemonNixVersion = readString(conn.from);
|
conn.daemonNixVersion = readString(conn.from);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (GET_PROTOCOL_MINOR(conn.daemonVersion) >= 35) {
|
||||||
|
conn.remoteTrustsUs = worker_proto::read(*this, conn.from, Phantom<std::optional<TrustedFlag>> {});
|
||||||
|
} else {
|
||||||
|
// We don't know the answer; protocol to old.
|
||||||
|
conn.remoteTrustsUs = std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
auto ex = conn.processStderr();
|
auto ex = conn.processStderr();
|
||||||
if (ex) std::rethrow_exception(ex);
|
if (ex) std::rethrow_exception(ex);
|
||||||
}
|
}
|
||||||
|
@ -1082,6 +1123,11 @@ unsigned int RemoteStore::getProtocol()
|
||||||
return conn->daemonVersion;
|
return conn->daemonVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<TrustedFlag> RemoteStore::isTrustedClient()
|
||||||
|
{
|
||||||
|
auto conn(getConnection());
|
||||||
|
return conn->remoteTrustsUs;
|
||||||
|
}
|
||||||
|
|
||||||
void RemoteStore::flushBadConnections()
|
void RemoteStore::flushBadConnections()
|
||||||
{
|
{
|
||||||
|
|
|
@ -144,6 +144,8 @@ public:
|
||||||
|
|
||||||
unsigned int getProtocol() override;
|
unsigned int getProtocol() override;
|
||||||
|
|
||||||
|
std::optional<TrustedFlag> isTrustedClient() override;
|
||||||
|
|
||||||
void flushBadConnections();
|
void flushBadConnections();
|
||||||
|
|
||||||
struct Connection
|
struct Connection
|
||||||
|
@ -151,6 +153,7 @@ public:
|
||||||
FdSink to;
|
FdSink to;
|
||||||
FdSource from;
|
FdSource from;
|
||||||
unsigned int daemonVersion;
|
unsigned int daemonVersion;
|
||||||
|
std::optional<TrustedFlag> remoteTrustsUs;
|
||||||
std::optional<std::string> daemonNixVersion;
|
std::optional<std::string> daemonNixVersion;
|
||||||
std::chrono::time_point<std::chrono::steady_clock> startTime;
|
std::chrono::time_point<std::chrono::steady_clock> startTime;
|
||||||
|
|
||||||
|
|
|
@ -509,6 +509,16 @@ struct S3BinaryCacheStoreImpl : virtual S3BinaryCacheStoreConfig, public virtual
|
||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For now, we conservatively say we don't know.
|
||||||
|
*
|
||||||
|
* \todo try to expose our S3 authentication status.
|
||||||
|
*/
|
||||||
|
std::optional<TrustedFlag> isTrustedClient() override
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
static std::set<std::string> uriSchemes() { return {"s3"}; }
|
static std::set<std::string> uriSchemes() { return {"s3"}; }
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -89,6 +89,7 @@ const uint32_t exportMagic = 0x4558494e;
|
||||||
|
|
||||||
|
|
||||||
enum BuildMode { bmNormal, bmRepair, bmCheck };
|
enum BuildMode { bmNormal, bmRepair, bmCheck };
|
||||||
|
enum TrustedFlag : bool { NotTrusted = false, Trusted = true };
|
||||||
|
|
||||||
struct BuildResult;
|
struct BuildResult;
|
||||||
|
|
||||||
|
@ -815,6 +816,17 @@ public:
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return/ whether store trusts *us*.
|
||||||
|
*
|
||||||
|
* `std::nullopt` means we do not know.
|
||||||
|
*
|
||||||
|
* @note This is the opposite of the StoreConfig::isTrusted
|
||||||
|
* store setting. That is about whether *we* trust the store.
|
||||||
|
*/
|
||||||
|
virtual std::optional<TrustedFlag> isTrustedClient() = 0;
|
||||||
|
|
||||||
|
|
||||||
virtual Path toRealPath(const Path & storePath)
|
virtual Path toRealPath(const Path & storePath)
|
||||||
{
|
{
|
||||||
return storePath;
|
return storePath;
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace nix {
|
||||||
#define WORKER_MAGIC_1 0x6e697863
|
#define WORKER_MAGIC_1 0x6e697863
|
||||||
#define WORKER_MAGIC_2 0x6478696f
|
#define WORKER_MAGIC_2 0x6478696f
|
||||||
|
|
||||||
#define PROTOCOL_VERSION (1 << 8 | 34)
|
#define PROTOCOL_VERSION (1 << 8 | 35)
|
||||||
#define GET_PROTOCOL_MAJOR(x) ((x) & 0xff00)
|
#define GET_PROTOCOL_MAJOR(x) ((x) & 0xff00)
|
||||||
#define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff)
|
#define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff)
|
||||||
|
|
||||||
|
@ -103,6 +103,7 @@ MAKE_WORKER_PROTO(, DerivedPath);
|
||||||
MAKE_WORKER_PROTO(, Realisation);
|
MAKE_WORKER_PROTO(, Realisation);
|
||||||
MAKE_WORKER_PROTO(, DrvOutput);
|
MAKE_WORKER_PROTO(, DrvOutput);
|
||||||
MAKE_WORKER_PROTO(, BuildResult);
|
MAKE_WORKER_PROTO(, BuildResult);
|
||||||
|
MAKE_WORKER_PROTO(, std::optional<TrustedFlag>);
|
||||||
|
|
||||||
MAKE_WORKER_PROTO(template<typename T>, std::vector<T>);
|
MAKE_WORKER_PROTO(template<typename T>, std::vector<T>);
|
||||||
MAKE_WORKER_PROTO(template<typename T>, std::set<T>);
|
MAKE_WORKER_PROTO(template<typename T>, std::set<T>);
|
||||||
|
|
|
@ -33,6 +33,10 @@ bool checkFail(const std::string & msg) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void checkInfo(const std::string & msg) {
|
||||||
|
notice(ANSI_BLUE "[INFO] " ANSI_NORMAL + msg);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CmdDoctor : StoreCommand
|
struct CmdDoctor : StoreCommand
|
||||||
|
@ -63,6 +67,7 @@ struct CmdDoctor : StoreCommand
|
||||||
success &= checkProfileRoots(store);
|
success &= checkProfileRoots(store);
|
||||||
}
|
}
|
||||||
success &= checkStoreProtocol(store->getProtocol());
|
success &= checkStoreProtocol(store->getProtocol());
|
||||||
|
checkTrustedUser(store);
|
||||||
|
|
||||||
if (!success)
|
if (!success)
|
||||||
throw Exit(2);
|
throw Exit(2);
|
||||||
|
@ -138,6 +143,14 @@ struct CmdDoctor : StoreCommand
|
||||||
|
|
||||||
return checkPass("Client protocol matches store protocol.");
|
return checkPass("Client protocol matches store protocol.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void checkTrustedUser(ref<Store> store)
|
||||||
|
{
|
||||||
|
std::string_view trusted = store->isTrustedClient()
|
||||||
|
? "trusted"
|
||||||
|
: "not trusted";
|
||||||
|
checkInfo(fmt("You are %s by store uri: %s", trusted, store->getUri()));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static auto rCmdDoctor = registerCommand<CmdDoctor>("doctor");
|
static auto rCmdDoctor = registerCommand<CmdDoctor>("doctor");
|
||||||
|
|
|
@ -28,15 +28,20 @@ struct CmdPingStore : StoreCommand, MixJSON
|
||||||
store->connect();
|
store->connect();
|
||||||
if (auto version = store->getVersion())
|
if (auto version = store->getVersion())
|
||||||
notice("Version: %s", *version);
|
notice("Version: %s", *version);
|
||||||
|
if (auto trusted = store->isTrustedClient())
|
||||||
|
notice("Trusted: %s", *trusted);
|
||||||
} else {
|
} else {
|
||||||
nlohmann::json res;
|
nlohmann::json res;
|
||||||
Finally printRes([&]() {
|
Finally printRes([&]() {
|
||||||
logger->cout("%s", res);
|
logger->cout("%s", res);
|
||||||
});
|
});
|
||||||
|
|
||||||
res["url"] = store->getUri();
|
res["url"] = store->getUri();
|
||||||
store->connect();
|
store->connect();
|
||||||
if (auto version = store->getVersion())
|
if (auto version = store->getVersion())
|
||||||
res["version"] = *version;
|
res["version"] = *version;
|
||||||
|
if (auto trusted = store->isTrustedClient())
|
||||||
|
res["trusted"] = *trusted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
4
tests/legacy-ssh-store.sh
Normal file
4
tests/legacy-ssh-store.sh
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
source common.sh
|
||||||
|
|
||||||
|
# Check that store ping trusted doesn't yet work with ssh://
|
||||||
|
nix --store ssh://localhost?remote-store=$TEST_ROOT/other-store store ping --json | jq -e 'has("trusted") | not'
|
|
@ -17,3 +17,6 @@ PATH2=$(nix path-info --store "$PWD/x" $CORRECT_PATH)
|
||||||
|
|
||||||
PATH3=$(nix path-info --store "local?root=$PWD/x" $CORRECT_PATH)
|
PATH3=$(nix path-info --store "local?root=$PWD/x" $CORRECT_PATH)
|
||||||
[ $CORRECT_PATH == $PATH3 ]
|
[ $CORRECT_PATH == $PATH3 ]
|
||||||
|
|
||||||
|
# Ensure store ping trusted works with local store
|
||||||
|
nix --store ./x store ping --json | jq -e '.trusted'
|
||||||
|
|
|
@ -17,6 +17,7 @@ nix_tests = \
|
||||||
ca/gc.sh \
|
ca/gc.sh \
|
||||||
gc.sh \
|
gc.sh \
|
||||||
remote-store.sh \
|
remote-store.sh \
|
||||||
|
legacy-ssh-store.sh \
|
||||||
lang.sh \
|
lang.sh \
|
||||||
experimental-features.sh \
|
experimental-features.sh \
|
||||||
fetchMercurial.sh \
|
fetchMercurial.sh \
|
||||||
|
|
|
@ -5,8 +5,19 @@ clearStore
|
||||||
# Ensure "fake ssh" remote store works just as legacy fake ssh would.
|
# Ensure "fake ssh" remote store works just as legacy fake ssh would.
|
||||||
nix --store ssh-ng://localhost?remote-store=$TEST_ROOT/other-store doctor
|
nix --store ssh-ng://localhost?remote-store=$TEST_ROOT/other-store doctor
|
||||||
|
|
||||||
|
# Ensure that store ping trusted works with ssh-ng://
|
||||||
|
nix --store ssh-ng://localhost?remote-store=$TEST_ROOT/other-store store ping --json | jq -e '.trusted'
|
||||||
|
|
||||||
startDaemon
|
startDaemon
|
||||||
|
|
||||||
|
if isDaemonNewer "2.15pre0"; then
|
||||||
|
# Ensure that ping works trusted with new daemon
|
||||||
|
nix store ping --json | jq -e '.trusted'
|
||||||
|
else
|
||||||
|
# And the the field is absent with the old daemon
|
||||||
|
nix store ping --json | jq -e 'has("trusted") | not'
|
||||||
|
fi
|
||||||
|
|
||||||
# Test import-from-derivation through the daemon.
|
# Test import-from-derivation through the daemon.
|
||||||
[[ $(nix eval --impure --raw --expr '
|
[[ $(nix eval --impure --raw --expr '
|
||||||
with import ./config.nix;
|
with import ./config.nix;
|
||||||
|
|
Loading…
Reference in a new issue