324 lines
9.1 KiB
Rust
324 lines
9.1 KiB
Rust
#[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();
|
|
}
|
|
}
|