From fd984f02c88527152f3bd59be94304c83cb3535f Mon Sep 17 00:00:00 2001 From: Ikey Doherty Date: Sun, 3 Mar 2024 01:16:44 +0000 Subject: [PATCH] client/postblit: Rework trigger scopes to allow ephemeral roots We rejigger the scope to incorporate the client scope and tightly control the logic behind the bind paths, ensuring all possible paths for trigger isolation and execution are properly accounted for. As a result we can now take advantage of system + transaction triggers from within ephemeral roots, powering the new boulder port. Signed-off-by: Ikey Doherty --- moss/src/client/mod.rs | 31 ++++++++++++-- moss/src/client/postblit.rs | 80 ++++++++++++++++++++++++++++++------- 2 files changed, 93 insertions(+), 18 deletions(-) diff --git a/moss/src/client/mod.rs b/moss/src/client/mod.rs index 13c8e373..08f7f10b 100644 --- a/moss/src/client/mod.rs +++ b/moss/src/client/mod.rs @@ -3,7 +3,8 @@ // SPDX-License-Identifier: MPL-2.0 use std::{ - fs, io, + fs::{self, create_dir_all}, + io, os::{fd::RawFd, unix::fs::symlink}, path::{Path, PathBuf}, time::Duration, @@ -219,7 +220,10 @@ impl Client { record_os_release(&self.installation.staging_dir(), Some(state.id))?; // Run all of the transaction triggers - let triggers = postblit::triggers(postblit::TriggerScope::Transaction(&self.installation), &fstree)?; + let triggers = postblit::triggers( + postblit::TriggerScope::Transaction(&self.installation, &self.scope), + &fstree, + )?; create_root_links(&self.installation.isolation_dir())?; for trigger in triggers { trigger.execute()?; @@ -235,7 +239,8 @@ impl Client { } // At this point we're allowed to run system triggers - let sys_triggers = postblit::triggers(postblit::TriggerScope::System(&self.installation), &fstree)?; + let sys_triggers = + postblit::triggers(postblit::TriggerScope::System(&self.installation, &self.scope), &fstree)?; for trigger in sys_triggers { trigger.execute()?; } @@ -245,6 +250,25 @@ impl Client { Scope::Ephemeral { blit_root } => { record_os_release(blit_root, None)?; create_root_links(blit_root)?; + create_root_links(&self.installation.isolation_dir())?; + + let etc = blit_root.join("etc"); + create_dir_all(etc)?; + + // ephemeral tx triggers + let triggers = postblit::triggers( + postblit::TriggerScope::Transaction(&self.installation, &self.scope), + &fstree, + )?; + for trigger in triggers { + trigger.execute()?; + } + // ephemeral system triggers + let sys_triggers = + postblit::triggers(postblit::TriggerScope::System(&self.installation, &self.scope), &fstree)?; + for trigger in sys_triggers { + trigger.execute()?; + } Ok(None) } } @@ -629,6 +653,7 @@ BUG_REPORT_URL="https://github.com/serpent-os""#, Ok(()) } +#[derive(Clone, Debug)] enum Scope { Stateful, Ephemeral { blit_root: PathBuf }, diff --git a/moss/src/client/postblit.rs b/moss/src/client/postblit.rs index d2f8aaaf..4ed11e9a 100644 --- a/moss/src/client/postblit.rs +++ b/moss/src/client/postblit.rs @@ -8,7 +8,10 @@ //! //! Note that currently we only load from `/usr/share/moss/triggers/{tx,sys.d}/*.yaml` //! and do not yet support local triggers -use std::{path::Path, process}; +use std::{ + path::{Path, PathBuf}, + process, +}; use container::Container; use itertools::Itertools; @@ -46,8 +49,52 @@ impl config::Config for SystemTrigger { /// Defines the scope of triggers #[derive(Clone, Copy, Debug)] pub(super) enum TriggerScope<'a> { - Transaction(&'a Installation), - System(&'a Installation), + Transaction(&'a Installation, &'a super::Scope), + System(&'a Installation, &'a super::Scope), +} + +impl<'a> TriggerScope<'a> { + // Determine the correct root directory + fn root_dir(&self) -> PathBuf { + match self { + TriggerScope::Transaction(install, scope) => match scope { + super::Scope::Stateful => install.staging_dir().clone(), + super::Scope::Ephemeral { blit_root } => blit_root.clone(), + }, + TriggerScope::System(install, scope) => match scope { + super::Scope::Stateful => install.root.clone(), + super::Scope::Ephemeral { blit_root } => blit_root.clone(), + }, + } + } + + /// Join "host" paths, outside the staging filesystem. Ensure no sandbox break for ephemeral + fn host_path(&self, path: impl AsRef) -> PathBuf { + match self { + TriggerScope::Transaction(install, scope) => match scope { + super::Scope::Stateful => install.root.join(path), + super::Scope::Ephemeral { blit_root } => blit_root.join(path), + }, + TriggerScope::System(install, scope) => match scope { + super::Scope::Stateful => install.root.join(path), + super::Scope::Ephemeral { blit_root } => blit_root.join(path), + }, + } + } + + /// Join guest paths, inside the staging filesystem. Ensure no sandbox break for ephemeral + fn guest_path(&self, path: impl AsRef) -> PathBuf { + match self { + TriggerScope::Transaction(install, scope) => match scope { + super::Scope::Stateful => install.staging_path(path), + super::Scope::Ephemeral { blit_root } => blit_root.join(path), + }, + TriggerScope::System(install, scope) => match scope { + super::Scope::Stateful => install.root.join(path), + super::Scope::Ephemeral { blit_root } => blit_root.join(path), + }, + } + } } #[derive(Debug)] @@ -66,12 +113,14 @@ pub(super) fn triggers<'a>( // Load appropriate triggers from their locations and convert back to a vec of Trigger let triggers = match scope { - TriggerScope::Transaction(install) => config::Manager::custom(install.staging_dir().join(trigger_root)) - .load::() - .into_iter() - .map(|t| t.0) - .collect_vec(), - TriggerScope::System(install) => config::Manager::custom(install.root.join(trigger_root)) + TriggerScope::Transaction(install, client_scope) => { + config::Manager::custom(scope.root_dir().join(trigger_root)) + .load::() + .into_iter() + .map(|t| t.0) + .collect_vec() + } + TriggerScope::System(install, client_scope) => config::Manager::custom(scope.root_dir().join(trigger_root)) .load::() .into_iter() .map(|t| t.0) @@ -92,18 +141,18 @@ pub(super) fn triggers<'a>( impl<'a> TriggerRunner<'a> { pub fn execute(&self) -> Result<(), Error> { match self.scope { - TriggerScope::Transaction(install) => { + TriggerScope::Transaction(install, client_scope) => { // TODO: Add caching support via /var/ let isolation = Container::new(install.isolation_dir()) .networking(false) .override_accounts(false) - .bind_ro(install.root.join("etc"), "/etc") - .bind_rw(install.staging_path("usr"), "/usr") + .bind_ro(self.scope.host_path("etc"), "/etc") + .bind_rw(self.scope.guest_path("usr"), "/usr") .work_dir("/"); Ok(isolation.run(|| execute_trigger_directly(&self.trigger))?) } - TriggerScope::System(install) => { + TriggerScope::System(install, client_scope) => { // OK, if the root == `/` then we can run directly, otherwise we need to containerise with RW. if install.root.to_string_lossy() == "/" { Ok(execute_trigger_directly(&self.trigger)?) @@ -111,9 +160,10 @@ impl<'a> TriggerRunner<'a> { let isolation = Container::new(install.isolation_dir()) .networking(false) .override_accounts(false) - .bind_rw(install.root.join("etc"), "/etc") - .bind_rw(install.root.join("usr"), "/usr") + .bind_rw(self.scope.host_path("etc"), "/etc") + .bind_rw(self.scope.guest_path("usr"), "/usr") .work_dir("/"); + Ok(isolation.run(|| execute_trigger_directly(&self.trigger))?) } }