Compose selected State Anchor paths over prepared roots
This commit is contained in:
parent
71b0266cf2
commit
4dd2b150d2
4 changed files with 1575 additions and 163 deletions
|
|
@ -7,8 +7,8 @@ use std::time::{Duration, Instant};
|
|||
|
||||
use lazaret_core::codec::decode;
|
||||
use lazaret_core::identity::{
|
||||
ConfigurationFingerprint, DerivedPolicyDigest, DomainId, GenerationId, NetworkSidecarId,
|
||||
PidfsProcessIdentityV1,
|
||||
ConfigurationFingerprint, DerivedPolicyDigest, DomainId, GenerationId, NamespaceIdentity,
|
||||
NetworkSidecarId, PidfsProcessIdentityV1,
|
||||
};
|
||||
use lazaret_core::network::{CanonicalDerivedNetworkPolicy, derive_policy};
|
||||
use lazaret_core::records::{
|
||||
|
|
@ -175,7 +175,7 @@ where
|
|||
},
|
||||
stage,
|
||||
acknowledge,
|
||||
enter_domain_namespaces,
|
||||
enter_generation_network_namespace,
|
||||
)?;
|
||||
drop(helper_ready_signal);
|
||||
drop(helper_exit);
|
||||
|
|
@ -214,18 +214,28 @@ where
|
|||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
fn enter_domain_namespaces() -> io::Result<()> {
|
||||
pub(crate) fn enter_generation_filesystem_namespaces()
|
||||
-> io::Result<(NamespaceIdentity, NamespaceIdentity)> {
|
||||
let uid = getuid().as_raw();
|
||||
let gid = getgid().as_raw();
|
||||
rustix::thread::unshare(UnshareFlags::NEWUSER)?;
|
||||
std::fs::write("/proc/self/setgroups", "deny")?;
|
||||
std::fs::write("/proc/self/uid_map", format!("0 {uid} 1\n"))?;
|
||||
std::fs::write("/proc/self/gid_map", format!("0 {gid} 1\n"))?;
|
||||
rustix::thread::unshare(UnshareFlags::NEWNS | UnshareFlags::NEWNET)?;
|
||||
rustix::thread::unshare(UnshareFlags::NEWNS)?;
|
||||
mount_change(
|
||||
"/",
|
||||
MountPropagationFlags::PRIVATE | MountPropagationFlags::REC,
|
||||
)?;
|
||||
Ok((
|
||||
namespace_identity(Path::new("/proc/self/ns/user"))?,
|
||||
namespace_identity(Path::new("/proc/self/ns/mnt"))?,
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
fn enter_generation_network_namespace() -> io::Result<()> {
|
||||
rustix::thread::unshare(UnshareFlags::NEWNET)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -219,6 +219,8 @@ pub struct SandboxHandoffAuthority<'configuration> {
|
|||
configuration_authority: ConfigurationAuthorityIdentity,
|
||||
command: RunscCommand,
|
||||
derived: Vec<DerivedHandoffAuthority>,
|
||||
prepared_root: PathBuf,
|
||||
prepared_root_identity: FileIdentity,
|
||||
}
|
||||
|
||||
impl SandboxHandoffAuthority<'_> {
|
||||
|
|
@ -241,17 +243,30 @@ impl SandboxHandoffAuthority<'_> {
|
|||
self.configuration
|
||||
.workspace()
|
||||
.open_authority()
|
||||
.map_err(changed_configuration_authority)?;
|
||||
.map_err(|error| changed_authority_at("workspace", error))?;
|
||||
for volume in self.configuration.volumes() {
|
||||
volume
|
||||
.open_source()
|
||||
.map_err(changed_configuration_authority)?;
|
||||
.map_err(|error| changed_authority_at("volume", error))?;
|
||||
}
|
||||
let prepared_root = open_absolute_directory(&self.prepared_root)?;
|
||||
if descriptor_identity(&prepared_root).map_err(|error| {
|
||||
SandboxBuildError::new(
|
||||
SandboxBuildErrorKind::ChangedAuthority,
|
||||
format!("prepared root identity: {error}"),
|
||||
)
|
||||
})? != self.prepared_root_identity
|
||||
{
|
||||
return Err(SandboxBuildError::new(
|
||||
SandboxBuildErrorKind::ChangedAuthority,
|
||||
"prepared root changed identity after bundle construction",
|
||||
));
|
||||
}
|
||||
let runtime_root = self
|
||||
.configuration
|
||||
.runtime_domain_root()
|
||||
.open_authority()
|
||||
.map_err(changed_configuration_authority)?;
|
||||
.map_err(|error| changed_authority_at("Runtime Domain", error))?;
|
||||
for authority in &self.derived {
|
||||
let descriptor = open_runtime_source(
|
||||
&runtime_root,
|
||||
|
|
@ -259,7 +274,16 @@ impl SandboxHandoffAuthority<'_> {
|
|||
authority.expected_type,
|
||||
authority.expected_bytes.is_some(),
|
||||
)?;
|
||||
if descriptor_identity(&descriptor)? != authority.identity {
|
||||
if descriptor_identity(&descriptor).map_err(|error| {
|
||||
SandboxBuildError::new(
|
||||
SandboxBuildErrorKind::ChangedAuthority,
|
||||
format!(
|
||||
"runtime handoff source {} identity: {error}",
|
||||
authority.relative_path.display()
|
||||
),
|
||||
)
|
||||
})? != authority.identity
|
||||
{
|
||||
return Err(SandboxBuildError::new(
|
||||
SandboxBuildErrorKind::ChangedAuthority,
|
||||
"runtime handoff source changed identity",
|
||||
|
|
@ -416,6 +440,7 @@ pub struct SandboxBuildRequest<'a> {
|
|||
pub expected_network_namespace: NamespaceIdentity,
|
||||
pub root_process: RootProcess,
|
||||
pub terminal: Option<StagedHostTerminal>,
|
||||
pub prepared_root: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
|
|
@ -458,6 +483,8 @@ pub struct SandboxBundle {
|
|||
configuration_fingerprint: ConfigurationFingerprint,
|
||||
configuration_authority: ConfigurationAuthorityIdentity,
|
||||
runtime_sources: Vec<RuntimeHandoffSource>,
|
||||
prepared_root: PathBuf,
|
||||
prepared_root_identity: FileIdentity,
|
||||
}
|
||||
|
||||
impl SandboxBundle {
|
||||
|
|
@ -497,16 +524,29 @@ impl SandboxBundle {
|
|||
configuration
|
||||
.workspace()
|
||||
.open_authority()
|
||||
.map_err(changed_configuration_authority)?;
|
||||
.map_err(|error| changed_authority_at("workspace", error))?;
|
||||
for volume in configuration.volumes() {
|
||||
volume
|
||||
.open_source()
|
||||
.map_err(changed_configuration_authority)?;
|
||||
.map_err(|error| changed_authority_at("volume", error))?;
|
||||
}
|
||||
let runtime_root = configuration
|
||||
.runtime_domain_root()
|
||||
.open_authority()
|
||||
.map_err(changed_configuration_authority)?;
|
||||
.map_err(|error| changed_authority_at("Runtime Domain", error))?;
|
||||
let prepared_root = open_absolute_directory(&self.prepared_root)?;
|
||||
if descriptor_identity(&prepared_root).map_err(|error| {
|
||||
SandboxBuildError::new(
|
||||
SandboxBuildErrorKind::ChangedAuthority,
|
||||
format!("prepared root identity: {error}"),
|
||||
)
|
||||
})? != self.prepared_root_identity
|
||||
{
|
||||
return Err(SandboxBuildError::new(
|
||||
SandboxBuildErrorKind::ChangedAuthority,
|
||||
"prepared root changed identity after bundle construction",
|
||||
));
|
||||
}
|
||||
let derived = self
|
||||
.runtime_sources
|
||||
.iter()
|
||||
|
|
@ -517,7 +557,15 @@ impl SandboxBundle {
|
|||
source.expected_type,
|
||||
source.expected_bytes.is_some(),
|
||||
)?;
|
||||
let identity = descriptor_identity(&descriptor)?;
|
||||
let identity = descriptor_identity(&descriptor).map_err(|error| {
|
||||
SandboxBuildError::new(
|
||||
SandboxBuildErrorKind::ChangedAuthority,
|
||||
format!(
|
||||
"runtime handoff source {} identity: {error}",
|
||||
source.relative_path.display()
|
||||
),
|
||||
)
|
||||
})?;
|
||||
if source
|
||||
.build_identity
|
||||
.is_some_and(|expected| expected != identity)
|
||||
|
|
@ -545,6 +593,8 @@ impl SandboxBundle {
|
|||
configuration_authority: self.configuration_authority.clone(),
|
||||
command: self.command.clone(),
|
||||
derived,
|
||||
prepared_root: self.prepared_root.clone(),
|
||||
prepared_root_identity: self.prepared_root_identity,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -590,6 +640,8 @@ pub fn build_sandbox_bundle(
|
|||
if let Some(terminal) = &request.terminal {
|
||||
terminal.revalidate(request.configuration)?;
|
||||
}
|
||||
let prepared_root = open_absolute_directory(&request.prepared_root)?;
|
||||
let prepared_root_identity = descriptor_identity(&prepared_root)?;
|
||||
|
||||
let paths = runtime_paths(request.configuration, request.container_id)?;
|
||||
let mounts = build_mounts(
|
||||
|
|
@ -604,7 +656,7 @@ pub fn build_sandbox_bundle(
|
|||
)?;
|
||||
let linux = build_linux()?;
|
||||
let root = RootBuilder::default()
|
||||
.path(paths.rootfs.clone())
|
||||
.path(request.prepared_root.clone())
|
||||
.readonly(false)
|
||||
.build()
|
||||
.map_err(oci_error)?;
|
||||
|
|
@ -632,6 +684,8 @@ pub fn build_sandbox_bundle(
|
|||
configuration_fingerprint: request.configuration.configuration_fingerprint(),
|
||||
configuration_authority: ConfigurationAuthorityIdentity::capture(request.configuration),
|
||||
runtime_sources,
|
||||
prepared_root: request.prepared_root,
|
||||
prepared_root_identity,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -639,7 +693,7 @@ struct RuntimePaths {
|
|||
container_name: String,
|
||||
bundle: PathBuf,
|
||||
runsc_root: PathBuf,
|
||||
rootfs: PathBuf,
|
||||
overlay: PathBuf,
|
||||
control_sources: PathBuf,
|
||||
}
|
||||
|
||||
|
|
@ -662,7 +716,7 @@ fn handoff_runtime_sources(
|
|||
),
|
||||
(PathBuf::from("runsc"), HandoffPathType::Directory),
|
||||
(
|
||||
PathBuf::from("state-anchor/merged"),
|
||||
PathBuf::from("runsc-overlay").join(&paths.container_name),
|
||||
HandoffPathType::Directory,
|
||||
),
|
||||
(container.clone(), HandoffPathType::Directory),
|
||||
|
|
@ -699,6 +753,14 @@ fn handoff_runtime_sources(
|
|||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
for name in ["resolv.conf", "hosts", "hostname"] {
|
||||
sources.push(RuntimeHandoffSource {
|
||||
relative_path: Path::new("state-anchor/runtime-files").join(name),
|
||||
expected_type: HandoffPathType::RegularFile,
|
||||
build_identity: None,
|
||||
expected_bytes: None,
|
||||
});
|
||||
}
|
||||
if let Some(terminal) = terminal {
|
||||
sources.push(RuntimeHandoffSource {
|
||||
relative_path: terminal.relative_source.clone(),
|
||||
|
|
@ -727,7 +789,15 @@ fn open_runtime_source(
|
|||
Mode::empty(),
|
||||
ResolveFlags::BENEATH | ResolveFlags::NO_MAGICLINKS | ResolveFlags::NO_SYMLINKS,
|
||||
)
|
||||
.map_err(staging_error)?;
|
||||
.map_err(|error| {
|
||||
SandboxBuildError::new(
|
||||
SandboxBuildErrorKind::ChangedAuthority,
|
||||
format!(
|
||||
"runtime handoff source {}: {error}",
|
||||
relative_path.display()
|
||||
),
|
||||
)
|
||||
})?;
|
||||
let stat = rustix::fs::fstat(&descriptor).map_err(staging_error)?;
|
||||
let actual = FileType::from_raw_mode(stat.st_mode);
|
||||
let matches = match expected_type {
|
||||
|
|
@ -793,7 +863,7 @@ fn runtime_paths(
|
|||
Ok(RuntimePaths {
|
||||
bundle: runtime_domain.join("bundles").join(&container_name),
|
||||
runsc_root: runtime_domain.join("runsc"),
|
||||
rootfs: runtime_domain.join("state-anchor").join("merged"),
|
||||
overlay: runtime_domain.join("runsc-overlay").join(&container_name),
|
||||
control_sources: runtime_domain.join("sandbox-control").join(&container_name),
|
||||
container_name,
|
||||
})
|
||||
|
|
@ -811,6 +881,17 @@ fn build_mounts(
|
|||
false,
|
||||
false,
|
||||
)?);
|
||||
let runtime_domain = host_path(configuration.runtime_domain_root().canonical_label())?;
|
||||
for destination in &configuration.fingerprint_record().persistent_dirs {
|
||||
mounts.push(bind_mount(
|
||||
runtime_domain
|
||||
.join("state-anchor/merged")
|
||||
.join(destination.strip_prefix('/').unwrap_or(destination)),
|
||||
PathBuf::from(destination),
|
||||
false,
|
||||
false,
|
||||
)?);
|
||||
}
|
||||
for volume in configuration.volumes() {
|
||||
let record = volume.record();
|
||||
mounts.push(bind_mount(
|
||||
|
|
@ -821,6 +902,14 @@ fn build_mounts(
|
|||
)?);
|
||||
}
|
||||
mounts.extend(control_mounts(control_sources)?);
|
||||
for name in ["resolv.conf", "hosts", "hostname"] {
|
||||
mounts.push(bind_mount(
|
||||
runtime_domain.join("state-anchor/runtime-files").join(name),
|
||||
Path::new("/etc").join(name),
|
||||
true,
|
||||
true,
|
||||
)?);
|
||||
}
|
||||
if let Some(terminal) = terminal {
|
||||
mounts.push(bind_mount(
|
||||
host_path(configuration.runtime_domain_root().canonical_label())?
|
||||
|
|
@ -858,13 +947,13 @@ fn build_linux() -> Result<oci_spec::runtime::Linux, SandboxBuildError> {
|
|||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let uid_mapping = LinuxIdMappingBuilder::default()
|
||||
.container_id(0_u32)
|
||||
.host_id(rustix::process::getuid().as_raw())
|
||||
.host_id(0_u32)
|
||||
.size(1_u32)
|
||||
.build()
|
||||
.map_err(oci_error)?;
|
||||
let gid_mapping = LinuxIdMappingBuilder::default()
|
||||
.container_id(0_u32)
|
||||
.host_id(rustix::process::getgid().as_raw())
|
||||
.host_id(0_u32)
|
||||
.size(1_u32)
|
||||
.build()
|
||||
.map_err(oci_error)?;
|
||||
|
|
@ -891,6 +980,13 @@ fn build_runsc_command(paths: &RuntimePaths, bundle_authorization: [u8; 32]) ->
|
|||
OsString::from("-rootless"),
|
||||
OsString::from("-network=host"),
|
||||
OsString::from("-ignore-cgroups"),
|
||||
OsString::from(format!(
|
||||
"-overlay2=root:dir={}",
|
||||
paths
|
||||
.overlay
|
||||
.to_str()
|
||||
.expect("validated runtime path remains UTF-8")
|
||||
)),
|
||||
OsString::from("run"),
|
||||
OsString::from(format!(
|
||||
"-bundle={}",
|
||||
|
|
@ -905,6 +1001,23 @@ fn build_runsc_command(paths: &RuntimePaths, bundle_authorization: [u8; 32]) ->
|
|||
)
|
||||
}
|
||||
|
||||
fn open_absolute_directory(path: &Path) -> Result<OwnedFd, SandboxBuildError> {
|
||||
validate_absolute_normalized(path)?;
|
||||
openat2(
|
||||
rustix::fs::CWD,
|
||||
path,
|
||||
OFlags::PATH | OFlags::DIRECTORY | OFlags::CLOEXEC | OFlags::NOFOLLOW,
|
||||
Mode::empty(),
|
||||
ResolveFlags::NO_MAGICLINKS | ResolveFlags::NO_SYMLINKS,
|
||||
)
|
||||
.map_err(|error| {
|
||||
SandboxBuildError::new(
|
||||
SandboxBuildErrorKind::ChangedAuthority,
|
||||
format!("prepared root {}: {error}", path.display()),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn build_process(
|
||||
configuration: &ResolvedDomainConfiguration,
|
||||
root_process: RootProcess,
|
||||
|
|
@ -1094,6 +1207,13 @@ fn changed_configuration_authority(error: impl fmt::Display) -> SandboxBuildErro
|
|||
SandboxBuildError::new(SandboxBuildErrorKind::ChangedAuthority, error.to_string())
|
||||
}
|
||||
|
||||
fn changed_authority_at(label: &str, error: impl fmt::Display) -> SandboxBuildError {
|
||||
SandboxBuildError::new(
|
||||
SandboxBuildErrorKind::ChangedAuthority,
|
||||
format!("{label}: {error}"),
|
||||
)
|
||||
}
|
||||
|
||||
fn oci_error(error: impl fmt::Display) -> SandboxBuildError {
|
||||
SandboxBuildError::new(SandboxBuildErrorKind::OciSpecification, error.to_string())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use std::fs::File;
|
|||
use std::io;
|
||||
use std::os::fd::{AsFd, AsRawFd, OwnedFd};
|
||||
use std::os::unix::fs::FileExt;
|
||||
use std::path::PathBuf;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use lazaret_core::codec::{decode, encode};
|
||||
|
|
@ -32,8 +33,8 @@ use rustix::thread::CapabilitySet;
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::network::{
|
||||
NetworkAttestationContext, NetworkProbeRequest, PreparedNetwork, prepare_network,
|
||||
probe_current_network,
|
||||
NetworkAttestationContext, NetworkProbeRequest, PreparedNetwork,
|
||||
enter_generation_filesystem_namespaces, prepare_network, probe_current_network,
|
||||
};
|
||||
use crate::sandbox::RunscCommand;
|
||||
|
||||
|
|
@ -64,6 +65,8 @@ pub enum SupervisorEvent {
|
|||
StateAnchorStaged(StagedProcessOwnership),
|
||||
StateAnchorReady {
|
||||
staged: StagedProcessOwnership,
|
||||
user_namespace: lazaret_core::identity::NamespaceIdentity,
|
||||
mount_namespace: lazaret_core::identity::NamespaceIdentity,
|
||||
mount: lazaret_core::identity::MountIdentity,
|
||||
},
|
||||
SidecarStarted,
|
||||
|
|
@ -80,7 +83,9 @@ pub enum SupervisorEvent {
|
|||
SignalApplied(AttachedRunscSignal),
|
||||
SandboxStopped,
|
||||
SidecarStopped,
|
||||
StateAnchorUnmounted,
|
||||
StateAnchorUnmounted {
|
||||
mount: lazaret_core::identity::MountIdentity,
|
||||
},
|
||||
Exited,
|
||||
AttachedRunscExited,
|
||||
Stopped,
|
||||
|
|
@ -362,14 +367,31 @@ impl RuntimeSupervisorLaunch {
|
|||
target = target
|
||||
.argument("--state-lower-fd")
|
||||
.argument(authority.lower_fd().to_string())
|
||||
.argument("--state-lower-label")
|
||||
.argument(authority.lower_label())
|
||||
.argument("--state-persistent-root-fd")
|
||||
.argument(authority.persistent_root_fd().to_string())
|
||||
.argument("--state-persistent-fd")
|
||||
.argument(authority.persistent_anchor_fd().to_string())
|
||||
.argument("--state-persistent-label")
|
||||
.argument(authority.persistent_anchor_label())
|
||||
.argument("--state-runtime-root-fd")
|
||||
.argument(authority.runtime_root_fd().to_string())
|
||||
.argument("--state-runtime-fd")
|
||||
.argument(authority.runtime_anchor_fd().to_string())
|
||||
.argument("--state-runtime-label")
|
||||
.argument(authority.runtime_anchor_label())
|
||||
.argument("--state-lower-device")
|
||||
.argument(device.to_string())
|
||||
.argument("--state-lower-inode")
|
||||
.argument(inode.to_string());
|
||||
.argument(inode.to_string())
|
||||
.argument("--state-persistent-count")
|
||||
.argument(authority.persistent_destinations().len().to_string());
|
||||
for destination in authority.persistent_destinations() {
|
||||
target = target
|
||||
.argument("--state-persistent-destination")
|
||||
.argument(destination);
|
||||
}
|
||||
}
|
||||
if let (Some(policy_file), Some(digest), Some(fingerprint), Some(sidecar)) = (
|
||||
&self.policy_file,
|
||||
|
|
@ -468,9 +490,15 @@ struct RuntimeArguments {
|
|||
|
||||
struct RuntimeStateAnchorArguments {
|
||||
lower_fd: i32,
|
||||
lower_label: PathBuf,
|
||||
persistent_root_fd: i32,
|
||||
persistent_anchor_fd: i32,
|
||||
persistent_anchor_label: PathBuf,
|
||||
runtime_root_fd: i32,
|
||||
runtime_anchor_fd: i32,
|
||||
runtime_anchor_label: PathBuf,
|
||||
expected_lower: (u64, u64),
|
||||
persistent_destinations: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
struct RuntimeNetworkArguments {
|
||||
|
|
@ -492,14 +520,35 @@ fn parse_runtime_arguments(arguments: &[OsString]) -> io::Result<RuntimeArgument
|
|||
.peek()
|
||||
.is_some_and(|value| *value == "--state-lower-fd")
|
||||
{
|
||||
let lower_fd = parse_number(named(&mut arguments, "--state-lower-fd")?)?;
|
||||
let lower_label = PathBuf::from(named(&mut arguments, "--state-lower-label")?);
|
||||
let persistent_root_fd =
|
||||
parse_number(named(&mut arguments, "--state-persistent-root-fd")?)?;
|
||||
let persistent_anchor_fd = parse_number(named(&mut arguments, "--state-persistent-fd")?)?;
|
||||
let persistent_anchor_label =
|
||||
PathBuf::from(named(&mut arguments, "--state-persistent-label")?);
|
||||
let runtime_root_fd = parse_number(named(&mut arguments, "--state-runtime-root-fd")?)?;
|
||||
let runtime_anchor_fd = parse_number(named(&mut arguments, "--state-runtime-fd")?)?;
|
||||
let runtime_anchor_label = PathBuf::from(named(&mut arguments, "--state-runtime-label")?);
|
||||
let expected_lower = (
|
||||
parse_number(named(&mut arguments, "--state-lower-device")?)?,
|
||||
parse_number(named(&mut arguments, "--state-lower-inode")?)?,
|
||||
);
|
||||
let count: usize = parse_number(named(&mut arguments, "--state-persistent-count")?)?;
|
||||
let persistent_destinations = (0..count)
|
||||
.map(|_| named(&mut arguments, "--state-persistent-destination").map(PathBuf::from))
|
||||
.collect::<io::Result<Vec<_>>>()?;
|
||||
Some(RuntimeStateAnchorArguments {
|
||||
lower_fd: parse_number(named(&mut arguments, "--state-lower-fd")?)?,
|
||||
persistent_anchor_fd: parse_number(named(&mut arguments, "--state-persistent-fd")?)?,
|
||||
runtime_anchor_fd: parse_number(named(&mut arguments, "--state-runtime-fd")?)?,
|
||||
expected_lower: (
|
||||
parse_number(named(&mut arguments, "--state-lower-device")?)?,
|
||||
parse_number(named(&mut arguments, "--state-lower-inode")?)?,
|
||||
),
|
||||
lower_fd,
|
||||
lower_label,
|
||||
persistent_root_fd,
|
||||
persistent_anchor_fd,
|
||||
persistent_anchor_label,
|
||||
runtime_root_fd,
|
||||
runtime_anchor_fd,
|
||||
runtime_anchor_label,
|
||||
expected_lower,
|
||||
persistent_destinations,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
|
|
@ -604,9 +653,10 @@ fn parse_fixed_hex(value: &OsStr) -> io::Result<[u8; 32]> {
|
|||
|
||||
fn run_supervisor(arguments: &RuntimeArguments) -> io::Result<()> {
|
||||
require_lifetime_held()?;
|
||||
let namespaces = enter_generation_filesystem_namespaces()?;
|
||||
let control = std::io::stdout();
|
||||
let parent = current_process_identity()?;
|
||||
let mut state_anchor = prepare_state_anchor(arguments, parent, &control)?;
|
||||
let mut state_anchor = prepare_state_anchor(arguments, parent, namespaces, &control)?;
|
||||
let result = run_supervisor_with_state(arguments, parent, &control, &mut state_anchor);
|
||||
let cleanup = state_anchor.take().map(RuntimeStateAnchorMount::unmount);
|
||||
match (result, cleanup) {
|
||||
|
|
@ -623,6 +673,10 @@ fn run_supervisor(arguments: &RuntimeArguments) -> io::Result<()> {
|
|||
fn prepare_state_anchor(
|
||||
arguments: &RuntimeArguments,
|
||||
parent: lazaret_core::identity::PidfsProcessIdentityV1,
|
||||
namespaces: (
|
||||
lazaret_core::identity::NamespaceIdentity,
|
||||
lazaret_core::identity::NamespaceIdentity,
|
||||
),
|
||||
control: &std::io::Stdout,
|
||||
) -> io::Result<Option<RuntimeStateAnchorMount>> {
|
||||
let Some(state) = &arguments.state_anchor else {
|
||||
|
|
@ -636,8 +690,13 @@ fn prepare_state_anchor(
|
|||
}
|
||||
let authority = RuntimeStateAnchorAuthority::from_inherited(
|
||||
state.lower_fd,
|
||||
&state.lower_label,
|
||||
state.persistent_root_fd,
|
||||
state.persistent_anchor_fd,
|
||||
&state.persistent_anchor_label,
|
||||
state.runtime_root_fd,
|
||||
state.runtime_anchor_fd,
|
||||
&state.runtime_anchor_label,
|
||||
state.expected_lower,
|
||||
)
|
||||
.map_err(io::Error::other)?;
|
||||
|
|
@ -645,7 +704,7 @@ fn prepare_state_anchor(
|
|||
slot: HostProcessOwnershipSlot::StateAnchorDaemon,
|
||||
..arguments.ownership
|
||||
};
|
||||
authority
|
||||
let mount = authority
|
||||
.acquire(
|
||||
parent,
|
||||
ownership,
|
||||
|
|
@ -665,13 +724,18 @@ fn prepare_state_anchor(
|
|||
control,
|
||||
&SupervisorEvent::StateAnchorReady {
|
||||
staged: readiness.staged(),
|
||||
user_namespace: namespaces.0,
|
||||
mount_namespace: namespaces.1,
|
||||
mount: readiness.mount(),
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
.map(Some)
|
||||
.map_err(io::Error::other)
|
||||
.map_err(io::Error::other)?;
|
||||
mount
|
||||
.require_persistent_destinations(&state.persistent_destinations)
|
||||
.map_err(io::Error::other)?;
|
||||
Ok(Some(mount))
|
||||
}
|
||||
|
||||
fn run_supervisor_with_state(
|
||||
|
|
@ -698,6 +762,7 @@ fn run_supervisor_with_state(
|
|||
let pid_file = File::from(memfd_create("lazaret-runsc-pid", MemfdFlags::empty())?);
|
||||
fcntl_setfd(pid_file.as_fd(), FdFlags::empty())?;
|
||||
let runsc = runsc_target(arguments, pid_file.as_raw_fd())?;
|
||||
let delete = runsc_delete_target(arguments)?;
|
||||
let staged = std::cell::Cell::new(None);
|
||||
let process = match child_broker().acquire_with_stdio(
|
||||
parent,
|
||||
|
|
@ -762,7 +827,7 @@ fn run_supervisor_with_state(
|
|||
io::Error::other("sandbox commit acknowledgment was not received"),
|
||||
);
|
||||
}
|
||||
supervise(control, process, network, state_anchor)
|
||||
supervise(control, process, network, state_anchor, &delete)
|
||||
}
|
||||
|
||||
fn prepare_runtime_network(
|
||||
|
|
@ -858,6 +923,29 @@ fn runsc_target(arguments: &RuntimeArguments, pid_file: i32) -> io::Result<Targe
|
|||
Ok(target.with_parent_death_kill_edge())
|
||||
}
|
||||
|
||||
fn runsc_delete_target(arguments: &RuntimeArguments) -> io::Result<TargetCommand> {
|
||||
let run = arguments
|
||||
.runsc_arguments
|
||||
.iter()
|
||||
.position(|argument| argument == "run")
|
||||
.ok_or_else(|| io::Error::other("attached runsc command has no run operation"))?;
|
||||
let container = arguments
|
||||
.runsc_arguments
|
||||
.last()
|
||||
.filter(|container| {
|
||||
!container.is_empty() && !container.as_encoded_bytes().starts_with(b"-")
|
||||
})
|
||||
.ok_or_else(|| io::Error::other("attached runsc command has no container ID"))?;
|
||||
let mut target = TargetCommand::new(arguments.runsc_program.clone());
|
||||
for argument in &arguments.runsc_arguments[..run] {
|
||||
target = target.argument(argument);
|
||||
}
|
||||
Ok(target
|
||||
.argument("delete")
|
||||
.argument("--force")
|
||||
.argument(container))
|
||||
}
|
||||
|
||||
fn attached_runsc_standard_io(target: &TargetCommand) -> io::Result<ChildStandardIo> {
|
||||
if target.parent_edge() != ParentEdge::ParentDeathKill {
|
||||
return Err(io::Error::other(
|
||||
|
|
@ -896,6 +984,7 @@ fn supervise(
|
|||
process: RetainedProcess,
|
||||
mut network: Option<PreparedNetwork>,
|
||||
state_anchor: &mut Option<RuntimeStateAnchorMount>,
|
||||
delete: &TargetCommand,
|
||||
) -> io::Result<()> {
|
||||
let mut process = Some(process);
|
||||
loop {
|
||||
|
|
@ -912,6 +1001,7 @@ fn supervise(
|
|||
.take()
|
||||
.ok_or_else(|| io::Error::other("attached runsc handle is absent"))?,
|
||||
)?;
|
||||
child_broker().run_transient(delete)?;
|
||||
stop_network(network)?;
|
||||
send_message(control, &SupervisorEvent::AttachedRunscExited)?;
|
||||
return Ok(());
|
||||
|
|
@ -933,6 +1023,7 @@ fn supervise(
|
|||
if lease_lost {
|
||||
if let Some(process) = process.take() {
|
||||
terminate_and_reap(process)?;
|
||||
child_broker().run_transient(delete)?;
|
||||
}
|
||||
stop_network(network)?;
|
||||
return Ok(());
|
||||
|
|
@ -969,6 +1060,7 @@ fn supervise(
|
|||
&mut process,
|
||||
&mut network,
|
||||
state_anchor,
|
||||
delete,
|
||||
) {
|
||||
Ok(true) => return Ok(()),
|
||||
Ok(false) => {}
|
||||
|
|
@ -984,6 +1076,7 @@ fn handle_supervisor_message(
|
|||
process: &mut Option<RetainedProcess>,
|
||||
network: &mut Option<PreparedNetwork>,
|
||||
state_anchor: &mut Option<RuntimeStateAnchorMount>,
|
||||
delete: &TargetCommand,
|
||||
) -> io::Result<bool> {
|
||||
match message {
|
||||
CoordinatorMessage::SignalAttachedRunsc(signal) => {
|
||||
|
|
@ -999,6 +1092,7 @@ fn handle_supervisor_message(
|
|||
.take()
|
||||
.ok_or_else(|| io::Error::other("attached runsc is already stopped"))?,
|
||||
)?;
|
||||
child_broker().run_transient(delete)?;
|
||||
send_message(control, &SupervisorEvent::SandboxStopped)?;
|
||||
}
|
||||
CoordinatorMessage::StopSidecar => {
|
||||
|
|
@ -1014,12 +1108,16 @@ fn handle_supervisor_message(
|
|||
"State Anchor unmount preceded runtime resource stop",
|
||||
));
|
||||
}
|
||||
let mount = state_anchor
|
||||
.as_ref()
|
||||
.ok_or_else(|| io::Error::other("State Anchor is absent"))?
|
||||
.mount_identity();
|
||||
state_anchor
|
||||
.take()
|
||||
.ok_or_else(|| io::Error::other("State Anchor is absent"))?
|
||||
.unmount()
|
||||
.map_err(io::Error::other)?;
|
||||
send_message(control, &SupervisorEvent::StateAnchorUnmounted)?;
|
||||
send_message(control, &SupervisorEvent::StateAnchorUnmounted { mount })?;
|
||||
}
|
||||
CoordinatorMessage::Exit => {
|
||||
if process.is_some() || network.is_some() || state_anchor.is_some() {
|
||||
|
|
@ -1307,6 +1405,14 @@ mod tests {
|
|||
attached_runsc_standard_io(&runsc).unwrap().policy(),
|
||||
ChildStandardIoPolicy::Silenced
|
||||
);
|
||||
let delete = runsc_delete_target(&runsc_arguments).unwrap();
|
||||
assert_eq!(delete.program(), "runsc");
|
||||
assert_eq!(
|
||||
delete.arguments(),
|
||||
["-root=/runtime", "delete", "--force", "container"]
|
||||
.map(OsString::from)
|
||||
.as_slice()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ impl Fixture {
|
|||
fs::create_dir(&configuration_directory).unwrap();
|
||||
fs::create_dir(&workspace).unwrap();
|
||||
fs::create_dir(&runtime).unwrap();
|
||||
fs::create_dir(tree.path().join("prepared-root")).unwrap();
|
||||
let configuration_path = configuration_directory.join("lazaret.toml");
|
||||
fs::write(
|
||||
&configuration_path,
|
||||
|
|
@ -149,6 +150,7 @@ env = {{ UNSELECTED_SECRET = "env:UNSELECTED_HOST_SECRET" }}
|
|||
expected_network_namespace: current_network_namespace(),
|
||||
root_process,
|
||||
terminal: Some(self.terminal()),
|
||||
prepared_root: self.tree.path().join("prepared-root"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -163,7 +165,9 @@ env = {{ UNSELECTED_SECRET = "env:UNSELECTED_HOST_SECRET" }}
|
|||
for directory in [
|
||||
format!("bundles/{container}"),
|
||||
"runsc".to_owned(),
|
||||
"state-anchor/merged".to_owned(),
|
||||
format!("runsc-overlay/{container}"),
|
||||
"state-anchor/merged/root".to_owned(),
|
||||
"state-anchor/runtime-files".to_owned(),
|
||||
format!("sandbox-control/{container}/credentials"),
|
||||
format!("sandbox-control/{container}/mailbox/requests"),
|
||||
format!("sandbox-control/{container}/mailbox/results"),
|
||||
|
|
@ -171,6 +175,13 @@ env = {{ UNSELECTED_SECRET = "env:UNSELECTED_HOST_SECRET" }}
|
|||
] {
|
||||
fs::create_dir_all(runtime.join(directory)).unwrap();
|
||||
}
|
||||
for name in ["resolv.conf", "hosts", "hostname"] {
|
||||
fs::write(
|
||||
runtime.join("state-anchor/runtime-files").join(name),
|
||||
b"fixture",
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
fs::write(
|
||||
runtime.join(format!("bundles/{container}/config.json")),
|
||||
bundle.specification_json(),
|
||||
|
|
@ -291,13 +302,13 @@ fn deterministic_snapshot_has_only_private_namespaces_and_fixed_runtime_shape()
|
|||
assert_eq!(
|
||||
specification["linux"]["uidMappings"],
|
||||
serde_json::json!([{
|
||||
"hostID": rustix::process::getuid().as_raw(), "containerID": 0, "size": 1
|
||||
"hostID": 0, "containerID": 0, "size": 1
|
||||
}])
|
||||
);
|
||||
assert_eq!(
|
||||
specification["linux"]["gidMappings"],
|
||||
serde_json::json!([{
|
||||
"hostID": rustix::process::getgid().as_raw(), "containerID": 0, "size": 1
|
||||
"hostID": 0, "containerID": 0, "size": 1
|
||||
}])
|
||||
);
|
||||
|
||||
|
|
@ -323,6 +334,10 @@ fn deterministic_snapshot_has_only_private_namespaces_and_fixed_runtime_shape()
|
|||
"-rootless".to_owned(),
|
||||
"-network=host".to_owned(),
|
||||
"-ignore-cgroups".to_owned(),
|
||||
format!(
|
||||
"-overlay2=root:dir={runtime_domain}/runsc-overlay/{}",
|
||||
"11".repeat(32)
|
||||
),
|
||||
"run".to_owned(),
|
||||
format!(
|
||||
"-bundle={}/bundles/{}",
|
||||
|
|
@ -340,6 +355,15 @@ fn deterministic_snapshot_has_only_private_namespaces_and_fixed_runtime_shape()
|
|||
"11".repeat(32),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
specification["root"]["path"],
|
||||
fixture
|
||||
.tree
|
||||
.path()
|
||||
.join("prepared-root")
|
||||
.display()
|
||||
.to_string()
|
||||
);
|
||||
assert!(!first.command().arguments().iter().any(|argument| {
|
||||
matches!(
|
||||
argument.to_str(),
|
||||
|
|
@ -370,7 +394,7 @@ fn product_init_role_has_exact_terminal_neutral_rootless_bundle_shape() {
|
|||
assert_eq!(
|
||||
specification["linux"]["uidMappings"],
|
||||
serde_json::json!([{
|
||||
"hostID": rustix::process::getuid().as_raw(), "containerID": 0, "size": 1
|
||||
"hostID": 0, "containerID": 0, "size": 1
|
||||
}])
|
||||
);
|
||||
assert_eq!(
|
||||
|
|
@ -408,6 +432,7 @@ fn selected_environment_volume_modes_and_host_terminal_are_preserved() {
|
|||
|
||||
let mounts = mounts(&specification);
|
||||
assert!(options(mounts[WORKSPACE_DESTINATION]).contains("rw"));
|
||||
assert!(options(mounts["/root"]).contains("rw"));
|
||||
assert!(options(mounts["/root/read-only"]).contains("ro"));
|
||||
assert!(options(mounts["/root/read-write"]).contains("rw"));
|
||||
let terminfo = &mounts["/run/lazaret/terminfo/x/xterm-lazaret"];
|
||||
|
|
@ -434,6 +459,17 @@ fn selected_environment_volume_modes_and_host_terminal_are_preserved() {
|
|||
assert!(options(mounts[MAILBOX_RESULTS_DESTINATION]).contains("ro"));
|
||||
assert!(options(mounts[ESCALATION_CLIENT_DESTINATION]).contains("ro"));
|
||||
assert!(!options(mounts[ESCALATION_CLIENT_DESTINATION]).contains("noexec"));
|
||||
for name in ["resolv.conf", "hosts", "hostname"] {
|
||||
let destination = format!("/etc/{name}");
|
||||
let mount = &mounts[destination.as_str()];
|
||||
assert!(options(mount).contains("ro"));
|
||||
assert!(
|
||||
mount["source"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.ends_with(&format!("state-anchor/runtime-files/{name}"))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -542,6 +578,7 @@ fn hostile_terminal_names_sources_and_changed_entries_fail_closed() {
|
|||
expected_network_namespace: current_network_namespace(),
|
||||
root_process: root_process([]),
|
||||
terminal: Some(terminal),
|
||||
prepared_root: fixture.tree.path().join("prepared-root"),
|
||||
})
|
||||
.unwrap_err();
|
||||
assert_eq!(error.kind(), SandboxBuildErrorKind::ChangedAuthority);
|
||||
|
|
@ -578,6 +615,7 @@ fn invalid_processes_and_wrong_network_identity_fail_closed() {
|
|||
},
|
||||
root_process: root_process([]),
|
||||
terminal: Some(fixture.terminal()),
|
||||
prepared_root: fixture.tree.path().join("prepared-root"),
|
||||
})
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
|
|
@ -633,15 +671,8 @@ fn final_handoff_refuses_replaced_workspace_and_runtime_sources() {
|
|||
let handoff = bundle
|
||||
.capture_handoff_authority(&fixture.configuration)
|
||||
.unwrap();
|
||||
let runtime = host_path(
|
||||
fixture
|
||||
.configuration
|
||||
.runtime_domain_root()
|
||||
.canonical_label()
|
||||
.as_bytes(),
|
||||
);
|
||||
let rootfs = runtime.join("state-anchor/merged");
|
||||
fs::rename(&rootfs, runtime.join("state-anchor/selected")).unwrap();
|
||||
let rootfs = fixture.tree.path().join("prepared-root");
|
||||
fs::rename(&rootfs, fixture.tree.path().join("prepared-root-selected")).unwrap();
|
||||
fs::create_dir(&rootfs).unwrap();
|
||||
fs::write(rootfs.join("replacement-marker"), b"unchanged").unwrap();
|
||||
let error = handoff.revalidate().unwrap_err();
|
||||
|
|
|
|||
Loading…
Reference in a new issue