178 lines
5.8 KiB
Rust
178 lines
5.8 KiB
Rust
|
|
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
|
||
|
|
})
|
||
|
|
}
|