Initial work on substituter

This commit is contained in:
Artemis Tosini 2024-07-08 21:43:17 +00:00
parent 8d04ca8d3d
commit 6512b8d2cc
Signed by: artemist
GPG key ID: EE5227935FE3FF18
4 changed files with 648 additions and 0 deletions

281
Cargo.lock generated
View file

@ -134,6 +134,12 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bitflags"
version = "1.3.2"
@ -146,6 +152,15 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.16.0"
@ -177,11 +192,22 @@ dependencies = [
"serde",
]
[[package]]
name = "camino"
version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239"
[[package]]
name = "cc"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd"
dependencies = [
"jobserver",
"libc",
"once_cell",
]
[[package]]
name = "cfg-if"
@ -282,6 +308,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]]
name = "const-oid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "core-foundation"
version = "0.9.4"
@ -298,6 +330,52 @@ version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "cpufeatures"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "curve25519-dalek"
version = "4.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
dependencies = [
"cfg-if",
"cpufeatures",
"curve25519-dalek-derive",
"digest",
"fiat-crypto",
"rustc_version",
"subtle",
"zeroize",
]
[[package]]
name = "curve25519-dalek-derive"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "darling"
version = "0.20.8"
@ -333,6 +411,16 @@ dependencies = [
"syn",
]
[[package]]
name = "der"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
dependencies = [
"const-oid",
"zeroize",
]
[[package]]
name = "deranged"
version = "0.3.11"
@ -343,6 +431,16 @@ dependencies = [
"serde",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "dirs"
version = "5.0.1"
@ -391,6 +489,31 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "ed25519"
version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
"pkcs8",
"serde",
"signature",
]
[[package]]
name = "ed25519-dalek"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"
dependencies = [
"curve25519-dalek",
"ed25519",
"serde",
"sha2",
"subtle",
"zeroize",
]
[[package]]
name = "either"
version = "1.11.0"
@ -459,6 +582,12 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
[[package]]
name = "fiat-crypto"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]]
name = "filetime"
version = "0.2.23"
@ -553,6 +682,16 @@ dependencies = [
"slab",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.14"
@ -817,6 +956,15 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
[[package]]
name = "is_executable"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa9acdc6d67b75e626ad644734e8bc6df893d9cd2a834129065d3dd6158ea9c8"
dependencies = [
"winapi",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.0"
@ -829,6 +977,15 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "jobserver"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e"
dependencies = [
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.69"
@ -877,17 +1034,20 @@ name = "lix-installer"
version = "0.17.1"
dependencies = [
"async-trait",
"base64 0.22.1",
"bytes 1.6.0",
"clap",
"color-eyre",
"dirs",
"dyn-clone",
"ed25519-dalek",
"eyre",
"glob",
"indexmap 2.2.6",
"is_ci",
"nix",
"nix-config-parser",
"nix-nar",
"os-release",
"owo-colors 4.0.0",
"plist",
@ -897,6 +1057,7 @@ dependencies = [
"serde",
"serde_json",
"serde_with",
"sha2",
"strum",
"sysctl",
"tar",
@ -914,6 +1075,7 @@ dependencies = [
"walkdir",
"which",
"xz2",
"zstd",
]
[[package]]
@ -1007,6 +1169,18 @@ dependencies = [
"thiserror",
]
[[package]]
name = "nix-nar"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5549158a8b179c4fcd06a19f4bcc557db60c9cbd6771add9563f46c8d0325b5"
dependencies = [
"camino",
"is_executable",
"symlink",
"thiserror",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@ -1140,6 +1314,16 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkcs8"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
"spki",
]
[[package]]
name = "pkg-config"
version = "0.3.30"
@ -1367,6 +1551,15 @@ version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "0.38.34"
@ -1574,6 +1767,17 @@ dependencies = [
"syn",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
@ -1592,6 +1796,15 @@ dependencies = [
"libc",
]
[[package]]
name = "signature"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"rand_core",
]
[[package]]
name = "slab"
version = "0.4.9"
@ -1623,6 +1836,16 @@ version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "spki"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
"der",
]
[[package]]
name = "strsim"
version = "0.10.0"
@ -1657,6 +1880,12 @@ dependencies = [
"syn",
]
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "supports-color"
version = "2.1.0"
@ -1667,6 +1896,12 @@ dependencies = [
"is_ci",
]
[[package]]
name = "symlink"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a"
[[package]]
name = "syn"
version = "2.0.60"
@ -1995,6 +2230,12 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "typetag"
version = "0.2.16"
@ -2079,6 +2320,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.5.0"
@ -2421,3 +2668,37 @@ dependencies = [
"lzma-sys",
"tokio-io",
]
[[package]]
name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]]
name = "zstd"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "7.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa556e971e7b568dc775c136fc9de8c779b1c2fc3a63defaafadffdbd3181afa"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.12+zstd.1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13"
dependencies = [
"cc",
"pkg-config",
]

View file

@ -60,6 +60,11 @@ which = "6.0.0"
sysctl = "0.5.4"
walkdir = "2.3.3"
indexmap = { version = "2.0.2", features = ["serde"] }
nix-nar = "0.3.0"
zstd = { version = "0.13.2", default-features = false }
sha2 = "0.10.8"
ed25519-dalek = { version = "2.1.1", features = ["serde"] }
base64 = "0.22.1"
[dev-dependencies]
eyre = { version = "0.6.8", default-features = false, features = [ "track-caller" ] }

View file

@ -0,0 +1,360 @@
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use base64::Engine;
use ed25519_dalek::VerifyingKey;
use reqwest::Url;
use tracing::{span, Span};
use crate::{
action::{Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction},
parse_ssl_cert,
};
/// Fetch an output and its dependencies from a set of substituters,
/// given an output path, subsititer URLs, and trusted keys.
/// Also generates a ".reginfo" compatible with `nix-store --load-db`
/// Only implements a subset of nix substitution features:
/// * Substituter priorites are highest to lowest as given to [`plan`],
/// instead of priority from nix-cache-info
/// * narinfo signatures are always required
/// * ca-derivations are not supported
/// * NarHash must be sha256
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct FetchAndUnpackNixSubstituter {
/// Map from key name (e.g. cache.nixos.org-1) to parsed ed25519 key
trusted_keys: HashMap<String, VerifyingKey>,
/// Base URLs for substituters, e.g. https://cache.nixos.org/
substituters: Vec<Url>,
/// Desired derivation output, e.g.
/// `/nix/store/n50jk09x9hshwx1lh6k3qaiygc7yxbv9-lix-2.90.0-rc1`
target: StorePath,
/// Destination directory, normally temporary.
/// For compatibility with tarballs, files will be placed in
/// the nix/store subdirectory of the destination
dest: PathBuf,
/// Proxy used for all requests from substituters
proxy: Option<Url>,
/// Extra SSL certificates trusted for all requests
ssl_cert_file: Option<PathBuf>,
}
/// Root directory of the nix store.
/// Technically this could be something other than /nix/store,
/// but that is rarely done in production
const STORE_DIR: &str = "/nix/store/";
impl FetchAndUnpackNixSubstituter {
#[tracing::instrument(level = "debug", skip_all)]
pub async fn plan(
target: PathBuf,
dest: PathBuf,
trusted_keys: Vec<String>,
substituters: Vec<Url>,
proxy: Option<Url>,
ssl_cert_file: Option<PathBuf>,
) -> Result<StatefulAction<Self>, ActionError> {
let trusted_keys_parsed = trusted_keys
.iter()
.map(|key| parse_key(key))
.collect::<Result<HashMap<_, _>, _>>()
.map_err(Self::error)?;
if let Some(proxy) = &proxy {
match proxy.scheme() {
"https" | "http" | "socks5" => (),
_ => return Err(Self::error(SubstitutionError::UnknownProxyScheme)),
};
}
if let Some(ssl_cert_file) = &ssl_cert_file {
parse_ssl_cert(ssl_cert_file).await.map_err(Self::error)?;
}
Ok(Self {
target: StorePath::from_full_path(target)
.ok_or_else(|| Self::error(SubstitutionError::InvalidStorePath))?,
trusted_keys: trusted_keys_parsed,
dest,
proxy,
substituters,
ssl_cert_file,
}
.into())
}
}
#[async_trait::async_trait]
#[typetag::serde(name = "fetch_and_unpack_nix")]
impl Action for FetchAndUnpackNixSubstituter {
fn action_tag() -> ActionTag {
ActionTag("fetch_and_unpack_nix_substituter")
}
fn tracing_synopsis(&self) -> String {
format!(
"Fetch `{}` from substituters to `{}`",
self.target.full_path.to_string_lossy(),
self.dest.display()
)
}
fn tracing_span(&self) -> Span {
let span = span!(
tracing::Level::DEBUG,
"fetch_and_unpack_nix_substituter",
target = tracing::field::debug(&self.target.full_path),
proxy = tracing::field::Empty,
ssl_cert_file = tracing::field::Empty,
dest = tracing::field::display(self.dest.display()),
);
if let Some(proxy) = &self.proxy {
span.record("proxy", tracing::field::display(&proxy));
}
if let Some(ssl_cert_file) = &self.ssl_cert_file {
span.record(
"ssl_cert_file",
tracing::field::display(&ssl_cert_file.display()),
);
}
span
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
Ok(())
}
fn revert_description(&self) -> Vec<ActionDescription> {
vec![/* Deliberately empty -- this is a noop */]
}
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
Ok(())
}
}
/// Parse a nix trusted key into name and ed25519
fn parse_key(key: &str) -> Result<(String, VerifyingKey), SubstitutionError> {
let (name, key_base64) = key
.split_once(':')
.ok_or_else(|| SubstitutionError::PublicKey)?;
// seems to be the best way to handle keys both with and without padding
let key_bytes = base64::engine::general_purpose::STANDARD_NO_PAD
.decode(key_base64.trim_end_matches("=").as_bytes())
.map_err(|_| SubstitutionError::PublicKey)?;
let key_array: [u8; ed25519_dalek::PUBLIC_KEY_LENGTH] = key_bytes
.try_into()
.map_err(|_| SubstitutionError::PublicKey)?;
let verifying_key = ed25519_dalek::VerifyingKey::from_bytes(&key_array)
.map_err(|_| SubstitutionError::PublicKey)?;
Ok((name.to_string(), verifying_key))
}
/// Utility struct representing a store path
#[derive(Clone, Eq, Debug)]
struct StorePath {
/// Full path of the output including STORE_DIR,
/// as seen in StorePath in narinfo
/// e.g. `/nix/store/n50jk09x9hshwx1lh6k3qaiygc7yxbv9-lix-2.90.0-rc1`
pub full_path: String,
/// The base32 hash part of a store path,
/// e.g. `n50jk09x9hshwx1lh6k3qaiygc7yxbv9`
pub digest: String,
/// The full name of a path, not including STORE_DIR,
/// e.g. `n50jk09x9hshwx1lh6k3qaiygc7yxbv9-lix-2.90.0-rc1`
pub full_name: String,
}
impl StorePath {
pub fn from_full_path(full_path: &str) -> Option<Self> {
if !full_path.starts_with(STORE_DIR) {
return None;
}
let remaining =
let full_name = full_path.file_name()?.to_str()?.to_owned();
let (digest, _) = full_name.split_once('-')?;
Some(Self {
full_path,
digest: digest.to_owned(),
full_name,
})
}
pub fn from_full_name(full_name: String) -> Option<Self> {
let (digest, _) = full_name.split_once('-')?;
Some(Self {
full_path: Path::new(STORE_DIR).join(&full_name),
digest: digest.to_string(),
full_name,
})
}
}
impl PartialEq for StorePath {
fn eq(&self, other: &Self) -> bool {
self.full_path == other.full_path
}
}
impl serde::Serialize for StorePath {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.full_path.serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for StorePath {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_string(StorePathVisitor)
}
}
struct StorePathVisitor;
impl serde::de::Visitor<'_> for StorePathVisitor {
type Value = StorePath;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a valid nix store path starting with /nix/store")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
StorePath::from_full_path(Path::new(value).to_owned())
.ok_or_else(|| E::custom("invalid store path"))
}
}
/// Compression types for nar files.
/// Not exhaustive, this is just what
/// I've seen in the real world
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum NarCompression {
Zstd,
Xz,
}
impl NarCompression {
pub fn from_name(name: &str) -> Option<Self> {
match name {
"zstd" => Some(Self::Zstd),
"xz" => Some(Self::Zstd),
_ => None,
}
}
}
/// Extracted data from a narinfo.
/// May only be constructed by verifying signature
/// Missing some fields, like Deriver and FileHash because they aren't signed
#[derive(Debug, Clone, PartialEq, Eq)]
struct NarInfo {
/// Store path represented by the nar
pub store_path: StorePath,
/// Full URL to download the nar
pub url: Url,
/// Method used to compress the nar
pub compression: NarCompression,
/// sha256 hash of the nar after decompression
/// encoded in nix base32 format
pub nar_hash: String,
/// Size of the decompressed nar in bytes
pub nar_size: u64,
/// Other store paths referenced by
pub references: Vec<StorePath>,
}
impl NarInfo {
fn parse(substituter_url: &Url, contents: &bytes::Bytes) -> Result<Self, SubstitutionError> {
Ok(Self {
store_path: todo!(),
url: todo!(),
compression: todo!(),
nar_hash: todo!(),
nar_size: todo!(),
references: todo!(),
})
}
fn verify(
&self,
trusted_keys: &HashMap<String, VerifyingKey>,
) -> Result<(), SubstitutionError> {
// Fingerprint format not documented, but implemented in lix:
// https://git.lix.systems/lix-project/lix/src/commit/d461cc1d7b2f489c3886f147166ba5b5e0e37541/src/libstore/path-info.cc#L25
let fingerprint = format!(
"1;{};{};{};{}",
self.store_path.full_path.to_string_lossy(),
self.nar_hash,
self.nar_size,
self.references
.iter()
.map(|reference| reference.full_path)
.collect::<Vec<String>>()
.join(",")
);
todo!()
}
pub fn parse_and_verify(
trusted_keys: &HashMap<String, VerifyingKey>,
substituter_url: &Url,
expected_store_path: &StorePath,
contents: &bytes::Bytes,
) -> Result<Self, SubstitutionError> {
let parsed = Self::parse(substituter_url, contents)?;
if &parsed.store_path != expected_store_path {
return Err(SubstitutionError::BadNarInfo);
}
parsed.verify(trusted_keys)?;
Ok(parsed)
}
}
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum SubstitutionError {
#[error("Unarchiving error")]
Unarchive(#[source] std::io::Error),
#[error("Unknown proxy scheme, `https://`, `socks5://`, and `http://` supported")]
UnknownProxyScheme,
#[error("Invalid public key")]
/// Normally an ed25519_dalek::SignatureError,
/// but that comes with no extra information so no need to include it
PublicKey,
#[error("Bad narinfo signature")]
BadNarInfo,
#[error("Invalid nix store path")]
InvalidStorePath,
}
impl From<SubstitutionError> for ActionErrorKind {
fn from(val: SubstitutionError) -> Self {
ActionErrorKind::Custom(Box::new(val))
}
}

View file

@ -9,6 +9,7 @@ pub(crate) mod create_or_merge_nix_config;
pub(crate) mod create_user;
pub(crate) mod delete_user;
pub(crate) mod fetch_and_unpack_nix;
pub(crate) mod fetch_and_unpack_nix_substituter;
pub(crate) mod move_unpacked_nix;
pub(crate) mod remove_directory;
pub(crate) mod setup_default_profile;
@ -22,6 +23,7 @@ pub use create_or_merge_nix_config::CreateOrMergeNixConfig;
pub use create_user::CreateUser;
pub use delete_user::DeleteUser;
pub use fetch_and_unpack_nix::{FetchAndUnpackNix, FetchUrlError};
pub use fetch_and_unpack_nix_substituter::{FetchAndUnpackNixSubstituter, SubstitutionError};
pub use move_unpacked_nix::{MoveUnpackedNix, MoveUnpackedNixError};
pub use remove_directory::RemoveDirectory;
pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileError};