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
|
|
@ -223,8 +223,10 @@ impl OciRegistryTransport {
|
|||
}
|
||||
verify_platform(&config_json, platform)?;
|
||||
validate_manifest(&manifest)?;
|
||||
let layout = LayoutWriter::create(destination)?;
|
||||
layout.write_verified_blob(&manifest.config.digest, config_json.as_bytes())?;
|
||||
let layout = LayoutWriter::create(destination).context("creating private OCI layout")?;
|
||||
layout
|
||||
.write_verified_blob(&manifest.config.digest, config_json.as_bytes())
|
||||
.context("writing verified OCI config")?;
|
||||
for descriptor in &manifest.layers {
|
||||
let size = u64::try_from(descriptor.size).context("negative OCI layer size")?;
|
||||
if size > MAX_LAYER_BYTES {
|
||||
|
|
@ -235,23 +237,33 @@ impl OciRegistryTransport {
|
|||
layout.require_existing_blob(&descriptor.digest, size)?;
|
||||
continue;
|
||||
}
|
||||
let (file, temporary) = layout.create_blob(digest_component)?;
|
||||
let (file, temporary) = layout
|
||||
.create_blob(digest_component)
|
||||
.with_context(|| format!("creating OCI layer {}", descriptor.digest))?;
|
||||
let file = tokio::fs::File::from_std(file);
|
||||
if let Err(error) = client.pull_blob(&reference, descriptor, file).await {
|
||||
layout.remove_temporary_blob(&temporary);
|
||||
return Err(error)
|
||||
.with_context(|| format!("pulling OCI layer {}", descriptor.digest));
|
||||
}
|
||||
let observed = layout.temporary_blob_size(&temporary)?;
|
||||
let observed = layout
|
||||
.temporary_blob_size(&temporary)
|
||||
.with_context(|| format!("measuring OCI layer {}", descriptor.digest))?;
|
||||
if observed != size {
|
||||
bail!("OCI layer {} size mismatch", descriptor.digest);
|
||||
}
|
||||
layout.publish_blob(&temporary, digest_component)?;
|
||||
layout
|
||||
.publish_blob(&temporary, digest_component)
|
||||
.with_context(|| format!("publishing OCI layer {}", descriptor.digest))?;
|
||||
}
|
||||
let local_manifest = serde_json::to_vec(&manifest)?;
|
||||
let local_digest = format!("sha256:{}", hex(&Sha256::digest(&local_manifest)));
|
||||
layout.write_verified_blob(&local_digest, &local_manifest)?;
|
||||
layout.write_layout_index(&local_digest, local_manifest.len())?;
|
||||
layout
|
||||
.write_verified_blob(&local_digest, &local_manifest)
|
||||
.context("writing verified OCI manifest")?;
|
||||
layout
|
||||
.write_layout_index(&local_digest, local_manifest.len())
|
||||
.context("writing OCI layout index")?;
|
||||
let selected_manifest_digest = parse_sha256(&local_digest)?;
|
||||
let source_identity = format!(
|
||||
"oci-v1\0{}\0{}\0{manifest_digest}",
|
||||
|
|
@ -406,13 +418,19 @@ impl LayoutWriter {
|
|||
bail!("OCI descriptor digest mismatch for {digest}");
|
||||
}
|
||||
let component = digest_component(digest)?;
|
||||
if self.blob_exists(component)? {
|
||||
if self
|
||||
.blob_exists(component)
|
||||
.context("checking verified OCI blob destination")?
|
||||
{
|
||||
return self.require_existing_blob(digest, bytes.len() as u64);
|
||||
}
|
||||
let (mut file, temporary) = self.create_blob(component)?;
|
||||
file.write_all(bytes)?;
|
||||
file.sync_all()?;
|
||||
let (mut file, temporary) = self
|
||||
.create_blob(component)
|
||||
.context("creating verified OCI blob")?;
|
||||
file.write_all(bytes).context("writing verified OCI blob")?;
|
||||
file.sync_all().context("syncing verified OCI blob")?;
|
||||
self.publish_blob(&temporary, component)
|
||||
.context("publishing verified OCI blob")
|
||||
}
|
||||
|
||||
fn blob_exists(&self, component: &str) -> Result<bool> {
|
||||
|
|
@ -552,11 +570,16 @@ fn open_directory(parent: &File, path: &Path) -> std::io::Result<File> {
|
|||
}
|
||||
|
||||
fn open_relative_file(parent: &File, path: &Path, flags: OFlags) -> std::io::Result<File> {
|
||||
let mode = if flags.contains(OFlags::CREATE) {
|
||||
Mode::from_raw_mode(0o600)
|
||||
} else {
|
||||
Mode::empty()
|
||||
};
|
||||
openat2(
|
||||
parent,
|
||||
path,
|
||||
flags | OFlags::NOFOLLOW | OFlags::CLOEXEC,
|
||||
Mode::from_raw_mode(0o600),
|
||||
mode,
|
||||
confined_resolve(),
|
||||
)
|
||||
.map(File::from)
|
||||
|
|
@ -566,7 +589,11 @@ fn open_relative_file(parent: &File, path: &Path, flags: OFlags) -> std::io::Res
|
|||
fn write_relative_atomic(root: &File, path: &Path, bytes: &[u8]) -> Result<()> {
|
||||
let name = path.file_name().context("layout file has no name")?;
|
||||
let parent_path = path.parent().unwrap_or(Path::new("."));
|
||||
let parent = open_directory(root, parent_path)?;
|
||||
let parent = if parent_path.as_os_str().is_empty() {
|
||||
File::from(rustix::io::dup(root)?)
|
||||
} else {
|
||||
open_directory(root, parent_path)?
|
||||
};
|
||||
let temporary = format!(".{}.partial", name.to_string_lossy());
|
||||
let mut file = open_relative_file(
|
||||
&parent,
|
||||
|
|
@ -709,6 +736,25 @@ mod tests {
|
|||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn atomic_layout_write_accepts_a_root_level_destination() {
|
||||
let tree = tempfile::tempdir().unwrap();
|
||||
let root = File::open(tree.path()).unwrap();
|
||||
write_relative_atomic(&root, Path::new("oci-layout"), b"layout").unwrap();
|
||||
assert_eq!(
|
||||
std::fs::read(tree.path().join("oci-layout")).unwrap(),
|
||||
b"layout"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confined_read_open_uses_an_empty_creation_mode() {
|
||||
let tree = tempfile::tempdir().unwrap();
|
||||
let root = File::open(tree.path()).unwrap();
|
||||
let error = open_relative_file(&root, Path::new("absent"), OFlags::RDONLY).unwrap_err();
|
||||
assert_eq!(error.kind(), std::io::ErrorKind::NotFound);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preexisting_transport_parents_require_exact_private_mode() {
|
||||
for name in ["image-transports", "blobs", "sha256"] {
|
||||
|
|
|
|||
Loading…
Reference in a new issue