Replace lexical Rust fragments with real modules
This commit is contained in:
parent
f8849cdb5f
commit
247819250b
9 changed files with 3302 additions and 2671 deletions
|
|
@ -24,6 +24,20 @@ use oci_spec::runtime::{
|
|||
use rustix::fs::{AtFlags, FileType, Mode, OFlags, ResolveFlags, StatxFlags, openat2, statx};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use mounts::{bind_mount, control_mounts, standard_mounts};
|
||||
use runtime_sources::{
|
||||
build_linux, build_mounts, build_process, build_runsc_command, descriptor_identity,
|
||||
handoff_runtime_sources, open_absolute_directory, open_runtime_source, require_exact_bytes,
|
||||
runtime_paths,
|
||||
};
|
||||
use validation::{
|
||||
changed_authority_at, changed_configuration_authority, confined_resolve_flags, hex_lower,
|
||||
host_path, oci_error, open_or_create_staging_directory, read_descriptor,
|
||||
require_network_namespace, require_staged_file, stage_exact_file, staging_error,
|
||||
validate_absolute_normalized, validate_compiled_terminfo, validate_sandbox_executable,
|
||||
validate_terminal_name,
|
||||
};
|
||||
|
||||
pub const NETWORK_NAMESPACE_PATH: &str = "/proc/self/ns/net";
|
||||
|
||||
const OCI_VERSION: &str = "1.2.1";
|
||||
|
|
@ -700,6 +714,6 @@ fn build_sandbox_bundle_from_source(
|
|||
})
|
||||
}
|
||||
|
||||
include!("sandbox/runtime_sources.rs");
|
||||
include!("sandbox/mounts.rs");
|
||||
include!("sandbox/validation.rs");
|
||||
mod mounts;
|
||||
mod runtime_sources;
|
||||
mod validation;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,11 @@
|
|||
fn standard_mounts() -> Result<Vec<Mount>, SandboxBuildError> {
|
||||
use super::{
|
||||
CREDENTIALS_DESTINATION, DEV_DESTINATION, ESCALATION_CLIENT_DESTINATION,
|
||||
MAILBOX_REQUESTS_DESTINATION, MAILBOX_RESULTS_DESTINATION, Mount, MountBuilder,
|
||||
PROC_DESTINATION, Path, PathBuf, SANDBOX_INIT_DESTINATION, SYS_DESTINATION, SandboxBuildError,
|
||||
oci_error, validate_absolute_normalized,
|
||||
};
|
||||
|
||||
pub(super) fn standard_mounts() -> Result<Vec<Mount>, SandboxBuildError> {
|
||||
[
|
||||
(
|
||||
PROC_DESTINATION,
|
||||
|
|
@ -56,7 +63,7 @@ fn standard_mounts() -> Result<Vec<Mount>, SandboxBuildError> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn control_mounts(control_sources: &Path) -> Result<Vec<Mount>, SandboxBuildError> {
|
||||
pub(super) fn control_mounts(control_sources: &Path) -> Result<Vec<Mount>, SandboxBuildError> {
|
||||
[
|
||||
("credentials", CREDENTIALS_DESTINATION, true, false),
|
||||
(
|
||||
|
|
@ -91,7 +98,7 @@ fn control_mounts(control_sources: &Path) -> Result<Vec<Mount>, SandboxBuildErro
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn bind_mount(
|
||||
pub(super) fn bind_mount(
|
||||
source: PathBuf,
|
||||
destination: PathBuf,
|
||||
read_only: bool,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,24 @@
|
|||
struct RuntimePaths {
|
||||
container_name: String,
|
||||
bundle: PathBuf,
|
||||
runsc_root: PathBuf,
|
||||
overlay: PathBuf,
|
||||
control_sources: PathBuf,
|
||||
use super::{
|
||||
AtFlags, BTreeMap, Capabilities, ContainerId, DEFAULT_PATH, File, FileIdentity, FileType,
|
||||
HandoffPathType, LinuxBuilder, LinuxCapabilitiesBuilder, LinuxIdMappingBuilder,
|
||||
LinuxNamespaceBuilder, LinuxNamespaceType, Mode, Mount, NETWORK_NAMESPACE_PATH, OFlags,
|
||||
OsString, OwnedFd, Path, PathBuf, ProcessBuilder, Read, ResolveFlags,
|
||||
ResolvedDomainConfiguration, RootProcess, RunscCommand, RuntimeHandoffSource,
|
||||
SandboxBuildError, SandboxBuildErrorKind, StagedHostTerminal, StatxFlags,
|
||||
TERMINFO_ROOT_DESTINATION, UserBuilder, VolumeAccess, WORKSPACE_DESTINATION, bind_mount,
|
||||
control_mounts, hex_lower, host_path, oci_error, openat2, staging_error, standard_mounts,
|
||||
statx, validate_absolute_normalized,
|
||||
};
|
||||
|
||||
pub(super) struct RuntimePaths {
|
||||
pub(super) container_name: String,
|
||||
pub(super) bundle: PathBuf,
|
||||
pub(super) runsc_root: PathBuf,
|
||||
pub(super) overlay: PathBuf,
|
||||
pub(super) control_sources: PathBuf,
|
||||
}
|
||||
|
||||
fn handoff_runtime_sources(
|
||||
pub(super) fn handoff_runtime_sources(
|
||||
paths: &RuntimePaths,
|
||||
terminal: Option<&StagedHostTerminal>,
|
||||
specification_json: &[u8],
|
||||
|
|
@ -81,7 +93,7 @@ fn handoff_runtime_sources(
|
|||
sources
|
||||
}
|
||||
|
||||
fn open_runtime_source(
|
||||
pub(super) fn open_runtime_source(
|
||||
runtime_root: &OwnedFd,
|
||||
relative_path: &Path,
|
||||
expected_type: HandoffPathType,
|
||||
|
|
@ -122,7 +134,10 @@ fn open_runtime_source(
|
|||
Ok(descriptor)
|
||||
}
|
||||
|
||||
fn require_exact_bytes(descriptor: OwnedFd, expected: &[u8]) -> Result<(), SandboxBuildError> {
|
||||
pub(super) fn require_exact_bytes(
|
||||
descriptor: OwnedFd,
|
||||
expected: &[u8],
|
||||
) -> Result<(), SandboxBuildError> {
|
||||
let limit = u64::try_from(expected.len())
|
||||
.ok()
|
||||
.and_then(|length| length.checked_add(1))
|
||||
|
|
@ -146,7 +161,7 @@ fn require_exact_bytes(descriptor: OwnedFd, expected: &[u8]) -> Result<(), Sandb
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn descriptor_identity(descriptor: &OwnedFd) -> Result<FileIdentity, SandboxBuildError> {
|
||||
pub(super) fn descriptor_identity(descriptor: &OwnedFd) -> Result<FileIdentity, SandboxBuildError> {
|
||||
let stat = rustix::fs::fstat(descriptor).map_err(staging_error)?;
|
||||
let extended =
|
||||
statx(descriptor, "", AtFlags::EMPTY_PATH, StatxFlags::MNT_ID).map_err(staging_error)?;
|
||||
|
|
@ -163,7 +178,7 @@ fn descriptor_identity(descriptor: &OwnedFd) -> Result<FileIdentity, SandboxBuil
|
|||
})
|
||||
}
|
||||
|
||||
fn runtime_paths(
|
||||
pub(super) fn runtime_paths(
|
||||
configuration: &ResolvedDomainConfiguration,
|
||||
container_id: ContainerId,
|
||||
) -> Result<RuntimePaths, SandboxBuildError> {
|
||||
|
|
@ -178,7 +193,7 @@ fn runtime_paths(
|
|||
})
|
||||
}
|
||||
|
||||
fn build_mounts(
|
||||
pub(super) fn build_mounts(
|
||||
configuration: &ResolvedDomainConfiguration,
|
||||
terminal: Option<&StagedHostTerminal>,
|
||||
control_sources: &Path,
|
||||
|
|
@ -232,7 +247,7 @@ fn build_mounts(
|
|||
Ok(mounts)
|
||||
}
|
||||
|
||||
fn build_linux() -> Result<oci_spec::runtime::Linux, SandboxBuildError> {
|
||||
pub(super) fn build_linux() -> Result<oci_spec::runtime::Linux, SandboxBuildError> {
|
||||
let private_namespaces = [
|
||||
LinuxNamespaceType::Pid,
|
||||
LinuxNamespaceType::Ipc,
|
||||
|
|
@ -276,7 +291,10 @@ fn build_linux() -> Result<oci_spec::runtime::Linux, SandboxBuildError> {
|
|||
.map_err(oci_error)
|
||||
}
|
||||
|
||||
fn build_runsc_command(paths: &RuntimePaths, bundle_authorization: [u8; 32]) -> RunscCommand {
|
||||
pub(super) fn build_runsc_command(
|
||||
paths: &RuntimePaths,
|
||||
bundle_authorization: [u8; 32],
|
||||
) -> RunscCommand {
|
||||
RunscCommand::new(
|
||||
"runsc",
|
||||
[
|
||||
|
|
@ -311,7 +329,7 @@ fn build_runsc_command(paths: &RuntimePaths, bundle_authorization: [u8; 32]) ->
|
|||
)
|
||||
}
|
||||
|
||||
fn open_absolute_directory(path: &Path) -> Result<OwnedFd, SandboxBuildError> {
|
||||
pub(super) fn open_absolute_directory(path: &Path) -> Result<OwnedFd, SandboxBuildError> {
|
||||
validate_absolute_normalized(path)?;
|
||||
openat2(
|
||||
rustix::fs::CWD,
|
||||
|
|
@ -328,7 +346,7 @@ fn open_absolute_directory(path: &Path) -> Result<OwnedFd, SandboxBuildError> {
|
|||
})
|
||||
}
|
||||
|
||||
fn build_process(
|
||||
pub(super) fn build_process(
|
||||
configuration: &ResolvedDomainConfiguration,
|
||||
root_process: RootProcess,
|
||||
terminal: Option<&StagedHostTerminal>,
|
||||
|
|
@ -385,4 +403,3 @@ fn build_process(
|
|||
.build()
|
||||
.map_err(oci_error)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,12 @@
|
|||
fn require_network_namespace(expected: NamespaceIdentity) -> Result<(), SandboxBuildError> {
|
||||
use super::{
|
||||
AsFd, AtFlags, Component, File, FileType, MAX_TERMINFO_BYTES, Mode, NETWORK_NAMESPACE_PATH,
|
||||
NamespaceIdentity, OFlags, OsStrExt, OsString, OsStringExt, OwnedFd, Path, PathBuf, Read,
|
||||
ResolveFlags, SandboxBuildError, SandboxBuildErrorKind, Write, fmt, openat2,
|
||||
};
|
||||
|
||||
pub(super) fn require_network_namespace(
|
||||
expected: NamespaceIdentity,
|
||||
) -> Result<(), SandboxBuildError> {
|
||||
let observed = lazaret_linux::filesystem::namespace_identity(Path::new(NETWORK_NAMESPACE_PATH))
|
||||
.map_err(|error| {
|
||||
SandboxBuildError::new(
|
||||
|
|
@ -15,22 +23,22 @@ fn require_network_namespace(expected: NamespaceIdentity) -> Result<(), SandboxB
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn changed_configuration_authority(error: impl fmt::Display) -> SandboxBuildError {
|
||||
pub(super) fn changed_configuration_authority(error: impl fmt::Display) -> SandboxBuildError {
|
||||
SandboxBuildError::new(SandboxBuildErrorKind::ChangedAuthority, error.to_string())
|
||||
}
|
||||
|
||||
fn changed_authority_at(label: &str, error: impl fmt::Display) -> SandboxBuildError {
|
||||
pub(super) 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 {
|
||||
pub(super) fn oci_error(error: impl fmt::Display) -> SandboxBuildError {
|
||||
SandboxBuildError::new(SandboxBuildErrorKind::OciSpecification, error.to_string())
|
||||
}
|
||||
|
||||
fn validate_sandbox_executable(program: &str) -> Result<(), SandboxBuildError> {
|
||||
pub(super) fn validate_sandbox_executable(program: &str) -> Result<(), SandboxBuildError> {
|
||||
validate_absolute_normalized(Path::new(program)).map_err(|_| {
|
||||
SandboxBuildError::new(
|
||||
SandboxBuildErrorKind::InvalidProcess,
|
||||
|
|
@ -39,7 +47,7 @@ fn validate_sandbox_executable(program: &str) -> Result<(), SandboxBuildError> {
|
|||
})
|
||||
}
|
||||
|
||||
fn validate_terminal_name(term: &str) -> Result<(), SandboxBuildError> {
|
||||
pub(super) fn validate_terminal_name(term: &str) -> Result<(), SandboxBuildError> {
|
||||
let valid = !term.is_empty()
|
||||
&& term.len() <= 64
|
||||
&& term
|
||||
|
|
@ -55,7 +63,10 @@ fn validate_terminal_name(term: &str) -> Result<(), SandboxBuildError> {
|
|||
}
|
||||
}
|
||||
|
||||
fn validate_compiled_terminfo(term: &str, bytes: &[u8]) -> Result<(), SandboxBuildError> {
|
||||
pub(super) fn validate_compiled_terminfo(
|
||||
term: &str,
|
||||
bytes: &[u8],
|
||||
) -> Result<(), SandboxBuildError> {
|
||||
if bytes.len() < 12 || bytes.len() > MAX_TERMINFO_BYTES {
|
||||
return Err(invalid_terminfo("compiled entry has an invalid size"));
|
||||
}
|
||||
|
|
@ -113,7 +124,7 @@ fn invalid_terminfo(detail: &str) -> SandboxBuildError {
|
|||
SandboxBuildError::new(SandboxBuildErrorKind::InvalidTerminal, detail)
|
||||
}
|
||||
|
||||
fn open_or_create_staging_directory(
|
||||
pub(super) fn open_or_create_staging_directory(
|
||||
parent: OwnedFd,
|
||||
name: &str,
|
||||
) -> Result<OwnedFd, SandboxBuildError> {
|
||||
|
|
@ -142,7 +153,7 @@ fn open_or_create_staging_directory(
|
|||
Ok(directory)
|
||||
}
|
||||
|
||||
fn stage_exact_file(
|
||||
pub(super) fn stage_exact_file(
|
||||
directory: &OwnedFd,
|
||||
name: &str,
|
||||
bytes: &[u8],
|
||||
|
|
@ -197,7 +208,7 @@ fn open_staged_file(directory: &OwnedFd, name: &str) -> rustix::io::Result<Owned
|
|||
)
|
||||
}
|
||||
|
||||
fn require_staged_file(descriptor: &OwnedFd) -> Result<(), SandboxBuildError> {
|
||||
pub(super) fn require_staged_file(descriptor: &OwnedFd) -> Result<(), SandboxBuildError> {
|
||||
let stat = rustix::fs::fstat(descriptor).map_err(staging_error)?;
|
||||
if !FileType::from_raw_mode(stat.st_mode).is_file()
|
||||
|| stat.st_uid != rustix::process::getuid().as_raw()
|
||||
|
|
@ -212,7 +223,7 @@ fn require_staged_file(descriptor: &OwnedFd) -> Result<(), SandboxBuildError> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn read_descriptor(descriptor: OwnedFd) -> Result<Vec<u8>, SandboxBuildError> {
|
||||
pub(super) fn read_descriptor(descriptor: OwnedFd) -> Result<Vec<u8>, SandboxBuildError> {
|
||||
let mut bytes = Vec::new();
|
||||
File::from(descriptor)
|
||||
.take((MAX_TERMINFO_BYTES + 1) as u64)
|
||||
|
|
@ -224,18 +235,18 @@ fn read_descriptor(descriptor: OwnedFd) -> Result<Vec<u8>, SandboxBuildError> {
|
|||
Ok(bytes)
|
||||
}
|
||||
|
||||
fn confined_resolve_flags() -> ResolveFlags {
|
||||
pub(super) fn confined_resolve_flags() -> ResolveFlags {
|
||||
ResolveFlags::BENEATH
|
||||
| ResolveFlags::NO_MAGICLINKS
|
||||
| ResolveFlags::NO_SYMLINKS
|
||||
| ResolveFlags::NO_XDEV
|
||||
}
|
||||
|
||||
fn staging_error(error: impl fmt::Display) -> SandboxBuildError {
|
||||
pub(super) fn staging_error(error: impl fmt::Display) -> SandboxBuildError {
|
||||
SandboxBuildError::new(SandboxBuildErrorKind::ChangedAuthority, error.to_string())
|
||||
}
|
||||
|
||||
fn validate_absolute_normalized(path: &Path) -> Result<(), SandboxBuildError> {
|
||||
pub(super) fn validate_absolute_normalized(path: &Path) -> Result<(), SandboxBuildError> {
|
||||
if !path.is_absolute()
|
||||
|| path.as_os_str().as_bytes().contains(&0)
|
||||
|| path
|
||||
|
|
@ -260,13 +271,15 @@ fn utf8_path(path: &Path) -> Result<&str, SandboxBuildError> {
|
|||
})
|
||||
}
|
||||
|
||||
fn host_path(path: &lazaret_core::identity::HostPath) -> Result<PathBuf, SandboxBuildError> {
|
||||
pub(super) fn host_path(
|
||||
path: &lazaret_core::identity::HostPath,
|
||||
) -> Result<PathBuf, SandboxBuildError> {
|
||||
let path = PathBuf::from(OsString::from_vec(path.as_bytes().to_vec()));
|
||||
validate_absolute_normalized(&path)?;
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn hex_lower(bytes: &[u8]) -> String {
|
||||
pub(super) fn hex_lower(bytes: &[u8]) -> String {
|
||||
let mut output = String::with_capacity(bytes.len() * 2);
|
||||
for byte in bytes {
|
||||
use fmt::Write as _;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,14 @@ use crate::network::{
|
|||
};
|
||||
use crate::sandbox::RunscCommand;
|
||||
|
||||
use handoff::{build_runtime_handoff, parse_runtime_handoff};
|
||||
use runtime::{
|
||||
attached_runsc_standard_io, fail_network, fail_runtime_components, fail_with_runtime_cleanup,
|
||||
receive_message, require_lifetime_held, runsc_delete_target, runsc_target, send_message,
|
||||
supervise, wait_and_reap, wait_for_runsc_ready,
|
||||
};
|
||||
use startup::{RuntimeGitRouting, inherited_directory_at, run_supervisor};
|
||||
|
||||
pub const PRIVATE_RUNTIME_ROLE: &str = "--lazaret-runtime-supervisor-role";
|
||||
const MAX_CONTROL_MESSAGE: usize = 16 * 1024;
|
||||
const MAX_RUNTIME_HANDOFF_BYTES: usize = 64 * 1024;
|
||||
|
|
@ -605,7 +613,9 @@ struct RuntimeNetworkArguments {
|
|||
network_sidecar_id: NetworkSidecarId,
|
||||
}
|
||||
|
||||
include!("supervision/handoff.rs");
|
||||
include!("supervision/startup.rs");
|
||||
include!("supervision/runtime.rs");
|
||||
include!("supervision/tests.rs");
|
||||
mod handoff;
|
||||
mod runtime;
|
||||
mod startup;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
use super::{
|
||||
AsRawFd, ConfigurationFingerprint, CoordinatorId, DerivedPolicyDigest, Deserialize, DomainId,
|
||||
File, FileType, GenerationId, HostProcessOwnershipSlot, MAX_RUNTIME_HANDOFF_BYTES,
|
||||
NetworkSidecarId, OsStr, OsStrExt, OsString, OsStringExt, PathBuf, ProcessOwnershipContext,
|
||||
Read, RuntimeArguments, RuntimeGitRoutingArguments, RuntimeGitRoutingLaunch,
|
||||
RuntimeNetworkArguments, RuntimeStateAnchorArguments, RuntimeSupervisorLaunch, Serialize,
|
||||
StateAnchorLaunchAuthority, decode, encode, fstat, io, require_write_sealed,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
enum RuntimeDescriptorKindV1 {
|
||||
Directory,
|
||||
|
|
@ -72,10 +81,7 @@ impl lazaret_core::records::SchemaType for RuntimeSupervisorHandoff {
|
|||
const NAME: &'static str = "RuntimeSupervisorHandoff";
|
||||
}
|
||||
|
||||
fn descriptor(
|
||||
number: i32,
|
||||
kind: RuntimeDescriptorKindV1,
|
||||
) -> io::Result<RuntimeDescriptorV1> {
|
||||
fn descriptor(number: i32, kind: RuntimeDescriptorKindV1) -> io::Result<RuntimeDescriptorV1> {
|
||||
if number < 0 {
|
||||
return Err(io::Error::other("Runtime Supervisor descriptor is invalid"));
|
||||
}
|
||||
|
|
@ -99,7 +105,7 @@ fn descriptor(
|
|||
})
|
||||
}
|
||||
|
||||
fn build_runtime_handoff(launch: &RuntimeSupervisorLaunch) -> io::Result<Vec<u8>> {
|
||||
pub(super) fn build_runtime_handoff(launch: &RuntimeSupervisorLaunch) -> io::Result<Vec<u8>> {
|
||||
let state_anchor = launch
|
||||
.state_anchor
|
||||
.as_ref()
|
||||
|
|
@ -128,9 +134,7 @@ fn build_runtime_handoff(launch: &RuntimeSupervisorLaunch) -> io::Result<Vec<u8>
|
|||
});
|
||||
let encoded = encode(&handoff).map_err(io::Error::other)?;
|
||||
if encoded.len() > MAX_RUNTIME_HANDOFF_BYTES {
|
||||
return Err(io::Error::other(
|
||||
"Runtime Supervisor handoff is oversized",
|
||||
));
|
||||
return Err(io::Error::other("Runtime Supervisor handoff is oversized"));
|
||||
}
|
||||
Ok(encoded)
|
||||
}
|
||||
|
|
@ -139,10 +143,7 @@ fn build_state_anchor_handoff(
|
|||
authority: &StateAnchorLaunchAuthority,
|
||||
) -> io::Result<RuntimeStateAnchorHandoffV1> {
|
||||
Ok(RuntimeStateAnchorHandoffV1 {
|
||||
lower: descriptor(
|
||||
authority.lower_fd(),
|
||||
RuntimeDescriptorKindV1::Directory,
|
||||
)?,
|
||||
lower: descriptor(authority.lower_fd(), RuntimeDescriptorKindV1::Directory)?,
|
||||
lower_label: authority.lower_label().as_os_str().as_bytes().to_vec(),
|
||||
persistent_root: descriptor(
|
||||
authority.persistent_root_fd(),
|
||||
|
|
@ -236,10 +237,7 @@ fn build_network_handoff(
|
|||
.as_ref()
|
||||
.map(|policy| -> io::Result<RuntimeNetworkHandoffV1> {
|
||||
Ok(RuntimeNetworkHandoffV1 {
|
||||
policy: descriptor(
|
||||
policy.as_raw_fd(),
|
||||
RuntimeDescriptorKindV1::RegularFile,
|
||||
)?,
|
||||
policy: descriptor(policy.as_raw_fd(), RuntimeDescriptorKindV1::RegularFile)?,
|
||||
policy_digest: launch
|
||||
.policy_digest
|
||||
.ok_or_else(|| io::Error::other("network policy digest is missing"))?,
|
||||
|
|
@ -264,7 +262,7 @@ fn build_network_handoff(
|
|||
Ok(network)
|
||||
}
|
||||
|
||||
fn parse_runtime_handoff(arguments: &[OsString]) -> io::Result<RuntimeArguments> {
|
||||
pub(super) fn parse_runtime_handoff(arguments: &[OsString]) -> io::Result<RuntimeArguments> {
|
||||
let [descriptor] = arguments else {
|
||||
return Err(io::Error::other(
|
||||
"Runtime Supervisor requires one handoff descriptor",
|
||||
|
|
@ -289,12 +287,10 @@ fn parse_runtime_handoff(arguments: &[OsString]) -> io::Result<RuntimeArguments>
|
|||
.runsc_arguments
|
||||
.iter()
|
||||
.any(|argument| argument.as_slice() == b"run")
|
||||
|| handoff.runsc_arguments.iter().any(|argument| {
|
||||
matches!(
|
||||
argument.as_slice(),
|
||||
b"-detach" | b"--detach" | b"-d"
|
||||
)
|
||||
})
|
||||
|| handoff
|
||||
.runsc_arguments
|
||||
.iter()
|
||||
.any(|argument| matches!(argument.as_slice(), b"-detach" | b"--detach" | b"-d"))
|
||||
{
|
||||
return Err(io::Error::other(
|
||||
"Runtime Supervisor requires foreground attached runsc",
|
||||
|
|
@ -397,9 +393,7 @@ fn runtime_state_anchor_arguments(
|
|||
lower_label: PathBuf::from(OsString::from_vec(state.lower_label)),
|
||||
persistent_root_fd: state.persistent_root.number,
|
||||
persistent_anchor_fd: state.persistent_anchor.number,
|
||||
persistent_anchor_label: PathBuf::from(OsString::from_vec(
|
||||
state.persistent_anchor_label,
|
||||
)),
|
||||
persistent_anchor_label: PathBuf::from(OsString::from_vec(state.persistent_anchor_label)),
|
||||
runtime_root_fd: state.runtime_root.number,
|
||||
runtime_anchor_fd: state.runtime_anchor.number,
|
||||
runtime_anchor_label: PathBuf::from(OsString::from_vec(state.runtime_anchor_label)),
|
||||
|
|
@ -442,6 +436,10 @@ mod handoff_tests {
|
|||
use rustix::fs::{MemfdFlags, memfd_create};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::sandbox::RunscCommand;
|
||||
use lazaret_linux::filesystem::sealed_memfd;
|
||||
use lazaret_linux::process::TargetCommand;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn minimal_handoff(arguments: Vec<Vec<u8>>) -> RuntimeSupervisorHandoff {
|
||||
|
|
@ -529,8 +527,8 @@ mod handoff_tests {
|
|||
assert_eq!(
|
||||
Sha256::digest(&encoded).as_slice(),
|
||||
[
|
||||
123, 0, 18, 227, 164, 11, 96, 15, 4, 201, 99, 25, 198, 122, 175, 149, 170,
|
||||
165, 12, 40, 209, 64, 119, 67, 10, 109, 128, 44, 201, 103, 14, 182,
|
||||
123, 0, 18, 227, 164, 11, 96, 15, 4, 201, 99, 25, 198, 122, 175, 149, 170, 165, 12,
|
||||
40, 209, 64, 119, 67, 10, 109, 128, 44, 201, 103, 14, 182,
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
|
|
@ -581,8 +579,7 @@ mod handoff_tests {
|
|||
let second_tree = tempfile::tempdir().unwrap();
|
||||
let first = File::open(first_tree.path()).unwrap();
|
||||
let second = File::open(second_tree.path()).unwrap();
|
||||
let first_slot =
|
||||
descriptor(first.as_raw_fd(), RuntimeDescriptorKindV1::Directory).unwrap();
|
||||
let first_slot = descriptor(first.as_raw_fd(), RuntimeDescriptorKindV1::Directory).unwrap();
|
||||
let second_slot =
|
||||
descriptor(second.as_raw_fd(), RuntimeDescriptorKindV1::Directory).unwrap();
|
||||
|
||||
|
|
@ -592,10 +589,7 @@ mod handoff_tests {
|
|||
|
||||
let mut identity_drift = state_record(first_slot);
|
||||
identity_drift.lower.inode = identity_drift.lower.inode.wrapping_add(1);
|
||||
identity_drift.expected_lower = (
|
||||
identity_drift.lower.device,
|
||||
identity_drift.lower.inode,
|
||||
);
|
||||
identity_drift.expected_lower = (identity_drift.lower.device, identity_drift.lower.inode);
|
||||
assert!(parse_record(&with_state(identity_drift)).is_err());
|
||||
|
||||
let mut substitution = state_record(first_slot);
|
||||
|
|
@ -631,9 +625,7 @@ mod handoff_tests {
|
|||
};
|
||||
assert!(build_runtime_handoff(&launch).is_err());
|
||||
assert!(parse_runtime_handoff(&[]).is_err());
|
||||
assert!(
|
||||
parse_runtime_handoff(&[OsString::from("1"), OsString::from("2")]).is_err()
|
||||
);
|
||||
assert!(parse_runtime_handoff(&[OsString::from("1"), OsString::from("2")]).is_err());
|
||||
}
|
||||
|
||||
fn state_record(slot: RuntimeDescriptorV1) -> RuntimeStateAnchorHandoffV1 {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,16 @@
|
|||
fn runsc_target(arguments: &RuntimeArguments, pid_file: i32) -> io::Result<TargetCommand> {
|
||||
use super::{
|
||||
AsFd, ChildStandardIo, CoordinatorMessage, Duration, File, FileExt, Instant,
|
||||
MAX_CONTROL_MESSAGE, NamespaceIdentity, ParentEdge, PollFd, PollFlags, PreparedNetwork,
|
||||
READINESS_TIMEOUT, RetainedProcess, RuntimeArguments, RuntimeGitRouting,
|
||||
RuntimeStateAnchorAbsenceAuthority, RuntimeStateAnchorMount, Serialize, Signal,
|
||||
SupervisorEvent, TERMINATION_GRACE, TargetCommand, Timespec, child_broker, decode, encode,
|
||||
inherited_directory_at, io, mount_identity, poll, read, write,
|
||||
};
|
||||
|
||||
pub(super) fn runsc_target(
|
||||
arguments: &RuntimeArguments,
|
||||
pid_file: i32,
|
||||
) -> io::Result<TargetCommand> {
|
||||
let run = arguments
|
||||
.runsc_arguments
|
||||
.iter()
|
||||
|
|
@ -15,7 +27,7 @@ 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> {
|
||||
pub(super) fn runsc_delete_target(arguments: &RuntimeArguments) -> io::Result<TargetCommand> {
|
||||
let run = arguments
|
||||
.runsc_arguments
|
||||
.iter()
|
||||
|
|
@ -38,7 +50,7 @@ fn runsc_delete_target(arguments: &RuntimeArguments) -> io::Result<TargetCommand
|
|||
.argument(container))
|
||||
}
|
||||
|
||||
fn attached_runsc_standard_io(target: &TargetCommand) -> io::Result<ChildStandardIo> {
|
||||
pub(super) fn attached_runsc_standard_io(target: &TargetCommand) -> io::Result<ChildStandardIo> {
|
||||
if target.parent_edge() != ParentEdge::ParentDeathKill {
|
||||
return Err(io::Error::other(
|
||||
"attached runsc requires the parent-death KILL edge",
|
||||
|
|
@ -47,7 +59,7 @@ fn attached_runsc_standard_io(target: &TargetCommand) -> io::Result<ChildStandar
|
|||
Ok(ChildStandardIo::silenced())
|
||||
}
|
||||
|
||||
fn wait_for_runsc_ready(pid_file: &File, process: &RetainedProcess) -> io::Result<u32> {
|
||||
pub(super) fn wait_for_runsc_ready(pid_file: &File, process: &RetainedProcess) -> io::Result<u32> {
|
||||
let deadline = Instant::now() + READINESS_TIMEOUT;
|
||||
let mut bytes = [0_u8; 32];
|
||||
loop {
|
||||
|
|
@ -72,7 +84,7 @@ fn wait_for_runsc_ready(pid_file: &File, process: &RetainedProcess) -> io::Resul
|
|||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
|
||||
fn supervise(
|
||||
pub(super) fn supervise(
|
||||
control: &std::io::Stdout,
|
||||
process: RetainedProcess,
|
||||
mut git_routing: Option<RuntimeGitRouting>,
|
||||
|
|
@ -376,7 +388,7 @@ fn terminate_and_reap(process: RetainedProcess) -> io::Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
fn fail_with_runtime_cleanup(
|
||||
pub(super) fn fail_with_runtime_cleanup(
|
||||
process: RetainedProcess,
|
||||
git_routing: Option<RuntimeGitRouting>,
|
||||
network: Option<PreparedNetwork>,
|
||||
|
|
@ -427,7 +439,7 @@ fn cleanup_stopped_runtime(
|
|||
}
|
||||
}
|
||||
|
||||
fn fail_runtime_components<T>(
|
||||
pub(super) fn fail_runtime_components<T>(
|
||||
git_routing: Option<RuntimeGitRouting>,
|
||||
network: Option<PreparedNetwork>,
|
||||
error: io::Error,
|
||||
|
|
@ -494,7 +506,7 @@ fn stop_git_routing(routing: Option<RuntimeGitRouting>) -> io::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn wait_and_reap(process: RetainedProcess) -> io::Result<()> {
|
||||
pub(super) fn wait_and_reap(process: RetainedProcess) -> io::Result<()> {
|
||||
let deadline = Instant::now() + TERMINATION_GRACE;
|
||||
while !process.has_exited()? && Instant::now() < deadline {
|
||||
std::thread::sleep(Duration::from_millis(5));
|
||||
|
|
@ -509,7 +521,7 @@ fn wait_and_reap(process: RetainedProcess) -> io::Result<()> {
|
|||
child_broker().reap(process)
|
||||
}
|
||||
|
||||
fn fail_network<T>(network: Option<PreparedNetwork>, error: io::Error) -> io::Result<T> {
|
||||
pub(super) fn fail_network<T>(network: Option<PreparedNetwork>, error: io::Error) -> io::Result<T> {
|
||||
match stop_network(network) {
|
||||
Ok(()) => Err(error),
|
||||
Err(cleanup) => Err(io::Error::new(
|
||||
|
|
@ -526,7 +538,7 @@ fn stop_network(network: Option<PreparedNetwork>) -> io::Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
fn require_lifetime_held() -> io::Result<()> {
|
||||
pub(super) fn require_lifetime_held() -> io::Result<()> {
|
||||
if lifetime_lost()? {
|
||||
Err(io::Error::other("Host Coordinator lifetime lease is lost"))
|
||||
} else {
|
||||
|
|
@ -543,7 +555,7 @@ fn lifetime_lost() -> io::Result<bool> {
|
|||
.intersects(PollFlags::IN | PollFlags::HUP | PollFlags::ERR))
|
||||
}
|
||||
|
||||
fn send_message<T: lazaret_core::records::SchemaType + Serialize>(
|
||||
pub(super) fn send_message<T: lazaret_core::records::SchemaType + Serialize>(
|
||||
descriptor: impl AsFd,
|
||||
message: &T,
|
||||
) -> io::Result<()> {
|
||||
|
|
@ -558,7 +570,7 @@ fn send_message<T: lazaret_core::records::SchemaType + Serialize>(
|
|||
send_all(descriptor.as_fd(), &encoded)
|
||||
}
|
||||
|
||||
fn receive_message<T>(descriptor: impl AsFd) -> io::Result<T>
|
||||
pub(super) fn receive_message<T>(descriptor: impl AsFd) -> io::Result<T>
|
||||
where
|
||||
T: lazaret_core::records::SchemaType + serde::de::DeserializeOwned + Serialize,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,20 @@
|
|||
fn run_supervisor(arguments: &RuntimeArguments) -> io::Result<()> {
|
||||
use super::{
|
||||
AsFd, AsRawFd, CapabilitySet, CoordinatorMessage, Duration, FdFlags, File, FileType, GitSentry,
|
||||
HostProcessOwnershipSlot, Instant, MemfdFlags, Mode, MountIdentity, NamespaceIdentity,
|
||||
NetworkAttestationContext, OFlags, PRIVATE_ROUTER_ROLE, PathBuf, PipeFlags, PreparedNetwork,
|
||||
ProcessOwnershipContext, READINESS_TIMEOUT, Read, ResolveFlags, RetainedProcess,
|
||||
RuntimeArguments, RuntimeGitRoutingArguments, RuntimeNetworkArguments,
|
||||
RuntimeStateAnchorAbsenceAuthority, RuntimeStateAnchorAuthority, RuntimeStateAnchorInherited,
|
||||
RuntimeStateAnchorMount, StagedProcessOwnership, SupervisorEvent, TargetCommand,
|
||||
attached_runsc_standard_io, capability, child_broker, current_process_identity,
|
||||
enter_generation_filesystem_namespaces, enter_generation_network_namespace, fail_network,
|
||||
fail_runtime_components, fail_with_runtime_cleanup, fcntl_setfd, fstat, io, memfd_create,
|
||||
mount_identity, openat2, pipe_with, prepare_network, probe_current_network, receive_message,
|
||||
require_lifetime_held, runsc_delete_target, runsc_target, send_message, supervise,
|
||||
wait_and_reap, wait_for_runsc_ready,
|
||||
};
|
||||
|
||||
pub(super) fn run_supervisor(arguments: &RuntimeArguments) -> io::Result<()> {
|
||||
require_lifetime_held()?;
|
||||
let namespaces = enter_generation_filesystem_namespaces()?;
|
||||
if arguments.network.is_none() {
|
||||
|
|
@ -223,12 +239,12 @@ fn run_supervisor_with_state(
|
|||
)
|
||||
}
|
||||
|
||||
struct RuntimeGitRouting {
|
||||
process: RetainedProcess,
|
||||
shutdown: File,
|
||||
sentry: GitSentry,
|
||||
mountpoint: PathBuf,
|
||||
mount: MountIdentity,
|
||||
pub(super) struct RuntimeGitRouting {
|
||||
pub(super) process: RetainedProcess,
|
||||
pub(super) shutdown: File,
|
||||
pub(super) sentry: GitSentry,
|
||||
pub(super) mountpoint: PathBuf,
|
||||
pub(super) mount: MountIdentity,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
|
|
@ -523,7 +539,7 @@ fn reopen_exact(
|
|||
Ok(reopened)
|
||||
}
|
||||
|
||||
fn inherited_directory_at(path: impl AsRef<std::path::Path>) -> io::Result<File> {
|
||||
pub(super) fn inherited_directory_at(path: impl AsRef<std::path::Path>) -> io::Result<File> {
|
||||
let file = File::open(path)?;
|
||||
if !FileType::from_raw_mode(fstat(&file)?.st_mode).is_dir() {
|
||||
return Err(io::Error::other(
|
||||
|
|
@ -650,4 +666,3 @@ fn prepare_runtime_network(
|
|||
}
|
||||
Ok(prepared)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,235 +1,232 @@
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ffi::OsString;
|
||||
use std::ffi::OsString;
|
||||
|
||||
use lazaret_core::identity::{CoordinatorId, DomainId, GenerationId};
|
||||
use lazaret_core::records::HostProcessOwnershipSlot;
|
||||
use lazaret_linux::ipc::private_seqpacket_pair;
|
||||
use lazaret_linux::process::ChildStandardIoPolicy;
|
||||
use rustix::pipe::{PipeFlags, pipe_with};
|
||||
use sha2::{Digest, Sha256};
|
||||
use lazaret_core::identity::{CoordinatorId, DomainId, GenerationId};
|
||||
use lazaret_core::records::HostProcessOwnershipSlot;
|
||||
use lazaret_linux::ipc::private_seqpacket_pair;
|
||||
use lazaret_linux::process::ChildStandardIoPolicy;
|
||||
use rustix::pipe::{PipeFlags, pipe_with};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use super::*;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn private_control_record_vectors_are_stable() {
|
||||
let namespace = NamespaceIdentity {
|
||||
device: lazaret_core::identity::DeviceId::new(1),
|
||||
inode: lazaret_core::identity::InodeId::new(2),
|
||||
};
|
||||
let mount = MountIdentity {
|
||||
mount_id: lazaret_core::identity::MountId::new(3),
|
||||
device: lazaret_core::identity::DeviceId::new(4),
|
||||
inode: lazaret_core::identity::InodeId::new(5),
|
||||
};
|
||||
let records = [
|
||||
encode(&AttachedRunscSignal::Continue).unwrap(),
|
||||
encode(&SupervisorEvent::StateAnchorAbsenceObserved {
|
||||
user_namespace: namespace,
|
||||
mount_namespace: namespace,
|
||||
mount,
|
||||
observed: mount,
|
||||
})
|
||||
.unwrap(),
|
||||
encode(&CoordinatorMessage::ObserveStateAnchorAbsence {
|
||||
user_namespace: namespace,
|
||||
mount_namespace: namespace,
|
||||
mount,
|
||||
})
|
||||
.unwrap(),
|
||||
];
|
||||
let mut corpus = Sha256::new();
|
||||
for record in records {
|
||||
corpus.update(record.len().to_be_bytes());
|
||||
corpus.update(record);
|
||||
}
|
||||
assert_eq!(
|
||||
corpus.finalize().as_slice(),
|
||||
[
|
||||
59, 166, 131, 49, 39, 47, 193, 4, 70, 106, 107, 188, 204, 94, 145, 7, 250,
|
||||
164, 101, 22, 121, 174, 24, 13, 138, 108, 62, 97, 17, 162, 129, 89,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_stream_framing_round_trips_typed_events() {
|
||||
let (coordinator, supervisor) = private_seqpacket_pair().unwrap();
|
||||
let expected = SupervisorEvent::Refused("refused".into());
|
||||
send_message(&supervisor, &expected).unwrap();
|
||||
assert_eq!(
|
||||
CoordinatorChannel::new(coordinator).receive().unwrap(),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coordinator_channel_preserves_phased_resource_commands() {
|
||||
let (coordinator, supervisor) = private_seqpacket_pair().unwrap();
|
||||
let channel = CoordinatorChannel::new(coordinator);
|
||||
channel.mount_state_anchor().unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::MountStateAnchor
|
||||
);
|
||||
channel.start_git_routing().unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::StartGitRouting
|
||||
);
|
||||
channel.start_sidecar().unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::StartSidecar
|
||||
);
|
||||
channel.stop_sandbox().unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::StopSandbox
|
||||
);
|
||||
channel.stop_git_routing().unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::StopGitRouting
|
||||
);
|
||||
channel.stop_sidecar().unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::StopSidecar
|
||||
);
|
||||
channel.unmount_state_anchor().unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::UnmountStateAnchor
|
||||
);
|
||||
let user_namespace = NamespaceIdentity {
|
||||
device: lazaret_core::identity::DeviceId::new(11),
|
||||
inode: lazaret_core::identity::InodeId::new(12),
|
||||
};
|
||||
let mount_namespace = NamespaceIdentity {
|
||||
device: lazaret_core::identity::DeviceId::new(13),
|
||||
inode: lazaret_core::identity::InodeId::new(14),
|
||||
};
|
||||
let mount = MountIdentity {
|
||||
mount_id: lazaret_core::identity::MountId::new(15),
|
||||
device: lazaret_core::identity::DeviceId::new(16),
|
||||
inode: lazaret_core::identity::InodeId::new(17),
|
||||
};
|
||||
channel
|
||||
.observe_state_anchor_absence(user_namespace, mount_namespace, mount)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::ObserveStateAnchorAbsence {
|
||||
user_namespace,
|
||||
mount_namespace,
|
||||
mount,
|
||||
}
|
||||
);
|
||||
channel.exit().unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::Exit
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_supervisor_and_runsc_keep_distinct_edge_and_stdio_policies() {
|
||||
let ownership = ProcessOwnershipContext {
|
||||
domain_id: DomainId::new([1; 32]),
|
||||
generation_id: GenerationId::new(7),
|
||||
coordinator_id: CoordinatorId::new([2; 32]),
|
||||
slot: HostProcessOwnershipSlot::SandboxRuntimeSupervisor,
|
||||
};
|
||||
let launch = RuntimeSupervisorLaunch {
|
||||
target: TargetCommand::new("/proc/self/exe").with_lifetime_pipe_parent_edge(),
|
||||
handoff_file: None,
|
||||
ownership,
|
||||
policy_file: None,
|
||||
policy_digest: None,
|
||||
configuration_fingerprint: None,
|
||||
runsc_command: RunscCommand::new(
|
||||
"runsc",
|
||||
[OsString::from("run"), OsString::from("container")],
|
||||
[0; 32],
|
||||
),
|
||||
network_sidecar_id: None,
|
||||
state_anchor: None,
|
||||
git_routing: None,
|
||||
};
|
||||
let (_coordinator_control, supervisor_control) = private_seqpacket_pair().unwrap();
|
||||
let (supervisor_lifetime, _coordinator_lifetime) = pipe_with(PipeFlags::CLOEXEC).unwrap();
|
||||
assert_eq!(launch.target().parent_edge(), ParentEdge::LifetimePipe);
|
||||
assert_eq!(
|
||||
launch
|
||||
.child_standard_io(supervisor_control, supervisor_lifetime)
|
||||
.unwrap()
|
||||
.policy(),
|
||||
ChildStandardIoPolicy::PrivateControlWithLifetimePipe
|
||||
);
|
||||
|
||||
let runsc_arguments = RuntimeArguments {
|
||||
ownership: ProcessOwnershipContext {
|
||||
slot: HostProcessOwnershipSlot::SandboxAttachedRunsc,
|
||||
..ownership
|
||||
},
|
||||
state_anchor: None,
|
||||
git_routing: None,
|
||||
network: None,
|
||||
runsc_program: "runsc".into(),
|
||||
runsc_arguments: ["-root=/runtime", "run", "container"]
|
||||
.map(OsString::from)
|
||||
.into(),
|
||||
};
|
||||
let runsc = runsc_target(&runsc_arguments, 9).unwrap();
|
||||
assert_eq!(runsc.parent_edge(), ParentEdge::ParentDeathKill);
|
||||
assert_eq!(
|
||||
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]
|
||||
fn runtime_handoff_preserves_exact_ownership_and_runsc_arguments() {
|
||||
let ownership = ProcessOwnershipContext {
|
||||
domain_id: DomainId::new([1; 32]),
|
||||
generation_id: GenerationId::new(7),
|
||||
coordinator_id: CoordinatorId::new([2; 32]),
|
||||
slot: HostProcessOwnershipSlot::SandboxRuntimeSupervisor,
|
||||
};
|
||||
let launch = RuntimeSupervisorLaunch::from_runsc_command(
|
||||
ownership,
|
||||
&RunscCommand::new(
|
||||
"runsc",
|
||||
[
|
||||
OsString::from("-root=/runtime"),
|
||||
OsString::from("run"),
|
||||
OsString::from("container"),
|
||||
],
|
||||
[0; 32],
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
launch.target().arguments().first(),
|
||||
Some(&OsString::from(PRIVATE_RUNTIME_ROLE))
|
||||
);
|
||||
assert_eq!(launch.target().arguments().len(), 2);
|
||||
let parsed = parse_runtime_handoff(&launch.target().arguments()[1..]).unwrap();
|
||||
assert_eq!(parsed.ownership.domain_id, ownership.domain_id);
|
||||
assert_eq!(parsed.ownership.generation_id, ownership.generation_id);
|
||||
assert_eq!(parsed.ownership.coordinator_id, ownership.coordinator_id);
|
||||
assert_eq!(
|
||||
parsed.runsc_arguments,
|
||||
["-root=/runtime", "run", "container"].map(OsString::from)
|
||||
);
|
||||
#[test]
|
||||
fn private_control_record_vectors_are_stable() {
|
||||
let namespace = NamespaceIdentity {
|
||||
device: lazaret_core::identity::DeviceId::new(1),
|
||||
inode: lazaret_core::identity::InodeId::new(2),
|
||||
};
|
||||
let mount = MountIdentity {
|
||||
mount_id: lazaret_core::identity::MountId::new(3),
|
||||
device: lazaret_core::identity::DeviceId::new(4),
|
||||
inode: lazaret_core::identity::InodeId::new(5),
|
||||
};
|
||||
let records = [
|
||||
encode(&AttachedRunscSignal::Continue).unwrap(),
|
||||
encode(&SupervisorEvent::StateAnchorAbsenceObserved {
|
||||
user_namespace: namespace,
|
||||
mount_namespace: namespace,
|
||||
mount,
|
||||
observed: mount,
|
||||
})
|
||||
.unwrap(),
|
||||
encode(&CoordinatorMessage::ObserveStateAnchorAbsence {
|
||||
user_namespace: namespace,
|
||||
mount_namespace: namespace,
|
||||
mount,
|
||||
})
|
||||
.unwrap(),
|
||||
];
|
||||
let mut corpus = Sha256::new();
|
||||
for record in records {
|
||||
corpus.update(record.len().to_be_bytes());
|
||||
corpus.update(record);
|
||||
}
|
||||
assert_eq!(
|
||||
corpus.finalize().as_slice(),
|
||||
[
|
||||
59, 166, 131, 49, 39, 47, 193, 4, 70, 106, 107, 188, 204, 94, 145, 7, 250, 164, 101,
|
||||
22, 121, 174, 24, 13, 138, 108, 62, 97, 17, 162, 129, 89,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_stream_framing_round_trips_typed_events() {
|
||||
let (coordinator, supervisor) = private_seqpacket_pair().unwrap();
|
||||
let expected = SupervisorEvent::Refused("refused".into());
|
||||
send_message(&supervisor, &expected).unwrap();
|
||||
assert_eq!(
|
||||
CoordinatorChannel::new(coordinator).receive().unwrap(),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coordinator_channel_preserves_phased_resource_commands() {
|
||||
let (coordinator, supervisor) = private_seqpacket_pair().unwrap();
|
||||
let channel = CoordinatorChannel::new(coordinator);
|
||||
channel.mount_state_anchor().unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::MountStateAnchor
|
||||
);
|
||||
channel.start_git_routing().unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::StartGitRouting
|
||||
);
|
||||
channel.start_sidecar().unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::StartSidecar
|
||||
);
|
||||
channel.stop_sandbox().unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::StopSandbox
|
||||
);
|
||||
channel.stop_git_routing().unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::StopGitRouting
|
||||
);
|
||||
channel.stop_sidecar().unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::StopSidecar
|
||||
);
|
||||
channel.unmount_state_anchor().unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::UnmountStateAnchor
|
||||
);
|
||||
let user_namespace = NamespaceIdentity {
|
||||
device: lazaret_core::identity::DeviceId::new(11),
|
||||
inode: lazaret_core::identity::InodeId::new(12),
|
||||
};
|
||||
let mount_namespace = NamespaceIdentity {
|
||||
device: lazaret_core::identity::DeviceId::new(13),
|
||||
inode: lazaret_core::identity::InodeId::new(14),
|
||||
};
|
||||
let mount = MountIdentity {
|
||||
mount_id: lazaret_core::identity::MountId::new(15),
|
||||
device: lazaret_core::identity::DeviceId::new(16),
|
||||
inode: lazaret_core::identity::InodeId::new(17),
|
||||
};
|
||||
channel
|
||||
.observe_state_anchor_absence(user_namespace, mount_namespace, mount)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::ObserveStateAnchorAbsence {
|
||||
user_namespace,
|
||||
mount_namespace,
|
||||
mount,
|
||||
}
|
||||
);
|
||||
channel.exit().unwrap();
|
||||
assert_eq!(
|
||||
receive_message::<CoordinatorMessage>(&supervisor).unwrap(),
|
||||
CoordinatorMessage::Exit
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_supervisor_and_runsc_keep_distinct_edge_and_stdio_policies() {
|
||||
let ownership = ProcessOwnershipContext {
|
||||
domain_id: DomainId::new([1; 32]),
|
||||
generation_id: GenerationId::new(7),
|
||||
coordinator_id: CoordinatorId::new([2; 32]),
|
||||
slot: HostProcessOwnershipSlot::SandboxRuntimeSupervisor,
|
||||
};
|
||||
let launch = RuntimeSupervisorLaunch {
|
||||
target: TargetCommand::new("/proc/self/exe").with_lifetime_pipe_parent_edge(),
|
||||
handoff_file: None,
|
||||
ownership,
|
||||
policy_file: None,
|
||||
policy_digest: None,
|
||||
configuration_fingerprint: None,
|
||||
runsc_command: RunscCommand::new(
|
||||
"runsc",
|
||||
[OsString::from("run"), OsString::from("container")],
|
||||
[0; 32],
|
||||
),
|
||||
network_sidecar_id: None,
|
||||
state_anchor: None,
|
||||
git_routing: None,
|
||||
};
|
||||
let (_coordinator_control, supervisor_control) = private_seqpacket_pair().unwrap();
|
||||
let (supervisor_lifetime, _coordinator_lifetime) = pipe_with(PipeFlags::CLOEXEC).unwrap();
|
||||
assert_eq!(launch.target().parent_edge(), ParentEdge::LifetimePipe);
|
||||
assert_eq!(
|
||||
launch
|
||||
.child_standard_io(supervisor_control, supervisor_lifetime)
|
||||
.unwrap()
|
||||
.policy(),
|
||||
ChildStandardIoPolicy::PrivateControlWithLifetimePipe
|
||||
);
|
||||
|
||||
let runsc_arguments = RuntimeArguments {
|
||||
ownership: ProcessOwnershipContext {
|
||||
slot: HostProcessOwnershipSlot::SandboxAttachedRunsc,
|
||||
..ownership
|
||||
},
|
||||
state_anchor: None,
|
||||
git_routing: None,
|
||||
network: None,
|
||||
runsc_program: "runsc".into(),
|
||||
runsc_arguments: ["-root=/runtime", "run", "container"]
|
||||
.map(OsString::from)
|
||||
.into(),
|
||||
};
|
||||
let runsc = runsc_target(&runsc_arguments, 9).unwrap();
|
||||
assert_eq!(runsc.parent_edge(), ParentEdge::ParentDeathKill);
|
||||
assert_eq!(
|
||||
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]
|
||||
fn runtime_handoff_preserves_exact_ownership_and_runsc_arguments() {
|
||||
let ownership = ProcessOwnershipContext {
|
||||
domain_id: DomainId::new([1; 32]),
|
||||
generation_id: GenerationId::new(7),
|
||||
coordinator_id: CoordinatorId::new([2; 32]),
|
||||
slot: HostProcessOwnershipSlot::SandboxRuntimeSupervisor,
|
||||
};
|
||||
let launch = RuntimeSupervisorLaunch::from_runsc_command(
|
||||
ownership,
|
||||
&RunscCommand::new(
|
||||
"runsc",
|
||||
[
|
||||
OsString::from("-root=/runtime"),
|
||||
OsString::from("run"),
|
||||
OsString::from("container"),
|
||||
],
|
||||
[0; 32],
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
launch.target().arguments().first(),
|
||||
Some(&OsString::from(PRIVATE_RUNTIME_ROLE))
|
||||
);
|
||||
assert_eq!(launch.target().arguments().len(), 2);
|
||||
let parsed = parse_runtime_handoff(&launch.target().arguments()[1..]).unwrap();
|
||||
assert_eq!(parsed.ownership.domain_id, ownership.domain_id);
|
||||
assert_eq!(parsed.ownership.generation_id, ownership.generation_id);
|
||||
assert_eq!(parsed.ownership.coordinator_id, ownership.coordinator_id);
|
||||
assert_eq!(
|
||||
parsed.runsc_arguments,
|
||||
["-root=/runtime", "run", "container"].map(OsString::from)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue