lazaret/crates/lazaret-host-coordinator/tests/prepared-worker.rs

178 lines
5.8 KiB
Rust
Raw Permalink Normal View History

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::<Vec<_>>();
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<Vec<u8>> {
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
})
}