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
|
forgejo: vigress8
|
||||||
github: vigress8
|
github: vigress8
|
||||||
|
|
||||||
|
vlaci:
|
||||||
|
github: vlaci
|
||||||
|
|
||||||
vlinkz:
|
vlinkz:
|
||||||
display_name: Victor Fuentes
|
display_name: Victor Fuentes
|
||||||
forgejo: vlinkz
|
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/libcmd/built-path.hh"
|
||||||
#include "lix/libexpr/eval.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/libexpr/get-drvs.hh"
|
||||||
#include "lix/libutil/types.hh"
|
#include "lix/libutil/types.hh"
|
||||||
#include "lix/libutil/json-fwd.hh"
|
#include "lix/libutil/json-fwd.hh"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "lix/libcmd/command.hh"
|
#include "lix/libcmd/command.hh"
|
||||||
|
#include "lix/libflakes/init.hh"
|
||||||
#include "lix/libstore/store-api.hh"
|
#include "lix/libstore/store-api.hh"
|
||||||
#include "lix/libstore/local-fs-store.hh"
|
#include "lix/libstore/local-fs-store.hh"
|
||||||
#include "lix/libstore/derivations.hh"
|
#include "lix/libstore/derivations.hh"
|
||||||
@@ -119,6 +120,8 @@ ref<eval_cache::CachingEvaluator> EvalCommand::getEvaluator()
|
|||||||
startReplOnEvalErrors ? AbstractNixRepl::runSimple : nullptr
|
startReplOnEvalErrors ? AbstractNixRepl::runSimple : nullptr
|
||||||
);
|
);
|
||||||
|
|
||||||
|
flake::initFlakes(evalState.get());
|
||||||
|
|
||||||
evalState->repair = repair;
|
evalState->repair = repair;
|
||||||
}
|
}
|
||||||
return ref<eval_cache::CachingEvaluator>::unsafeFromPtr(evalState);
|
return ref<eval_cache::CachingEvaluator>::unsafeFromPtr(evalState);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#include "lix/libutil/args.hh"
|
#include "lix/libutil/args.hh"
|
||||||
#include "lix/libcmd/common-eval-args.hh"
|
#include "lix/libcmd/common-eval-args.hh"
|
||||||
#include "lix/libstore/path.hh"
|
#include "lix/libstore/path.hh"
|
||||||
#include "lix/libexpr/flake/lockfile.hh"
|
#include "lix/libflakes/expr/lockfile.hh"
|
||||||
#include "lix/libutil/async.hh"
|
#include "lix/libutil/async.hh"
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#include "lix/libexpr/eval.hh"
|
#include "lix/libexpr/eval.hh"
|
||||||
#include "lix/libfetchers/fetchers.hh"
|
#include "lix/libfetchers/fetchers.hh"
|
||||||
#include "lix/libfetchers/registry.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/libstore/store-api.hh"
|
||||||
#include "lix/libcmd/command.hh"
|
#include "lix/libcmd/command.hh"
|
||||||
#include "lix/libutil/async.hh"
|
#include "lix/libutil/async.hh"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#include "lix/libcmd/common-eval-args.hh"
|
#include "lix/libcmd/common-eval-args.hh"
|
||||||
#include "lix/libexpr/eval.hh"
|
#include "lix/libexpr/eval.hh"
|
||||||
#include "lix/libexpr/get-drvs.hh"
|
#include "lix/libexpr/get-drvs.hh"
|
||||||
#include "lix/libexpr/flake/flake.hh"
|
#include "lix/libflakes/expr/flake.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#include "lix/libexpr/attr-path.hh"
|
#include "lix/libexpr/attr-path.hh"
|
||||||
#include "lix/libcmd/common-eval-args.hh"
|
#include "lix/libcmd/common-eval-args.hh"
|
||||||
#include "lix/libexpr/eval.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 "lix/libexpr/eval-cache.hh"
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#include "lix/libcmd/installables.hh"
|
#include "lix/libcmd/installables.hh"
|
||||||
#include "lix/libexpr/eval-cache.hh"
|
#include "lix/libexpr/eval-cache.hh"
|
||||||
#include "lix/libexpr/eval.hh"
|
#include "lix/libexpr/eval.hh"
|
||||||
#include "lix/libexpr/flake/flake.hh"
|
#include "lix/libflakes/expr/flake.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
#include "lix/libexpr/eval-settings.hh"
|
#include "lix/libexpr/eval-settings.hh"
|
||||||
#include "lix/libstore/store-api.hh"
|
#include "lix/libstore/store-api.hh"
|
||||||
#include "lix/libmain/shared.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/libexpr/eval-cache.hh"
|
||||||
#include "lix/libfetchers/registry.hh"
|
#include "lix/libfetchers/registry.hh"
|
||||||
#include "lix/libstore/build-result.hh"
|
#include "lix/libstore/build-result.hh"
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ libcmd = library(
|
|||||||
liblixstore,
|
liblixstore,
|
||||||
liblixfetchers,
|
liblixfetchers,
|
||||||
liblixexpr,
|
liblixexpr,
|
||||||
|
liblixflakes,
|
||||||
liblixmain,
|
liblixmain,
|
||||||
liblix_doc,
|
liblix_doc,
|
||||||
boehm,
|
boehm,
|
||||||
@@ -76,6 +77,7 @@ liblixcmd = declare_dependency(
|
|||||||
dependencies : [
|
dependencies : [
|
||||||
liblixutil,
|
liblixutil,
|
||||||
liblixstore,
|
liblixstore,
|
||||||
|
liblixflakes,
|
||||||
kj,
|
kj,
|
||||||
],
|
],
|
||||||
link_with : libcmd,
|
link_with : libcmd,
|
||||||
|
|||||||
+2
-2
@@ -22,8 +22,8 @@
|
|||||||
#include "lix/libexpr/get-drvs.hh"
|
#include "lix/libexpr/get-drvs.hh"
|
||||||
#include "lix/libstore/derivations.hh"
|
#include "lix/libstore/derivations.hh"
|
||||||
#include "lix/libstore/globals.hh"
|
#include "lix/libstore/globals.hh"
|
||||||
#include "lix/libexpr/flake/flake.hh"
|
#include "lix/libflakes/expr/flake.hh"
|
||||||
#include "lix/libexpr/flake/lockfile.hh"
|
#include "lix/libflakes/expr/lockfile.hh"
|
||||||
#include "lix/libcmd/editor-for.hh"
|
#include "lix/libcmd/editor-for.hh"
|
||||||
#include "lix/libutil/finally.hh"
|
#include "lix/libutil/finally.hh"
|
||||||
#include "lix/libcmd/markdown.hh"
|
#include "lix/libcmd/markdown.hh"
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ renameInGlobalScope: false
|
|||||||
Load, parse and return the Nix expression in the file *path*.
|
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
|
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
|
`__toString` attribute or a `outPath` attribute (as derivations typically have).
|
||||||
inputs typically have).
|
|
||||||
|
|
||||||
If *path* is a directory, the file `default.nix` in that directory
|
If *path* is a directory, the file `default.nix` in that directory
|
||||||
is loaded.
|
is loaded.
|
||||||
|
|||||||
@@ -6,48 +6,12 @@
|
|||||||
|
|
||||||
namespace nix {
|
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()
|
EvalSettings::EvalSettings()
|
||||||
{
|
{
|
||||||
auto var = getEnv("NIX_PATH");
|
//auto var = getEnv("NIX_PATH");
|
||||||
if (var) nixPath.setDefault(parseNixPath(*var));
|
// TODO(flakes): move that allowed prefix and parse it from eval plugins.
|
||||||
|
//if (var) nixPath.setDefault(parseNixPath(*var, ));
|
||||||
}
|
}
|
||||||
|
|
||||||
Strings EvalSettings::getDefaultNixPath()
|
Strings EvalSettings::getDefaultNixPath()
|
||||||
|
|||||||
+83
-25
@@ -21,7 +21,7 @@
|
|||||||
#include "lix/libexpr/print.hh"
|
#include "lix/libexpr/print.hh"
|
||||||
#include "lix/libexpr/gc-small-vector.hh"
|
#include "lix/libexpr/gc-small-vector.hh"
|
||||||
#include "lix/libfetchers/fetch-to-store.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/exit.hh"
|
||||||
#include "lix/libutil/json.hh"
|
#include "lix/libutil/json.hh"
|
||||||
#include "symbol-table.hh"
|
#include "symbol-table.hh"
|
||||||
@@ -318,14 +318,16 @@ EvalBuiltins::EvalBuiltins(
|
|||||||
EvalPaths::EvalPaths(
|
EvalPaths::EvalPaths(
|
||||||
AsyncIoRoot & aio,
|
AsyncIoRoot & aio,
|
||||||
const ref<Store> & store,
|
const ref<Store> & store,
|
||||||
SearchPath searchPath,
|
SearchPath initialSearchPath,
|
||||||
|
bool pureEval,
|
||||||
EvalErrorContext & errors
|
EvalErrorContext & errors
|
||||||
)
|
)
|
||||||
: store(store)
|
: store(store)
|
||||||
, searchPath_(std::move(searchPath))
|
, searchPath_(std::move(initialSearchPath))
|
||||||
|
, pureEval(pureEval)
|
||||||
, errors(errors)
|
, errors(errors)
|
||||||
{
|
{
|
||||||
if (evalSettings.restrictEval || evalSettings.pureEval) {
|
if (evalSettings.restrictEval || pureEval) {
|
||||||
allowedPaths = AllowedPath{.allowAllChildren = false};
|
allowedPaths = AllowedPath{.allowAllChildren = false};
|
||||||
|
|
||||||
for (auto & i : searchPath_.elements) {
|
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(
|
Evaluator::Evaluator(
|
||||||
AsyncIoRoot & aio,
|
AsyncIoRoot & aio,
|
||||||
const SearchPath & _searchPath,
|
const SearchPath & _searchPath,
|
||||||
@@ -357,16 +423,7 @@ Evaluator::Evaluator(
|
|||||||
std::function<ReplExitStatus(EvalState & es, ValMap const & extraEnv)> debugRepl
|
std::function<ReplExitStatus(EvalState & es, ValMap const & extraEnv)> debugRepl
|
||||||
)
|
)
|
||||||
: s(symbols)
|
: s(symbols)
|
||||||
, paths(aio, store, [&] {
|
, paths(aio, store, _searchPath, evalSettings.pureEval, errors)
|
||||||
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)
|
|
||||||
, builtins(mem, symbols, paths.searchPath(), store->config().storeDir)
|
, builtins(mem, symbols, paths.searchPath(), store->config().storeDir)
|
||||||
, repair(NoRepair)
|
, repair(NoRepair)
|
||||||
, store(store)
|
, store(store)
|
||||||
@@ -392,6 +449,11 @@ Evaluator::Evaluator(
|
|||||||
box_ptr<EvalState> Evaluator::begin(AsyncIoRoot & aio)
|
box_ptr<EvalState> Evaluator::begin(AsyncIoRoot & aio)
|
||||||
{
|
{
|
||||||
assert(!activeEval);
|
assert(!activeEval);
|
||||||
|
if (auto envNixPath = getEnvNonEmpty("NIX_PATH")) {
|
||||||
|
paths.parseAndConfigureEnvironmentNixPath(*envNixPath);
|
||||||
|
} else {
|
||||||
|
paths.configureSettingNixPath(evalSettings.nixPath);
|
||||||
|
}
|
||||||
return box_ptr<EvalState>::unsafeFromNonnull(
|
return box_ptr<EvalState>::unsafeFromNonnull(
|
||||||
std::unique_ptr<EvalState>(new EvalState(aio, *this))
|
std::unique_ptr<EvalState>(new EvalState(aio, *this))
|
||||||
);
|
);
|
||||||
@@ -2972,7 +3034,12 @@ try {
|
|||||||
|
|
||||||
std::optional<std::string> res;
|
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 {
|
try {
|
||||||
auto storePath = TRY_AWAIT(fetchers::downloadTarball(
|
auto storePath = TRY_AWAIT(fetchers::downloadTarball(
|
||||||
store, EvalSettings::resolvePseudoUrl(value), "source", false)).tree.storePath;
|
store, EvalSettings::resolvePseudoUrl(value), "source", false)).tree.storePath;
|
||||||
@@ -2984,16 +3051,7 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (value.starts_with("flake:")) {
|
else if (!res) {
|
||||||
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 {
|
|
||||||
auto path = absPath(value);
|
auto path = absPath(value);
|
||||||
if (pathExists(path))
|
if (pathExists(path))
|
||||||
res = { path };
|
res = { path };
|
||||||
|
|||||||
+28
-1
@@ -320,20 +320,47 @@ struct EvalErrorContext
|
|||||||
|
|
||||||
class EvalPaths
|
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;
|
ref<Store> store;
|
||||||
|
std::set<std::string> allowedNixPathPrefixes;
|
||||||
|
std::vector<Responder> searchPathResponders;
|
||||||
SearchPath searchPath_;
|
SearchPath searchPath_;
|
||||||
|
bool pureEval;
|
||||||
EvalErrorContext & errors;
|
EvalErrorContext & errors;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
EvalPaths(
|
EvalPaths(
|
||||||
AsyncIoRoot & aio,
|
AsyncIoRoot & aio,
|
||||||
const ref<Store> & store,
|
const ref<Store> & store,
|
||||||
SearchPath searchPath,
|
SearchPath initialSearchPath,
|
||||||
|
bool pureEval,
|
||||||
EvalErrorContext & errors
|
EvalErrorContext & errors
|
||||||
);
|
);
|
||||||
|
|
||||||
|
void parseAndConfigureEnvironmentNixPath(std::string const & value);
|
||||||
|
void configureSettingNixPath(std::list<std::string> const & value);
|
||||||
|
|
||||||
const SearchPath & searchPath() const { return searchPath_; }
|
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:
|
private:
|
||||||
struct AllowedPath
|
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_hasContext(EvalState & state, Value * * args, Value & v);
|
||||||
void prim_unsafeDiscardOutputDependency(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',
|
install_dir : includedir / 'lix/libexpr',
|
||||||
)
|
)
|
||||||
endforeach
|
endforeach
|
||||||
subdir('flake')
|
|
||||||
|
|
||||||
libexpr_setting_definitions = files(
|
libexpr_setting_definitions = files(
|
||||||
# keep-sorted start
|
# keep-sorted start
|
||||||
@@ -79,7 +78,6 @@ builtin_definitions = files(
|
|||||||
'builtins/filter.md',
|
'builtins/filter.md',
|
||||||
'builtins/filterSource.md',
|
'builtins/filterSource.md',
|
||||||
'builtins/findFile.md',
|
'builtins/findFile.md',
|
||||||
'builtins/flakeRefToString.md',
|
|
||||||
'builtins/floor.md',
|
'builtins/floor.md',
|
||||||
'builtins/foldlStrict.md',
|
'builtins/foldlStrict.md',
|
||||||
'builtins/fromJSON.md',
|
'builtins/fromJSON.md',
|
||||||
@@ -90,7 +88,6 @@ builtin_definitions = files(
|
|||||||
'builtins/getAttr.md',
|
'builtins/getAttr.md',
|
||||||
'builtins/getContext.md',
|
'builtins/getContext.md',
|
||||||
'builtins/getEnv.md',
|
'builtins/getEnv.md',
|
||||||
'builtins/getFlake.md',
|
|
||||||
'builtins/groupBy.md',
|
'builtins/groupBy.md',
|
||||||
'builtins/hasAttr.md',
|
'builtins/hasAttr.md',
|
||||||
'builtins/hasContext.md',
|
'builtins/hasContext.md',
|
||||||
@@ -116,7 +113,6 @@ builtin_definitions = files(
|
|||||||
'builtins/match.md',
|
'builtins/match.md',
|
||||||
'builtins/mul.md',
|
'builtins/mul.md',
|
||||||
'builtins/parseDrvName.md',
|
'builtins/parseDrvName.md',
|
||||||
'builtins/parseFlakeRef.md',
|
|
||||||
'builtins/partition.md',
|
'builtins/partition.md',
|
||||||
'builtins/path.md',
|
'builtins/path.md',
|
||||||
'builtins/pathExists.md',
|
'builtins/pathExists.md',
|
||||||
@@ -207,10 +203,6 @@ libexpr_sources = files(
|
|||||||
'eval-error.cc',
|
'eval-error.cc',
|
||||||
'eval-settings.cc',
|
'eval-settings.cc',
|
||||||
'eval.cc',
|
'eval.cc',
|
||||||
'flake/config.cc',
|
|
||||||
'flake/flake.cc',
|
|
||||||
'flake/flakeref.cc',
|
|
||||||
'flake/lockfile.cc',
|
|
||||||
'function-trace.cc',
|
'function-trace.cc',
|
||||||
'gc-alloc.cc',
|
'gc-alloc.cc',
|
||||||
'get-drvs.cc',
|
'get-drvs.cc',
|
||||||
@@ -242,9 +234,6 @@ libexpr_headers = files(
|
|||||||
'eval-inline.hh',
|
'eval-inline.hh',
|
||||||
'eval-settings.hh',
|
'eval-settings.hh',
|
||||||
'eval.hh',
|
'eval.hh',
|
||||||
'flake/flake.hh',
|
|
||||||
'flake/flakeref.hh',
|
|
||||||
'flake/lockfile.hh',
|
|
||||||
'function-trace.hh',
|
'function-trace.hh',
|
||||||
'gc-alloc.hh',
|
'gc-alloc.hh',
|
||||||
'gc-small-vector.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.
|
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.
|
- 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 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`.
|
- 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.
|
- [`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/logging.hh"
|
||||||
#include "lix/libutil/json.hh"
|
#include "lix/libutil/json.hh"
|
||||||
#include "lix/libutil/users.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.hh"
|
||||||
#include "lix/libexpr/eval-settings.hh"
|
#include "lix/libexpr/eval-settings.hh"
|
||||||
#include "lix/libexpr/extra-primops.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/libstore/store-api.hh"
|
||||||
#include "lix/libfetchers/fetchers.hh"
|
#include "lix/libfetchers/fetchers.hh"
|
||||||
#include "lix/libutil/async.hh"
|
#include "lix/libutil/async.hh"
|
||||||
@@ -994,6 +995,12 @@ void prim_getFlake(EvalState & state, Value * * args, Value & v)
|
|||||||
v);
|
v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static RegisterPrimOp primop_getFlake(PrimOp{{
|
||||||
|
.name = "getFlake",
|
||||||
|
.arity = 1,
|
||||||
|
.fun = prim_getFlake,
|
||||||
|
}});
|
||||||
|
|
||||||
void prim_parseFlakeRef(
|
void prim_parseFlakeRef(
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
Value * * args,
|
Value * * args,
|
||||||
@@ -1015,6 +1022,12 @@ void prim_parseFlakeRef(
|
|||||||
v.mkAttrs(binds);
|
v.mkAttrs(binds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static RegisterPrimOp primop_parseFlakeRef(PrimOp{{
|
||||||
|
.name = "parseFlakeRef",
|
||||||
|
.arity = 1,
|
||||||
|
.fun = prim_parseFlakeRef,
|
||||||
|
}});
|
||||||
|
|
||||||
void prim_flakeRefToString(
|
void prim_flakeRefToString(
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
Value * * args,
|
Value * * args,
|
||||||
@@ -1053,6 +1066,12 @@ void prim_flakeRefToString(
|
|||||||
v.mkString(flakeRef.to_string());
|
v.mkString(flakeRef.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static RegisterPrimOp primop_flakeRefToString(PrimOp{{
|
||||||
|
.name = "flakeRefToString",
|
||||||
|
.arity = 1,
|
||||||
|
.fun = prim_flakeRefToString,
|
||||||
|
}});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Fingerprint LockedFlake::getFingerprint() const
|
Fingerprint LockedFlake::getFingerprint() const
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
#include "lix/libexpr/eval.hh"
|
#include "lix/libexpr/eval.hh"
|
||||||
#include "lix/libutil/types.hh"
|
#include "lix/libutil/types.hh"
|
||||||
#include "lix/libexpr/flake/flakeref.hh"
|
#include "lix/libflakes/expr/flakeref.hh"
|
||||||
#include "lix/libexpr/flake/lockfile.hh"
|
#include "lix/libflakes/expr/lockfile.hh"
|
||||||
#include "lix/libexpr/value.hh"
|
#include "lix/libexpr/value.hh"
|
||||||
|
|
||||||
namespace nix {
|
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/libstore/store-api.hh"
|
||||||
#include "lix/libutil/async.hh"
|
#include "lix/libutil/async.hh"
|
||||||
#include "lix/libutil/regex.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/libstore/store-api.hh"
|
||||||
#include "lix/libutil/error.hh"
|
#include "lix/libutil/error.hh"
|
||||||
#include "lix/libutil/json.hh"
|
#include "lix/libutil/json.hh"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include "lix/libexpr/flake/flakeref.hh"
|
#include "lix/libflakes/expr/flakeref.hh"
|
||||||
#include "lix/libutil/json-fwd.hh"
|
#include "lix/libutil/json-fwd.hh"
|
||||||
|
|
||||||
namespace nix {
|
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"\'' ],
|
command : [ 'bash', '-c', 'echo \'R"__NIX_STR(\' | cat - @INPUT@ && echo \')__NIX_STR"\'' ],
|
||||||
input : 'call-flake.nix',
|
input : 'call-flake.nix',
|
||||||
output : '@PLAINNAME@.gen.hh',
|
output : '@PLAINNAME@.gen.hh',
|
||||||
capture : true,
|
capture : true,
|
||||||
install : 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')
|
subdir('libfetchers')
|
||||||
# libexpr depends on all of the above
|
# libexpr depends on all of the above
|
||||||
subdir('libexpr')
|
subdir('libexpr')
|
||||||
|
# libflakes depends on all of the above
|
||||||
|
subdir('libflakes')
|
||||||
# libmain depends on libutil and libstore
|
# libmain depends on libutil and libstore
|
||||||
subdir('libmain')
|
subdir('libmain')
|
||||||
# libcmd depends on everything
|
# libcmd depends on everything
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
#include "lix/libexpr/eval.hh"
|
#include "lix/libexpr/eval.hh"
|
||||||
#include "lix/libexpr/eval-inline.hh" // IWYU pragma: keep
|
#include "lix/libexpr/eval-inline.hh" // IWYU pragma: keep
|
||||||
#include "lix/libexpr/eval-settings.hh"
|
#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/libexpr/get-drvs.hh"
|
||||||
#include "lix/libstore/store-api.hh"
|
#include "lix/libstore/store-api.hh"
|
||||||
#include "lix/libstore/derivations.hh"
|
#include "lix/libstore/derivations.hh"
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ nix = executable(
|
|||||||
liblixstore,
|
liblixstore,
|
||||||
liblixfetchers,
|
liblixfetchers,
|
||||||
liblixexpr,
|
liblixexpr,
|
||||||
|
liblixflakes,
|
||||||
liblixmain,
|
liblixmain,
|
||||||
liblixcmd,
|
liblixcmd,
|
||||||
boehm,
|
boehm,
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
#include "lix/libstore/derivations.hh"
|
#include "lix/libstore/derivations.hh"
|
||||||
#include "lix/libutil/archive.hh"
|
#include "lix/libutil/archive.hh"
|
||||||
#include "lix/libstore/builtins/buildenv.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 "lix/libutil/regex.hh"
|
||||||
#include "user-env.hh"
|
#include "user-env.hh"
|
||||||
#include "lix/libstore/profiles.hh"
|
#include "lix/libstore/profiles.hh"
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
#include "lix/libmain/common-args.hh"
|
#include "lix/libmain/common-args.hh"
|
||||||
#include "lix/libmain/shared.hh"
|
#include "lix/libmain/shared.hh"
|
||||||
#include "lix/libexpr/eval.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/libstore/store-api.hh"
|
||||||
#include "lix/libfetchers/fetchers.hh"
|
#include "lix/libfetchers/fetchers.hh"
|
||||||
#include "lix/libutil/url-parts.hh"
|
#include "lix/libutil/url-parts.hh"
|
||||||
|
|||||||
+9
-3
@@ -112,6 +112,7 @@ endif
|
|||||||
|
|
||||||
enable_nix_eval_jobs = get_option('nix-eval-jobs')
|
enable_nix_eval_jobs = get_option('nix-eval-jobs')
|
||||||
enable_tests = get_option('enable-tests')
|
enable_tests = get_option('enable-tests')
|
||||||
|
enable_contrib_plugins = get_option('enable-contrib-plugins')
|
||||||
|
|
||||||
tests_args = []
|
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.
|
# 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.
|
# Mostly this is load-bearing on the plugin tests defined in tests/functional/plugins/meson.build.
|
||||||
shared_module_link_args = []
|
shared_module_link_args = []
|
||||||
|
# This is a stricter additional set of link flags.
|
||||||
|
strict_shared_module_link_args = []
|
||||||
if is_darwin
|
if is_darwin
|
||||||
shared_module_link_args += ['-undefined', 'suppress', '-flat_namespace']
|
shared_module_link_args += ['-undefined', 'suppress', '-flat_namespace']
|
||||||
|
strict_shared_module_link_args += ['-flat_namespace']
|
||||||
elif is_linux
|
elif is_linux
|
||||||
# -Wl,-z,defs is the equivalent, but a comment in the Make buildsystem says that breaks
|
strict_shared_module_link_args += ['-Wl,-z,defs']
|
||||||
# Clang sanitizers on Linux.
|
|
||||||
# FIXME(Qyriad): is that true?
|
|
||||||
endif
|
endif
|
||||||
configdata = { }
|
configdata = { }
|
||||||
|
|
||||||
@@ -694,6 +696,10 @@ if enable_tests
|
|||||||
subdir('tests/functional2')
|
subdir('tests/functional2')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if enable_contrib_plugins
|
||||||
|
subdir('contrib/plugins')
|
||||||
|
endif
|
||||||
|
|
||||||
subdir('meson/clang-tidy')
|
subdir('meson/clang-tidy')
|
||||||
|
|
||||||
subproject('nix-eval-jobs', required : enable_nix_eval_jobs)
|
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)',
|
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,
|
option('tests-color', type : 'boolean', value : true,
|
||||||
description : 'set to false to disable color output in gtest',
|
description : 'set to false to disable color output in gtest',
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@ pre-commit-run {
|
|||||||
enable = true;
|
enable = true;
|
||||||
package = pkgs.llvmPackages.libclang.python;
|
package = pkgs.llvmPackages.libclang.python;
|
||||||
entry = "${pkgs.llvmPackages.libclang.python}/bin/git-clang-format --binary ${pkgs.llvmPackages.clang-tools}/bin/clang-format";
|
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 = [
|
types = [
|
||||||
"c++"
|
"c++"
|
||||||
"file"
|
"file"
|
||||||
|
|||||||
@@ -252,6 +252,7 @@ stdenv.mkDerivation (finalAttrs: {
|
|||||||
./doc
|
./doc
|
||||||
./lix
|
./lix
|
||||||
./misc
|
./misc
|
||||||
|
./contrib/plugins
|
||||||
./COPYING
|
./COPYING
|
||||||
]
|
]
|
||||||
++ lib.optionals lintInsteadOfBuild [ ./.clang-tidy ]
|
++ lib.optionals lintInsteadOfBuild [ ./.clang-tidy ]
|
||||||
@@ -333,6 +334,10 @@ stdenv.mkDerivation (finalAttrs: {
|
|||||||
yq
|
yq
|
||||||
lsof
|
lsof
|
||||||
zstd
|
zstd
|
||||||
|
# For mTLS tests.
|
||||||
|
curl
|
||||||
|
openssl
|
||||||
|
python3
|
||||||
]
|
]
|
||||||
++ lib.optional hostPlatform.isLinux util-linuxMinimal
|
++ lib.optional hostPlatform.isLinux util-linuxMinimal
|
||||||
++ lib.optional (!officialRelease && buildUnreleasedNotes) build-release-notes
|
++ lib.optional (!officialRelease && buildUnreleasedNotes) build-release-notes
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ if test -d "$TEST_ROOT"; then
|
|||||||
killDaemon
|
killDaemon
|
||||||
rm -rf "$TEST_ROOT"
|
rm -rf "$TEST_ROOT"
|
||||||
fi
|
fi
|
||||||
mkdir "$TEST_ROOT"
|
mkdir -p "$TEST_ROOT"
|
||||||
|
|
||||||
mkdir "$NIX_STORE_DIR"
|
mkdir "$NIX_STORE_DIR"
|
||||||
mkdir "$NIX_LOCALSTATE_DIR"
|
mkdir "$NIX_LOCALSTATE_DIR"
|
||||||
|
|||||||
@@ -173,6 +173,12 @@ if get_option('default_library') != 'static'
|
|||||||
functional_tests_scripts += ['plugins.sh']
|
functional_tests_scripts += ['plugins.sh']
|
||||||
endif
|
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
|
# 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.
|
# buildsystem. See the comments at the top of setup-functional-tests.py for why this is here.
|
||||||
meson.add_install_script(
|
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 <gtest/gtest.h>
|
||||||
|
|
||||||
#include "lix/libexpr/flake/flakeref.hh"
|
#include "lix/libflakes/expr/flakeref.hh"
|
||||||
|
#include "tests/libexpr.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
class FlakeRefTest : public LibExprTest {};
|
||||||
|
|
||||||
|
|
||||||
/* ----------- tests for flake/flakeref.hh --------------------------------------------------*/
|
/* ----------- tests for flake/flakeref.hh --------------------------------------------------*/
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
* to_string
|
* to_string
|
||||||
* --------------------------------------------------------------------------*/
|
* --------------------------------------------------------------------------*/
|
||||||
|
|
||||||
TEST(to_string, doesntReencodeUrl) {
|
TEST_F(FlakeRefTest, doesntReencodeUrl) {
|
||||||
auto s = "http://localhost:8181/test/+3d.tar.gz";
|
auto s = "http://localhost:8181/test/+3d.tar.gz";
|
||||||
auto flakeref = parseFlakeRef(s);
|
auto flakeref = parseFlakeRef(s);
|
||||||
auto parsed = flakeref.to_string();
|
auto parsed = flakeref.to_string();
|
||||||
+36
-1
@@ -207,7 +207,6 @@ libexpr_tests_sources = files(
|
|||||||
'libexpr/attr-path.cc',
|
'libexpr/attr-path.cc',
|
||||||
'libexpr/derived-path.cc',
|
'libexpr/derived-path.cc',
|
||||||
'libexpr/error_traces.cc',
|
'libexpr/error_traces.cc',
|
||||||
'libexpr/flakeref.cc',
|
|
||||||
'libexpr/json.cc',
|
'libexpr/json.cc',
|
||||||
'libexpr/primops.cc',
|
'libexpr/primops.cc',
|
||||||
'libexpr/search-path.cc',
|
'libexpr/search-path.cc',
|
||||||
@@ -247,6 +246,42 @@ test(
|
|||||||
verbose : false,
|
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(
|
libcmd_tester = executable(
|
||||||
'liblixcmd-tests',
|
'liblixcmd-tests',
|
||||||
files('libcmd/args.cc'),
|
files('libcmd/args.cc'),
|
||||||
|
|||||||
Reference in New Issue
Block a user