Compose selected State Anchor paths over prepared roots

This commit is contained in:
ms2k 2026-07-17 03:58:27 +09:00
commit 4dd2b150d2

View file

@ -14,7 +14,8 @@ use lazaret_core::identity::{
};
use lazaret_core::records::{PrepareOperation, PrepareStageState};
use lazaret_prepared_lower::{
IdMap, IdMapRange, PrepareRecovery, PrepareRequest, PreparedLowerStore, ReclaimOutcome,
IdMap, IdMapRange, PrepareRecovery, PrepareRequest, PreparedDestination,
PreparedDestinationKind, PreparedLowerStore, ReclaimOutcome,
};
use lazaret_storage_catalog::{Catalog, CatalogError};
use serde_json::json;
@ -59,6 +60,7 @@ impl Fixture {
source_identity: b"oci-v1\0registry.invalid\0fixture\0sha256:source".to_vec(),
selected_manifest_digest: manifest,
id_map: current_id_map(),
destinations: Vec::new(),
}
}
@ -85,6 +87,55 @@ fn preexisting_fixed_children_require_exact_private_mode() {
}
}
#[test]
fn published_artifact_authority_is_revalidated_beneath_the_retained_root() {
let fixture = Fixture::new();
let request = fixture.request(
1,
&[(
layer(&[Entry {
path: "payload",
kind: EntryType::Regular,
data: b"content",
target: None,
mode: 0o644,
}]),
OCI_TAR,
)],
);
let artifact = fixture.store.prepare(&request).unwrap();
let authority = fixture.store.open_artifact_authority(&artifact).unwrap();
let metadata = authority.metadata().unwrap();
assert_eq!(metadata.dev(), artifact.device.get());
assert_eq!(metadata.ino(), artifact.inode.get());
}
#[test]
fn replaced_published_artifact_is_refused() {
let fixture = Fixture::new();
let request = fixture.request(
1,
&[(
layer(&[Entry {
path: "payload",
kind: EntryType::Regular,
data: b"content",
target: None,
mode: 0o644,
}]),
OCI_TAR,
)],
);
let artifact = fixture.store.prepare(&request).unwrap();
let artifact_root = fixture.artifact_root(&artifact);
let displaced = fixture.root.join("displaced-artifact");
std::fs::rename(&artifact_root, &displaced).unwrap();
std::fs::create_dir(&artifact_root).unwrap();
assert!(fixture.store.open_artifact_authority(&artifact).is_err());
}
#[test]
#[allow(clippy::disallowed_methods, clippy::disallowed_types)]
fn retained_fixed_child_bind_mount_is_refused_when_mount_setup_is_available() {
@ -155,6 +206,10 @@ struct Entry<'a> {
fn layer(entries: &[Entry<'_>]) -> Vec<u8> {
let uid = rustix::process::geteuid().as_raw();
let gid = rustix::process::getegid().as_raw();
layer_with_owner(entries, uid, gid)
}
fn layer_with_owner(entries: &[Entry<'_>], uid: u32, gid: u32) -> Vec<u8> {
let mut bytes = Vec::new();
{
let mut builder = Builder::new(&mut bytes);
@ -514,6 +569,115 @@ fn layer_entry_matching_internal_spool_name_is_preserved() {
);
}
#[test]
fn root_directory_layer_entry_is_applied() {
let fixture = Fixture::new();
let bytes = layer(&[
Entry {
path: ".",
kind: EntryType::Directory,
data: b"",
target: None,
mode: 0o755,
},
Entry {
path: "payload",
kind: EntryType::Regular,
data: b"content",
target: None,
mode: 0o644,
},
]);
let request = fixture.request(5, &[(bytes, OCI_TAR)]);
let artifact = fixture.store.prepare(&request).unwrap();
let root = fixture.artifact_root(&artifact);
assert_eq!(std::fs::read(root.join("payload")).unwrap(), b"content");
assert_eq!(
root.metadata().unwrap().permissions().mode() & 0o7777,
0o755
);
}
#[test]
fn container_root_owner_maps_to_invoking_host_ids() {
let fixture = Fixture::new();
let bytes = layer_with_owner(
&[Entry {
path: "payload",
kind: EntryType::Regular,
data: b"content",
target: None,
mode: 0o640,
}],
0,
0,
);
let mut request = fixture.request(6, &[(bytes, OCI_TAR)]);
request.id_map = IdMap {
uid: vec![IdMapRange {
container_id: 0,
namespace_id: rustix::process::geteuid().as_raw(),
size: 1,
}],
gid: vec![IdMapRange {
container_id: 0,
namespace_id: rustix::process::getegid().as_raw(),
size: 1,
}],
};
let artifact = fixture.store.prepare(&request).unwrap();
let metadata = fixture
.artifact_root(&artifact)
.join("payload")
.metadata()
.unwrap();
assert_eq!(metadata.uid(), rustix::process::geteuid().as_raw());
assert_eq!(metadata.gid(), rustix::process::getegid().as_raw());
}
#[test]
fn unmapped_nonzero_owners_refuse_before_publication() {
for (operation, uid, gid) in [(7, 1, 0), (8, 0, 1)] {
let fixture = Fixture::new();
let bytes = layer_with_owner(
&[Entry {
path: "payload",
kind: EntryType::Regular,
data: b"content",
target: None,
mode: 0o640,
}],
uid,
gid,
);
let mut request = fixture.request(operation, &[(bytes, OCI_TAR)]);
request.id_map = IdMap {
uid: vec![IdMapRange {
container_id: 0,
namespace_id: rustix::process::geteuid().as_raw(),
size: 1,
}],
gid: vec![IdMapRange {
container_id: 0,
namespace_id: rustix::process::getegid().as_raw(),
size: 1,
}],
};
assert!(fixture.store.prepare(&request).is_err());
assert!(
fixture
.catalog
.snapshot()
.unwrap()
.prepared_artifacts
.is_empty()
);
}
}
#[test]
fn root_opaque_whiteout_removes_only_lower_layer_entries() {
let fixture = Fixture::new();
@ -971,6 +1135,98 @@ fn reclaim_refuses_forged_identity_and_leases() {
assert!(fixture.artifact_root(&artifact).exists());
}
#[test]
fn destination_layout_is_materialized_and_canonical_in_artifact_identity() {
let fixture = Fixture::new();
let bytes = layer_with_owner(&[], 0, 0);
let mut first = fixture.request(72, &[(bytes.clone(), OCI_TAR)]);
first.id_map = id_map_with_container_root();
first.destinations = vec![
PreparedDestination {
path: "/workspace".into(),
kind: PreparedDestinationKind::Directory,
},
PreparedDestination {
path: "/run/lazaret/bin/entry".into(),
kind: PreparedDestinationKind::RegularFile,
},
PreparedDestination {
path: "/workspace".into(),
kind: PreparedDestinationKind::Directory,
},
];
let artifact = fixture.store.prepare(&first).unwrap();
let root = fixture.artifact_root(&artifact);
assert!(root.join("workspace").is_dir());
assert!(root.join("run/lazaret/bin/entry").is_file());
let mut reordered = fixture.request(73, &[(bytes.clone(), OCI_TAR)]);
reordered.id_map = id_map_with_container_root();
reordered.destinations = first.destinations.iter().rev().cloned().collect();
assert_eq!(fixture.store.prepare(&reordered).unwrap(), artifact);
let mut unequal = fixture.request(74, &[(bytes, OCI_TAR)]);
unequal.id_map = id_map_with_container_root();
unequal.destinations = vec![PreparedDestination {
path: "/opt/other".into(),
kind: PreparedDestinationKind::Directory,
}];
assert_ne!(
fixture
.store
.prepare(&unequal)
.unwrap()
.prepared_artifact_id,
artifact.prepared_artifact_id
);
}
#[test]
fn destination_type_conflict_refuses_before_publication() {
let fixture = Fixture::new();
let bytes = layer_with_owner(
&[Entry {
path: "opt/lazaret-state",
kind: EntryType::Regular,
data: b"conflict",
target: None,
mode: 0o644,
}],
0,
0,
);
let mut request = fixture.request(75, &[(bytes, OCI_TAR)]);
request.id_map = id_map_with_container_root();
request.destinations = vec![PreparedDestination {
path: "/opt/lazaret-state".into(),
kind: PreparedDestinationKind::Directory,
}];
assert!(fixture.store.prepare(&request).is_err());
assert!(
fixture
.catalog
.snapshot()
.unwrap()
.prepared_artifacts
.is_empty()
);
}
fn id_map_with_container_root() -> IdMap {
IdMap {
uid: vec![IdMapRange {
container_id: 0,
namespace_id: rustix::process::geteuid().as_raw(),
size: 1,
}],
gid: vec![IdMapRange {
container_id: 0,
namespace_id: rustix::process::getegid().as_raw(),
size: 1,
}],
}
}
fn hex(bytes: &[u8]) -> String {
use std::fmt::Write as _;