diff --git a/tvix/Cargo.lock b/tvix/Cargo.lock index cb3ed4c09..781e143f2 100644 --- a/tvix/Cargo.lock +++ b/tvix/Cargo.lock @@ -4995,6 +4995,7 @@ dependencies = [ "clap", "count-write", "data-encoding", + "ed25519", "futures", "hyper-util", "lazy_static", diff --git a/tvix/store/Cargo.toml b/tvix/store/Cargo.toml index de7d0a504..dfe49e7ff 100644 --- a/tvix/store/Cargo.toml +++ b/tvix/store/Cargo.toml @@ -13,6 +13,7 @@ bytes = "1.4.0" clap = { version = "4.0", features = ["derive", "env"] } count-write = "0.1.0" data-encoding = "2.6.0" +ed25519 = "2.2.3" futures = "0.3.30" lazy_static = "1.4.0" nix-compat = { path = "../nix-compat", features = ["async"] } diff --git a/tvix/store/src/pathinfoservice/mod.rs b/tvix/store/src/pathinfoservice/mod.rs index d118a8af1..4b241d935 100644 --- a/tvix/store/src/pathinfoservice/mod.rs +++ b/tvix/store/src/pathinfoservice/mod.rs @@ -5,6 +5,7 @@ mod lru; mod memory; mod nix_http; mod redb; +mod signing_wrapper; mod sled; #[cfg(any(feature = "fuse", feature = "virtiofs"))] @@ -30,6 +31,7 @@ pub use self::lru::{LruPathInfoService, LruPathInfoServiceConfig}; pub use self::memory::{MemoryPathInfoService, MemoryPathInfoServiceConfig}; pub use self::nix_http::{NixHTTPPathInfoService, NixHTTPPathInfoServiceConfig}; pub use self::redb::{RedbPathInfoService, RedbPathInfoServiceConfig}; +pub use self::signing_wrapper::{KeyFileSigningPathInfoServiceConfig, SigningPathInfoService}; pub use self::sled::{SledPathInfoService, SledPathInfoServiceConfig}; #[cfg(feature = "cloud")] @@ -91,6 +93,7 @@ pub(crate) fn register_pathinfo_services(reg: &mut Registry) { reg.register::>, NixHTTPPathInfoServiceConfig>("nix"); reg.register::>, SledPathInfoServiceConfig>("sled"); reg.register::>, RedbPathInfoServiceConfig>("redb"); + reg.register::>, KeyFileSigningPathInfoServiceConfig>("keyfile-signing"); #[cfg(feature = "cloud")] { reg.register::>, BigtableParameters>( diff --git a/tvix/store/src/pathinfoservice/signing_wrapper.rs b/tvix/store/src/pathinfoservice/signing_wrapper.rs new file mode 100644 index 000000000..c5af18a82 --- /dev/null +++ b/tvix/store/src/pathinfoservice/signing_wrapper.rs @@ -0,0 +1,84 @@ +use super::PathInfoService; +use crate::proto::PathInfo; +use futures::stream::BoxStream; +use std::path::Path; +use std::sync::Arc; +use tonic::async_trait; + +use tvix_castore::composition::{CompositionContext, ServiceBuilder}; + +use tvix_castore::Error; + +use nix_compat::narinfo::{parse_keypair, SigningKey}; +use nix_compat::nixbase32; +use tracing::{instrument, warn}; + +pub struct SigningPathInfoService { + inner: T, + signing_key: Arc>, +} + +#[async_trait] +impl PathInfoService for SigningPathInfoService +where + T: PathInfoService, + S: ed25519::signature::Signer + Sync + Send, +{ + #[instrument(level = "trace", skip_all, fields(path_info.digest = nixbase32::encode(&digest)))] + async fn get(&self, digest: [u8; 20]) -> Result, Error> { + self.inner.get(digest).await + } + + async fn put(&self, path_info: PathInfo) -> Result { + let store_path = path_info.validate().map_err(|e| { + warn!(err=%e, "invalid PathInfo"); + Error::StorageError(e.to_string()) + })?; + let root_node = path_info.node.clone(); + let mut nar_info = path_info + .to_narinfo(store_path) + .ok_or(Error::StorageError("".to_string()))?; + nar_info.add_signature(self.signing_key.as_ref()); + let mut signed_path_info = PathInfo::from(&nar_info); + signed_path_info.node = root_node; + self.inner.put(signed_path_info).await + } + + fn list(&self) -> BoxStream<'static, Result> { + self.inner.list() + } +} + +#[derive(serde::Deserialize)] +pub struct KeyFileSigningPathInfoServiceConfig { + pub inner: String, + pub keyfile: Box, +} + +impl TryFrom for KeyFileSigningPathInfoServiceConfig { + type Error = Box; + fn try_from(_url: url::Url) -> Result { + Err(Error::StorageError( + "Instantiating a SigningPathInfoService from a url is not supported".into(), + ) + .into()) + } +} + +#[async_trait] +impl ServiceBuilder for KeyFileSigningPathInfoServiceConfig { + type Output = dyn PathInfoService; + async fn build<'a>( + &'a self, + _instance_name: &str, + context: &CompositionContext, + ) -> Result, Box> { + let inner = context.resolve(self.inner.clone()).await?; + let signing_key = Arc::new( + parse_keypair(tokio::fs::read_to_string(&self.keyfile).await?.trim()) + .map_err(|e| Error::StorageError(e.to_string()))? + .0, + ); + Ok(Arc::new(SigningPathInfoService { inner, signing_key })) + } +}