Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de0447c2c6 | ||
|
|
da75fb29e7 |
@@ -0,0 +1,14 @@
|
||||
plugin_mtls_store = shared_module(
|
||||
'plugin_mtls_store',
|
||||
'plugin_mtls_store.cc',
|
||||
dependencies : [
|
||||
liblixutil,
|
||||
liblixstore,
|
||||
liblixexpr,
|
||||
liblixfetchers,
|
||||
curl,
|
||||
],
|
||||
install : false,
|
||||
build_by_default : true,
|
||||
link_args : strict_shared_module_link_args,
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
R"(
|
||||
|
||||
**Store URL format**: `https+mtls://...`
|
||||
|
||||
This store allows a binary cache to be accessed via the HTTPS
|
||||
protocol with mutual TLS mandated.
|
||||
|
||||
Two parameters can be passed to the query string:
|
||||
|
||||
- `tls-certificate`, a path to the TLS client certificate (optional)
|
||||
- `tls-private-key`, a path to the TLS private key backing the client certificate (required)
|
||||
|
||||
)"
|
||||
@@ -0,0 +1,102 @@
|
||||
#include "lix/libstore/store-api.hh"
|
||||
#include "lix/libutil/config.hh"
|
||||
#include "lix/libstore/http-binary-cache-store.hh"
|
||||
#include <stdlib.h>
|
||||
#include <curl/curl.h>
|
||||
|
||||
namespace nix {
|
||||
struct mTLSBinaryCacheStoreConfig : HttpBinaryCacheStoreConfig
|
||||
{
|
||||
using HttpBinaryCacheStoreConfig::HttpBinaryCacheStoreConfig;
|
||||
|
||||
const std::string name() override
|
||||
{
|
||||
return "mTLS HTTP Binary Cache Store";
|
||||
}
|
||||
|
||||
std::string doc() override
|
||||
{
|
||||
return
|
||||
#include "mtls-http-binary-cache-store.md"
|
||||
;
|
||||
}
|
||||
|
||||
PathsSetting<nix::Path> tlsCertificate{
|
||||
this,
|
||||
"",
|
||||
"tls-certificate",
|
||||
"Path of an optional TLS client certificate in PEM format as expected by CURLOPT_SSLCERT"
|
||||
};
|
||||
|
||||
PathsSetting<nix::Path> tlsKey{
|
||||
this,
|
||||
"",
|
||||
"tls-private-key",
|
||||
"Path of an TLS client certificate private key in PEM format as expected by CURLOPT_SSLKEY"
|
||||
};
|
||||
};
|
||||
|
||||
struct mTLSBinaryCacheStoreImpl : public HttpBinaryCacheStore
|
||||
{
|
||||
struct Keyring
|
||||
{
|
||||
nix::Path tlsCertificate;
|
||||
nix::Path tlsKey;
|
||||
};
|
||||
|
||||
mTLSBinaryCacheStoreConfig config_;
|
||||
std::shared_ptr<Keyring> keyring;
|
||||
|
||||
mTLSBinaryCacheStoreConfig & config() override
|
||||
{
|
||||
return config_;
|
||||
}
|
||||
const mTLSBinaryCacheStoreConfig & config() const override
|
||||
{
|
||||
return config_;
|
||||
}
|
||||
|
||||
mTLSBinaryCacheStoreImpl(
|
||||
const std::string & uriScheme, const Path & _cacheUri, mTLSBinaryCacheStoreConfig config
|
||||
)
|
||||
: Store(config)
|
||||
, HttpBinaryCacheStore("https", _cacheUri, config)
|
||||
, config_(std::move(config))
|
||||
, keyring(std::make_shared<Keyring>(config_.tlsCertificate.get(), config_.tlsKey.get()))
|
||||
{
|
||||
}
|
||||
|
||||
FileTransferOptions makeOptions(Headers && headers = {}) override
|
||||
{
|
||||
auto options = HttpBinaryCacheStore::makeOptions(std::move(headers));
|
||||
auto baseExtraSetup = std::move(options.extraSetup);
|
||||
auto keyring = this->keyring;
|
||||
|
||||
options.extraSetup =
|
||||
[keyring, baseExtraSetup{std::move(baseExtraSetup)}](CURL * req) {
|
||||
if (baseExtraSetup) {
|
||||
baseExtraSetup(req);
|
||||
}
|
||||
|
||||
if (!keyring->tlsCertificate.empty()) {
|
||||
curl_easy_setopt(req, CURLOPT_SSLCERT, keyring->tlsCertificate.c_str());
|
||||
}
|
||||
|
||||
curl_easy_setopt(req, CURLOPT_SSLKEY, keyring->tlsKey.c_str());
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
static std::set<std::string> uriSchemes()
|
||||
{
|
||||
return {"https+mtls"};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
extern "C" void nix_plugin_entry()
|
||||
{
|
||||
nix::StoreImplementations::add<nix::mTLSBinaryCacheStoreImpl, nix::mTLSBinaryCacheStoreConfig>();
|
||||
}
|
||||
@@ -258,6 +258,9 @@ vigress8:
|
||||
forgejo: vigress8
|
||||
github: vigress8
|
||||
|
||||
vlaci:
|
||||
github: vlaci
|
||||
|
||||
vlinkz:
|
||||
display_name: Victor Fuentes
|
||||
forgejo: vlinkz
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
synopsis: "mTLS store connections via a plugin"
|
||||
issues: []
|
||||
cls: [3754, 3696, 3697, 3698]
|
||||
category: Improvements
|
||||
credits: [raito, horrors, mic92, vlaci]
|
||||
---
|
||||
|
||||
To support use cases requiring mutual TLS (mTLS) authentication when connecting
|
||||
to remote Nix stores, e.g. private stores, we have introduced a **contributed**
|
||||
mTLS plugin extending the Lix store interface.
|
||||
|
||||
This design follows an extensibility model which was brought up [by a proposal
|
||||
of making Kerberos authentication possible in Lix
|
||||
directly](https://gerrit.lix.systems/c/lix/+/3637).
|
||||
|
||||
This mTLS plugin serves as a concrete example of how store connection
|
||||
mechanisms can be modularized through external plugins, without extending Lix
|
||||
core. This idea can be generalized to integrate automatic certificate renewal
|
||||
or advanced integrations with secrets engine or posture checks.
|
||||
|
||||
It enables custom TLS client certificates to be used for authenticating against
|
||||
a remote store that enforces mTLS.
|
||||
|
||||
To use the plugin, configure Lix manually by setting in your `nix.conf`:
|
||||
|
||||
```
|
||||
plugin-files = /a/path/to/libplugin_mtls_store.so
|
||||
```
|
||||
|
||||
Currently, this must be done explicitly. In the future, Nixpkgs will provide a
|
||||
mechanism to reference an up-to-date and curated set of plugins automatically.
|
||||
|
||||
Making plugins easily consumable outside of Nixpkgs (e.g., from external plugin
|
||||
registries or binary distributions) remains an open question and will require
|
||||
further design.
|
||||
|
||||
Contributed plugins come with significantly reduced **stability** and
|
||||
**maintenance** guarantees compared to the Lix core. We encourage users who
|
||||
depend on a given plugin to take on maintenance responsibilities and apply for
|
||||
ownership within the Lix mono-repository. These plugins are subject to removal
|
||||
at any time.
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include "lix/libcmd/built-path.hh"
|
||||
#include "lix/libexpr/eval.hh"
|
||||
#include "lix/libexpr/flake/flakeref.hh"
|
||||
#include "lix/libflakes/expr/flakeref.hh"
|
||||
#include "lix/libexpr/get-drvs.hh"
|
||||
#include "lix/libutil/types.hh"
|
||||
#include "lix/libutil/json-fwd.hh"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "lix/libcmd/command.hh"
|
||||
#include "lix/libflakes/init.hh"
|
||||
#include "lix/libstore/store-api.hh"
|
||||
#include "lix/libstore/local-fs-store.hh"
|
||||
#include "lix/libstore/derivations.hh"
|
||||
@@ -119,6 +120,8 @@ ref<eval_cache::CachingEvaluator> EvalCommand::getEvaluator()
|
||||
startReplOnEvalErrors ? AbstractNixRepl::runSimple : nullptr
|
||||
);
|
||||
|
||||
flake::initFlakes(evalState.get());
|
||||
|
||||
evalState->repair = repair;
|
||||
}
|
||||
return ref<eval_cache::CachingEvaluator>::unsafeFromPtr(evalState);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "lix/libutil/args.hh"
|
||||
#include "lix/libcmd/common-eval-args.hh"
|
||||
#include "lix/libstore/path.hh"
|
||||
#include "lix/libexpr/flake/lockfile.hh"
|
||||
#include "lix/libflakes/expr/lockfile.hh"
|
||||
#include "lix/libutil/async.hh"
|
||||
|
||||
#include <optional>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "lix/libexpr/eval.hh"
|
||||
#include "lix/libfetchers/fetchers.hh"
|
||||
#include "lix/libfetchers/registry.hh"
|
||||
#include "lix/libexpr/flake/flakeref.hh"
|
||||
#include "lix/libflakes/expr/flakeref.hh"
|
||||
#include "lix/libstore/store-api.hh"
|
||||
#include "lix/libcmd/command.hh"
|
||||
#include "lix/libutil/async.hh"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "lix/libcmd/common-eval-args.hh"
|
||||
#include "lix/libexpr/eval.hh"
|
||||
#include "lix/libexpr/get-drvs.hh"
|
||||
#include "lix/libexpr/flake/flake.hh"
|
||||
#include "lix/libflakes/expr/flake.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "lix/libexpr/attr-path.hh"
|
||||
#include "lix/libcmd/common-eval-args.hh"
|
||||
#include "lix/libexpr/eval.hh"
|
||||
#include "lix/libexpr/flake/flake.hh"
|
||||
#include "lix/libflakes/expr/flake.hh"
|
||||
#include "lix/libexpr/eval-cache.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "lix/libcmd/installables.hh"
|
||||
#include "lix/libexpr/eval-cache.hh"
|
||||
#include "lix/libexpr/eval.hh"
|
||||
#include "lix/libexpr/flake/flake.hh"
|
||||
#include "lix/libflakes/expr/flake.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include "lix/libexpr/eval-settings.hh"
|
||||
#include "lix/libstore/store-api.hh"
|
||||
#include "lix/libmain/shared.hh"
|
||||
#include "lix/libexpr/flake/flake.hh"
|
||||
#include "lix/libflakes/expr/flake.hh"
|
||||
#include "lix/libexpr/eval-cache.hh"
|
||||
#include "lix/libfetchers/registry.hh"
|
||||
#include "lix/libstore/build-result.hh"
|
||||
|
||||
@@ -45,6 +45,7 @@ libcmd = library(
|
||||
liblixstore,
|
||||
liblixfetchers,
|
||||
liblixexpr,
|
||||
liblixflakes,
|
||||
liblixmain,
|
||||
liblix_doc,
|
||||
boehm,
|
||||
@@ -76,6 +77,7 @@ liblixcmd = declare_dependency(
|
||||
dependencies : [
|
||||
liblixutil,
|
||||
liblixstore,
|
||||
liblixflakes,
|
||||
kj,
|
||||
],
|
||||
link_with : libcmd,
|
||||
|
||||
+2
-2
@@ -22,8 +22,8 @@
|
||||
#include "lix/libexpr/get-drvs.hh"
|
||||
#include "lix/libstore/derivations.hh"
|
||||
#include "lix/libstore/globals.hh"
|
||||
#include "lix/libexpr/flake/flake.hh"
|
||||
#include "lix/libexpr/flake/lockfile.hh"
|
||||
#include "lix/libflakes/expr/flake.hh"
|
||||
#include "lix/libflakes/expr/lockfile.hh"
|
||||
#include "lix/libcmd/editor-for.hh"
|
||||
#include "lix/libutil/finally.hh"
|
||||
#include "lix/libcmd/markdown.hh"
|
||||
|
||||
@@ -6,8 +6,7 @@ renameInGlobalScope: false
|
||||
Load, parse and return the Nix expression in the file *path*.
|
||||
|
||||
The value *path* can be a path, a string, or an attribute set with an
|
||||
`__toString` attribute or a `outPath` attribute (as derivations or flake
|
||||
inputs typically have).
|
||||
`__toString` attribute or a `outPath` attribute (as derivations typically have).
|
||||
|
||||
If *path* is a directory, the file `default.nix` in that directory
|
||||
is loaded.
|
||||
|
||||
@@ -6,48 +6,12 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* Very hacky way to parse $NIX_PATH, which is colon-separated, but
|
||||
can contain URLs (e.g. "nixpkgs=https://bla...:foo=https://"). */
|
||||
static Strings parseNixPath(const std::string & s)
|
||||
{
|
||||
Strings res;
|
||||
|
||||
auto p = s.begin();
|
||||
|
||||
while (p != s.end()) {
|
||||
auto start = p;
|
||||
auto start2 = p;
|
||||
|
||||
while (p != s.end() && *p != ':') {
|
||||
if (*p == '=') start2 = p + 1;
|
||||
++p;
|
||||
}
|
||||
|
||||
if (p == s.end()) {
|
||||
if (p != start) res.push_back(std::string(start, p));
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == ':') {
|
||||
auto prefix = std::string(start2, s.end());
|
||||
if (EvalSettings::isPseudoUrl(prefix) || prefix.starts_with("flake:")) {
|
||||
++p;
|
||||
while (p != s.end() && *p != ':') ++p;
|
||||
}
|
||||
res.push_back(std::string(start, p));
|
||||
if (p == s.end()) break;
|
||||
}
|
||||
|
||||
++p;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
EvalSettings::EvalSettings()
|
||||
{
|
||||
auto var = getEnv("NIX_PATH");
|
||||
if (var) nixPath.setDefault(parseNixPath(*var));
|
||||
//auto var = getEnv("NIX_PATH");
|
||||
// TODO(flakes): move that allowed prefix and parse it from eval plugins.
|
||||
//if (var) nixPath.setDefault(parseNixPath(*var, ));
|
||||
}
|
||||
|
||||
Strings EvalSettings::getDefaultNixPath()
|
||||
|
||||
+83
-25
@@ -21,7 +21,7 @@
|
||||
#include "lix/libexpr/print.hh"
|
||||
#include "lix/libexpr/gc-small-vector.hh"
|
||||
#include "lix/libfetchers/fetch-to-store.hh"
|
||||
#include "lix/libexpr/flake/flakeref.hh"
|
||||
#include "lix/libfetchers/fetchers.hh"
|
||||
#include "lix/libutil/exit.hh"
|
||||
#include "lix/libutil/json.hh"
|
||||
#include "symbol-table.hh"
|
||||
@@ -318,14 +318,16 @@ EvalBuiltins::EvalBuiltins(
|
||||
EvalPaths::EvalPaths(
|
||||
AsyncIoRoot & aio,
|
||||
const ref<Store> & store,
|
||||
SearchPath searchPath,
|
||||
SearchPath initialSearchPath,
|
||||
bool pureEval,
|
||||
EvalErrorContext & errors
|
||||
)
|
||||
: store(store)
|
||||
, searchPath_(std::move(searchPath))
|
||||
, searchPath_(std::move(initialSearchPath))
|
||||
, pureEval(pureEval)
|
||||
, errors(errors)
|
||||
{
|
||||
if (evalSettings.restrictEval || evalSettings.pureEval) {
|
||||
if (evalSettings.restrictEval || pureEval) {
|
||||
allowedPaths = AllowedPath{.allowAllChildren = false};
|
||||
|
||||
for (auto & i : searchPath_.elements) {
|
||||
@@ -349,6 +351,70 @@ EvalPaths::EvalPaths(
|
||||
}
|
||||
}
|
||||
|
||||
/* Very hacky way to parse $NIX_PATH, which is colon-separated, but
|
||||
can contain URLs (e.g. "nixpkgs=https://bla...:foo=https://"). */
|
||||
static Strings parseNixPath(const std::string & s, const std::set<std::string> & allowedPrefixes)
|
||||
{
|
||||
Strings res;
|
||||
|
||||
auto p = s.begin();
|
||||
|
||||
while (p != s.end()) {
|
||||
auto start = p;
|
||||
auto start2 = p;
|
||||
|
||||
while (p != s.end() && *p != ':') {
|
||||
if (*p == '=') start2 = p + 1;
|
||||
++p;
|
||||
}
|
||||
|
||||
if (p == s.end()) {
|
||||
if (p != start) res.push_back(std::string(start, p));
|
||||
break;
|
||||
}
|
||||
|
||||
if (*p == ':') {
|
||||
auto prefix = std::string(start2, s.end());
|
||||
|
||||
auto matchesAllowedPrefixes = std::any_of(
|
||||
allowedPrefixes.begin(),
|
||||
allowedPrefixes.end(),
|
||||
[&](const std::string & allowedPrefix) {
|
||||
return prefix.starts_with(allowedPrefix);
|
||||
}
|
||||
);
|
||||
if (EvalSettings::isPseudoUrl(prefix) || matchesAllowedPrefixes) {
|
||||
++p;
|
||||
while (p != s.end() && *p != ':') ++p;
|
||||
}
|
||||
res.push_back(std::string(start, p));
|
||||
if (p == s.end()) break;
|
||||
}
|
||||
|
||||
++p;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void EvalPaths::parseAndConfigureEnvironmentNixPath(std::string const & value)
|
||||
{
|
||||
if (!pureEval) {
|
||||
for (auto & elem : parseNixPath(value, allowedNixPathPrefixes)) {
|
||||
searchPath_.elements.emplace_back(SearchPath::Elem::parse(elem));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EvalPaths::configureSettingNixPath(std::list<std::string> const & values)
|
||||
{
|
||||
if (!pureEval) {
|
||||
for (auto & elem : values) {
|
||||
searchPath_.elements.emplace_back(SearchPath::Elem::parse(elem));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Evaluator::Evaluator(
|
||||
AsyncIoRoot & aio,
|
||||
const SearchPath & _searchPath,
|
||||
@@ -357,16 +423,7 @@ Evaluator::Evaluator(
|
||||
std::function<ReplExitStatus(EvalState & es, ValMap const & extraEnv)> debugRepl
|
||||
)
|
||||
: s(symbols)
|
||||
, paths(aio, store, [&] {
|
||||
SearchPath searchPath;
|
||||
if (!evalSettings.pureEval) {
|
||||
for (auto & i : _searchPath.elements)
|
||||
searchPath.elements.emplace_back(SearchPath::Elem {i});
|
||||
for (auto & i : evalSettings.nixPath.get())
|
||||
searchPath.elements.emplace_back(SearchPath::Elem::parse(i));
|
||||
}
|
||||
return searchPath;
|
||||
}(), errors)
|
||||
, paths(aio, store, _searchPath, evalSettings.pureEval, errors)
|
||||
, builtins(mem, symbols, paths.searchPath(), store->config().storeDir)
|
||||
, repair(NoRepair)
|
||||
, store(store)
|
||||
@@ -392,6 +449,11 @@ Evaluator::Evaluator(
|
||||
box_ptr<EvalState> Evaluator::begin(AsyncIoRoot & aio)
|
||||
{
|
||||
assert(!activeEval);
|
||||
if (auto envNixPath = getEnvNonEmpty("NIX_PATH")) {
|
||||
paths.parseAndConfigureEnvironmentNixPath(*envNixPath);
|
||||
} else {
|
||||
paths.configureSettingNixPath(evalSettings.nixPath);
|
||||
}
|
||||
return box_ptr<EvalState>::unsafeFromNonnull(
|
||||
std::unique_ptr<EvalState>(new EvalState(aio, *this))
|
||||
);
|
||||
@@ -2972,7 +3034,12 @@ try {
|
||||
|
||||
std::optional<std::string> res;
|
||||
|
||||
if (EvalSettings::isPseudoUrl(value)) {
|
||||
for (auto & responder : searchPathResponders) {
|
||||
if (responder.canResolve(value))
|
||||
res = TRY_AWAIT(responder.resolve(value, store));
|
||||
}
|
||||
|
||||
if (!res && EvalSettings::isPseudoUrl(value)) {
|
||||
try {
|
||||
auto storePath = TRY_AWAIT(fetchers::downloadTarball(
|
||||
store, EvalSettings::resolvePseudoUrl(value), "source", false)).tree.storePath;
|
||||
@@ -2984,16 +3051,7 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
else if (value.starts_with("flake:")) {
|
||||
experimentalFeatureSettings.require(Xp::Flakes);
|
||||
auto flakeRef = parseFlakeRef(value.substr(6), {}, true, false);
|
||||
debug("fetching flake search path element '%s''", value);
|
||||
auto storePath =
|
||||
TRY_AWAIT(TRY_AWAIT(flakeRef.resolve(store)).fetchTree(store)).first.storePath;
|
||||
res = {store->toRealPath(storePath)};
|
||||
}
|
||||
|
||||
else {
|
||||
else if (!res) {
|
||||
auto path = absPath(value);
|
||||
if (pathExists(path))
|
||||
res = { path };
|
||||
|
||||
+28
-1
@@ -320,20 +320,47 @@ struct EvalErrorContext
|
||||
|
||||
class EvalPaths
|
||||
{
|
||||
struct Responder {
|
||||
std::function<bool (std::string const &)> canResolve;
|
||||
std::function<kj::Promise<Result<std::optional<std::string>>> (std::string const &, ref<Store>)> resolve;
|
||||
};
|
||||
|
||||
ref<Store> store;
|
||||
std::set<std::string> allowedNixPathPrefixes;
|
||||
std::vector<Responder> searchPathResponders;
|
||||
SearchPath searchPath_;
|
||||
bool pureEval;
|
||||
EvalErrorContext & errors;
|
||||
|
||||
public:
|
||||
EvalPaths(
|
||||
AsyncIoRoot & aio,
|
||||
const ref<Store> & store,
|
||||
SearchPath searchPath,
|
||||
SearchPath initialSearchPath,
|
||||
bool pureEval,
|
||||
EvalErrorContext & errors
|
||||
);
|
||||
|
||||
void parseAndConfigureEnvironmentNixPath(std::string const & value);
|
||||
void configureSettingNixPath(std::list<std::string> const & value);
|
||||
|
||||
const SearchPath & searchPath() const { return searchPath_; }
|
||||
|
||||
std::string resolvePseudoUrl(std::string_view url) {
|
||||
if (url.starts_with("channel:"))
|
||||
return "https://channels.nixos.org/" + std::string(url.substr(8)) + "/nixexprs.tar.xz";
|
||||
else
|
||||
return std::string(url);
|
||||
}
|
||||
|
||||
bool registerAllowedNixPathPrefix(const std::string & allowedPrefix) {
|
||||
auto [_, added] = allowedNixPathPrefixes.insert(allowedPrefix);
|
||||
return added;
|
||||
}
|
||||
void registerSearchPathResolver(std::function<bool (std::string const &)> eligible, std::function<kj::Promise<Result<std::optional<std::string>>> (std::string const & value, ref<Store> store)> resolver) {
|
||||
searchPathResponders.push_back({.canResolve=eligible,.resolve=resolver});
|
||||
}
|
||||
|
||||
private:
|
||||
struct AllowedPath
|
||||
{
|
||||
|
||||
@@ -19,12 +19,4 @@ void prim_getContext(EvalState & state, Value * * args, Value & v);
|
||||
void prim_hasContext(EvalState & state, Value * * args, Value & v);
|
||||
void prim_unsafeDiscardOutputDependency(EvalState & state, Value * * args, Value & v);
|
||||
|
||||
namespace flake {
|
||||
|
||||
void prim_flakeRefToString(EvalState & state, Value * * args, Value & v);
|
||||
void prim_getFlake(EvalState & state, Value * * args, Value & v);
|
||||
void prim_parseFlakeRef(EvalState & state, Value * * args, Value & v);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ foreach header : [ 'imported-drv-to-derivation.nix', 'fetchurl.nix' ]
|
||||
install_dir : includedir / 'lix/libexpr',
|
||||
)
|
||||
endforeach
|
||||
subdir('flake')
|
||||
|
||||
libexpr_setting_definitions = files(
|
||||
# keep-sorted start
|
||||
@@ -79,7 +78,6 @@ builtin_definitions = files(
|
||||
'builtins/filter.md',
|
||||
'builtins/filterSource.md',
|
||||
'builtins/findFile.md',
|
||||
'builtins/flakeRefToString.md',
|
||||
'builtins/floor.md',
|
||||
'builtins/foldlStrict.md',
|
||||
'builtins/fromJSON.md',
|
||||
@@ -90,7 +88,6 @@ builtin_definitions = files(
|
||||
'builtins/getAttr.md',
|
||||
'builtins/getContext.md',
|
||||
'builtins/getEnv.md',
|
||||
'builtins/getFlake.md',
|
||||
'builtins/groupBy.md',
|
||||
'builtins/hasAttr.md',
|
||||
'builtins/hasContext.md',
|
||||
@@ -116,7 +113,6 @@ builtin_definitions = files(
|
||||
'builtins/match.md',
|
||||
'builtins/mul.md',
|
||||
'builtins/parseDrvName.md',
|
||||
'builtins/parseFlakeRef.md',
|
||||
'builtins/partition.md',
|
||||
'builtins/path.md',
|
||||
'builtins/pathExists.md',
|
||||
@@ -207,10 +203,6 @@ libexpr_sources = files(
|
||||
'eval-error.cc',
|
||||
'eval-settings.cc',
|
||||
'eval.cc',
|
||||
'flake/config.cc',
|
||||
'flake/flake.cc',
|
||||
'flake/flakeref.cc',
|
||||
'flake/lockfile.cc',
|
||||
'function-trace.cc',
|
||||
'gc-alloc.cc',
|
||||
'get-drvs.cc',
|
||||
@@ -242,9 +234,6 @@ libexpr_headers = files(
|
||||
'eval-inline.hh',
|
||||
'eval-settings.hh',
|
||||
'eval.hh',
|
||||
'flake/flake.hh',
|
||||
'flake/flakeref.hh',
|
||||
'flake/lockfile.hh',
|
||||
'function-trace.hh',
|
||||
'gc-alloc.hh',
|
||||
'gc-small-vector.hh',
|
||||
|
||||
@@ -12,7 +12,6 @@ Pure evaluation mode ensures that the result of Nix expressions is fully determi
|
||||
|
||||
Access is nonetheless allowed to (absolute) paths in the Nix store that are returned by builtins like [`builtins.filterSource`](@docroot@/language/builtins.md#builtins-filterSource), [`builtins.fetchTarball`](@docroot@/language/builtins.md#builtins-fetchTarball) and similar.
|
||||
- Impure fetches such as not specifying a commit ID for `builtins.fetchGit` or not specifying a hash for `builtins.fetchTarball` are rejected.
|
||||
- In flakes, access to relative paths outside of the root of the flake's source tree (often, a git repository) is rejected.
|
||||
- The evaluator ignores `NIX_PATH`, `-I` and the `nix-path` setting. Thus, [`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtins-nixPath) is an empty list.
|
||||
- The builtins [`builtins.currentSystem`](@docroot@/language/builtin-constants.md#builtins-currentSystem) and [`builtins.currentTime`](@docroot@/language/builtin-constants.md#builtins-currentTime) are absent from `builtins`.
|
||||
- [`builtins.getEnv`](@docroot@/language/builtin-constants.md#builtins-currentSystem) always returns empty string for any variable.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "lix/libexpr/flake/flake.hh"
|
||||
#include "lix/libflakes/expr/flake.hh"
|
||||
#include "lix/libutil/logging.hh"
|
||||
#include "lix/libutil/json.hh"
|
||||
#include "lix/libutil/users.hh"
|
||||
@@ -1,8 +1,9 @@
|
||||
#include "lix/libexpr/flake/flake.hh"
|
||||
#include "lix/libflakes/expr/flake.hh"
|
||||
#include "lix/libexpr/eval.hh"
|
||||
#include "lix/libexpr/eval-settings.hh"
|
||||
#include "lix/libexpr/extra-primops.hh"
|
||||
#include "lix/libexpr/flake/lockfile.hh"
|
||||
#include "lix/libexpr/primops.hh"
|
||||
#include "lix/libflakes/expr/lockfile.hh"
|
||||
#include "lix/libstore/store-api.hh"
|
||||
#include "lix/libfetchers/fetchers.hh"
|
||||
#include "lix/libutil/async.hh"
|
||||
@@ -994,6 +995,12 @@ void prim_getFlake(EvalState & state, Value * * args, Value & v)
|
||||
v);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_getFlake(PrimOp{{
|
||||
.name = "getFlake",
|
||||
.arity = 1,
|
||||
.fun = prim_getFlake,
|
||||
}});
|
||||
|
||||
void prim_parseFlakeRef(
|
||||
EvalState & state,
|
||||
Value * * args,
|
||||
@@ -1015,6 +1022,12 @@ void prim_parseFlakeRef(
|
||||
v.mkAttrs(binds);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_parseFlakeRef(PrimOp{{
|
||||
.name = "parseFlakeRef",
|
||||
.arity = 1,
|
||||
.fun = prim_parseFlakeRef,
|
||||
}});
|
||||
|
||||
void prim_flakeRefToString(
|
||||
EvalState & state,
|
||||
Value * * args,
|
||||
@@ -1053,6 +1066,12 @@ void prim_flakeRefToString(
|
||||
v.mkString(flakeRef.to_string());
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_flakeRefToString(PrimOp{{
|
||||
.name = "flakeRefToString",
|
||||
.arity = 1,
|
||||
.fun = prim_flakeRefToString,
|
||||
}});
|
||||
|
||||
}
|
||||
|
||||
Fingerprint LockedFlake::getFingerprint() const
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
#include "lix/libexpr/eval.hh"
|
||||
#include "lix/libutil/types.hh"
|
||||
#include "lix/libexpr/flake/flakeref.hh"
|
||||
#include "lix/libexpr/flake/lockfile.hh"
|
||||
#include "lix/libflakes/expr/flakeref.hh"
|
||||
#include "lix/libflakes/expr/lockfile.hh"
|
||||
#include "lix/libexpr/value.hh"
|
||||
|
||||
namespace nix {
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "lix/libexpr/flake/flakeref.hh"
|
||||
#include "lix/libflakes/expr/flakeref.hh"
|
||||
#include "lix/libstore/store-api.hh"
|
||||
#include "lix/libutil/async.hh"
|
||||
#include "lix/libutil/regex.hh"
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "lix/libexpr/flake/lockfile.hh"
|
||||
#include "lix/libflakes/expr/lockfile.hh"
|
||||
#include "lix/libstore/store-api.hh"
|
||||
#include "lix/libutil/error.hh"
|
||||
#include "lix/libutil/json.hh"
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "lix/libexpr/flake/flakeref.hh"
|
||||
#include "lix/libflakes/expr/flakeref.hh"
|
||||
#include "lix/libutil/json-fwd.hh"
|
||||
|
||||
namespace nix {
|
||||
@@ -1,8 +1,8 @@
|
||||
libexpr_generated_headers += custom_target(
|
||||
libflakes_generated_headers += custom_target(
|
||||
command : [ 'bash', '-c', 'echo \'R"__NIX_STR(\' | cat - @INPUT@ && echo \')__NIX_STR"\'' ],
|
||||
input : 'call-flake.nix',
|
||||
output : '@PLAINNAME@.gen.hh',
|
||||
capture : true,
|
||||
install : true,
|
||||
install_dir : includedir / 'lix/libexpr/flake',
|
||||
install_dir : includedir / 'lix/libflakes/expr',
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
namespace nix {
|
||||
class EvalState;
|
||||
struct Value;
|
||||
|
||||
namespace flake {
|
||||
void prim_flakeRefToString(EvalState & state, Value * * args, Value & v);
|
||||
void prim_getFlake(EvalState & state, Value * * args, Value & v);
|
||||
void prim_parseFlakeRef(EvalState & state, Value * * args, Value & v);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "lix/libflakes/init.hh"
|
||||
#include "lix/libflakes/expr/flakeref.hh"
|
||||
#include "lix/libexpr/eval.hh"
|
||||
#include "lix/libstore/store-api.hh"
|
||||
|
||||
// TODO: register the builtins
|
||||
// register the setting for flakes (eval-cache)?
|
||||
|
||||
namespace nix::flake {
|
||||
void initFlakes(Evaluator * eval)
|
||||
{
|
||||
// We inform the evaluator about Flakes during NIX_PATH processing.
|
||||
eval->paths.registerAllowedNixPathPrefix("flake:");
|
||||
eval->paths.registerSearchPathResolver([&](std::string const & value) {
|
||||
return value.starts_with("flake:");
|
||||
}, [&](std::string const & value, ref<Store> store) -> kj::Promise<Result<std::optional<std::string>>> {
|
||||
auto flakeRef = parseFlakeRef(value.substr(6), {}, true, false);
|
||||
debug("fetching flake search path element '%s'", value);
|
||||
auto storePath =
|
||||
TRY_AWAIT(TRY_AWAIT(flakeRef.resolve(store)).fetchTree(store)).first.storePath;
|
||||
|
||||
co_return {store->toRealPath(storePath)};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
namespace nix {
|
||||
class Evaluator;
|
||||
|
||||
namespace flake {
|
||||
void initFlakes(Evaluator * eval);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
prefix=@prefix@
|
||||
libdir=@libdir@
|
||||
includedir=@includedir@
|
||||
|
||||
Name: Lix libflakes
|
||||
Description: Lix Package Manager (libflakes)
|
||||
Version: @PACKAGE_VERSION@
|
||||
# dependencies on boost is omitted since it is optional (only required by some headers)
|
||||
Requires: lix-base lix-util lix-store lix-fetchers @BOEHM_IF_FOUND@
|
||||
Libs: -L${libdir} -llixflakes
|
||||
@@ -0,0 +1,157 @@
|
||||
libflakes_generated_headers = []
|
||||
|
||||
subdir('expr')
|
||||
|
||||
# libflakes_setting_definitions = files(
|
||||
# # keep-sorted start
|
||||
# # keep-sorted end
|
||||
# )
|
||||
# libflakes_settings_header = custom_target(
|
||||
# command : [
|
||||
# python.full_path(),
|
||||
# '@SOURCE_ROOT@/lix/code-generation/build_settings.py',
|
||||
# '--kernel', host_machine.system(),
|
||||
# '--header', '@OUTPUT@',
|
||||
# '--experimental-features', '@SOURCE_ROOT@/lix/libutil/experimental-features',
|
||||
# '@INPUT@',
|
||||
# ],
|
||||
# input : libflakes_setting_definitions,
|
||||
# output : 'libflakes-settings.gen.inc',
|
||||
# install : true,
|
||||
# install_dir : includedir / 'lix/libflakes',
|
||||
# )
|
||||
|
||||
builtin_definitions = files(
|
||||
# keep-sorted start
|
||||
'builtins/flakeRefToString.md',
|
||||
'builtins/getFlake.md',
|
||||
'builtins/parseFlakeRef.md',
|
||||
# keep-sorted end
|
||||
)
|
||||
builtins_gen = custom_target(
|
||||
command : [
|
||||
python.full_path(),
|
||||
'@SOURCE_ROOT@/lix/code-generation/build_builtins.py',
|
||||
'--header', '@OUTPUT0@',
|
||||
'--docs', '@OUTPUT1@',
|
||||
'--experimental-features', '@SOURCE_ROOT@/lix/libutil/experimental-features',
|
||||
'@INPUT@'
|
||||
],
|
||||
input : builtin_definitions,
|
||||
output : [
|
||||
'register-builtins.gen.inc',
|
||||
'builtins.md',
|
||||
],
|
||||
)
|
||||
register_builtins_header = builtins_gen[0]
|
||||
builtins_md = builtins_gen[1]
|
||||
|
||||
libflakes_sources = files(
|
||||
# keep-sorted start
|
||||
'expr/config.cc',
|
||||
'expr/flake.cc',
|
||||
'expr/flakeref.cc',
|
||||
'expr/lockfile.cc',
|
||||
'init.cc',
|
||||
# keep-sorted end
|
||||
)
|
||||
|
||||
libflakes_headers = files(
|
||||
# keep-sorted start
|
||||
'expr/flake.hh',
|
||||
'expr/flakeref.hh',
|
||||
'expr/lockfile.hh',
|
||||
'init.hh'
|
||||
# keep-sorted end
|
||||
)
|
||||
|
||||
dependencies = [
|
||||
liblixutil,
|
||||
liblixstore,
|
||||
liblixfetchers,
|
||||
liblixexpr,
|
||||
boehm,
|
||||
boost,
|
||||
kj,
|
||||
nlohmann_json,
|
||||
toml11,
|
||||
]
|
||||
|
||||
libflakes_temp = library(
|
||||
is_static ? 'lixflakes_temp' : 'lixflakes',
|
||||
libflakes_sources,
|
||||
# libflakes_settings_header,
|
||||
libflakes_generated_headers,
|
||||
register_builtins_header,
|
||||
dependencies : dependencies,
|
||||
# for shared.hh
|
||||
include_directories : [
|
||||
'../libmain',
|
||||
],
|
||||
cpp_pch : cpp_pch,
|
||||
install : not is_static,
|
||||
# FIXME(Qyriad): is this right?
|
||||
install_rpath : libdir,
|
||||
)
|
||||
# FIXME: remove when https://git.lix.systems/lix-project/lix/issues/359 is fixed.
|
||||
# FIXME: replace by prelink when https://github.com/mesonbuild/meson/pull/14846 is widely available.
|
||||
if is_static
|
||||
libflakes_prelink = custom_target(
|
||||
'lixflakes-prelink',
|
||||
output : 'lixflakes-prelink.o',
|
||||
input : libflakes_temp,
|
||||
command : [
|
||||
cxx.cmd_array(),
|
||||
'-r',
|
||||
'-o',
|
||||
'@OUTPUT@',
|
||||
is_darwin ? '-Wl,-force_load' : '-Wl,--whole-archive',
|
||||
'@INPUT@',
|
||||
],
|
||||
)
|
||||
libflakes = library(
|
||||
'lixflakes',
|
||||
[libflakes_prelink],
|
||||
dependencies : dependencies,
|
||||
install : true,
|
||||
)
|
||||
else
|
||||
libflakes = libflakes_temp
|
||||
endif
|
||||
|
||||
install_headers(
|
||||
libflakes_headers,
|
||||
subdir : 'lix/libflakes',
|
||||
preserve_path : true,
|
||||
)
|
||||
|
||||
liblixflakes = declare_dependency(
|
||||
include_directories : include_directories('../..'),
|
||||
# sources : libflakes_settings_header,
|
||||
dependencies : [
|
||||
liblixutil,
|
||||
liblixexpr,
|
||||
liblixfetchers,
|
||||
boehm,
|
||||
boost,
|
||||
kj,
|
||||
],
|
||||
link_with : libflakes,
|
||||
)
|
||||
|
||||
meson.override_dependency('lix-flakes', liblixflakes)
|
||||
|
||||
# FIXME: not using the pkg-config module because it creates way too many deps
|
||||
# while meson migration is in progress, and we want to not include boost here
|
||||
configure_file(
|
||||
input : 'lix-flakes.pc.in',
|
||||
output : 'lix-flakes.pc',
|
||||
install_dir : libdir / 'pkgconfig',
|
||||
configuration : {
|
||||
'prefix' : prefix,
|
||||
'libdir' : libdir,
|
||||
'includedir' : includedir,
|
||||
'PACKAGE_VERSION' : meson.project_version(),
|
||||
'BOEHM_IF_FOUND' : boehm.found() ? 'bdw-gc' : '',
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
extern "C" void nix_plugin_entry()
|
||||
{
|
||||
// 1. register an allowed prefix for NIX_PATH parsing
|
||||
// 2. register a search path resolver
|
||||
}
|
||||
@@ -22,6 +22,8 @@ subdir('libstore')
|
||||
subdir('libfetchers')
|
||||
# libexpr depends on all of the above
|
||||
subdir('libexpr')
|
||||
# libflakes depends on all of the above
|
||||
subdir('libflakes')
|
||||
# libmain depends on libutil and libstore
|
||||
subdir('libmain')
|
||||
# libcmd depends on everything
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
#include "lix/libexpr/eval.hh"
|
||||
#include "lix/libexpr/eval-inline.hh" // IWYU pragma: keep
|
||||
#include "lix/libexpr/eval-settings.hh"
|
||||
#include "lix/libexpr/flake/flake.hh"
|
||||
#include "lix/libflakes/expr/flake.hh"
|
||||
#include "lix/libexpr/get-drvs.hh"
|
||||
#include "lix/libstore/store-api.hh"
|
||||
#include "lix/libstore/derivations.hh"
|
||||
|
||||
@@ -172,6 +172,7 @@ nix = executable(
|
||||
liblixstore,
|
||||
liblixfetchers,
|
||||
liblixexpr,
|
||||
liblixflakes,
|
||||
liblixmain,
|
||||
liblixcmd,
|
||||
boehm,
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
#include "lix/libstore/derivations.hh"
|
||||
#include "lix/libutil/archive.hh"
|
||||
#include "lix/libstore/builtins/buildenv.hh"
|
||||
#include "lix/libexpr/flake/flakeref.hh"
|
||||
#include "lix/libflakes/expr/flakeref.hh"
|
||||
#include "lix/libutil/regex.hh"
|
||||
#include "user-env.hh"
|
||||
#include "lix/libstore/profiles.hh"
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
#include "lix/libmain/common-args.hh"
|
||||
#include "lix/libmain/shared.hh"
|
||||
#include "lix/libexpr/eval.hh"
|
||||
#include "lix/libexpr/flake/flake.hh"
|
||||
#include "lix/libflakes/expr/flake.hh"
|
||||
#include "lix/libstore/store-api.hh"
|
||||
#include "lix/libfetchers/fetchers.hh"
|
||||
#include "lix/libutil/url-parts.hh"
|
||||
|
||||
+9
-3
@@ -112,6 +112,7 @@ endif
|
||||
|
||||
enable_nix_eval_jobs = get_option('nix-eval-jobs')
|
||||
enable_tests = get_option('enable-tests')
|
||||
enable_contrib_plugins = get_option('enable-contrib-plugins')
|
||||
|
||||
tests_args = []
|
||||
|
||||
@@ -229,12 +230,13 @@ is_x64 = host_machine.cpu_family() == 'x86_64'
|
||||
# This corresponds to the $(1)_ALLOW_UNDEFINED option from the Make buildsystem.
|
||||
# Mostly this is load-bearing on the plugin tests defined in tests/functional/plugins/meson.build.
|
||||
shared_module_link_args = []
|
||||
# This is a stricter additional set of link flags.
|
||||
strict_shared_module_link_args = []
|
||||
if is_darwin
|
||||
shared_module_link_args += ['-undefined', 'suppress', '-flat_namespace']
|
||||
strict_shared_module_link_args += ['-flat_namespace']
|
||||
elif is_linux
|
||||
# -Wl,-z,defs is the equivalent, but a comment in the Make buildsystem says that breaks
|
||||
# Clang sanitizers on Linux.
|
||||
# FIXME(Qyriad): is that true?
|
||||
strict_shared_module_link_args += ['-Wl,-z,defs']
|
||||
endif
|
||||
configdata = { }
|
||||
|
||||
@@ -694,6 +696,10 @@ if enable_tests
|
||||
subdir('tests/functional2')
|
||||
endif
|
||||
|
||||
if enable_contrib_plugins
|
||||
subdir('contrib/plugins')
|
||||
endif
|
||||
|
||||
subdir('meson/clang-tidy')
|
||||
|
||||
subproject('nix-eval-jobs', required : enable_nix_eval_jobs)
|
||||
|
||||
@@ -36,6 +36,10 @@ option('enable-tests', type : 'boolean', value : true,
|
||||
description : 'whether to enable tests or not (requires rapidcheck and gtest)',
|
||||
)
|
||||
|
||||
option('enable-contrib-plugins', type : 'boolean', value : true,
|
||||
description : 'whether to build contributed plugins'
|
||||
)
|
||||
|
||||
option('tests-color', type : 'boolean', value : true,
|
||||
description : 'set to false to disable color output in gtest',
|
||||
)
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ pre-commit-run {
|
||||
enable = true;
|
||||
package = pkgs.llvmPackages.libclang.python;
|
||||
entry = "${pkgs.llvmPackages.libclang.python}/bin/git-clang-format --binary ${pkgs.llvmPackages.clang-tools}/bin/clang-format";
|
||||
files = "^(lix/|tests/)";
|
||||
files = "^(lix/|tests/|contrib/plugins/)";
|
||||
types = [
|
||||
"c++"
|
||||
"file"
|
||||
|
||||
@@ -252,6 +252,7 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
./doc
|
||||
./lix
|
||||
./misc
|
||||
./contrib/plugins
|
||||
./COPYING
|
||||
]
|
||||
++ lib.optionals lintInsteadOfBuild [ ./.clang-tidy ]
|
||||
@@ -333,6 +334,10 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
yq
|
||||
lsof
|
||||
zstd
|
||||
# For mTLS tests.
|
||||
curl
|
||||
openssl
|
||||
python3
|
||||
]
|
||||
++ lib.optional hostPlatform.isLinux util-linuxMinimal
|
||||
++ lib.optional (!officialRelease && buildUnreleasedNotes) build-release-notes
|
||||
|
||||
@@ -10,7 +10,7 @@ if test -d "$TEST_ROOT"; then
|
||||
killDaemon
|
||||
rm -rf "$TEST_ROOT"
|
||||
fi
|
||||
mkdir "$TEST_ROOT"
|
||||
mkdir -p "$TEST_ROOT"
|
||||
|
||||
mkdir "$NIX_STORE_DIR"
|
||||
mkdir "$NIX_LOCALSTATE_DIR"
|
||||
|
||||
@@ -173,6 +173,12 @@ if get_option('default_library') != 'static'
|
||||
functional_tests_scripts += ['plugins.sh']
|
||||
endif
|
||||
|
||||
if get_option('default_library') != 'static' and get_option('enable-contrib-plugins')
|
||||
functional_tests_scripts += [
|
||||
'plugins/mtls/substituter-ssl-client-cert.sh'
|
||||
]
|
||||
endif
|
||||
|
||||
# TODO(Qyriad): this will hopefully be able to be removed when we remove the autoconf+Make
|
||||
# buildsystem. See the comments at the top of setup-functional-tests.py for why this is here.
|
||||
meson.add_install_script(
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
import http.server
|
||||
import ssl
|
||||
import socketserver
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
import textwrap
|
||||
from typing import Any
|
||||
|
||||
class NixCacheHandler(http.server.BaseHTTPRequestHandler):
|
||||
protocol_version: str = 'HTTP/1.1'
|
||||
|
||||
def do_GET(self) -> None:
|
||||
# Get client certificate information
|
||||
try:
|
||||
client_cert: dict[str, Any] | None = self.request.getpeercert()
|
||||
except Exception as e:
|
||||
print(f"Error getting client certificate: {e}", file=sys.stderr)
|
||||
self.send_error(403, "Invalid client certificate")
|
||||
return
|
||||
|
||||
if not client_cert:
|
||||
self.send_error(403, "No client certificate provided")
|
||||
return
|
||||
|
||||
# Additional validation - check if certificate chain is valid
|
||||
subject: tuple[tuple[tuple[str, str], ...], ...] | None = client_cert.get('subject')
|
||||
if not subject:
|
||||
self.send_error(403, "Invalid client certificate: No subject")
|
||||
return
|
||||
|
||||
# Log client info
|
||||
print(f"Client connected: {subject}", file=sys.stderr)
|
||||
print(f"Path requested: {self.path}", file=sys.stderr)
|
||||
|
||||
# Handle nix-cache-info endpoint
|
||||
if self.path == '/nix-cache-info':
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'text/plain')
|
||||
self.send_header('Connection', 'close') # Explicitly close after response
|
||||
test_root: str | None = os.environ.get('TEST_ROOT')
|
||||
if not test_root:
|
||||
store_root: str = '/nix/store'
|
||||
else:
|
||||
store_root = os.path.join(test_root, 'store')
|
||||
|
||||
# Nix cache info format
|
||||
cache_info: str = textwrap.dedent(f"""\
|
||||
StoreDir: {store_root}
|
||||
WantMassQuery: 1
|
||||
Priority: 30
|
||||
""")
|
||||
self.send_header('Content-Length', str(len(cache_info)))
|
||||
self.end_headers()
|
||||
self.wfile.write(cache_info.encode())
|
||||
self.wfile.flush() # Ensure data is sent
|
||||
|
||||
# Handle .narinfo requests
|
||||
elif self.path.endswith('.narinfo'):
|
||||
# Return 404 for all narinfo requests (empty cache)
|
||||
self.send_response(404)
|
||||
self.send_header('Content-Length', '0')
|
||||
self.send_header('Connection', 'close')
|
||||
self.end_headers()
|
||||
|
||||
else:
|
||||
self.send_response(404)
|
||||
self.send_header('Content-Length', '0')
|
||||
self.send_header('Connection', 'close')
|
||||
self.end_headers()
|
||||
|
||||
def log_message(self, format: str, *args: Any) -> None:
|
||||
# Suppress standard logging
|
||||
pass
|
||||
|
||||
def run_server(port_fifo_path: str, certfile: str, keyfile: str, ca_certfile: str) -> None:
|
||||
# Create SSL context
|
||||
context: ssl.SSLContext = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||
context.load_cert_chain(certfile=certfile, keyfile=keyfile)
|
||||
context.verify_mode = ssl.VerifyMode.CERT_REQUIRED
|
||||
context.check_hostname = False # We're not checking hostnames for client certs
|
||||
context.load_verify_locations(cafile=ca_certfile)
|
||||
|
||||
# Bind to a free port
|
||||
with socketserver.TCPServer(("localhost", 0), NixCacheHandler) as httpd:
|
||||
port = httpd.server_address[1] # Extract chosen port
|
||||
|
||||
# Wrap with TLS
|
||||
httpd.socket = context.wrap_socket(httpd.socket, server_side=True)
|
||||
|
||||
# Write the port to the FIFO
|
||||
with open(port_fifo_path, 'w') as fifo:
|
||||
fifo.write(f"{port}\n")
|
||||
fifo.flush()
|
||||
|
||||
print(f"Server running on port {port}", file=sys.stderr)
|
||||
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
httpd.shutdown()
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser: argparse.ArgumentParser = argparse.ArgumentParser(description='Nix binary cache server with SSL client verification')
|
||||
parser.add_argument('--port-fifo', type=str, required=True, help="FIFO where to inform about the port taken")
|
||||
parser.add_argument('--cert', required=True, help='Server certificate file')
|
||||
parser.add_argument('--key', required=True, help='Server private key file')
|
||||
parser.add_argument('--ca-cert', required=True, help='CA certificate for client verification')
|
||||
|
||||
args: argparse.Namespace = parser.parse_args()
|
||||
|
||||
run_server(args.port_fifo, args.cert, args.key, args.ca_cert)
|
||||
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# shellcheck source=common.sh
|
||||
source ../../common.sh
|
||||
|
||||
# Load the mTLS plugin for these tests.
|
||||
loadContribPlugin "mtls_store"
|
||||
|
||||
# Generate test certificates using EC keys for faster generation
|
||||
|
||||
# Generate CA with EC key
|
||||
openssl ecparam -genkey -name prime256v1 -out "$TEST_ROOT/ca.key" 2>/dev/null
|
||||
openssl req -new -x509 -days 1 -key "$TEST_ROOT/ca.key" -out "$TEST_ROOT/ca.crt" \
|
||||
-subj "/C=US/ST=Test/L=Test/O=TestCA/CN=Test CA" 2>/dev/null
|
||||
|
||||
# Generate server certificate with EC key
|
||||
openssl ecparam -genkey -name prime256v1 -out "$TEST_ROOT/server.key" 2>/dev/null
|
||||
openssl req -new -key "$TEST_ROOT/server.key" -out "$TEST_ROOT/server.csr" \
|
||||
-subj "/C=US/ST=Test/L=Test/O=TestServer/CN=localhost" 2>/dev/null
|
||||
openssl x509 -req -days 1 -in "$TEST_ROOT/server.csr" -CA "$TEST_ROOT/ca.crt" -CAkey "$TEST_ROOT/ca.key" \
|
||||
-set_serial 01 -out "$TEST_ROOT/server.crt" 2>/dev/null
|
||||
|
||||
# Generate client certificate with EC key
|
||||
openssl ecparam -genkey -name prime256v1 -out "$TEST_ROOT/client.key" 2>/dev/null
|
||||
openssl req -new -key "$TEST_ROOT/client.key" -out "$TEST_ROOT/client.csr" \
|
||||
-subj "/C=US/ST=Test/L=Test/O=TestClient/CN=Nix Test Client" 2>/dev/null
|
||||
openssl x509 -req -days 1 -in "$TEST_ROOT/client.csr" -CA "$TEST_ROOT/ca.crt" -CAkey "$TEST_ROOT/ca.key" \
|
||||
-set_serial 02 -out "$TEST_ROOT/client.crt" 2>/dev/null
|
||||
|
||||
# Start the server and have it write its chosen port to the FIFO
|
||||
FIFO_PATH="$TEST_ROOT/server-port.fifo"
|
||||
mkfifo "$FIFO_PATH"
|
||||
python3 "$PWD/nix-binary-cache-ssl-server.py" \
|
||||
--port-fifo "$FIFO_PATH" \
|
||||
--cert "$TEST_ROOT/server.crt" \
|
||||
--key "$TEST_ROOT/server.key" \
|
||||
--ca-cert "$TEST_ROOT/ca.crt" &
|
||||
SERVER_PID=$!
|
||||
|
||||
# Function to stop server on exit
|
||||
stopServer() {
|
||||
kill "$SERVER_PID" 2>/dev/null || true
|
||||
wait "$SERVER_PID" 2>/dev/null || true
|
||||
rm -f "$FIFO_PATH"
|
||||
}
|
||||
trap stopServer EXIT
|
||||
|
||||
# Read port from the FIFO (waits until server writes to it) but timeouts after 5s.
|
||||
if ! PORT=$(timeout 5s bash -c "read -r line < '$FIFO_PATH'; echo \"\$line\""); then
|
||||
echo "Timed out waiting for server to write port to FIFO" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! curl -sSf -k --cert "$TEST_ROOT/client.crt" --key "$TEST_ROOT/client.key" \
|
||||
"https://localhost:$PORT/nix-cache-info" > /dev/null; then
|
||||
if kill -0 "$SERVER_PID" 2>/dev/null; then
|
||||
echo "Server started but did not respond to curl" >&2
|
||||
else
|
||||
echo "Server failed to start" >&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 1: Verify server rejects connections without client certificate
|
||||
echo "Testing connection without client certificate (should fail)..." >&2
|
||||
if curl -s -k "https://localhost:$PORT/nix-cache-info" 2>&1 | grep -q "certificate required"; then
|
||||
echo "FAIL: Server should have rejected connection" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 2: Verify server accepts connections with client certificate
|
||||
echo "Testing connection with client certificate..." >&2
|
||||
RESPONSE=$(curl -v -s -k --cert "$TEST_ROOT/client.crt" --key "$TEST_ROOT/client.key" \
|
||||
"https://localhost:$PORT/nix-cache-info")
|
||||
|
||||
if ! echo "$RESPONSE" | grepQuiet "StoreDir: "; then
|
||||
echo "FAIL: Server should have accepted client certificate: $RESPONSE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 3: Test Nix with SSL client certificate parameters
|
||||
# Set up substituter URL with SSL parameters
|
||||
sslCache="https+mtls://localhost:$PORT?tls-certificate=$TEST_ROOT/client.crt&tls-private-key=$TEST_ROOT/client.key"
|
||||
|
||||
# Configure Nix to trust our CA
|
||||
export NIX_SSL_CERT_FILE="$TEST_ROOT/ca.crt"
|
||||
|
||||
# Test nix store info
|
||||
nix store ping --store "$sslCache" --json # | jq -e '.url' | grepQuiet "https://localhost:$PORT"
|
||||
|
||||
# Test 4: Verify incorrect client certificate is rejected
|
||||
# Generate a different client cert not signed by our CA (also using EC)
|
||||
openssl ecparam -genkey -name prime256v1 -out "$TEST_ROOT/wrong.key" 2>/dev/null
|
||||
openssl req -new -x509 -days 1 -key "$TEST_ROOT/wrong.key" -out "$TEST_ROOT/wrong.crt" \
|
||||
-subj "/C=US/ST=Test/L=Test/O=Wrong/CN=Wrong Client" 2>/dev/null
|
||||
|
||||
wrongCache="https+mtls://localhost:$PORT?tls-certificate=$TEST_ROOT/wrong.crt&tls-private-key=$TEST_ROOT/wrong.key"
|
||||
|
||||
rm -rf "$TEST_HOME"
|
||||
|
||||
# This should fail
|
||||
if nix store ping --download-attempts 0 --store "$wrongCache"; then
|
||||
echo "FAIL: Should have rejected wrong certificate" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,16 +1,20 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "lix/libexpr/flake/flakeref.hh"
|
||||
#include "lix/libflakes/expr/flakeref.hh"
|
||||
#include "tests/libexpr.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
class FlakeRefTest : public LibExprTest {};
|
||||
|
||||
|
||||
/* ----------- tests for flake/flakeref.hh --------------------------------------------------*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* to_string
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
TEST(to_string, doesntReencodeUrl) {
|
||||
TEST_F(FlakeRefTest, doesntReencodeUrl) {
|
||||
auto s = "http://localhost:8181/test/+3d.tar.gz";
|
||||
auto flakeref = parseFlakeRef(s);
|
||||
auto parsed = flakeref.to_string();
|
||||
+36
-1
@@ -207,7 +207,6 @@ libexpr_tests_sources = files(
|
||||
'libexpr/attr-path.cc',
|
||||
'libexpr/derived-path.cc',
|
||||
'libexpr/error_traces.cc',
|
||||
'libexpr/flakeref.cc',
|
||||
'libexpr/json.cc',
|
||||
'libexpr/primops.cc',
|
||||
'libexpr/search-path.cc',
|
||||
@@ -247,6 +246,42 @@ test(
|
||||
verbose : false,
|
||||
)
|
||||
|
||||
libflakes_tests_sources = files(
|
||||
'libflakes/flakeref.cc',
|
||||
)
|
||||
|
||||
libflakes_tester = executable(
|
||||
'liblixflakes-tests',
|
||||
libflakes_tests_sources,
|
||||
dependencies : [
|
||||
libasanoptions,
|
||||
liblixexpr_test_support,
|
||||
liblixstore_test_support,
|
||||
liblixstore,
|
||||
liblixutil,
|
||||
liblixexpr,
|
||||
liblixfetchers,
|
||||
liblixflakes,
|
||||
rapidcheck,
|
||||
gtest,
|
||||
nlohmann_json,
|
||||
kj
|
||||
],
|
||||
cpp_pch : cpp_pch
|
||||
)
|
||||
|
||||
test(
|
||||
'libflakes-unit-tests',
|
||||
libflakes_tester,
|
||||
args : tests_args,
|
||||
env : default_test_env + {
|
||||
'_NIX_TEST_UNIT_DATA': meson.project_source_root() / 'tests/unit/libflakes/data',
|
||||
},
|
||||
suite : 'check',
|
||||
protocol : 'gtest',
|
||||
verbose : false,
|
||||
)
|
||||
|
||||
libcmd_tester = executable(
|
||||
'liblixcmd-tests',
|
||||
files('libcmd/args.cc'),
|
||||
|
||||
Reference in New Issue
Block a user