Confine image preparation beneath retained storage authority
This commit is contained in:
parent
fbd332c31f
commit
c0b139256e
1 changed files with 694 additions and 103 deletions
|
|
@ -3,10 +3,12 @@ use std::io;
|
|||
use std::os::fd::AsFd;
|
||||
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use lazaret_core::configuration::ConfinedPathPlan;
|
||||
use lazaret_core::identity::{
|
||||
DeviceId, HostPath, IdMapDigest, InodeId, PolicyId, PrepareOperationId, PreparedArtifactId,
|
||||
SelectedManifestDigest, SourceIdentity,
|
||||
|
|
@ -21,8 +23,10 @@ use rustix::fs::{
|
|||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::layer::{
|
||||
IdMap, POLICY_NAME, PreparationLimits, apply_layout, remove_children, verify_layout,
|
||||
IdMap, LayoutSource, POLICY_NAME, PreparationLimits, apply_layout, remove_children,
|
||||
verify_layout,
|
||||
};
|
||||
use crate::transport::TransportedImage;
|
||||
|
||||
const PHYSICAL_LOCK_WAIT: Duration = Duration::from_secs(5);
|
||||
const LOCK_RETRY: Duration = Duration::from_millis(2);
|
||||
|
|
@ -51,7 +55,7 @@ pub enum ReclaimOutcome {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PreparedLowerStore {
|
||||
root: PathBuf,
|
||||
root: Arc<File>,
|
||||
catalog: Catalog,
|
||||
limits: PreparationLimits,
|
||||
}
|
||||
|
|
@ -63,20 +67,43 @@ impl PreparedLowerStore {
|
|||
///
|
||||
/// Returns an error if the root cannot be opened or any fixed path is not a real directory.
|
||||
pub fn initialize(root: impl Into<PathBuf>, catalog: Catalog) -> Result<Self> {
|
||||
let root = root.into();
|
||||
let root = File::open(&root).context("opening Storage Root")?;
|
||||
Self::initialize_retained(root, catalog)
|
||||
}
|
||||
|
||||
/// Retains the exact validated Storage Root and creates only fixed children beneath it.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if Storage Root authority drifts or a fixed child is not a real directory.
|
||||
pub fn under_storage_root(storage_root: &ConfinedPathPlan, catalog: Catalog) -> Result<Self> {
|
||||
let storage_root = File::from(
|
||||
storage_root
|
||||
.open_authority()
|
||||
.context("opening validated Storage Root")?,
|
||||
);
|
||||
match mkdirat(&storage_root, "prepared-lower", Mode::from_raw_mode(0o700)) {
|
||||
Ok(()) | Err(rustix::io::Errno::EXIST) => {}
|
||||
Err(error) => return Err(error.into()),
|
||||
}
|
||||
let root = open_directory_at(&storage_root, Path::new("prepared-lower"))?;
|
||||
Self::initialize_retained(root, catalog)
|
||||
}
|
||||
|
||||
fn initialize_retained(root: File, catalog: Catalog) -> Result<Self> {
|
||||
let store = Self {
|
||||
root: root.into(),
|
||||
root: Arc::new(root),
|
||||
catalog,
|
||||
limits: PreparationLimits::default(),
|
||||
};
|
||||
let root = File::open(&store.root).context("opening Storage Root")?;
|
||||
for name in ["artifacts", "stages", "quarantine"] {
|
||||
match mkdirat(&root, name, Mode::from_raw_mode(0o700)) {
|
||||
match mkdirat(&store.root, name, Mode::from_raw_mode(0o700)) {
|
||||
Ok(()) | Err(rustix::io::Errno::EXIST) => {}
|
||||
Err(error) => return Err(error.into()),
|
||||
}
|
||||
open_directory_at(&root, Path::new(name))?;
|
||||
open_directory_at(&store.root, Path::new(name))?;
|
||||
}
|
||||
open_lock_at(&root)?;
|
||||
open_lock_at(&store.root)?;
|
||||
Ok(store)
|
||||
}
|
||||
|
||||
|
|
@ -93,10 +120,41 @@ impl PreparedLowerStore {
|
|||
/// Refuses corrupt or unsupported OCI input, forged physical ownership, catalog conflicts,
|
||||
/// resource limits, and publication failures.
|
||||
pub fn prepare(&self, request: &PrepareRequest) -> Result<PreparedArtifact> {
|
||||
self.prepare_from_source(request, &LayoutSource::Ambient(&request.layout_path))
|
||||
}
|
||||
|
||||
/// Verifies and prepares an image through its retained transport-layout authority.
|
||||
///
|
||||
/// # Errors
|
||||
/// Refuses ambient-only transported images and all normal preparation validation failures.
|
||||
pub fn prepare_transported(
|
||||
&self,
|
||||
operation_id: PrepareOperationId,
|
||||
image: &TransportedImage,
|
||||
id_map: IdMap,
|
||||
) -> Result<PreparedArtifact> {
|
||||
let layout = image
|
||||
.retained_layout()
|
||||
.context("transported image has no retained layout authority")?;
|
||||
let request = PrepareRequest {
|
||||
operation_id,
|
||||
layout_path: image.layout_path.clone(),
|
||||
source_identity: image.source_identity.clone(),
|
||||
selected_manifest_digest: image.selected_manifest_digest,
|
||||
id_map,
|
||||
};
|
||||
self.prepare_from_source(&request, &LayoutSource::Retained(layout.root()))
|
||||
}
|
||||
|
||||
fn prepare_from_source(
|
||||
&self,
|
||||
request: &PrepareRequest,
|
||||
layout: &LayoutSource<'_>,
|
||||
) -> Result<PreparedArtifact> {
|
||||
if request.source_identity.is_empty() || request.source_identity.len() > 4096 {
|
||||
bail!("source identity is empty or exceeds 4096 bytes");
|
||||
}
|
||||
let verified = verify_layout(&request.layout_path, self.limits)?;
|
||||
let verified = verify_layout(layout, self.limits)?;
|
||||
if request.selected_manifest_digest != verified.manifest_digest {
|
||||
bail!("selected manifest identity does not match verified OCI layout");
|
||||
}
|
||||
|
|
@ -133,14 +191,8 @@ impl PreparedLowerStore {
|
|||
}
|
||||
return Err(error.into());
|
||||
}
|
||||
if let Err(error) = apply_layout(
|
||||
&request.layout_path,
|
||||
&stage,
|
||||
&verified,
|
||||
&request.id_map,
|
||||
self.limits,
|
||||
)
|
||||
.and_then(|()| sync_tree(&stage))
|
||||
if let Err(error) = apply_layout(layout, &stage, &verified, &request.id_map, self.limits)
|
||||
.and_then(|()| sync_tree(&stage))
|
||||
{
|
||||
if self
|
||||
.remove_exact_path(&stage_relative, device, inode)
|
||||
|
|
@ -358,16 +410,15 @@ impl PreparedLowerStore {
|
|||
}
|
||||
|
||||
fn create_stage(&self, relative: &Path) -> Result<File> {
|
||||
let stages = open_directory_at(&File::open(&self.root)?, Path::new("stages"))?;
|
||||
let stages = open_directory_at(&self.root, Path::new("stages"))?;
|
||||
let name = relative.file_name().context("stage has no name")?;
|
||||
mkdirat(&stages, name, Mode::from_raw_mode(0o700))?;
|
||||
open_directory_at(&stages, Path::new(name))
|
||||
}
|
||||
|
||||
fn open_relative(&self, relative: &Path) -> Result<Option<File>> {
|
||||
let root = File::open(&self.root)?;
|
||||
match openat2(
|
||||
&root,
|
||||
&self.root,
|
||||
relative,
|
||||
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC | OFlags::NOFOLLOW,
|
||||
Mode::empty(),
|
||||
|
|
@ -380,30 +431,27 @@ impl PreparedLowerStore {
|
|||
}
|
||||
|
||||
fn rename_relative(&self, from: &Path, to: &Path) -> Result<()> {
|
||||
let root = File::open(&self.root)?;
|
||||
renameat(&root, from, &root, to)?;
|
||||
renameat(&self.root, from, &self.root, to)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rename_relative_noreplace(&self, from: &Path, to: &Path) -> Result<()> {
|
||||
let root = File::open(&self.root)?;
|
||||
renameat_with(&root, from, &root, to, RenameFlags::NOREPLACE)?;
|
||||
renameat_with(&self.root, from, &self.root, to, RenameFlags::NOREPLACE)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_exact_path(&self, relative: &Path, device: DeviceId, inode: InodeId) -> Result<()> {
|
||||
let root = File::open(&self.root)?;
|
||||
let file = open_directory_at(&root, relative)?;
|
||||
let file = open_directory_at(&self.root, relative)?;
|
||||
require_identity(&file, device, inode)?;
|
||||
remove_children(&file)?;
|
||||
unlinkat(&root, relative, AtFlags::REMOVEDIR)?;
|
||||
unlinkat(&self.root, relative, AtFlags::REMOVEDIR)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn physical_lock(&self) -> Result<ExclusiveFileLock> {
|
||||
let deadline = Instant::now() + PHYSICAL_LOCK_WAIT;
|
||||
loop {
|
||||
let file = open_lock_at(&File::open(&self.root)?)?;
|
||||
let file = open_lock_at(&self.root)?;
|
||||
match try_exclusive_flock(file) {
|
||||
Ok(lock) => return Ok(lock),
|
||||
Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
|
||||
|
|
@ -419,7 +467,7 @@ impl PreparedLowerStore {
|
|||
}
|
||||
|
||||
fn sync_fixed_directory(&self, name: &str) -> Result<()> {
|
||||
open_directory_at(&File::open(&self.root)?, Path::new(name))?.sync_all()?;
|
||||
open_directory_at(&self.root, Path::new(name))?.sync_all()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue