feat(tvix/store): Add a signing pathinfoservice

Add a new path-info-service that wraps transparently around another
except that it signs all the incoming path infos with the provided
signer.

Change-Id: I845ddfdf01d14c503c796b2b80c720dab98be091
This commit is contained in:
sinavir 2024-07-22 15:09:10 +02:00
parent 9385b3bcfb
commit e8c8164d78
4 changed files with 89 additions and 0 deletions

1
tvix/Cargo.lock generated
View file

@ -4995,6 +4995,7 @@ dependencies = [
"clap",
"count-write",
"data-encoding",
"ed25519",
"futures",
"hyper-util",
"lazy_static",

View file

@ -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"] }

View file

@ -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::<Box<dyn ServiceBuilder<Output = dyn PathInfoService>>, NixHTTPPathInfoServiceConfig>("nix");
reg.register::<Box<dyn ServiceBuilder<Output = dyn PathInfoService>>, SledPathInfoServiceConfig>("sled");
reg.register::<Box<dyn ServiceBuilder<Output = dyn PathInfoService>>, RedbPathInfoServiceConfig>("redb");
reg.register::<Box<dyn ServiceBuilder<Output = dyn PathInfoService>>, KeyFileSigningPathInfoServiceConfig>("keyfile-signing");
#[cfg(feature = "cloud")]
{
reg.register::<Box<dyn ServiceBuilder<Output = dyn PathInfoService>>, BigtableParameters>(

View file

@ -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<T, S> {
inner: T,
signing_key: Arc<SigningKey<S>>,
}
#[async_trait]
impl<T, S> PathInfoService for SigningPathInfoService<T, S>
where
T: PathInfoService,
S: ed25519::signature::Signer<ed25519::Signature> + Sync + Send,
{
#[instrument(level = "trace", skip_all, fields(path_info.digest = nixbase32::encode(&digest)))]
async fn get(&self, digest: [u8; 20]) -> Result<Option<PathInfo>, Error> {
self.inner.get(digest).await
}
async fn put(&self, path_info: PathInfo) -> Result<PathInfo, Error> {
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<PathInfo, Error>> {
self.inner.list()
}
}
#[derive(serde::Deserialize)]
pub struct KeyFileSigningPathInfoServiceConfig {
pub inner: String,
pub keyfile: Box<Path>,
}
impl TryFrom<url::Url> for KeyFileSigningPathInfoServiceConfig {
type Error = Box<dyn std::error::Error + Send + Sync>;
fn try_from(_url: url::Url) -> Result<Self, Self::Error> {
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<Arc<dyn PathInfoService>, Box<dyn std::error::Error + Send + Sync + 'static>> {
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 }))
}
}