diff --git a/src/tardev-snapshotter/Cargo.lock b/src/tardev-snapshotter/Cargo.lock index 878d74ce6793..bc4dc7f7b8a0 100644 --- a/src/tardev-snapshotter/Cargo.lock +++ b/src/tardev-snapshotter/Cargo.lock @@ -546,6 +546,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "0.2.9" @@ -1279,8 +1285,10 @@ dependencies = [ "env_logger", "flate2", "futures", + "hex", "log", "nix 0.24.3", + "serde", "serde_json", "sha2", "tarindex", diff --git a/src/tardev-snapshotter/Cargo.toml b/src/tardev-snapshotter/Cargo.toml index ec4122af8bf6..165342668c0c 100644 --- a/src/tardev-snapshotter/Cargo.toml +++ b/src/tardev-snapshotter/Cargo.toml @@ -24,4 +24,8 @@ uuid = { version = "1.0", features = ["v4"] } nix = "0.24.2" devicemapper = "0.33.1" anyhow = "=1.0.58" -zerocopy = "0.6.1" \ No newline at end of file +zerocopy = "0.6.1" + +# YAML file serialization/deserialization. +serde = { version = "1.0.159", features = ["derive"] } +hex = { version = "0.4.3" } \ No newline at end of file diff --git a/src/tardev-snapshotter/src/snapshotter.rs b/src/tardev-snapshotter/src/snapshotter.rs index b5d5c492a725..291936718118 100644 --- a/src/tardev-snapshotter/src/snapshotter.rs +++ b/src/tardev-snapshotter/src/snapshotter.rs @@ -1,30 +1,148 @@ +use anyhow::{anyhow, Context, Result}; use base64::prelude::{Engine, BASE64_STANDARD}; use containerd_client::{services::v1::ReadContentRequest, tonic::Request, with_namespace, Client}; use containerd_snapshots::{api, Info, Kind, Snapshotter, Usage}; -use log::{debug, info, trace}; +use log::{debug, error, info, trace}; +use nix::mount::MsFlags; +use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; +use std::io::Write; +use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; -use std::{collections::HashMap, fs, fs::OpenOptions, fs::File, io, io::Read, io::Seek, os::unix::ffi::OsStrExt, process::Command}; +use std::process::Stdio; +use std::{ + collections::HashMap, fs, fs::File, fs::OpenOptions, io, io::Read, io::Seek, + os::unix::ffi::OsStrExt, process::Command, +}; use tokio::io::{AsyncSeekExt, AsyncWriteExt}; use tokio::sync::RwLock; use tonic::Status; use uuid::Uuid; -use std::os::unix::fs::PermissionsExt; -//use nix::unistd::{chown, Gid, Uid}; -use anyhow::{anyhow, Context, Result}; use zerocopy::AsBytes; -use nix::mount::MsFlags; const ROOT_HASH_LABEL: &str = "io.katacontainers.dm-verity.root-hash"; +const ROOT_HASH_SIG_LABEL: &str = "io.katacontainers.dm-verity.root-hash-sig"; +const SALT_LABEL: &str = "io.katacontainers.dm-verity.salt"; const TARGET_LAYER_DIGEST_LABEL: &str = "containerd.io/snapshot/cri.layer-digest"; +const TARGET_LAYER_MEDIA_TYPE_LABEL: &str = "containerd.io/snapshot/cri.layer-media-type"; + +const TAR_GZ_EXTENSION: &str = "tar.gz"; +const TAR_EXTENSION: &str = "tar"; + +#[derive(Serialize, Deserialize)] +struct ImageInfo { + name: String, + layers: Vec, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone)] +struct LayerInfo { + digest: String, // `sha256:` + hex encoded + root_hash: String, // hex encoded + signature: String, // base64 encoded + salt: String, // hex encoded +} + +#[derive(PartialEq, Clone)] +struct LayerInfoSalt { + info: LayerInfo, + salt_bytes: Vec, +} struct Store { root: PathBuf, + signatures: Option>, // digest to layer info } +const SIGNATURE_STORE: &str = "/var/lib/containerd/io.containerd.snapshotter.v1.tardev/signatures"; + impl Store { fn new(root: &Path) -> Self { - Self { root: root.into() } + Self { + root: root.into(), + signatures: None, + } + } + + fn lazy_read_signatures(&mut self) -> Result<()> { + if self.signatures == None { + info!("Loading signatures"); + self.read_signatures() + .context("Failed to read signatures")?; + } + + Ok(()) + } + + fn read_signatures(&mut self) -> Result<()> { + let paths = std::fs::read_dir(Path::new(SIGNATURE_STORE))?; + let mut signatures = HashMap::new(); + for signatures_json_path in paths { + let signatures_json = std::fs::read_to_string( + signatures_json_path + .context("failed to load signature file path")? + .path(), + )?; + let image_info_list = serde_json::from_str::>(signatures_json.as_str())?; + for image_info in image_info_list { + for layer_info in image_info.layers { + signatures.insert( + layer_info.digest.clone(), + LayerInfoSalt { + info: layer_info.clone(), + salt_bytes: hex::decode(layer_info.salt) + .context("failed to decode salt")?, + }, + ); + } + } + } + + self.signatures = Some(signatures); + + Ok(()) + } + + fn get_info_from_digest(&self, digest: &str) -> Result { + self.signatures + .as_ref() + .context("signatures not loaded")? + .get(digest) + .context("missing signature for the requested layer") + .cloned() + } + + fn load_signature(&self, hash: &str, signature: &str) -> Result { + let signature_name = format!("verity:{hash}"); + + // https://lkml.org/lkml/2019/7/17/762 + // https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/verity.html + let keyctl = Command::new("keyctl") + .stdin(Stdio::piped()) + .arg("padd") + .arg("user") + .arg(&signature_name) + .arg("@s") + .spawn() + .context("failed to start keyctl")?; + keyctl + .stdin + .as_ref() + .context("failed to bind to the input of keyctl")? + .write_all( + &BASE64_STANDARD + .decode(signature) + .context("failed to decode signature")?, + ) + .context("failed to write keyctl input")?; + let output = keyctl + .wait_with_output() + .context("failed to wait for keyctl output")?; + if !output.status.success() { + return Err(anyhow::anyhow!("failed to load signature, keyctl failed")); + } + + Ok(signature_name) } /// Creates the name of the directory that containerd can use to extract a layer into. @@ -79,8 +197,14 @@ impl Store { /// Reads the information from storage for the given snapshot name. fn read_snapshot(&self, name: &str) -> Result { let path = self.snapshot_path(name, false)?; - let file = fs::File::open(path)?; - serde_json::from_reader(file).map_err(|_| Status::unknown("unable to read snapshot")) + let file = fs::File::open(&path).map_err(|e| { + Status::unknown(format!( + "unable to open snapshot ('{}'): {e}", + path.display() + )) + })?; + serde_json::from_reader(file) + .map_err(|_| Status::unknown(format!("unable to read snapshot ('{}')", path.display()))) } /// Writes to storage the given snapshot information. @@ -103,8 +227,10 @@ impl Store { let name = self.snapshot_path(&info.name, true)?; // TODO: How to specify the file mode (e.g., 0600)? let file = OpenOptions::new().write(true).create_new(true).open(name)?; - serde_json::to_writer_pretty(file, &info) - .map_err(|_| Status::internal("unable to write snapshot")) + let foo = serde_json::to_writer_pretty(file, &info) + .map_err(|_| Status::internal("unable to write snapshot")); + foo?; + Ok(()) } /// Creates a new snapshot for use. @@ -124,9 +250,15 @@ impl Store { } // ported over from kata agent - // prepares a dm-verity target configuration by reading metadata from a file (block/loop device) + // prepares a dm-verity target configuration by reading metadata from a file (block/loop device) // and returning the parameters required to set up the device-mapper verity target - fn prepare_dm_target(&self, path: &str, hash: &str) -> Result<(u64, u64, String, String)> { + fn prepare_dm_target( + &self, + path: &str, + hash: &str, + signature_name: &str, + salt: &str, + ) -> Result<(u64, u64, String, String)> { info!("prepare_dm_target for loop device"); let mut file = File::open(path)?; let size = file.seek(std::io::SeekFrom::End(0))?; @@ -157,23 +289,26 @@ impl Store { } // generate dm-verity table, use all zero salt - // TODO: Store other parameters in super block: version, hash type, salt. - Ok(( - 0, - data_size / 512, - "verity".into(), - format!( - "1 {path} {path} {data_block_size} {hash_block_size - } {} {} sha256 {hash} 0000000000000000000000000000000000000000000000000000000000000000", + // TODO: Store other parameters in super block: version, hash type, + // salt. + let construction_parameters = format!( + "1 {path} {path} {data_block_size} {hash_block_size} {} {} sha256 {hash} {salt} 2 root_hash_sig_key_desc {signature_name}", data_size / data_block_size, - (data_size + hash_block_size - 1) / hash_block_size - ), - )) + (data_size + hash_block_size - 1) / hash_block_size, + ); + trace!("dm-verity construction params: {construction_parameters}"); + Ok((0, data_size / 512, "verity".into(), construction_parameters)) } // Creates dm-verity device for a given layer file - fn create_dm_verity_device(&self, layer_path: &str, root_hash: &str) -> Result { - let dm = devicemapper::DM::new()?; + fn create_dm_verity_device( + &self, + layer_path: &str, + root_hash: &str, + root_hash_sig_name: &str, + salt: &str, + ) -> Result { + let dm = devicemapper::DM::new()?; let layer_name = Path::new(layer_path) .file_name() .ok_or_else(|| anyhow!("Unable to get file name from layer path"))? @@ -229,7 +364,8 @@ impl Store { let device_path = loop_device; // Step 3: Prepare DM-Verity target - let target = self.prepare_dm_target(device_path, root_hash)?; + let target = + self.prepare_dm_target(device_path, root_hash, root_hash_sig_name, salt)?; // Step 4: Load the DM table for DM-Verity dm.table_load(&id, &[target], opts) @@ -250,9 +386,8 @@ impl Store { // Remove the DM device if it was created if let Err(remove_err) = dm.device_remove(&id, devicemapper::DmOptions::default()) { info!( - "Unable to remove DM device ({}): {:?}", - layer_name, - remove_err + "Unable to remove DM device ({}): {:?}", + layer_name, remove_err ); } @@ -314,20 +449,16 @@ impl Store { flags, Some(options), ) - .map_err(|e| { - anyhow!( - "Failed to mount {} to {} with error: {}", - source, - target, - e - ) - })?; + .map_err(|e| anyhow!("Failed to mount {} to {} with error: {}", source, target, e))?; Ok(()) } - - fn mounts_from_snapshot(&self, parent: &str, do_mount: bool) -> Result, Status> { + fn mounts_from_snapshot( + &self, + parent: &str, + do_mount: bool, + ) -> Result, Status> { const PREFIX: &str = "io.katacontainers.fs-opt"; // Get chain of layers. @@ -340,25 +471,36 @@ impl Store { let src_prefix = self.root.join("layers"); let mut mounted_layers = Vec::new(); while let Some(p) = next_parent { - let info = self.read_snapshot(&p)?; + let infor = self.read_snapshot(&p); + let info = match infor { + Ok(a) => a, + Err(b) => { + error!("failed to read snapshot: {}", b); + return Err(b); + } + }; if info.kind != Kind::Committed { return Err(Status::failed_precondition( "parent snapshot is not committed", )); } - let root_hash = if let Some(rh) = info.labels.get(ROOT_HASH_LABEL) { - rh - } else { - return Err(Status::failed_precondition( - "parent snapshot has no root hash stored", - )); + let root_hash = match info.labels.get(ROOT_HASH_LABEL) { + Some(rh) => rh, + None => { + return Err(Status::failed_precondition( + "parent snapshot has no root hash stored", + )); + } }; let name = name_to_hash(&p); let layer_info = format!( "{name},tar,ro,{PREFIX}.block_device=file,{PREFIX}.is-layer,{PREFIX}.root-hash={root_hash}"); - info!("mounts_from_snapshot(): processing snapshots: {}, layername: {}", &info.name, &name); + info!( + "mounts_from_snapshot(): processing snapshots: {}, layername: {}", + &info.name, &name + ); if do_mount { info!("mounts_from_snapshot(): performing tarfs mounting via dm-verity"); @@ -371,15 +513,17 @@ impl Store { Path::new(p).to_path_buf() } } else { - return Err(Status::invalid_argument("Missing source path in layer info")); + return Err(Status::invalid_argument( + "Missing source path in layer info", + )); }; info!("src: {}", src.display()); - + let fs_type = fields.next().ok_or_else(|| { Status::invalid_argument("Missing filesystem type in layer info") })?; info!("fs_type: {}", fs_type); - + let fs_opts = fields .filter(|o| !o.starts_with("io.katacontainers.")) .fold(String::new(), |a, b| { @@ -390,7 +534,7 @@ impl Store { } }); info!("fs_opts: {}", fs_opts); - + let mount_path = self.root.join("mounts").join(&name); info!("mount_path: {}", mount_path.display()); std::fs::create_dir_all(&mount_path)?; @@ -403,9 +547,40 @@ impl Store { name, dm_verity_device ); } else { + let root_hash_sig = match info.labels.get(ROOT_HASH_SIG_LABEL) { + Some(rh) => rh, + None => { + return Err(Status::failed_precondition( + "parent snapshot has no root hash stored", + )); + } + }; + let salt = match info.labels.get(SALT_LABEL) { + Some(rh) => rh, + None => { + return Err(Status::failed_precondition( + "parent snapshot has no root hash stored", + )); + } + }; + + let signature_name = match self.load_signature(root_hash, root_hash_sig) { + Ok(name) => name, + Err(e) => { + return Err(Status::failed_precondition(format!( + "Failed to load signature: {e}" + ))) + } + }; + // Step 1: Create a dm-verity device for the tarfs layer let created_dm_verity_device = self - .create_dm_verity_device(src.to_str().unwrap(), root_hash) + .create_dm_verity_device( + src.to_str().unwrap(), + root_hash, + &signature_name, + salt, + ) .map_err(|e| { Status::internal(format!( "Failed to create dm-verity device for source {:?}: {:?}", @@ -431,13 +606,19 @@ impl Store { } else { // Mount the dm-verity device to the mount path let flags = MsFlags::MS_RDONLY; // Read-only to ensure integrity - self.mount_dm_verity_device(&dm_verity_device, mount_path.to_str().unwrap(), fs_type, &fs_opts, flags) - .map_err(|e| { - Status::internal(format!( - "Failed to mount dm-verity device {} to {:?}: {:?}", - dm_verity_device, mount_path, e - )) - })?; + self.mount_dm_verity_device( + &dm_verity_device, + mount_path.to_str().unwrap(), + fs_type, + &fs_opts, + flags, + ) + .map_err(|e| { + Status::internal(format!( + "Failed to mount dm-verity device {} to {:?}: {:?}", + dm_verity_device, mount_path, e + )) + })?; info!( "mounted single layer dm-verity device {} to {:?}", dm_verity_device, mount_path @@ -453,32 +634,35 @@ impl Store { "{PREFIX}.layer={}", BASE64_STANDARD.encode(layer_info.as_bytes()) )); - + next_parent = (!info.parent.is_empty()).then_some(info.parent); } if do_mount { info!("mounts_from_snapshot(): perform overlay mounting"); - let overlay_target = self.root.join("overlay").join(Uuid::new_v4().to_string()); - let overlay_upper = overlay_target.join("upper"); - let overlay_work = overlay_target.join("work"); + let overlay_root = self.root.join("overlay").join(Uuid::new_v4().to_string()); + let overlay_target = overlay_root.join("mount"); + let overlay_upper = overlay_root.join("upper"); + let overlay_work = overlay_root.join("work"); std::fs::create_dir_all(&overlay_upper)?; std::fs::create_dir_all(&overlay_work)?; std::fs::create_dir_all(&overlay_target)?; fs::set_permissions(&overlay_upper, fs::Permissions::from_mode(0o755))?; fs::set_permissions(&overlay_work, fs::Permissions::from_mode(0o755))?; - - // Prepare the list of lowerdirs from mounted dm-verity layers + + // Prepare the list of lowerdirs from mounted dm-verity layers let lowerdirs = mounted_layers .iter() .map(|layer| layer.to_string_lossy().into_owned()) .collect::>() .join(":"); - info!("Combining dm-verity layers into overlay lowerdirs: {}", lowerdirs); + info!( + "Combining dm-verity layers into overlay lowerdirs: {}", + lowerdirs + ); // DEBUG: Validate that lowerdirs does not exceed PATH_MAX - info!("Lowerdirs string len: {}", lowerdirs.len()); if lowerdirs.len() > 4096 { return Err(Status::internal(format!( "Lowerdirs string exceeds allowable length: {}", @@ -486,19 +670,28 @@ impl Store { ))); } - // Perform an overlay mount - let status = Command::new("mount") - .arg("none") - .arg(&overlay_target) - .args(&["-t", "overlay","-o", &format!("lowerdir={},upperdir={},workdir={}", - lowerdirs, overlay_upper.to_string_lossy(), overlay_work.to_string_lossy()),]) - .status()?; - if !status.success() { - return Err(Status::internal(format!( - "Failed to perform overlay mount at {:?}", - overlay_target - ))); - } + // Perform an overlay mount + let opts = format!( + "lowerdir={},upperdir={},workdir={}", + lowerdirs, + overlay_upper.to_string_lossy(), + overlay_work.to_string_lossy() + ); + nix::mount::mount( + Some(""), + &overlay_target, + Some("overlay"), + MsFlags::empty(), + Some(opts.as_str()), + ) + .map_err(|e| { + Status::internal(format!( + "Failed to mount overlay to {} with error: {}", + overlay_target.display(), + e + )) + })?; + info!("Overlay mount completed at {:?}", overlay_target); // Clean up dm-verity and loop devices @@ -588,8 +781,12 @@ impl TarDevSnapshotter { let extract_dir; { let mut store = self.store.write().await; - extract_dir = store.extract_dir_to_write(&key)?; - store.write_snapshot(Kind::Active, key, parent, labels)?; + extract_dir = store.extract_dir_to_write(&key).map_err(|e| { + Status::unknown(format!("failed to extract directory to write: {e}")) + })?; + store + .write_snapshot(Kind::Active, key.clone(), parent, labels) + .map_err(|e| Status::unknown(format!("failed to write the snapshot: {e}")))?; } Ok(vec![api::types::Mount { r#type: "bind".into(), @@ -644,88 +841,175 @@ impl TarDevSnapshotter { key: String, parent: String, mut labels: HashMap, + salt: Vec, + root_hash: String, // hex encoded + root_hash_sig: String, // base64 encoded + salt_str: String, // base64 encoded ) -> Result<(), Status> { let dir = self.store.read().await.staging_dir()?; + let base_name = dir.path().join(name_to_hash(&key)); + let snapshot_name = base_name.clone(); + { let Some(digest_str) = labels.get(TARGET_LAYER_DIGEST_LABEL) else { + error!("missing target layer digest label"); return Err(Status::invalid_argument( "missing target layer digest label", )); }; - let name = dir.path().join(name_to_hash(&key)); - let mut gzname = name.clone(); - gzname.set_extension("gz"); - trace!("Fetching layer image to {:?}", &gzname); - self.get_layer_image(&gzname, digest_str).await?; - - // TODO: Decompress in stream instead of reopening. - // Decompress data. - trace!("Decompressing {:?} to {:?}", &gzname, &name); - let root_hash = tokio::task::spawn_blocking(move || -> io::Result<_> { - let compressed = fs::File::open(&gzname)?; + // Determine image layer media type + let Some(media_type) = labels.get(TARGET_LAYER_MEDIA_TYPE_LABEL) else { + error!("missing target layer media type label"); + return Err(Status::invalid_argument( + "missing target layer media type label", + )); + }; + + trace!("layer digest {} media_type: {:?}", digest_str, media_type); + + let layer_type = match media_type.as_str() { + "application/vnd.docker.image.rootfs.diff.tar.gzip" + | "application/vnd.oci.image.layer.v1.tar+gzip" => TAR_GZ_EXTENSION, + "application/vnd.oci.image.layer.v1.tar" + | "application/vnd.docker.image.rootfs.diff.tar" => TAR_EXTENSION, + _ => { + error!("unsupported media type: {}", media_type); + return Err(Status::invalid_argument(format!( + "unsupported media type: {}", + media_type + ))); + } + }; + + let upstream_name = base_name.with_extension(layer_type); + + trace!("Fetching {} layer image to {:?}", layer_type, upstream_name); + self.get_layer_image(&upstream_name, digest_str).await?; + + // Process the layer + let generated_root_hash = tokio::task::spawn_blocking(move || -> Result<_> { + if layer_type == TAR_EXTENSION { + trace!("Renaming {:?} to {:?}", &upstream_name, &base_name); + std::fs::rename(&upstream_name, &base_name)?; + } let mut file = OpenOptions::new() .read(true) .write(true) .create(true) - .truncate(true) - .open(&name)?; - let mut gz_decoder = flate2::read::GzDecoder::new(compressed); - std::io::copy(&mut gz_decoder, &mut file)?; + .truncate(layer_type == TAR_GZ_EXTENSION) + .open(&base_name)?; + if layer_type == TAR_GZ_EXTENSION { + trace!("Decompressing {:?} to {:?}", &upstream_name, &base_name); + let compressed = fs::File::open(&upstream_name)?; + let mut gz_decoder = flate2::read::GzDecoder::new(compressed); + std::io::copy(&mut gz_decoder, &mut file) + .context("failed to copy payload from gz decoder")?; + } - trace!("Appending index to {:?}", &name); - file.rewind()?; - tarindex::append_index(&mut file)?; + trace!("Appending index to {:?}", &base_name); + file.rewind().context("failed to rewind the file handle")?; + tarindex::append_index(&mut file).context("failed to append tar index")?; - trace!("Appending dm-verity tree to {:?}", &name); - let root_hash = verity::append_tree::(&mut file)?; + trace!("Appending dm-verity tree to {:?}", &base_name); + let root_hash = verity::append_tree::(&mut file, &salt) + .context("failed to append verity tree")?; - trace!("Root hash for {:?} is {:x}", &name, root_hash); + trace!("Root hash for {:?} is {:x}", &base_name, root_hash); Ok(root_hash) }) .await - .map_err(|_| Status::unknown("error in worker task"))??; + .map_err(|e| Status::unknown(format!("error in worker task: {e}")))? + .map_err(|e| Status::unknown(format!("failed to extract image layer: {e}")))?; + + let generated_root_hash = format!("{:x}", generated_root_hash); + + if root_hash != generated_root_hash { + return Err(Status::internal(format!( + "root hash mismatch: expected {}, got {}", + root_hash, generated_root_hash + ))); + } // Store a label with the root hash so that we can recall it later when mounting. - labels.insert(ROOT_HASH_LABEL.into(), format!("{:x}", root_hash)); + labels.insert(ROOT_HASH_LABEL.into(), root_hash); + labels.insert(ROOT_HASH_SIG_LABEL.into(), root_hash_sig); + labels.insert(SALT_LABEL.into(), salt_str); } // Move file to its final location and write the snapshot. { - let from = dir.path().join(name_to_hash(&key)); let mut store = self.store.write().await; let to = store.layer_path_to_write(&key)?; - trace!("Renaming from {:?} to {:?}", &from, &to); - tokio::fs::rename(from, to).await?; + trace!("Renaming from {:?} to {:?}", &snapshot_name, &to); + tokio::fs::rename(snapshot_name, to).await?; store.write_snapshot(Kind::Committed, key, parent, labels)?; } trace!("Layer prepared"); Ok(()) } -} -#[tonic::async_trait] -impl Snapshotter for TarDevSnapshotter { - type Error = Status; + async fn commit_impl( + &self, + name: String, + key: String, + labels: HashMap, + ) -> Result<(), Status> { + let (layer_info, parent) = { + // Needs to be in the closure to release the lock + let mut store = self.store.write().await; + let info = store.read_snapshot(&key)?; + if info.kind != Kind::Active { + return Err(Status::failed_precondition("snapshot is not active")); + } - async fn stat(&self, key: String) -> Result { - trace!("stat({})", key); - self.store.read().await.read_snapshot(&key) - } + let digest = match info.labels.get(TARGET_LAYER_DIGEST_LABEL) { + None => { + return Err(Status::unimplemented( + "no support for commiting arbitrary snapshots", + )); + } + Some(digest) => digest, + }; - async fn update( - &self, - info: Info, - fieldpaths: Option>, - ) -> Result { - trace!("update({:?}, {:?})", info, fieldpaths); - Err(Status::unimplemented("no support for updating snapshots")) + let result = store.lazy_read_signatures(); + match result { + Err(e) => { + return Err(Status::failed_precondition(format!( + "signature read failure: {e}" + ))) + } + Ok(()) => (), + }; + + trace!("Looking up layer info for {digest}"); + let layer_info = match store.get_info_from_digest(digest) { + Ok(layer_info) => layer_info, + Err(e) => { + return Err(Status::failed_precondition(format!( + "signature missing for layer with digest {digest}: {e}" + ))); + } + }; + + (layer_info, info.parent) + }; + + self.prepare_image_layer( + name, + parent, + labels, + layer_info.salt_bytes, + layer_info.info.root_hash, + layer_info.info.signature, + layer_info.info.salt, + ) + .await } - async fn usage(&self, key: String) -> Result { - trace!("usage({})", key); + async fn usage_impl(&self, key: String) -> Result { let store = self.store.read().await; let info = store.read_snapshot(&key)?; @@ -743,8 +1027,7 @@ impl Snapshotter for TarDevSnapshotter { }) } - async fn mounts(&self, key: String) -> Result, Self::Error> { - trace!("mounts({})", key); + async fn mounts_impl(&self, key: String) -> Result, Status> { let store = self.store.read().await; let info = store.read_snapshot(&key)?; @@ -764,11 +1047,91 @@ impl Snapshotter for TarDevSnapshotter { options: Vec::new(), }]) } else { - info!("mounts(): snapshot: {}, ready to use, preparing itself and parents ", &info.name); + info!( + "mounts(): snapshot: {}, ready to use, preparing itself and parents ", + &info.name + ); store.mounts_from_snapshot(&info.parent, true) } } + async fn remove_impl(&self, key: String) -> Result<(), Status> { + let store = self.store.write().await; + + // TODO: Move this to store. + if let Ok(info) = store.read_snapshot(&key) { + match info.kind { + Kind::Committed => { + if info.labels.get(TARGET_LAYER_DIGEST_LABEL).is_some() { + // Try to delete a layer. It's ok if it's not found. + if let Err(e) = fs::remove_file(store.layer_path(&key)) { + if e.kind() != io::ErrorKind::NotFound { + return Err(e.into()); + } + } + } + } + Kind::Active => { + if let Err(e) = tokio::fs::remove_dir_all(store.extract_dir(&key)).await { + if e.kind() != io::ErrorKind::NotFound { + return Err(e.into()); + } + } + } + _ => {} + } + } + + let name = store.snapshot_path(&key, false)?; + fs::remove_file(name)?; + + Ok(()) + } +} + +#[tonic::async_trait] +impl Snapshotter for TarDevSnapshotter { + type Error = Status; + + async fn stat(&self, key: String) -> Result { + trace!("stat({})", key); + + let result = self.store.read().await.read_snapshot(&key); + + if result.is_err() { + error!("stat() failed with: {:#?}", result); + } + return result; + } + + async fn update( + &self, + info: Info, + fieldpaths: Option>, + ) -> Result { + trace!("update({:?}, {:?})", info, fieldpaths); + Err(Status::unimplemented("no support for updating snapshots")) + } + + async fn usage(&self, key: String) -> Result { + trace!("usage({})", key); + + let result = self.usage_impl(key).await; + if result.is_err() { + error!("usage() failed with: {:#?}", result); + } + return result; + } + + async fn mounts(&self, key: String) -> Result, Self::Error> { + trace!("mounts({})", key); + let result = self.mounts_impl(key).await; + if result.is_err() { + error!("mounts() failed with: {:#?}", result); + } + return result; + } + async fn prepare( &self, key: String, @@ -777,18 +1140,25 @@ impl Snapshotter for TarDevSnapshotter { ) -> Result, Status> { trace!("prepare({}, {}, {:?})", key, parent, labels); - // There are two reasons for preparing a snapshot: to build an image and to actually use it - // as a container image. We determine the reason by the presence of the snapshot-ref label. - if labels.get(TARGET_LAYER_DIGEST_LABEL).is_some() { - info!("prepare(): prepare a staging dir for containerd tar data extraction"); - self.prepare_unpack_dir(key, parent, labels).await - } else { - info!("prepare(): create active snapshot"); - self.store - .write() - .await - .prepare_snapshot_for_use(Kind::Active, key, parent, labels) + let result = { + // There are two reasons for preparing a snapshot: to build an image and to actually use it + // as a container image. We determine the reason by the presence of the snapshot-ref label. + if labels.get(TARGET_LAYER_DIGEST_LABEL).is_some() { + info!("prepare(): prepare a staging dir for containerd tar data extraction"); + self.prepare_unpack_dir(key, parent, labels).await + } else { + info!("prepare(): create active snapshot"); + self.store + .write() + .await + .prepare_snapshot_for_use(Kind::Active, key, parent, labels) + } + }; + + if result.is_err() { + error!("prepare() failed with: {:#?}", result); } + return result; } async fn view( @@ -798,10 +1168,17 @@ impl Snapshotter for TarDevSnapshotter { labels: HashMap, ) -> Result, Self::Error> { trace!("view({}, {}, {:?})", key, parent, labels); - self.store - .write() - .await - .prepare_snapshot_for_use(Kind::View, key, parent, labels) + + let result = + self.store + .write() + .await + .prepare_snapshot_for_use(Kind::View, key, parent, labels); + + if result.is_err() { + error!("view() failed with {:#?}", result); + } + return result; } async fn commit( @@ -811,57 +1188,21 @@ impl Snapshotter for TarDevSnapshotter { labels: HashMap, ) -> Result<(), Self::Error> { trace!("commit({}, {}, {:?})", name, key, labels); - - let info; - { - let store = self.store.write().await; - info = store.read_snapshot(&key)?; - if info.kind != Kind::Active { - return Err(Status::failed_precondition("snapshot is not active")); - } - } - - if info.labels.get(TARGET_LAYER_DIGEST_LABEL).is_some() { - self.prepare_image_layer(name, info.parent, labels).await - } else { - Err(Status::unimplemented( - "no support for commiting arbitrary snapshots", - )) + let result = self.commit_impl(name, key, labels).await; + if result.is_err() { + error!("commit() failed with: {:#?}", result); } + return result; } async fn remove(&self, key: String) -> Result<(), Self::Error> { trace!("remove({})", key); - let store = self.store.write().await; - // TODO: Move this to store. - if let Ok(info) = store.read_snapshot(&key) { - match info.kind { - Kind::Committed => { - if info.labels.get(TARGET_LAYER_DIGEST_LABEL).is_some() { - // Try to delete a layer. It's ok if it's not found. - if let Err(e) = fs::remove_file(store.layer_path(&key)) { - if e.kind() != io::ErrorKind::NotFound { - return Err(e.into()); - } - } - } - } - Kind::Active => { - if let Err(e) = tokio::fs::remove_dir_all(store.extract_dir(&key)).await { - if e.kind() != io::ErrorKind::NotFound { - return Err(e.into()); - } - } - } - _ => {} - } + let result = self.remove_impl(key).await; + if result.is_err() { + error!("remove() failed with {:#?}", result); } - - let name = store.snapshot_path(&key, false)?; - fs::remove_file(name)?; - - Ok(()) + return result; } type InfoStream = impl tokio_stream::Stream> + Send + 'static; @@ -891,4 +1232,4 @@ fn name_to_hash(name: &str) -> String { None => hasher.update(name), } format!("{:x}", hasher.finalize()) -} \ No newline at end of file +} diff --git a/src/tardev-snapshotter/tardev-snapshotter.service b/src/tardev-snapshotter/tardev-snapshotter.service index 15b1b2521167..a839fcf5cbc3 100644 --- a/src/tardev-snapshotter/tardev-snapshotter.service +++ b/src/tardev-snapshotter/tardev-snapshotter.service @@ -1,11 +1,15 @@ [Unit] Description=tardev containerd snapshotter daemon After=network.target +After=local-fs.target +Requires=local-fs.target +StartLimitIntervalSec=0 [Service] ExecStart=/usr/bin/tardev-snapshotter /var/lib/containerd/io.containerd.snapshotter.v1.tardev /run/containerd/tardev-snapshotter.sock -Environment="RUST_LOG=tardev_snapshotter=trace" -Restart=on-failure +Restart=always +RestartSec=3 +OOMScoreAdjust=-999 [Install] -WantedBy=kubelet.service +WantedBy=multi-user.target \ No newline at end of file diff --git a/src/tardev-snapshotter/verity/src/lib.rs b/src/tardev-snapshotter/verity/src/lib.rs index 30c59e87cfda..8bc174589636 100644 --- a/src/tardev-snapshotter/verity/src/lib.rs +++ b/src/tardev-snapshotter/verity/src/lib.rs @@ -1,5 +1,5 @@ use generic_array::{typenum::Unsigned, GenericArray}; -use sha2::{digest::OutputSizeUser, Digest}; +use sha2::Digest; use std::fs::File; use std::io::{self, Read, Seek, SeekFrom, Write}; use zerocopy::byteorder::{LE, U32, U64}; @@ -228,11 +228,10 @@ pub fn write_to(f: &mut File) -> impl FnMut(&mut File, &[u8], u64) -> io::Result pub fn append_tree( file: &mut File, + salt: &[u8], ) -> io::Result> { let file_size = file.seek(io::SeekFrom::End(0))?; file.rewind()?; - let mut salt = Vec::new(); - salt.resize(::OutputSize::USIZE, 0); let verity = Verity::::new(file_size, 4096, 4096, &salt, file_size)?; traverse_file(file, 0, true, verity, &mut |f, data, offset| { f.seek(SeekFrom::Start(offset))?; diff --git a/src/tools/sign-oci-layer-root-hashes/.gitignore b/src/tools/sign-oci-layer-root-hashes/.gitignore new file mode 100644 index 000000000000..ae573fefd9ce --- /dev/null +++ b/src/tools/sign-oci-layer-root-hashes/.gitignore @@ -0,0 +1,2 @@ +layers-cache.json +src/version.rs diff --git a/src/tools/sign-oci-layer-root-hashes/Cargo.lock b/src/tools/sign-oci-layer-root-hashes/Cargo.lock new file mode 100644 index 000000000000..c2b33d79e651 --- /dev/null +++ b/src/tools/sign-oci-layer-root-hashes/Cargo.lock @@ -0,0 +1,2341 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" + +[[package]] +name = "async-trait" +version = "0.1.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[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.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32a994c2b3ca201d9b263612a374263f05e7adde37c4707f693dcd375076d1f" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b918671670962b48bc23753aef0c51d072dca6f52f01f800854ada6ddb7f7d3" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.52.0", +] + +[[package]] +name = "clap" +version = "4.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "containerd-client" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbd55a5b186b60273ed7361d18d566ede8d66db962bafd702dd4db7fd30f23f" +dependencies = [ + "prost", + "prost-types", + "tokio", + "tonic", + "tonic-build 0.9.2", + "tower", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +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 = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + +[[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 = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "docker_credential" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31951f49556e34d90ed28342e1df7e1cb7a229c4cab0aecc627b5d91edd41d07" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.52.0", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "libz-ng-sys", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "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.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "h2" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 2.2.3", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-auth" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643c9bbf6a4ea8a656d6b4cd53d34f79e3f841ad5203c1a55fb7d761923bc255" +dependencies = [ + "memchr", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "js-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jwt" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" +dependencies = [ + "base64 0.13.1", + "crypto-common", + "digest", + "hmac", + "serde", + "serde_json", + "sha2", +] + +[[package]] +name = "k8s-cri" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1ac03a0ee89d53fc350989682a56915a4f93fe7b51801a1066cb3caeb2a23f" +dependencies = [ + "prost", + "serde", + "tonic", + "tonic-build 0.8.4", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libz-ng-sys" +version = "1.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6409efc61b12687963e602df8ecf70e8ddacf95bc6576bcf16e3ac6328083c5" +dependencies = [ + "cmake", + "libc", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "oci" +version = "0.1.0" +dependencies = [ + "libc", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "oci-distribution" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a635cabf7a6eb4e5f13e9e82bd9503b7c2461bf277132e38638a935ebd684b4" +dependencies = [ + "bytes", + "chrono", + "futures-util", + "http", + "http-auth", + "jwt", + "lazy_static", + "olpc-cjson", + "regex", + "reqwest", + "serde", + "serde_json", + "sha2", + "thiserror", + "tokio", + "tracing", + "unicase", +] + +[[package]] +name = "olpc-cjson" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d637c9c15b639ccff597da8f4fa968300651ad2f1e968aefc3b4927a6fb2027a" +dependencies = [ + "serde", + "serde_json", + "unicode-normalization", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl" +version = "0.10.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap 2.2.3", +] + +[[package]] +name = "pin-project" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +dependencies = [ + "bytes", + "heck", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 1.0.109", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "prost-types" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "reqwest" +version = "0.11.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "winreg", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-transcode" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "590c0e25c2a5bb6e85bf5c1bce768ceb86b316e7a01bdf07d2cb4ec2271990e2" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "serde_ignored" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8e319a36d1b52126a0d608f24e93b2d81297091818cd70625fcf50a15d84ddf" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_json" +version = "1.0.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" +dependencies = [ + "indexmap 1.9.3", + "ryu", + "serde", + "yaml-rust", +] + +[[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 = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "solar" +version = "3.2.0-azl1.genpolicy1" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.21.7", + "clap", + "containerd-client", + "docker_credential", + "env_logger", + "flate2", + "fs2", + "futures", + "generic-array", + "hex", + "k8s-cri", + "log", + "oci", + "oci-distribution", + "openssl", + "rand", + "serde", + "serde-transcode", + "serde_ignored", + "serde_json", + "serde_yaml", + "sha2", + "tarindex", + "tempfile", + "tokio", + "tonic", + "tower", + "zerocopy", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tar" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tarfs-defs" +version = "0.1.0" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "tarindex" +version = "0.1.0" +dependencies = [ + "tar", + "tarfs-defs", + "zerocopy", +] + +[[package]] +name = "tempfile" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tonic" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" +dependencies = [ + "async-trait", + "axum", + "base64 0.21.7", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tonic-build" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6fdaae4c2c638bb70fe42803a26fbd6fc6ac8c72f5c59f67ecc2a2dcabf4b07" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +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 = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.58", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" + +[[package]] +name = "wasm-streams" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "zerocopy" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854e949ac82d619ee9a14c66a1b674ac730422372ccb759ce0c39cabcf2bf8e6" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] diff --git a/src/tools/sign-oci-layer-root-hashes/Cargo.toml b/src/tools/sign-oci-layer-root-hashes/Cargo.toml new file mode 100644 index 000000000000..57493f5eeb1e --- /dev/null +++ b/src/tools/sign-oci-layer-root-hashes/Cargo.toml @@ -0,0 +1,67 @@ +# Copyright (c) 2024 Microsoft Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +[package] +name = "solar" +version = "3.2.0-azl1.genpolicy1" +authors = ["The Kata Containers community "] +edition = "2021" +license = "Apache-2.0" + +[dependencies] +# Logging. +env_logger = "0.10.0" +log = "0.4.17" + +# Command line parsing. +clap = { version = "4.1.8", features = ["derive"] } + +# YAML file serialization/deserialization. +base64 = "0.21.0" +serde = { version = "1.0.159", features = ["derive"] } + +# Newer serde_yaml versions are using unsafe-libyaml instead of yaml-rust, +# and incorrectly change on serialization: +# +# value: "yes" +# +# to: +# +# value: yes +# +# In YAML, the value yes without quotes is reserved for boolean, +# and confuses kubectl, that expects a string value. +serde_yaml = "0.8" + +# Container repository. +anyhow = "1.0.32" +async-trait = "0.1.68" +docker_credential = "1.3.1" +flate2 = { version = "1.0.26", features = ["zlib-ng"], default-features = false } +oci-distribution = { version = "0.10.0" } +openssl = { version = "0.10.54" } +serde_ignored = "0.1.7" +serde_json = "1.0.39" +serde-transcode = "1.1.1" +tokio = {version = "1.33.0", features = ["rt-multi-thread"]} + +# OCI container specs. +oci = { path = "../../libs/oci" } + +# dm-verity root hash support +generic-array = "0.14.6" +sha2 = "0.10.6" +tarindex = { path = "../../tardev-snapshotter/tarindex" } +tempfile = "3.5.0" +zerocopy = "0.6.1" +fs2 = "0.4.3" +k8s-cri = "0.7.0" +tonic = "0.9.2" +tower = "0.4.13" +[target.'cfg(target_os = "linux")'.dependencies] +containerd-client = "0.4.0" +rand = "0.8.5" +futures = "0.3.31" +hex = "0.4.3" \ No newline at end of file diff --git a/src/tools/sign-oci-layer-root-hashes/Makefile b/src/tools/sign-oci-layer-root-hashes/Makefile new file mode 100644 index 000000000000..75e4f3d68a6e --- /dev/null +++ b/src/tools/sign-oci-layer-root-hashes/Makefile @@ -0,0 +1,51 @@ +# Copyright (c) 2020 Intel Corporation +# Portions Copyright (c) Microsoft Corporation. +# +# SPDX-License-Identifier: Apache-2.0 +# + +COMMIT_HASH := $(shell git rev-parse HEAD 2>/dev/null || true) +# appends '-dirty' to the commit hash if there are uncommitted changes +COMMIT_INFO := $(if $(shell git status --porcelain --untracked-files=no 2>/dev/null || true),${COMMIT_HASH}-dirty,${COMMIT_HASH}) + +GENERATED_CODE = src/version.rs + +GENERATED_REPLACEMENTS= COMMIT_INFO +GENERATED_FILES := + +GENERATED_FILES += $(GENERATED_CODE) + +$(GENERATED_FILES): %: %.in + sed $(foreach r,$(GENERATED_REPLACEMENTS),-e 's|@$r@|$($r)|g') "$<" > "$@" + +.DEFAULT_GOAL := default +default: build + +build: $(GENERATED_FILES) + # @RUSTFLAGS="$(EXTRA_RUSTFLAGS) --deny warnings" cargo build --$(BUILD_TYPE) + @RUSTFLAGS="$(EXTRA_RUSTFLAGS)" cargo build --$(BUILD_TYPE) + +static-checks-build: + @echo "INFO: static-checks-build do nothing.." + +clean: + cargo clean + rm -f $(GENERATED_FILES) + +vendor: + cargo vendor + +test: + +install: $(GENERATED_FILES) + @RUSTFLAGS="$(EXTRA_RUSTFLAGS) --deny warnings" cargo install --locked --target $(TRIPLE) --path . + +check: $(GENERATED_CODE) standard_rust_check + +.PHONY: \ + build \ + check \ + clean \ + install \ + test \ + vendor diff --git a/src/tools/sign-oci-layer-root-hashes/src/main.rs b/src/tools/sign-oci-layer-root-hashes/src/main.rs new file mode 100644 index 000000000000..52313a76781c --- /dev/null +++ b/src/tools/sign-oci-layer-root-hashes/src/main.rs @@ -0,0 +1,209 @@ +// Copyright (c) 2023 Microsoft Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +use std::{ + fs, + path::PathBuf, + process::{Command, Stdio}, +}; + +use anyhow::{Context, Error}; +use base64::{engine::general_purpose, Engine}; +use futures::future; +use serde::{Deserialize, Serialize}; +use std::io::Write; + +mod registry; +#[cfg(target_os = "linux")] +mod registry_containerd; +mod utils; +mod verity; +mod version; + +#[derive(Serialize, Deserialize)] +struct ImageInfo { + name: String, + layers: Vec, +} + +#[derive(Serialize, Deserialize)] +struct LayerInfo { + digest: String, + root_hash: String, + signature: String, + salt: String, +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + env_logger::init(); + let config = utils::Config::new(); + + if config.version { + println!( + "SOLaR tool: id: {}, version: {}, commit: {}", + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_VERSION"), + version::COMMIT_INFO + ); + return Ok(()); + } + + if config.images.is_none() && config.image.is_none() { + return Err(anyhow::anyhow!("No images specified")); + } + + if config.signer.exists() { + if !config.signer.is_file() { + return Err(anyhow::anyhow!("Signer certificate is not a file")); + } + } else { + return Err(anyhow::anyhow!("Signer certificate does not exist")); + } + + if config.key.exists() { + if !config.key.is_file() { + return Err(anyhow::anyhow!("Key file is not a file")); + } + } else { + return Err(anyhow::anyhow!("Key file does not exist")); + } + + get_root_hashes(&config) + .await + .context("Failed to get root hashes")?; + + Ok(()) +} + +fn sign_hash( + hash: &String, + key: &PathBuf, + password: &String, + signer: &PathBuf, +) -> Result { + let openssl = Command::new("openssl") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .arg("smime") + .arg("-sign") + .arg("-nocerts") + .arg("-noattr") + .arg("-binary") + .arg("-inkey") + .arg(key) + .arg("-passin") + .arg(password) + .arg("-signer") + .arg(signer) + .arg("-outform") + .arg("der") + .spawn() + .context("Failed to spawn openssl")?; + + write!( + openssl + .stdin + .as_ref() + .context("Failed to open openssl stdin")?, + "{}", + hash + ) + .context("Failed to write hash to openssl stdin")?; + let output = openssl + .wait_with_output() + .context("Failed to retrieve openssl output")?; + if !output.status.success() { + return Err(anyhow::anyhow!( + "Openssl failed with exit code: {}", + output.status + )); + } + let base64_output = general_purpose::STANDARD.encode(&output.stdout); + + Ok(base64_output) +} + +async fn get_container_image_root_hashes( + image: &str, + config: &utils::Config, +) -> Result { + let container = registry::get_container(&config, image) + .await + .context("Failed to get container image")?; + let layers = container.get_image_layers(); + let hash_signatures = layers + .iter() + .map(|layer| { + let digest = layer.digest.clone(); + let root_hash = layer.verity_hash.clone(); + let signature = sign_hash(&root_hash, &config.key, &config.passphrase, &config.signer) + .context("Failed to sign hash")?; + Ok(LayerInfo { + digest, + root_hash, + salt: hex::encode(layer.salt), + signature, + }) + }) + .collect::, Error>>() + .context("Failed to collect hash signatures")?; + + Ok(ImageInfo { + name: image.to_string(), + layers: hash_signatures, + }) +} + +async fn get_root_hashes(config: &utils::Config) -> Result, Error> { + let mut image_tags: Vec = vec![]; + if let Some(images) = &config.images { + image_tags.append( + fs::read_to_string(images) + .context("Failed to read image tags file")? + .lines() + .map(|line| line.to_string()) + .collect::>() + .as_mut(), + ); + } else if let Some(images) = &config.image { + image_tags.append(images.clone().as_mut()); + } else { + return Err(anyhow::anyhow!("No images specified")); + }; + let images = future::try_join_all( + image_tags + .iter() + .map(|image| get_container_image_root_hashes(&image, config)), + ) + .await + .context("Failed to gather signatures for requested images")?; + + let signatures_json = + serde_json::to_string(&images).context("Failed to serialize hash signatures to json")?; + if config.output.is_some() { + std::fs::create_dir_all( + config + .output + .as_ref() + .context("Failed to get output path")? + .parent() + .context("Failed to get output directory path")?, + ) + .context("Failed to create the output directory")?; + std::fs::write( + config + .output + .as_ref() + .context("Failed to get output path")?, + signatures_json, + ) + .context("Failed to save the output json to a file")?; + } else { + println!("{}", signatures_json); + } + + Ok(images) +} diff --git a/src/tools/sign-oci-layer-root-hashes/src/registry.rs b/src/tools/sign-oci-layer-root-hashes/src/registry.rs new file mode 100644 index 000000000000..a0b09bca4d10 --- /dev/null +++ b/src/tools/sign-oci-layer-root-hashes/src/registry.rs @@ -0,0 +1,470 @@ +// Copyright (c) 2023 Microsoft Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +// Allow Docker image config field names. +#![allow(non_snake_case)] + +use crate::verity; + +use crate::utils::Config; +use anyhow::{anyhow, Context, Result}; +use docker_credential::{CredentialRetrievalError, DockerCredential}; +use fs2::FileExt; +use log::warn; +use log::{debug, info, LevelFilter}; +use oci_distribution::client::{linux_amd64_resolver, ClientConfig}; +use oci_distribution::{manifest, secrets::RegistryAuth, Client, Reference}; +use rand::rngs::ThreadRng; +use rand::Rng; +use serde::{Deserialize, Serialize}; +use sha2::{digest::typenum::Unsigned, digest::OutputSizeUser, Sha256}; +use std::fs::OpenOptions; +use std::io::BufWriter; +use std::{io, io::Seek, io::Write, path::Path}; +use tokio::io::AsyncWriteExt; + +pub(crate) type Salt = [u8; ::OutputSize::USIZE]; + +#[derive(Debug)] +pub(crate) struct VerityHash { + pub root_hash: String, + pub salt: Salt, +} + +/// Container image properties obtained from an OCI repository. +#[derive(Clone, Debug, Default)] +pub struct Container { + pub config_layer: DockerConfigLayer, + pub image_layers: Vec, +} + +/// Image config layer properties. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct DockerConfigLayer { + architecture: String, + config: DockerImageConfig, + pub rootfs: DockerRootfs, +} + +/// Image config properties. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +struct DockerImageConfig { + User: Option, + Tty: Option, + Env: Option>, + Cmd: Option>, + WorkingDir: Option, + Entrypoint: Option>, +} + +/// Container rootfs information. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct DockerRootfs { + r#type: String, + pub diff_ids: Vec, +} + +/// This application's image layer properties. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ImageLayer { + pub digest: String, + pub verity_hash: String, + pub salt: Salt, +} + +impl Container { + pub async fn new(use_cached_files: bool, image: &str) -> Result { + info!("============================================"); + info!("Pulling manifest and config for {:?}", image); + let reference: Reference = image.to_string().parse().unwrap(); + let auth = build_auth(&reference); + + let mut client = Client::new(ClientConfig { + platform_resolver: Some(Box::new(linux_amd64_resolver)), + ..Default::default() + }); + + match client.pull_manifest_and_config(&reference, &auth).await { + Ok((manifest, digest_hash, config_layer_str)) => { + debug!("digest_hash: {:?}", digest_hash); + debug!( + "manifest: {}", + serde_json::to_string_pretty(&manifest).unwrap() + ); + + // Log the contents of the config layer. + if log::max_level() >= LevelFilter::Debug { + let mut deserializer = serde_json::Deserializer::from_str(&config_layer_str); + let mut serializer = serde_json::Serializer::pretty(io::stderr()); + serde_transcode::transcode(&mut deserializer, &mut serializer).unwrap(); + } + + let config_layer: DockerConfigLayer = + serde_json::from_str(&config_layer_str).unwrap(); + let image_layers = get_image_layers( + use_cached_files, + &mut client, + &reference, + &manifest, + &config_layer, + ) + .await + .unwrap(); + + Ok(Container { + config_layer, + image_layers, + }) + } + Err(oci_distribution::errors::OciDistributionError::AuthenticationFailure(message)) => { + panic!("Container image registry authentication failure ({}). Are docker credentials set-up for current user?", &message); + } + Err(e) => { + panic!( + "Failed to pull container image manifest and config - error: {:#?}", + &e + ); + } + } + } + + pub fn get_image_layers(&self) -> Vec { + self.image_layers.clone() + } +} + +async fn get_image_layers( + use_cached_files: bool, + client: &mut Client, + reference: &Reference, + manifest: &manifest::OciImageManifest, + config_layer: &DockerConfigLayer, +) -> Result> { + let mut layer_index = 0; + let mut layers = Vec::new(); + let mut rng = rand::thread_rng(); + + for layer in &manifest.layers { + if layer + .media_type + .eq(manifest::IMAGE_DOCKER_LAYER_GZIP_MEDIA_TYPE) + || layer.media_type.eq(manifest::IMAGE_LAYER_GZIP_MEDIA_TYPE) + || layer.media_type.eq(manifest::IMAGE_LAYER_MEDIA_TYPE) + || layer + .media_type + .eq(manifest::IMAGE_DOCKER_LAYER_TAR_MEDIA_TYPE) + { + if layer_index < config_layer.rootfs.diff_ids.len() { + let verity_hash = get_verity_hash( + use_cached_files, + &layer.digest, + client, + reference, + &mut rng, + layer.media_type.eq(manifest::IMAGE_LAYER_MEDIA_TYPE) + || layer + .media_type + .eq(manifest::IMAGE_DOCKER_LAYER_TAR_MEDIA_TYPE), + ) + .await?; + layers.push(ImageLayer { + digest: layer.digest.clone(), + verity_hash: verity_hash.root_hash, + salt: verity_hash.salt, + }); + } else { + return Err(anyhow!("Too many Docker gzip layers")); + } + + layer_index += 1; + } + } + + Ok(layers) +} + +async fn get_verity_hash( + use_cached_files: bool, + layer_digest: &str, + client: &mut Client, + reference: &Reference, + rng: &mut ThreadRng, + decompressed: bool, +) -> Result { + let temp_dir = tempfile::tempdir_in(".")?; + let base_dir = temp_dir.path(); + let cache_file = "layers-cache.json"; + // Use file names supported by both Linux and Windows. + let file_name = str::replace(layer_digest, ":", "-"); + let mut decompressed_path = base_dir.join(file_name); + decompressed_path.set_extension("tar"); + + let mut compressed_path = decompressed_path.clone(); + compressed_path.set_extension("gz"); + + // get value from store and return if it exists + let verity_hash = if use_cached_files { + let verity_hash = read_verity_from_store(cache_file, layer_digest)?; + info!("Using cache file"); + + verity_hash + } else { + None + }; + + // create the layer files + let verity_hash_result = match verity_hash { + Some(v) => Ok(v), + None => { + create_decompressed_layer_file( + client, + reference, + layer_digest, + &decompressed_path, + if decompressed { + None + } else { + Some(&compressed_path) + }, + ) + .await + .context("Failed to create verity hash for {layer_digest}")?; + + let salt: Salt = rng.gen(); + let root_hash = get_verity_hash_value(&decompressed_path, &salt) + .context("Failed to get verity hash")?; + let verity_hash = VerityHash { root_hash, salt }; + if use_cached_files { + add_verity_to_store(cache_file, layer_digest, &verity_hash)?; + } + + Ok(verity_hash) + } + }; + + temp_dir.close()?; + match &verity_hash_result { + Ok(v) => { + info!("dm-verity root hash: {}", v.root_hash); + } + Err(_) => { + // remove the cache file if we're using it + if use_cached_files { + std::fs::remove_file(cache_file)?; + } + } + }; + + verity_hash_result +} + +// the store is a json file that matches layer hashes to verity hashes +pub(crate) fn add_verity_to_store( + cache_file: &str, + digest: &str, + verity_hash: &VerityHash, +) -> Result<()> { + // open the json file in read mode, create it if it doesn't exist + let read_file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(cache_file)?; + + let mut data: Vec = if let Ok(vec) = serde_json::from_reader(read_file) { + vec + } else { + // Delete the malformed file here if it's present + Vec::new() + }; + + // Add new data to the deserialized JSON + data.push(ImageLayer { + digest: digest.into(), + verity_hash: verity_hash.root_hash.clone(), + salt: verity_hash.salt, + }); + + // Serialize in pretty format + let serialized = serde_json::to_string_pretty(&data)?; + + // Open the JSON file to write + let file = OpenOptions::new().write(true).open(cache_file)?; + + // try to lock the file, if it fails, get the error + let result = file.try_lock_exclusive(); + if result.is_err() { + warn!("Waiting to lock file: {cache_file}"); + file.lock_exclusive()?; + } + // Write the serialized JSON to the file + let mut writer = BufWriter::new(&file); + writeln!(writer, "{}", serialized)?; + writer.flush()?; + file.unlock()?; + Ok(()) +} + +// helper function to read the verity hash from the store +// returns empty string if not found or file does not exist +pub(crate) fn read_verity_from_store(cache_file: &str, digest: &str) -> Result> { + match OpenOptions::new().read(true).open(cache_file) { + Ok(file) => match serde_json::from_reader(file) { + Result::, _>::Ok(layers) => { + for layer in layers { + if layer.digest == digest { + return Ok(Some(VerityHash { + root_hash: layer.verity_hash, + salt: layer.salt, + })); + } + } + } + Err(e) => { + warn!("read_verity_from_store: failed to read cached image layers: {e}"); + } + }, + Err(e) => { + info!("read_verity_from_store: failed to open cache file: {e}"); + } + } + + Ok(None) +} + +async fn create_decompressed_layer_file( + client: &mut Client, + reference: &Reference, + layer_digest: &str, + decompressed_path: &Path, + compressed_path: Option<&Path>, +) -> Result<()> { + match compressed_path { + Some(compressed_path) => { + pull_layer_file(client, reference, layer_digest, compressed_path).await?; + decompress_file(compressed_path, decompressed_path)?; + } + None => pull_layer_file(client, reference, layer_digest, decompressed_path).await?, + } + attach_tarfs_index(decompressed_path)?; + + Ok(()) +} + +async fn pull_layer_file( + client: &mut Client, + reference: &Reference, + layer_digest: &str, + path: &Path, +) -> Result<()> { + info!("Pulling layer {:?}", layer_digest); + let mut file = tokio::fs::File::create(&path) + .await + .map_err(|e| anyhow!(e))?; + client + .pull_blob(reference, layer_digest, &mut file) + .await + .map_err(|e| anyhow!(e))?; + file.flush().await.map_err(|e| anyhow!(e))?; + + Ok(()) +} + +fn decompress_file(compressed_path: &Path, decompressed_path: &Path) -> Result<()> { + info!("Decompressing layer"); + let compressed_file = std::fs::File::open(compressed_path).map_err(|e| anyhow!(e))?; + let mut decompressed_file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open(decompressed_path)?; + let mut gz_decoder = flate2::read::GzDecoder::new(compressed_file); + std::io::copy(&mut gz_decoder, &mut decompressed_file).map_err(|e| anyhow!(e))?; + decompressed_file.flush().map_err(|e| anyhow!(e))?; + + Ok(()) +} + +fn attach_tarfs_index(path: &Path) -> Result<()> { + info!("Adding tarfs index to layer"); + let mut file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .open(path)?; + tarindex::append_index(&mut file).map_err(|e| anyhow!(e))?; + file.flush().map_err(|e| anyhow!(e))?; + + Ok(()) +} + +pub fn get_verity_hash_value(path: &Path, salt: &Salt) -> Result { + info!("Calculating dm-verity root hash"); + let mut file = std::fs::File::open(path)?; + let size = file.seek(std::io::SeekFrom::End(0))?; + if size < 4096 { + return Err(anyhow!("Block device {:?} is too small: {size}", &path)); + } + + let v = verity::Verity::::new(size, 4096, 4096, salt, 0)?; + let hash = verity::traverse_file(&mut file, 0, false, v, &mut verity::no_write)?; + let result = format!("{:x}", hash); + + Ok(result) +} + +#[cfg(target_os = "linux")] +pub async fn get_container(config: &Config, image: &str) -> Result { + if let Some(socket_path) = &config.containerd_socket_path { + return Container::new_containerd_pull(config.use_cache, image, socket_path).await; + } + Container::new(config.use_cache, image).await +} + +#[cfg(target_os = "windows")] +pub async fn get_container(config: &Config, image: &str) -> Result { + Container::new(config.use_cache, image).await +} + +fn build_auth(reference: &Reference) -> RegistryAuth { + debug!("build_auth: {:?}", reference); + + let server = reference + .resolve_registry() + .strip_suffix('/') + .unwrap_or_else(|| reference.resolve_registry()); + + match docker_credential::get_credential(server) { + Ok(DockerCredential::UsernamePassword(username, password)) => { + debug!("build_auth: Found docker credentials"); + return RegistryAuth::Basic(username, password); + } + Ok(DockerCredential::IdentityToken(_)) => { + warn!("build_auth: Cannot use contents of docker config, identity token not supported. Using anonymous access."); + } + Err(CredentialRetrievalError::ConfigNotFound) => { + debug!("build_auth: Docker config not found - using anonymous access."); + } + Err(CredentialRetrievalError::NoCredentialConfigured) => { + debug!("build_auth: Docker credentials not configured - using anonymous access."); + } + Err(CredentialRetrievalError::ConfigReadError) => { + debug!("build_auth: Cannot read docker credentials - using anonymous access."); + } + Err(CredentialRetrievalError::HelperFailure { stdout, stderr }) => { + if stdout == "credentials not found in native keychain\n" { + // On WSL, this error is generated when credentials are not + // available in ~/.docker/config.json. + debug!("build_auth: Docker credentials not found - using anonymous access."); + } else { + warn!("build_auth: Docker credentials not found - using anonymous access. stderr = {}, stdout = {}", + &stderr, &stdout); + } + } + Err(e) => panic!("Error handling docker configuration file: {}", e), + } + + RegistryAuth::Anonymous +} diff --git a/src/tools/sign-oci-layer-root-hashes/src/registry_containerd.rs b/src/tools/sign-oci-layer-root-hashes/src/registry_containerd.rs new file mode 100644 index 000000000000..ecdfbe96f8e3 --- /dev/null +++ b/src/tools/sign-oci-layer-root-hashes/src/registry_containerd.rs @@ -0,0 +1,439 @@ +// Copyright (c) 2023 Microsoft Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +// Allow Docker image config field names. +#![allow(non_snake_case)] +use crate::registry::{self, Container, DockerConfigLayer, ImageLayer, Salt, VerityHash}; + +use anyhow::{anyhow, Context, Result}; +use containerd_client::{services::v1::GetImageRequest, with_namespace}; +use docker_credential::{CredentialRetrievalError, DockerCredential}; +use k8s_cri::v1::{image_service_client::ImageServiceClient, AuthConfig}; +use log::{debug, info, warn}; +use oci_distribution::Reference; +use rand::{rngs::ThreadRng, Rng}; +use std::{collections::HashMap, convert::TryFrom, io::Write, path::Path}; +use tokio::{ + io, + io::{AsyncSeekExt, AsyncWriteExt}, + net::UnixStream, +}; +use tonic::transport::{Endpoint, Uri}; +use tonic::Request; +use tower::service_fn; + +impl Container { + pub async fn new_containerd_pull( + use_cached_files: bool, + image: &str, + containerd_socket_path: &str, + ) -> Result { + info!("============================================"); + info!("Using containerd socket: {:?}", containerd_socket_path); + + let ctrd_path = containerd_socket_path.to_string(); + let containerd_channel = Endpoint::try_from("http://[::]") + .unwrap() + .connect_with_connector(service_fn(move |_: Uri| { + UnixStream::connect(ctrd_path.clone()) + })) + .await?; + + let ctrd_client = containerd_client::Client::from(containerd_channel.clone()); + let k8_cri_image_client = ImageServiceClient::new(containerd_channel); + + let image_ref: Reference = image.to_string().parse().unwrap(); + + info!("Pulling image: {:?}", image_ref); + + pull_image(&image_ref, k8_cri_image_client.clone()).await?; + + let image_ref_str = &image_ref.to_string(); + + let manifest = get_image_manifest(image_ref_str, &ctrd_client).await?; + let config_layer = get_config_layer(image_ref_str, k8_cri_image_client) + .await + .unwrap(); + let image_layers = + get_image_layers(use_cached_files, &manifest, &config_layer, &ctrd_client).await?; + + Ok(Container { + config_layer, + image_layers, + }) + } +} +pub async fn get_content( + digest: &str, + client: &containerd_client::Client, +) -> Result { + let req = containerd_client::services::v1::ReadContentRequest { + digest: digest.to_string(), + offset: 0, + size: 0, + }; + let req = with_namespace!(req, "k8s.io"); + let mut c = client.content(); + let resp = c.read(req).await?; + let mut stream = resp.into_inner(); + + if let Some(chunk) = stream.message().await? { + if chunk.offset < 0 { + return Err(anyhow!("Negative offset in chunk")); + } + return Ok(serde_json::from_slice(&chunk.data)?); + } + + Err(anyhow!("Unable to find content for digest: {}", digest)) +} + +pub async fn get_image_manifest( + image_ref: &str, + client: &containerd_client::Client, +) -> Result { + let mut imageChannel = client.images(); + + let req = GetImageRequest { + name: image_ref.to_string(), + }; + let req = with_namespace!(req, "k8s.io"); + let resp = imageChannel.get(req).await?; + + let image_digest = resp.into_inner().image.unwrap().target.unwrap().digest; + + // content may be an image manifest (https://github.com/opencontainers/image-spec/blob/main/manifest.md) + //or an image index (https://github.com/opencontainers/image-spec/blob/main/image-index.md) + let content = get_content(&image_digest, client).await?; + + let is_image_manifest = content.get("layers").is_some(); + + if is_image_manifest { + return Ok(content); + } + + // else, content is an image index + let image_index = content; + + let manifests = image_index["manifests"].as_array().unwrap(); + + let mut manifestAmd64 = &serde_json::Value::Null; + + for entry in manifests { + let platform = entry["platform"].as_object().unwrap(); + let architecture = platform["architecture"].as_str().unwrap(); + let os = platform["os"].as_str().unwrap(); + if architecture == "amd64" && os == "linux" { + manifestAmd64 = entry; + break; + } + } + + let image_digest = manifestAmd64["digest"].as_str().unwrap(); + + get_content(image_digest, client).await +} + +pub async fn get_config_layer( + image_ref: &str, + mut client: ImageServiceClient, +) -> Result { + let req = k8s_cri::v1::ImageStatusRequest { + image: Some(k8s_cri::v1::ImageSpec { + image: image_ref.to_string(), + annotations: HashMap::new(), + }), + verbose: true, + }; + + let resp = client.image_status(req).await?; + let image_layers = resp.into_inner(); + + let status_info: serde_json::Value = + serde_json::from_str(image_layers.info.get("info").unwrap())?; + let image_spec = status_info["imageSpec"].as_object().unwrap(); + let docker_config_layer: DockerConfigLayer = + serde_json::from_value(serde_json::to_value(image_spec)?)?; + + Ok(docker_config_layer) +} + +pub async fn pull_image( + image_ref: &Reference, + mut client: ImageServiceClient, +) -> Result<()> { + let auth = build_auth(image_ref); + + debug!("cri auth: {:?}", auth); + + let req = k8s_cri::v1::PullImageRequest { + image: Some(k8s_cri::v1::ImageSpec { + image: image_ref.to_string(), + annotations: HashMap::new(), + }), + auth, + sandbox_config: None, + }; + + client.pull_image(req).await?; + + Ok(()) +} + +pub fn build_auth(reference: &Reference) -> Option { + debug!("build_auth: {:?}", reference); + + let server = reference + .resolve_registry() + .strip_suffix('/') + .unwrap_or_else(|| reference.resolve_registry()); + + debug!("server: {:?}", server); + + match docker_credential::get_credential(server) { + Ok(DockerCredential::UsernamePassword(username, password)) => { + debug!("build_auth: Found docker credentials"); + return Some(AuthConfig { + username, + password, + auth: "".to_string(), + server_address: "".to_string(), + identity_token: "".to_string(), + registry_token: "".to_string(), + }); + } + Ok(DockerCredential::IdentityToken(identity_token)) => { + debug!("build_auth: Found identity token"); + return Some(AuthConfig { + username: "".to_string(), + password: "".to_string(), + auth: "".to_string(), + server_address: "".to_string(), + identity_token, + registry_token: "".to_string(), + }); + } + Err(CredentialRetrievalError::ConfigNotFound) => { + debug!("build_auth: Docker config not found - using anonymous access."); + } + Err(CredentialRetrievalError::NoCredentialConfigured) => { + debug!("build_auth: Docker credentials not configured - using anonymous access."); + } + Err(CredentialRetrievalError::ConfigReadError) => { + debug!("build_auth: Cannot read docker credentials - using anonymous access."); + } + Err(CredentialRetrievalError::HelperFailure { stdout, stderr }) => { + if stdout == "credentials not found in native keychain\n" { + // On WSL, this error is generated when credentials are not + // available in ~/.docker/config.json. + debug!("build_auth: Docker credentials not found - using anonymous access."); + } else { + warn!("build_auth: Docker credentials not found - using anonymous access. stderr = {}, stdout = {}", + &stderr, &stdout); + } + } + Err(e) => panic!("Error handling docker configuration file: {}", e), + } + + None +} + +const TAR_LAYER_TYPE: &str = "application/vnd.oci.image.layer.v1.tar"; +const TAR_DIFF_LAYER_TYPE: &str = "application/vnd.docker.image.rootfs.diff.tar"; + +pub async fn get_image_layers( + use_cached_files: bool, + manifest: &serde_json::Value, + config_layer: &DockerConfigLayer, + client: &containerd_client::Client, +) -> Result> { + let mut layer_index = 0; + let mut layersVec = Vec::new(); + + let layers = manifest["layers"].as_array().unwrap(); + let mut rng = rand::thread_rng(); + + for layer in layers { + let layer_media_type = layer["mediaType"].as_str().unwrap(); + if layer_media_type.eq("application/vnd.docker.image.rootfs.diff.tar.gzip") + || layer_media_type.eq("application/vnd.oci.image.layer.v1.tar+gzip") + || layer_media_type.eq(TAR_LAYER_TYPE) + || layer_media_type.eq(TAR_DIFF_LAYER_TYPE) + { + if layer_index < config_layer.rootfs.diff_ids.len() { + let layer_digest = layer["digest"].as_str().unwrap(); + let verity_hash = get_verity_hash( + use_cached_files, + layer_digest, + client, + &mut rng, + layer_media_type.eq(TAR_LAYER_TYPE) || layer_media_type.eq(TAR_DIFF_LAYER_TYPE), + ) + .await?; + let imageLayer = ImageLayer { + digest: layer_digest.to_string(), + verity_hash: verity_hash.root_hash, + salt: verity_hash.salt, + }; + layersVec.push(imageLayer); + } else { + return Err(anyhow!("Too many Docker gzip layers")); + } + layer_index += 1; + } + } + + Ok(layersVec) +} + +async fn get_verity_hash( + use_cached_files: bool, + layer_digest: &str, + client: &containerd_client::Client, + rng: &mut ThreadRng, + decompressed: bool, +) -> Result { + let temp_dir = tempfile::tempdir_in(".")?; + let base_dir = temp_dir.path(); + let cache_file = "layers-cache.json"; + // Use file names supported by both Linux and Windows. + let file_name = str::replace(layer_digest, ":", "-"); + let mut decompressed_path = base_dir.join(file_name); + decompressed_path.set_extension("tar"); + + let mut compressed_path = decompressed_path.clone(); + compressed_path.set_extension("gz"); + + let verity_hash = if use_cached_files { + let verity_hash = registry::read_verity_from_store(cache_file, layer_digest)?; + info!("Using cache file"); + + verity_hash + } else { + None + }; + + let verity_hash_result = match verity_hash { + Some(v) => Ok(v), + None => { + // go find verity hash if not found in cache + create_decompressed_layer_file( + client, + layer_digest, + &decompressed_path, + if decompressed { + None + } else { + Some(&compressed_path) + }, + ) + .await + .context(format!("Failed to create verity hash for {layer_digest}"))?; + + let salt: Salt = rng.gen(); + let root_hash = registry::get_verity_hash_value(&decompressed_path, &salt) + .context("Failed to get verity hash")?; + let verity_hash = VerityHash { root_hash, salt }; + if use_cached_files { + registry::add_verity_to_store(cache_file, layer_digest, &verity_hash)?; + } + + Ok(verity_hash) + } + }; + temp_dir.close()?; + match &verity_hash_result { + Ok(v) => { + info!("dm-verity root hash: {}", v.root_hash); + } + Err(_) => { + // remove the cache file if we're using it + if use_cached_files { + std::fs::remove_file(cache_file)?; + } + } + }; + + verity_hash_result +} + +async fn create_decompressed_layer_file( + client: &containerd_client::Client, + layer_digest: &str, + decompressed_path: &Path, + compressed_path: Option<&Path>, +) -> Result<()> { + match compressed_path { + Some(compressed_path) => { + pull_layer_file(client, layer_digest, compressed_path).await?; + decompress_file(compressed_path, decompressed_path)?; + } + None => pull_layer_file(client, layer_digest, decompressed_path).await?, + } + attach_tarfs_index(decompressed_path)?; + + Ok(()) +} + +fn attach_tarfs_index(path: &Path) -> Result<()> { + info!("Adding tarfs index to layer"); + let mut file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .open(path)?; + tarindex::append_index(&mut file).map_err(|e| anyhow!(e))?; + file.flush().map_err(|e| anyhow!(e))?; + Ok(()) +} + +fn decompress_file(compressed_path: &Path, decompressed_path: &Path) -> Result<()> { + let compressed_file = std::fs::File::open(compressed_path).map_err(|e| anyhow!(e))?; + let mut decompressed_file = std::fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open(decompressed_path)?; + let mut gz_decoder = flate2::read::GzDecoder::new(compressed_file); + std::io::copy(&mut gz_decoder, &mut decompressed_file).map_err(|e| anyhow!(e))?; + + decompressed_file.flush().map_err(|e| anyhow!(e))?; + Ok(()) +} + +async fn pull_layer_file( + client: &containerd_client::Client, + layer_digest: &str, + path: &Path, +) -> Result<()> { + info!("Pulling layer {layer_digest}"); + let mut file = tokio::fs::File::create(&path) + .await + .map_err(|e| anyhow!(e))?; + + let req = containerd_client::services::v1::ReadContentRequest { + digest: layer_digest.to_string(), + offset: 0, + size: 0, + }; + let req = with_namespace!(req, "k8s.io"); + let mut c = client.content(); + let resp = c.read(req).await?; + let mut stream = resp.into_inner(); + + while let Some(chunk) = stream.message().await? { + if chunk.offset < 0 { + return Err(anyhow!("Too many Docker gzip layers")); + } + file.seek(io::SeekFrom::Start(chunk.offset as u64)).await?; + file.write_all(&chunk.data).await?; + } + + file.flush() + .await + .map_err(|e| anyhow!(e)) + .expect("Failed to flush file"); + + Ok(()) +} diff --git a/src/tools/sign-oci-layer-root-hashes/src/utils.rs b/src/tools/sign-oci-layer-root-hashes/src/utils.rs new file mode 100644 index 000000000000..b0c265fc9060 --- /dev/null +++ b/src/tools/sign-oci-layer-root-hashes/src/utils.rs @@ -0,0 +1,87 @@ +// Copyright (c) 2023 Microsoft Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +use std::path::PathBuf; + +use clap::Parser; + +#[derive(Debug, Parser)] +struct CommandLineOptions { + #[clap(short, long, help = "Image tag")] + image: Option>, + + #[clap( + short, + long, + help = "Path to a file containing a newline-separated list of image tags" + )] + images: Option, + + #[clap( + short, + long, + help = "Create and use a cache of container image layer contents and dm-verity information (in ./layers_cache/)" + )] + use_cached_files: bool, + + #[clap( + short = 'd', + long, + help = "If specified, will use existing containerd service to pull container images. This option is only supported on Linux", + // from https://docs.rs/clap/4.1.8/clap/struct.Arg.html#method.default_missing_value + default_missing_value = "/var/run/containerd/containerd.sock", // used if flag is present but no value is given + num_args = 0..=1, + require_equals= true + )] + containerd_socket_path: Option, + + #[clap(short, long, help = "Print version information and exit")] + version: bool, + + #[clap(short, long, help = "Signer certificate")] + signer: PathBuf, + + #[clap(short, long, help = "Key file")] + key: PathBuf, + + #[clap(short, long, help = "OpenSSL key file pass phrase")] + passphrase: String, + + #[clap(short, long, help = "Signatures JSON file path output")] + output: Option, +} + +/// Application configuration, derived from on command line parameters. +#[derive(Clone, Debug)] +pub struct Config { + pub use_cache: bool, + + pub image: Option>, + pub images: Option, + + pub containerd_socket_path: Option, + pub version: bool, + pub signer: PathBuf, + pub key: PathBuf, + pub passphrase: String, + pub output: Option, +} + +impl Config { + pub fn new() -> Self { + let args = CommandLineOptions::parse(); + Self { + use_cache: args.use_cached_files, + image: args.image, + images: args.images, + containerd_socket_path: args.containerd_socket_path, + version: args.version, + signer: args.signer, + key: args.key, + passphrase: args.passphrase, + output: args.output, + } + } +} diff --git a/src/tools/sign-oci-layer-root-hashes/src/verity.rs b/src/tools/sign-oci-layer-root-hashes/src/verity.rs new file mode 100644 index 000000000000..0b2aa18afa28 --- /dev/null +++ b/src/tools/sign-oci-layer-root-hashes/src/verity.rs @@ -0,0 +1,222 @@ +// Copyright (c) 2023 Microsoft Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +use generic_array::{typenum::Unsigned, GenericArray}; +use sha2::Digest; +use std::fs::File; +use std::io::{self, Read, Seek, SeekFrom}; +use zerocopy::byteorder::{LE, U32, U64}; +use zerocopy::AsBytes; + +#[derive(Default, zerocopy::AsBytes, zerocopy::FromBytes, zerocopy::Unaligned)] +#[repr(C)] +pub struct SuperBlock { + pub data_block_size: U32, + pub hash_block_size: U32, + pub data_block_count: U64, +} + +#[derive(Clone)] +struct Level { + next_index: usize, + file_offset: u64, + data: Vec, +} + +pub struct Verity { + levels: Vec, + seeded: T, + data_block_size: usize, + hash_block_size: usize, + block_remaining_count: u64, + super_block: SuperBlock, +} + +impl Verity { + const HASH_SIZE: usize = T::OutputSize::USIZE; + + /// Creates a new `Verity` instance. + pub fn new( + data_size: u64, + data_block_size: usize, + hash_block_size: usize, + salt: &[u8], + mut write_file_offset: u64, + ) -> io::Result { + let level_count = { + let mut max_size = data_block_size as u64; + let mut count = 0usize; + + while max_size < data_size { + count += 1; + max_size *= (hash_block_size / Self::HASH_SIZE) as u64; + } + count + }; + + let data = vec![0; hash_block_size]; + let mut levels = Vec::new(); + levels.resize( + level_count, + Level { + next_index: 0, + file_offset: 0, + data, + }, + ); + + for (i, l) in levels.iter_mut().enumerate() { + let entry_size = (data_block_size as u64) + * ((hash_block_size / Self::HASH_SIZE) as u64).pow(level_count as u32 - i as u32); + let count = (data_size + entry_size - 1) / entry_size; + l.file_offset = write_file_offset; + write_file_offset += hash_block_size as u64 * count; + } + + let block_count = data_size / (data_block_size as u64); + Ok(Self { + levels, + seeded: T::new_with_prefix(salt), + data_block_size, + block_remaining_count: block_count, + hash_block_size, + super_block: SuperBlock { + data_block_size: (data_block_size as u32).into(), + hash_block_size: (hash_block_size as u32).into(), + data_block_count: block_count.into(), + }, + }) + } + + /// Determines if more blocks are expected. + /// + /// This is based on file size specified when this instance was created. + fn more_blocks(&self) -> bool { + self.block_remaining_count > 0 + } + + /// Adds the given hash to the level. + /// + /// Returns `true` is the level is now full; `false` is there is still room for more hashes. + fn add_hash(&mut self, l: usize, hash: &[u8]) -> bool { + let level = &mut self.levels[l]; + level.data[level.next_index * Self::HASH_SIZE..][..Self::HASH_SIZE].copy_from_slice(hash); + level.next_index += 1; + level.next_index >= self.hash_block_size / Self::HASH_SIZE + } + + /// Finalises the level despite potentially not having filled it. + /// + /// It zeroes out the remaining bytes of the level so that its hash can be calculated + /// consistently. + fn finalize_level(&mut self, l: usize) { + let level = &mut self.levels[l]; + for b in &mut level.data[level.next_index * Self::HASH_SIZE..] { + *b = 0; + } + level.next_index = 0; + } + + fn uplevel(&mut self, l: usize, reader: &mut File, writer: &mut F) -> io::Result + where + F: FnMut(&mut File, &[u8], u64) -> io::Result<()>, + { + self.finalize_level(l); + writer(reader, &self.levels[l].data, self.levels[l].file_offset)?; + self.levels[l].file_offset += self.hash_block_size as u64; + let h = self.digest(&self.levels[l].data); + Ok(self.add_hash(l - 1, h.as_slice())) + } + + fn digest(&self, block: &[u8]) -> GenericArray { + let mut hasher = self.seeded.clone(); + hasher.update(block); + hasher.finalize() + } + + fn add_block(&mut self, b: &[u8], reader: &mut File, writer: &mut F) -> io::Result<()> + where + F: FnMut(&mut File, &[u8], u64) -> io::Result<()>, + { + if self.block_remaining_count == 0 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "unexpected block", + )); + } + + self.block_remaining_count -= 1; + + let count = self.levels.len(); + let hash = self.digest(b); + if self.add_hash(count - 1, hash.as_slice()) { + // Go up the levels as far as it can. + for l in (1..count).rev() { + if !self.uplevel(l, reader, writer)? { + break; + } + } + } + Ok(()) + } + + fn finalize( + mut self, + write_superblock: bool, + reader: &mut File, + writer: &mut impl FnMut(&mut File, &[u8], u64) -> io::Result<()>, + ) -> io::Result> { + let len = self.levels.len(); + for mut l in (1..len).rev() { + if self.levels[l].next_index != 0 { + while l > 0 { + self.uplevel(l, reader, writer)?; + l -= 1; + } + break; + } + } + + self.finalize_level(0); + + writer(reader, &self.levels[0].data, self.levels[0].file_offset)?; + self.levels[0].file_offset += self.hash_block_size as u64; + + if write_superblock { + writer( + reader, + self.super_block.as_bytes(), + self.levels[len - 1].file_offset + 4096 - 512, + )?; + + // TODO: Align to the hash_block_size... + // Align to 4096 bytes. + writer(reader, &[0u8], self.levels[len - 1].file_offset + 4095)?; + } + + Ok(self.digest(&self.levels[0].data)) + } +} + +pub fn traverse_file( + file: &mut File, + mut read_offset: u64, + write_superblock: bool, + mut verity: Verity, + writer: &mut impl FnMut(&mut File, &[u8], u64) -> io::Result<()>, +) -> io::Result> { + let mut buf = vec![0; verity.data_block_size]; + while verity.more_blocks() { + file.seek(SeekFrom::Start(read_offset))?; + file.read_exact(&mut buf)?; + verity.add_block(&buf, file, writer)?; + read_offset += verity.data_block_size as u64; + } + verity.finalize(write_superblock, file, writer) +} + +pub fn no_write(_: &mut File, _: &[u8], _: u64) -> io::Result<()> { + Ok(()) +} diff --git a/src/tools/sign-oci-layer-root-hashes/src/version.rs.in b/src/tools/sign-oci-layer-root-hashes/src/version.rs.in new file mode 100644 index 000000000000..bf6d9ae80e3b --- /dev/null +++ b/src/tools/sign-oci-layer-root-hashes/src/version.rs.in @@ -0,0 +1,12 @@ +// Copyright (c) 2020 Intel Corporation +// Portions Copyright (c) Microsoft Corporation. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// +// WARNING: This file is auto-generated - DO NOT EDIT! +// + +#![allow(dead_code)] +pub const COMMIT_INFO: &str = "@COMMIT_INFO@";