lazaret/crates/lazaret-prepared-lower/tests/prepared_lower/semantics.rs

326 lines
9.1 KiB
Rust

use super::*;
#[test]
fn semantic_corpus_preserves_supported_changesets_and_read_only_lower_use() {
let fixture = Fixture::new();
let first = layer(&lower_entries());
let second = layer(&upper_entries());
let request = fixture.request(
1,
&[
(compress(&first, OCI_GZIP), OCI_GZIP),
(compress(&second, OCI_ZSTD), OCI_ZSTD),
],
);
let artifact = fixture.store.prepare(&request).unwrap();
let root = fixture.artifact_root(&artifact);
assert!(!root.join("gone").exists());
assert!(!root.join("opaque/old").exists());
assert_eq!(std::fs::read(root.join("opaque/new")).unwrap(), b"new");
assert_eq!(
std::fs::read(root.join("directory-to-file")).unwrap(),
b"replacement"
);
assert_eq!(
std::fs::read(root.join("file-to-directory/new")).unwrap(),
b"new"
);
assert_eq!(
std::fs::read_link(root.join("symbolic")).unwrap(),
Path::new("target")
);
assert_eq!(
std::fs::metadata(root.join("hard")).unwrap().ino(),
std::fs::metadata(root.join("target")).unwrap().ino()
);
assert_eq!(std::fs::read(root.join("forward")).unwrap(), b"forward");
assert_eq!(
std::fs::metadata(root.join("later"))
.unwrap()
.permissions()
.mode()
& 0o7777,
0o4755
);
std::fs::remove_dir_all(&request.layout_path).unwrap();
let mut lower = File::open(root.join("directory-to-file")).unwrap();
let mut bytes = Vec::new();
lower.read_to_end(&mut bytes).unwrap();
assert_eq!(bytes, b"replacement");
assert!(
fixture
.root
.join("stages")
.read_dir()
.unwrap()
.next()
.is_none()
);
}
#[test]
fn request_manifest_identity_must_match_verified_layout_manifest() {
let fixture = Fixture::new();
let bytes = layer(&[Entry {
path: "payload",
kind: EntryType::Regular,
data: b"content",
target: None,
mode: 0o644,
}]);
let mut request = fixture.request(3, &[(bytes, OCI_TAR)]);
request.selected_manifest_digest = [0xff; 32];
assert!(matches!(
fixture.store.prepare(&request),
Err(PreparedLowerError::ManifestSelection { .. })
));
let snapshot = fixture.catalog.snapshot().unwrap();
assert!(snapshot.prepared_artifacts.is_empty());
assert!(snapshot.prepare_operations.is_empty());
}
#[test]
fn layer_entry_matching_internal_spool_name_is_preserved() {
let fixture = Fixture::new();
let bytes = layer(&[Entry {
path: ".layer-0.tar",
kind: EntryType::Regular,
data: b"image-owned",
target: None,
mode: 0o640,
}]);
let request = fixture.request(4, &[(bytes, OCI_TAR)]);
let artifact = fixture.store.prepare(&request).unwrap();
assert_eq!(
std::fs::read(fixture.artifact_root(&artifact).join(".layer-0.tar")).unwrap(),
b"image-owned"
);
}
#[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!(matches!(
fixture.store.prepare(&request),
Err(PreparedLowerError::Materialization { .. })
));
assert!(
fixture
.catalog
.snapshot()
.unwrap()
.prepared_artifacts
.is_empty()
);
}
}
#[test]
fn root_opaque_whiteout_removes_only_lower_layer_entries() {
let fixture = Fixture::new();
let lower = layer(&[Entry {
path: "old",
kind: EntryType::Regular,
data: b"old",
target: None,
mode: 0o644,
}]);
let upper = layer(&[
Entry {
path: "new",
kind: EntryType::Regular,
data: b"new",
target: None,
mode: 0o644,
},
Entry {
path: ".wh..wh..opq",
kind: EntryType::Regular,
data: b"",
target: None,
mode: 0o000,
},
]);
let request = fixture.request(2, &[(lower, OCI_TAR), (upper, OCI_TAR)]);
let artifact = fixture.store.prepare(&request).unwrap();
let root = fixture.artifact_root(&artifact);
assert!(!root.join("old").exists());
assert_eq!(std::fs::read(root.join("new")).unwrap(), b"new");
}
#[test]
fn hostile_and_corrupt_layers_refuse_without_publication() {
let cases = [
layer(&[Entry {
path: "pipe",
kind: EntryType::Fifo,
data: b"",
target: None,
mode: 0o644,
}]),
layer(&[Entry {
path: "escape",
kind: EntryType::Link,
data: b"",
target: Some("../outside"),
mode: 0o644,
}]),
xattr_layer(),
];
for (index, bytes) in cases.into_iter().enumerate() {
let fixture = Fixture::new();
let request = fixture.request(u8::try_from(index).unwrap() + 10, &[(bytes, OCI_TAR)]);
assert!(matches!(
fixture.store.prepare(&request),
Err(PreparedLowerError::Materialization { .. })
));
let snapshot = fixture.catalog.snapshot().unwrap();
assert!(snapshot.prepared_artifacts.is_empty());
assert!(snapshot.prepare_operations.is_empty());
assert!(
fixture
.root
.join("stages")
.read_dir()
.unwrap()
.next()
.is_none()
);
}
let fixture = Fixture::new();
let bytes = layer(&[Entry {
path: "file",
kind: EntryType::Regular,
data: b"content",
target: None,
mode: 0o644,
}]);
let request = fixture.request(20, &[(bytes, OCI_TAR)]);
let blob = request
.layout_path
.join("blobs/sha256")
.read_dir()
.unwrap()
.map(|entry| entry.unwrap().path())
.find(|path| std::fs::metadata(path).unwrap().len() > 1024)
.unwrap();
OpenOptionsExt::append_byte(&blob);
assert!(matches!(
fixture.store.prepare(&request),
Err(PreparedLowerError::LayerValidation { .. })
));
assert!(
fixture
.catalog
.snapshot()
.unwrap()
.prepared_artifacts
.is_empty()
);
}
struct OpenOptionsExt;
impl OpenOptionsExt {
fn append_byte(path: &Path) {
let mut file = std::fs::OpenOptions::new().append(true).open(path).unwrap();
file.write_all(b"x").unwrap();
}
}