use std::fs::File; use std::io::Cursor; use std::os::unix::ffi::OsStringExt; use std::os::unix::fs::MetadataExt; use std::path::{Path, PathBuf}; use std::process::ExitCode; use lazaret_core::identity::PrepareOperationId; use lazaret_host_coordinator::prepared_worker::prepare_request_in_user_namespace; use lazaret_linux::user_namespace::UserNamespaceMap; use lazaret_prepared_lower::{IdMap, IdMapRange, PrepareRequest}; use serde_json::json; use sha2::{Digest, Sha256}; use tar::{Builder, EntryType, Header}; fn main() -> ExitCode { let arguments = std::env::args_os().skip(1).collect::>(); if let Some(outcome) = lazaret_host_coordinator::private_roles::REGISTRY.dispatch(&arguments) { return outcome.result.map_or_else( |error| { eprintln!("private role {} failed: {error}", outcome.name); ExitCode::FAILURE }, |()| ExitCode::SUCCESS, ); } run().map_or_else( |error| { eprintln!("prepared worker fixture failed: {error}"); ExitCode::FAILURE }, |()| ExitCode::SUCCESS, ) } fn run() -> std::io::Result<()> { let tree = tempfile::tempdir()?; let storage = tree.path().join("storage"); std::fs::create_dir(&storage)?; let (layout, manifest) = create_layout(tree.path())?; let map = UserNamespaceMap::resolve_current()?; let artifact = prepare_request_in_user_namespace( &File::open(&storage)?, &storage, &File::open(&layout)?, PrepareRequest { operation_id: PrepareOperationId::new([7; 32]), layout_path: layout, source_identity: b"oci-v1\0registry.invalid\0owner-fixture".to_vec(), selected_manifest_digest: manifest, id_map: prepared_id_map(&map), destinations: Vec::new(), }, &map, )?; let artifact_root = storage .join("prepared-lower") .join(PathBuf::from(std::ffi::OsString::from_vec( artifact.path.as_bytes().to_vec(), ))); let metadata = std::fs::metadata(artifact_root.join("gid-42"))?; let expected_gid = map.gid[1] .parent_id .checked_add(41) .ok_or_else(|| std::io::Error::other("expected GID overflows"))?; if metadata.uid() != map.uid[0].parent_id || metadata.gid() != expected_gid { return Err(std::io::Error::other(format!( "mapped owner differs: observed {}:{}, expected {}:{expected_gid}", metadata.uid(), metadata.gid(), map.uid[0].parent_id ))); } println!("PASS prepared worker materialized logical GID 42 through subordinate mapping"); Ok(()) } fn create_layout(root: &Path) -> std::io::Result<(PathBuf, [u8; 32])> { let path = root.join("layout"); let blobs = path.join("blobs/sha256"); std::fs::create_dir_all(&blobs)?; let layer = layer_with_gid_42()?; let layer_hash = Sha256::digest(&layer); std::fs::write(blobs.join(hex(&layer_hash)), &layer)?; let config = serde_json::to_vec(&json!({ "architecture": "amd64", "os": "linux", "rootfs": { "type": "layers", "diff_ids": [format!("sha256:{}", hex(&layer_hash))] } }))?; let config_hash = Sha256::digest(&config); std::fs::write(blobs.join(hex(&config_hash)), &config)?; let manifest = serde_json::to_vec(&json!({ "schemaVersion": 2, "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "digest": format!("sha256:{}", hex(&config_hash)), "size": config.len() }, "layers": [{ "mediaType": "application/vnd.oci.image.layer.v1.tar", "digest": format!("sha256:{}", hex(&layer_hash)), "size": layer.len() }] }))?; let manifest_hash: [u8; 32] = Sha256::digest(&manifest).into(); std::fs::write(blobs.join(hex(&manifest_hash)), &manifest)?; std::fs::write( path.join("index.json"), serde_json::to_vec(&json!({ "schemaVersion": 2, "manifests": [{ "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": format!("sha256:{}", hex(&manifest_hash)), "size": manifest.len() }] }))?, )?; std::fs::write( path.join("oci-layout"), b"{\"imageLayoutVersion\":\"1.0.0\"}\n", )?; Ok((path, manifest_hash)) } fn layer_with_gid_42() -> std::io::Result> { let mut bytes = Vec::new(); { let mut builder = Builder::new(&mut bytes); let mut header = Header::new_gnu(); header.set_path("gid-42")?; header.set_entry_type(EntryType::Regular); header.set_size(5); header.set_uid(0); header.set_gid(42); header.set_mode(0o644); header.set_mtime(1_600_000_123); header.set_cksum(); builder.append(&header, Cursor::new(b"probe"))?; builder.finish()?; } Ok(bytes) } fn prepared_id_map(map: &UserNamespaceMap) -> IdMap { IdMap { uid: map .uid .iter() .map(|range| IdMapRange { container_id: range.container_id, namespace_id: range.parent_id, size: range.size, }) .collect(), gid: map .gid .iter() .map(|range| IdMapRange { container_id: range.container_id, namespace_id: range.parent_id, size: range.size, }) .collect(), } } fn hex(bytes: &[u8]) -> String { use std::fmt::Write as _; bytes.iter().fold(String::new(), |mut output, byte| { write!(output, "{byte:02x}").expect("writing to String cannot fail"); output }) }