Compose selected State Anchor paths over prepared roots
This commit is contained in:
parent
71b0266cf2
commit
4dd2b150d2
1 changed files with 1575 additions and 163 deletions
|
|
@ -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 _;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue