Replace lexical Rust fragments with real modules
This commit is contained in:
parent
f8849cdb5f
commit
247819250b
78 changed files with 3302 additions and 2671 deletions
|
|
@ -506,6 +506,7 @@ mod tests {
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
use std::os::fd::AsRawFd;
|
||||||
use std::os::unix::ffi::OsStringExt;
|
use std::os::unix::ffi::OsStringExt;
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::os::unix::net::{UnixListener, UnixStream};
|
use std::os::unix::net::{UnixListener, UnixStream};
|
||||||
|
|
@ -517,11 +518,18 @@ mod tests {
|
||||||
};
|
};
|
||||||
use lazaret_core::records::{GenerationIdentity, ProtocolPublicationIdentity, ProtocolVersion};
|
use lazaret_core::records::{GenerationIdentity, ProtocolPublicationIdentity, ProtocolVersion};
|
||||||
|
|
||||||
|
fn test_artifact_root() -> std::path::PathBuf {
|
||||||
|
let root =
|
||||||
|
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../target/t/client");
|
||||||
|
fs::create_dir_all(&root).unwrap();
|
||||||
|
root.canonicalize().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn replaced_domain_roots_cannot_redirect_client_lock_or_control_authority() {
|
fn replaced_domain_roots_cannot_redirect_client_lock_or_control_authority() {
|
||||||
let persistent_tree = tempfile::Builder::new()
|
let persistent_tree = tempfile::Builder::new()
|
||||||
.prefix("lz-")
|
.prefix("lz-")
|
||||||
.tempdir_in("/tmp")
|
.tempdir_in(test_artifact_root())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let persistent_configuration = configuration(persistent_tree.path());
|
let persistent_configuration = configuration(persistent_tree.path());
|
||||||
let persistent_path = path(
|
let persistent_path = path(
|
||||||
|
|
@ -543,7 +551,7 @@ mod tests {
|
||||||
|
|
||||||
let runtime_tree = tempfile::Builder::new()
|
let runtime_tree = tempfile::Builder::new()
|
||||||
.prefix("lz-")
|
.prefix("lz-")
|
||||||
.tempdir_in("/tmp")
|
.tempdir_in(test_artifact_root())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let runtime_configuration = configuration(runtime_tree.path());
|
let runtime_configuration = configuration(runtime_tree.path());
|
||||||
let runtime_path = path(
|
let runtime_path = path(
|
||||||
|
|
@ -568,7 +576,7 @@ mod tests {
|
||||||
fn confined_control_discovery_connects_through_the_retained_root() {
|
fn confined_control_discovery_connects_through_the_retained_root() {
|
||||||
let tree = tempfile::Builder::new()
|
let tree = tempfile::Builder::new()
|
||||||
.prefix("lz-")
|
.prefix("lz-")
|
||||||
.tempdir_in("/tmp")
|
.tempdir_in(test_artifact_root())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let configuration = configuration(tree.path());
|
let configuration = configuration(tree.path());
|
||||||
let runtime_path = path(
|
let runtime_path = path(
|
||||||
|
|
@ -577,7 +585,9 @@ mod tests {
|
||||||
.canonical_label()
|
.canonical_label()
|
||||||
.as_bytes(),
|
.as_bytes(),
|
||||||
);
|
);
|
||||||
let listener = UnixListener::bind(runtime_path.join("c.sock")).unwrap();
|
let runtime = fs::File::open(&runtime_path).unwrap();
|
||||||
|
let socket_path = PathBuf::from(format!("/proc/self/fd/{}/c.sock", runtime.as_raw_fd()));
|
||||||
|
let listener = UnixListener::bind(&socket_path).unwrap();
|
||||||
fs::set_permissions(
|
fs::set_permissions(
|
||||||
runtime_path.join("c.sock"),
|
runtime_path.join("c.sock"),
|
||||||
fs::Permissions::from_mode(0o600),
|
fs::Permissions::from_mode(0o600),
|
||||||
|
|
@ -622,10 +632,10 @@ mod tests {
|
||||||
let (server, _) = listener.accept().unwrap();
|
let (server, _) = listener.accept().unwrap();
|
||||||
|
|
||||||
assert_eq!(observed, publication);
|
assert_eq!(observed, publication);
|
||||||
assert!(control_socket_is_live(&runtime_path.join("c.sock")));
|
assert!(control_socket_is_live(&socket_path));
|
||||||
let (probe, _) = listener.accept().unwrap();
|
let (probe, _) = listener.accept().unwrap();
|
||||||
drop((authority, client, server, probe, listener));
|
drop((authority, client, server, probe, listener));
|
||||||
assert!(!control_socket_is_live(&runtime_path.join("c.sock")));
|
assert!(!control_socket_is_live(&socket_path));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ use crate::records::{
|
||||||
ResolvedNetworkReadinessV1, ResolvedVolume, TRUSTED_HOST_CONFIGURATION_SCHEMA_ID,
|
ResolvedNetworkReadinessV1, ResolvedVolume, TRUSTED_HOST_CONFIGURATION_SCHEMA_ID,
|
||||||
TrustedHostConfigurationV1, VolumeAccess, VolumeKind,
|
TrustedHostConfigurationV1, VolumeAccess, VolumeKind,
|
||||||
};
|
};
|
||||||
|
use confined_io::{io_error, open_absolute_directory, validate_executable};
|
||||||
|
use validation::validate_sandbox_path;
|
||||||
|
|
||||||
pub const MAX_CONFIGURATION_BYTES: usize = 1_048_576;
|
pub const MAX_CONFIGURATION_BYTES: usize = 1_048_576;
|
||||||
|
|
||||||
|
|
@ -714,7 +716,12 @@ impl ResolvedDomainConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
include!("configuration/loading.rs");
|
mod confined_io;
|
||||||
include!("configuration/validation.rs");
|
mod loading;
|
||||||
include!("configuration/confined_io.rs");
|
mod validation;
|
||||||
include!("configuration/tests.rs");
|
|
||||||
|
pub use loading::{load_trusted_configuration, parse_trusted_configuration};
|
||||||
|
pub use validation::{derive_configuration_fingerprint, derive_domain_id};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,18 @@
|
||||||
fn validate_host_base(path: &Path, name: &str) -> Result<(), ConfigurationError> {
|
use super::validation::{
|
||||||
|
validate_declared_host_path, validate_environment_name, validate_sandbox_path,
|
||||||
|
};
|
||||||
|
use super::{
|
||||||
|
AsRawFd, BTreeMap, BTreeSet, ConfigurationError, ConfigurationErrorKind,
|
||||||
|
ConfigurationLoadRequest, ConfinedPathPlan, ConfinedPathType, EnvironmentReference,
|
||||||
|
EnvironmentValue, File, FileType, HostPath, Ipv4Prefix, MAX_CONFIGURATION_BYTES, Mode,
|
||||||
|
NetworkProbeTargetConfigurationV1, NetworkReadinessConfigurationV1, OFlags, OsStrExt, OsString,
|
||||||
|
OwnedFd, Path, PathBuf, ProfileConfigurationV1, Read, ResolveFlags, ResolvedAutoApprovalRule,
|
||||||
|
ResolvedDnsProbeTargetV1, ResolvedNetworkProbeTargetV1, ResolvedNetworkReadinessV1,
|
||||||
|
ResolvedVolume, Seek, SeekFrom, Stat, TrustedHostConfigurationV1, VolumeAccess, VolumeKind,
|
||||||
|
fmt, openat2,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(super) fn validate_host_base(path: &Path, name: &str) -> Result<(), ConfigurationError> {
|
||||||
if !path.is_absolute()
|
if !path.is_absolute()
|
||||||
|| path.as_os_str().is_empty()
|
|| path.as_os_str().is_empty()
|
||||||
|| path.as_os_str().as_bytes().contains(&0)
|
|| path.as_os_str().as_bytes().contains(&0)
|
||||||
|
|
@ -11,7 +25,7 @@ fn validate_host_base(path: &Path, name: &str) -> Result<(), ConfigurationError>
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config_home(request: &ConfigurationLoadRequest) -> PathBuf {
|
pub(super) fn config_home(request: &ConfigurationLoadRequest) -> PathBuf {
|
||||||
request
|
request
|
||||||
.xdg_config_home
|
.xdg_config_home
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -20,7 +34,7 @@ fn config_home(request: &ConfigurationLoadRequest) -> PathBuf {
|
||||||
.unwrap_or_else(|| request.home.join(".config"))
|
.unwrap_or_else(|| request.home.join(".config"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data_home(request: &ConfigurationLoadRequest) -> PathBuf {
|
pub(super) fn data_home(request: &ConfigurationLoadRequest) -> PathBuf {
|
||||||
request
|
request
|
||||||
.xdg_data_home
|
.xdg_data_home
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -29,7 +43,7 @@ fn data_home(request: &ConfigurationLoadRequest) -> PathBuf {
|
||||||
.unwrap_or_else(|| request.home.join(".local/share"))
|
.unwrap_or_else(|| request.home.join(".local/share"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expand_host_path(
|
pub(super) fn expand_host_path(
|
||||||
declaration: &str,
|
declaration: &str,
|
||||||
home: &Path,
|
home: &Path,
|
||||||
profile: Option<&Path>,
|
profile: Option<&Path>,
|
||||||
|
|
@ -49,7 +63,9 @@ fn expand_host_path(
|
||||||
Ok(PathBuf::from(declaration))
|
Ok(PathBuf::from(declaration))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_stable_configuration(plan: &ConfinedPathPlan) -> Result<Vec<u8>, ConfigurationError> {
|
pub(super) fn read_stable_configuration(
|
||||||
|
plan: &ConfinedPathPlan,
|
||||||
|
) -> Result<Vec<u8>, ConfigurationError> {
|
||||||
let path_descriptor = plan.open_authority()?;
|
let path_descriptor = plan.open_authority()?;
|
||||||
let before = rustix::fs::fstat(&path_descriptor).map_err(io_error)?;
|
let before = rustix::fs::fstat(&path_descriptor).map_err(io_error)?;
|
||||||
validate_configuration_stat(&before)?;
|
validate_configuration_stat(&before)?;
|
||||||
|
|
@ -93,7 +109,7 @@ fn read_stable_configuration(plan: &ConfinedPathPlan) -> Result<Vec<u8>, Configu
|
||||||
Ok(first)
|
Ok(first)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_configuration_stat(stat: &Stat) -> Result<(), ConfigurationError> {
|
pub(super) fn validate_configuration_stat(stat: &Stat) -> Result<(), ConfigurationError> {
|
||||||
let uid = rustix::process::getuid().as_raw();
|
let uid = rustix::process::getuid().as_raw();
|
||||||
if !FileType::from_raw_mode(stat.st_mode).is_file()
|
if !FileType::from_raw_mode(stat.st_mode).is_file()
|
||||||
|| stat.st_nlink != 1
|
|| stat.st_nlink != 1
|
||||||
|
|
@ -130,7 +146,7 @@ fn same_file_snapshot(left: &Stat, right: &Stat) -> bool {
|
||||||
&& left.st_ctime_nsec == right.st_ctime_nsec
|
&& left.st_ctime_nsec == right.st_ctime_nsec
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confined_existing(
|
pub(super) fn confined_existing(
|
||||||
canonical: &Path,
|
canonical: &Path,
|
||||||
expected: ConfinedPathType,
|
expected: ConfinedPathType,
|
||||||
executable: bool,
|
executable: bool,
|
||||||
|
|
@ -147,7 +163,7 @@ fn confined_existing(
|
||||||
Ok(plan)
|
Ok(plan)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_absolute_directory(path: &Path) -> Result<OwnedFd, ConfigurationError> {
|
pub(super) fn open_absolute_directory(path: &Path) -> Result<OwnedFd, ConfigurationError> {
|
||||||
openat2(
|
openat2(
|
||||||
rustix::fs::CWD,
|
rustix::fs::CWD,
|
||||||
path,
|
path,
|
||||||
|
|
@ -158,7 +174,7 @@ fn open_absolute_directory(path: &Path) -> Result<OwnedFd, ConfigurationError> {
|
||||||
.map_err(io_error)
|
.map_err(io_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_owned_directory(path: &Path, uid: u32) -> Result<PathBuf, ConfigurationError> {
|
pub(super) fn ensure_owned_directory(path: &Path, uid: u32) -> Result<PathBuf, ConfigurationError> {
|
||||||
validate_host_base(path, "Lazaret root")?;
|
validate_host_base(path, "Lazaret root")?;
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
let canonical = std::fs::canonicalize(path).map_err(io_error)?;
|
let canonical = std::fs::canonicalize(path).map_err(io_error)?;
|
||||||
|
|
@ -235,7 +251,7 @@ fn validate_owned_directory(descriptor: &OwnedFd, uid: u32) -> Result<(), Config
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_persistent_destinations(
|
pub(super) fn validate_persistent_destinations(
|
||||||
destinations: &[String],
|
destinations: &[String],
|
||||||
protected: &BTreeSet<String>,
|
protected: &BTreeSet<String>,
|
||||||
) -> Result<Vec<String>, ConfigurationError> {
|
) -> Result<Vec<String>, ConfigurationError> {
|
||||||
|
|
@ -250,7 +266,7 @@ fn validate_persistent_destinations(
|
||||||
Ok(destinations.to_vec())
|
Ok(destinations.to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn require_clear_of_protected(
|
pub(super) fn require_clear_of_protected(
|
||||||
destination: &str,
|
destination: &str,
|
||||||
protected: &BTreeSet<String>,
|
protected: &BTreeSet<String>,
|
||||||
) -> Result<(), ConfigurationError> {
|
) -> Result<(), ConfigurationError> {
|
||||||
|
|
@ -267,7 +283,7 @@ fn require_clear_of_protected(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn require_strict_child_of_one(
|
pub(super) fn require_strict_child_of_one(
|
||||||
destination: &str,
|
destination: &str,
|
||||||
parents: &[String],
|
parents: &[String],
|
||||||
) -> Result<(), ConfigurationError> {
|
) -> Result<(), ConfigurationError> {
|
||||||
|
|
@ -285,7 +301,7 @@ fn require_strict_child_of_one(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn require_pairwise_non_ancestral<'a>(
|
pub(super) fn require_pairwise_non_ancestral<'a>(
|
||||||
paths: impl IntoIterator<Item = &'a str>,
|
paths: impl IntoIterator<Item = &'a str>,
|
||||||
kind: ConfigurationErrorKind,
|
kind: ConfigurationErrorKind,
|
||||||
) -> Result<(), ConfigurationError> {
|
) -> Result<(), ConfigurationError> {
|
||||||
|
|
@ -319,7 +335,7 @@ fn sandbox_is_strict_descendant(child: &str, parent: &str) -> bool {
|
||||||
.is_some_and(|suffix| suffix.starts_with('/'))
|
.is_some_and(|suffix| suffix.starts_with('/'))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn require_pairwise_disjoint(
|
pub(super) fn require_pairwise_disjoint(
|
||||||
paths: &[&Path],
|
paths: &[&Path],
|
||||||
authorities: &[&ConfinedPathPlan],
|
authorities: &[&ConfinedPathPlan],
|
||||||
kind: ConfigurationErrorKind,
|
kind: ConfigurationErrorKind,
|
||||||
|
|
@ -343,15 +359,15 @@ fn require_pairwise_disjoint(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paths_intersect(left: &Path, right: &Path) -> bool {
|
pub(super) fn paths_intersect(left: &Path, right: &Path) -> bool {
|
||||||
left == right || left.starts_with(right) || right.starts_with(left)
|
left == right || left.starts_with(right) || right.starts_with(left)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_strict_descendant(child: &Path, parent: &Path) -> bool {
|
pub(super) fn is_strict_descendant(child: &Path, parent: &Path) -> bool {
|
||||||
child != parent && child.starts_with(parent)
|
child != parent && child.starts_with(parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_network(
|
pub(super) fn resolve_network(
|
||||||
configuration: &TrustedHostConfigurationV1,
|
configuration: &TrustedHostConfigurationV1,
|
||||||
selected: &ProfileConfigurationV1,
|
selected: &ProfileConfigurationV1,
|
||||||
) -> Result<(Vec<Ipv4Prefix>, Vec<Ipv4Prefix>), ConfigurationError> {
|
) -> Result<(Vec<Ipv4Prefix>, Vec<Ipv4Prefix>), ConfigurationError> {
|
||||||
|
|
@ -386,7 +402,7 @@ fn resolve_network(
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_READINESS_TARGETS: usize = 32;
|
const MAX_READINESS_TARGETS: usize = 32;
|
||||||
fn resolve_network_readiness(
|
pub(super) fn resolve_network_readiness(
|
||||||
readiness: &NetworkReadinessConfigurationV1,
|
readiness: &NetworkReadinessConfigurationV1,
|
||||||
allow: &[Ipv4Prefix],
|
allow: &[Ipv4Prefix],
|
||||||
deny: &[Ipv4Prefix],
|
deny: &[Ipv4Prefix],
|
||||||
|
|
@ -552,7 +568,7 @@ fn prefix_contains(prefix: Ipv4Prefix, address: [u8; 4]) -> bool {
|
||||||
u32::from_be_bytes(address) & mask == u32::from_be_bytes(prefix.address)
|
u32::from_be_bytes(address) & mask == u32::from_be_bytes(prefix.address)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_ipv4_prefix(value: &str) -> Result<Ipv4Prefix, ConfigurationError> {
|
pub(super) fn parse_ipv4_prefix(value: &str) -> Result<Ipv4Prefix, ConfigurationError> {
|
||||||
let (address, prefix) = value.split_once('/').ok_or_else(|| {
|
let (address, prefix) = value.split_once('/').ok_or_else(|| {
|
||||||
ConfigurationError::new(
|
ConfigurationError::new(
|
||||||
ConfigurationErrorKind::InvalidNetwork,
|
ConfigurationErrorKind::InvalidNetwork,
|
||||||
|
|
@ -595,7 +611,7 @@ fn parse_ipv4_prefix(value: &str) -> Result<Ipv4Prefix, ConfigurationError> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_environment_references(
|
pub(super) fn resolve_environment_references(
|
||||||
selected: &ProfileConfigurationV1,
|
selected: &ProfileConfigurationV1,
|
||||||
) -> Result<Vec<EnvironmentReference>, ConfigurationError> {
|
) -> Result<Vec<EnvironmentReference>, ConfigurationError> {
|
||||||
selected
|
selected
|
||||||
|
|
@ -618,7 +634,7 @@ fn resolve_environment_references(
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_environment_values(
|
pub(super) fn resolve_environment_values(
|
||||||
references: &[EnvironmentReference],
|
references: &[EnvironmentReference],
|
||||||
environment: &BTreeMap<String, OsString>,
|
environment: &BTreeMap<String, OsString>,
|
||||||
) -> Result<BTreeMap<String, EnvironmentValue>, ConfigurationError> {
|
) -> Result<BTreeMap<String, EnvironmentValue>, ConfigurationError> {
|
||||||
|
|
@ -645,7 +661,10 @@ fn resolve_environment_values(
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_executable(descriptor: &OwnedFd, uid: u32) -> Result<(), ConfigurationError> {
|
pub(super) fn validate_executable(
|
||||||
|
descriptor: &OwnedFd,
|
||||||
|
uid: u32,
|
||||||
|
) -> Result<(), ConfigurationError> {
|
||||||
let stat = rustix::fs::fstat(descriptor).map_err(io_error)?;
|
let stat = rustix::fs::fstat(descriptor).map_err(io_error)?;
|
||||||
let executable_by_invoking_user = if stat.st_uid == uid {
|
let executable_by_invoking_user = if stat.st_uid == uid {
|
||||||
stat.st_mode & 0o100 != 0
|
stat.st_mode & 0o100 != 0
|
||||||
|
|
@ -673,7 +692,7 @@ fn validate_executable(descriptor: &OwnedFd, uid: u32) -> Result<(), Configurati
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_volume(volume: &ResolvedVolume) -> Result<Vec<u8>, ConfigurationError> {
|
pub(super) fn encode_volume(volume: &ResolvedVolume) -> Result<Vec<u8>, ConfigurationError> {
|
||||||
let mut encoded = Vec::new();
|
let mut encoded = Vec::new();
|
||||||
push_bytes(&mut encoded, volume.canonical_host_source.as_bytes())?;
|
push_bytes(&mut encoded, volume.canonical_host_source.as_bytes())?;
|
||||||
push_bytes(&mut encoded, volume.container_path.as_bytes())?;
|
push_bytes(&mut encoded, volume.container_path.as_bytes())?;
|
||||||
|
|
@ -689,7 +708,9 @@ fn encode_volume(volume: &ResolvedVolume) -> Result<Vec<u8>, ConfigurationError>
|
||||||
Ok(encoded)
|
Ok(encoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_auto_approval(rule: &ResolvedAutoApprovalRule) -> Result<Vec<u8>, ConfigurationError> {
|
pub(super) fn encode_auto_approval(
|
||||||
|
rule: &ResolvedAutoApprovalRule,
|
||||||
|
) -> Result<Vec<u8>, ConfigurationError> {
|
||||||
let mut encoded = Vec::new();
|
let mut encoded = Vec::new();
|
||||||
push_bytes(&mut encoded, rule.canonical_executable.as_bytes())?;
|
push_bytes(&mut encoded, rule.canonical_executable.as_bytes())?;
|
||||||
push_declared_list(
|
push_declared_list(
|
||||||
|
|
@ -704,18 +725,18 @@ fn encode_auto_approval(rule: &ResolvedAutoApprovalRule) -> Result<Vec<u8>, Conf
|
||||||
Ok(encoded)
|
Ok(encoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_bytes(target: &mut Vec<u8>, bytes: &[u8]) -> Result<(), ConfigurationError> {
|
pub(super) fn push_bytes(target: &mut Vec<u8>, bytes: &[u8]) -> Result<(), ConfigurationError> {
|
||||||
let length = u32::try_from(bytes.len()).map_err(|_| fingerprint_overflow())?;
|
let length = u32::try_from(bytes.len()).map_err(|_| fingerprint_overflow())?;
|
||||||
target.extend_from_slice(&length.to_be_bytes());
|
target.extend_from_slice(&length.to_be_bytes());
|
||||||
target.extend_from_slice(bytes);
|
target.extend_from_slice(bytes);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_bool(target: &mut Vec<u8>, value: bool) {
|
pub(super) fn push_bool(target: &mut Vec<u8>, value: bool) {
|
||||||
target.push(u8::from(value));
|
target.push(u8::from(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_sorted_list(
|
pub(super) fn push_sorted_list(
|
||||||
target: &mut Vec<u8>,
|
target: &mut Vec<u8>,
|
||||||
items: impl IntoIterator<Item = Result<Vec<u8>, ConfigurationError>>,
|
items: impl IntoIterator<Item = Result<Vec<u8>, ConfigurationError>>,
|
||||||
) -> Result<(), ConfigurationError> {
|
) -> Result<(), ConfigurationError> {
|
||||||
|
|
@ -748,11 +769,11 @@ fn fingerprint_overflow() -> ConfigurationError {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn host_path(path: &Path) -> HostPath {
|
pub(super) fn host_path(path: &Path) -> HostPath {
|
||||||
HostPath::new(path.as_os_str().as_bytes().to_vec())
|
HostPath::new(path.as_os_str().as_bytes().to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hex_lower(bytes: &[u8]) -> String {
|
pub(super) fn hex_lower(bytes: &[u8]) -> String {
|
||||||
const HEX: &[u8; 16] = b"0123456789abcdef";
|
const HEX: &[u8; 16] = b"0123456789abcdef";
|
||||||
let mut output = String::with_capacity(bytes.len() * 2);
|
let mut output = String::with_capacity(bytes.len() * 2);
|
||||||
for byte in bytes {
|
for byte in bytes {
|
||||||
|
|
@ -762,6 +783,6 @@ fn hex_lower(bytes: &[u8]) -> String {
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
fn io_error(error: impl fmt::Display) -> ConfigurationError {
|
pub(super) fn io_error(error: impl fmt::Display) -> ConfigurationError {
|
||||||
ConfigurationError::new(ConfigurationErrorKind::Io, error.to_string())
|
ConfigurationError::new(ConfigurationErrorKind::Io, error.to_string())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,22 @@
|
||||||
|
use super::confined_io::{
|
||||||
|
config_home, confined_existing, data_home, ensure_owned_directory, expand_host_path, hex_lower,
|
||||||
|
host_path, io_error, is_strict_descendant, paths_intersect, read_stable_configuration,
|
||||||
|
require_clear_of_protected, require_pairwise_disjoint, require_pairwise_non_ancestral,
|
||||||
|
require_strict_child_of_one, resolve_environment_references, resolve_environment_values,
|
||||||
|
resolve_network, resolve_network_readiness, validate_configuration_stat, validate_executable,
|
||||||
|
validate_host_base, validate_persistent_destinations,
|
||||||
|
};
|
||||||
|
use super::validation::{
|
||||||
|
validate_profile_name, validate_sandbox_path, validate_structural_configuration, validate_text,
|
||||||
|
};
|
||||||
|
use super::{
|
||||||
|
ConfigurationError, ConfigurationErrorKind, ConfigurationFingerprintRecordV1,
|
||||||
|
ConfigurationLoadRequest, ConfinedPathPlan, ConfinedPathType, MAX_CONFIGURATION_BYTES, OFlags,
|
||||||
|
OsStrExt, PathBuf, ResolvedAutoApproval, ResolvedAutoApprovalRule, ResolvedDomainConfiguration,
|
||||||
|
ResolvedVolume, ResolvedVolumeAuthority, TrustedHostConfigurationV1, VolumeKind,
|
||||||
|
derive_configuration_fingerprint, derive_domain_id,
|
||||||
|
};
|
||||||
|
|
||||||
/// Parses the closed trusted-host configuration document without resolving host authority.
|
/// Parses the closed trusted-host configuration document without resolving host authority.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
#[cfg(test)]
|
use super::validation::validate_path_suffix;
|
||||||
mod tests {
|
use super::{DescriptorIdentity, descriptor_identities_alias};
|
||||||
use super::{DescriptorIdentity, descriptor_identities_alias, validate_path_suffix};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bind_mount_identity_aliases_across_distinct_mount_ids() {
|
fn bind_mount_identity_aliases_across_distinct_mount_ids() {
|
||||||
let original = DescriptorIdentity {
|
let original = DescriptorIdentity {
|
||||||
device: 7,
|
device: 7,
|
||||||
inode: 11,
|
inode: 11,
|
||||||
|
|
@ -19,13 +18,13 @@ mod tests {
|
||||||
};
|
};
|
||||||
assert!(descriptor_identities_alias(original, bind_alias));
|
assert!(descriptor_identities_alias(original, bind_alias));
|
||||||
assert!(!descriptor_identities_alias(original, distinct));
|
assert!(!descriptor_identities_alias(original, distinct));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn path_suffix_rejects_every_expansion_and_shell_metacharacter() {
|
fn path_suffix_rejects_every_expansion_and_shell_metacharacter() {
|
||||||
for metacharacter in [
|
for metacharacter in [
|
||||||
'*', '?', '[', ']', '{', '}', '$', '~', '\\', '`', '\'', '"', ';', '|', '&', '<', '>',
|
'*', '?', '[', ']', '{', '}', '$', '~', '\\', '`', '\'', '"', ';', '|', '&', '<', '>', '(',
|
||||||
'(', ')', '!', '\n', '\r', '\t',
|
')', '!', '\n', '\r', '\t',
|
||||||
] {
|
] {
|
||||||
assert!(
|
assert!(
|
||||||
validate_path_suffix(&format!("safe{metacharacter}suffix")).is_err(),
|
validate_path_suffix(&format!("safe{metacharacter}suffix")).is_err(),
|
||||||
|
|
@ -33,5 +32,4 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
assert!(validate_path_suffix("safe path/with+literal@characters").is_ok());
|
assert!(validate_path_suffix("safe path/with+literal@characters").is_ok());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,13 @@
|
||||||
|
use super::confined_io::{
|
||||||
|
encode_auto_approval, encode_volume, parse_ipv4_prefix, push_bool, push_bytes,
|
||||||
|
push_sorted_list, require_pairwise_non_ancestral,
|
||||||
|
};
|
||||||
|
use super::{
|
||||||
|
ConfigurationError, ConfigurationErrorKind, ConfigurationFingerprint,
|
||||||
|
ConfigurationFingerprintRecordV1, Digest, DomainId, Sha256,
|
||||||
|
TRUSTED_HOST_CONFIGURATION_SCHEMA_ID, TrustedHostConfigurationV1,
|
||||||
|
};
|
||||||
|
|
||||||
/// Derives the specification-framed v1 Domain ID.
|
/// Derives the specification-framed v1 Domain ID.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
|
|
@ -96,7 +106,7 @@ pub fn derive_configuration_fingerprint(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_structural_configuration(
|
pub(super) fn validate_structural_configuration(
|
||||||
configuration: &TrustedHostConfigurationV1,
|
configuration: &TrustedHostConfigurationV1,
|
||||||
) -> Result<(), ConfigurationError> {
|
) -> Result<(), ConfigurationError> {
|
||||||
if configuration.schema_id() != TRUSTED_HOST_CONFIGURATION_SCHEMA_ID {
|
if configuration.schema_id() != TRUSTED_HOST_CONFIGURATION_SCHEMA_ID {
|
||||||
|
|
@ -186,7 +196,7 @@ fn validate_image_reference(reference: &str) -> Result<(), ConfigurationError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_profile_name(name: &str) -> Result<(), ConfigurationError> {
|
pub(super) fn validate_profile_name(name: &str) -> Result<(), ConfigurationError> {
|
||||||
let bytes = name.as_bytes();
|
let bytes = name.as_bytes();
|
||||||
let valid = (1..=64).contains(&bytes.len())
|
let valid = (1..=64).contains(&bytes.len())
|
||||||
&& bytes[0].is_ascii_alphanumeric()
|
&& bytes[0].is_ascii_alphanumeric()
|
||||||
|
|
@ -204,7 +214,7 @@ fn validate_profile_name(name: &str) -> Result<(), ConfigurationError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_environment_name(name: &str) -> Result<(), ConfigurationError> {
|
pub(super) fn validate_environment_name(name: &str) -> Result<(), ConfigurationError> {
|
||||||
let mut bytes = name.bytes();
|
let mut bytes = name.bytes();
|
||||||
let valid = bytes
|
let valid = bytes
|
||||||
.next()
|
.next()
|
||||||
|
|
@ -220,7 +230,7 @@ fn validate_environment_name(name: &str) -> Result<(), ConfigurationError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_text(value: &str, field: &str) -> Result<(), ConfigurationError> {
|
pub(super) fn validate_text(value: &str, field: &str) -> Result<(), ConfigurationError> {
|
||||||
if value.contains('\0') {
|
if value.contains('\0') {
|
||||||
Err(ConfigurationError::new(
|
Err(ConfigurationError::new(
|
||||||
ConfigurationErrorKind::InvalidProfile,
|
ConfigurationErrorKind::InvalidProfile,
|
||||||
|
|
@ -231,7 +241,7 @@ fn validate_text(value: &str, field: &str) -> Result<(), ConfigurationError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_declared_host_path(
|
pub(super) fn validate_declared_host_path(
|
||||||
declaration: &str,
|
declaration: &str,
|
||||||
allow_profile: bool,
|
allow_profile: bool,
|
||||||
) -> Result<(), ConfigurationError> {
|
) -> Result<(), ConfigurationError> {
|
||||||
|
|
@ -256,7 +266,7 @@ fn validate_declared_host_path(
|
||||||
validate_path_suffix(suffix)
|
validate_path_suffix(suffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_sandbox_path(path: &str) -> Result<(), ConfigurationError> {
|
pub(super) fn validate_sandbox_path(path: &str) -> Result<(), ConfigurationError> {
|
||||||
validate_text(path, "sandbox path")?;
|
validate_text(path, "sandbox path")?;
|
||||||
let suffix = path.strip_prefix('/').ok_or_else(|| {
|
let suffix = path.strip_prefix('/').ok_or_else(|| {
|
||||||
ConfigurationError::new(
|
ConfigurationError::new(
|
||||||
|
|
@ -267,7 +277,7 @@ fn validate_sandbox_path(path: &str) -> Result<(), ConfigurationError> {
|
||||||
validate_path_suffix(suffix)
|
validate_path_suffix(suffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_path_suffix(suffix: &str) -> Result<(), ConfigurationError> {
|
pub(super) fn validate_path_suffix(suffix: &str) -> Result<(), ConfigurationError> {
|
||||||
if suffix.is_empty()
|
if suffix.is_empty()
|
||||||
|| suffix.ends_with('/')
|
|| suffix.ends_with('/')
|
||||||
|| suffix
|
|| suffix
|
||||||
|
|
|
||||||
|
|
@ -845,4 +845,14 @@ pub struct DomainStore {
|
||||||
pub session_terminals: Vec<SessionTerminal>,
|
pub session_terminals: Vec<SessionTerminal>,
|
||||||
pub mailboxes: Vec<Mailbox>,
|
pub mailboxes: Vec<Mailbox>,
|
||||||
}
|
}
|
||||||
include!("lifecycle/ownership.rs");
|
mod ownership;
|
||||||
|
|
||||||
|
pub use ownership::{
|
||||||
|
GitRoutingOwnership, HelperOwnership, NetworkOwnership, ParticipantOwnership,
|
||||||
|
PreparedLowerOwnership, PublicationOwnership, SandboxOwnership, StateAnchorOwnership,
|
||||||
|
StateAnchorReleaseEvidence,
|
||||||
|
};
|
||||||
|
use ownership::{
|
||||||
|
validate_existing_process_progress, validate_option_progress, validate_participant_activation,
|
||||||
|
validate_participant_progress, validate_participant_release, validate_process_progress,
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,10 @@
|
||||||
|
use super::{
|
||||||
|
BundleDigest, ContainerId, DerivedPolicyDigest, Deserialize, DeviceId, GenerationId, HostPath,
|
||||||
|
InodeId, MountIdentity, NamespaceIdentity, NetworkReadinessEvidence, NetworkSidecarId,
|
||||||
|
ParticipantRole, ParticipantState, PersistedHostProcessOwnership, PidfsProcessIdentityV1,
|
||||||
|
PreparedArtifactId, ReadinessFingerprint, RecordTransitionError, Serialize, SourceIdentity,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
pub enum ParticipantOwnership {
|
pub enum ParticipantOwnership {
|
||||||
PreparedLower(PreparedLowerOwnership),
|
PreparedLower(PreparedLowerOwnership),
|
||||||
|
|
@ -49,7 +56,7 @@ impl ParticipantOwnership {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_intent(&self) -> bool {
|
pub(super) fn is_intent(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::PreparedLower(ownership) => ownership
|
Self::PreparedLower(ownership) => ownership
|
||||||
.lease_state
|
.lease_state
|
||||||
|
|
@ -58,7 +65,7 @@ impl ParticipantOwnership {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_release_intent(&self) -> bool {
|
pub(super) fn is_release_intent(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::PreparedLower(ownership) => ownership
|
Self::PreparedLower(ownership) => ownership
|
||||||
.lease_state
|
.lease_state
|
||||||
|
|
@ -67,7 +74,11 @@ impl ParticipantOwnership {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_active_for(&self, state: ParticipantState, generation_id: GenerationId) -> bool {
|
pub(super) fn is_active_for(
|
||||||
|
&self,
|
||||||
|
state: ParticipantState,
|
||||||
|
generation_id: GenerationId,
|
||||||
|
) -> bool {
|
||||||
match (self, state) {
|
match (self, state) {
|
||||||
(Self::PreparedLower(ownership), ParticipantState::LeaseHeld) => {
|
(Self::PreparedLower(ownership), ParticipantState::LeaseHeld) => {
|
||||||
ownership.is_lease_held()
|
ownership.is_lease_held()
|
||||||
|
|
@ -84,7 +95,7 @@ impl ParticipantOwnership {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cleared(&self) -> Self {
|
pub(super) fn cleared(&self) -> Self {
|
||||||
Self::empty(self.role())
|
Self::empty(self.role())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -564,7 +575,7 @@ impl PublicationOwnership {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_participant_progress(
|
pub(super) fn validate_participant_progress(
|
||||||
old: Option<&ParticipantOwnership>,
|
old: Option<&ParticipantOwnership>,
|
||||||
next: Option<&ParticipantOwnership>,
|
next: Option<&ParticipantOwnership>,
|
||||||
) -> Result<(), RecordTransitionError> {
|
) -> Result<(), RecordTransitionError> {
|
||||||
|
|
@ -577,7 +588,7 @@ fn validate_participant_progress(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_participant_release(
|
pub(super) fn validate_participant_release(
|
||||||
old: Option<&ParticipantOwnership>,
|
old: Option<&ParticipantOwnership>,
|
||||||
next: Option<&ParticipantOwnership>,
|
next: Option<&ParticipantOwnership>,
|
||||||
) -> Result<(), RecordTransitionError> {
|
) -> Result<(), RecordTransitionError> {
|
||||||
|
|
@ -587,7 +598,7 @@ fn validate_participant_release(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_participant_activation(
|
pub(super) fn validate_participant_activation(
|
||||||
old: Option<&ParticipantOwnership>,
|
old: Option<&ParticipantOwnership>,
|
||||||
next: Option<&ParticipantOwnership>,
|
next: Option<&ParticipantOwnership>,
|
||||||
) -> Result<(), RecordTransitionError> {
|
) -> Result<(), RecordTransitionError> {
|
||||||
|
|
@ -613,7 +624,7 @@ fn validate_prepared_lower_lease_progress(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_option_progress<T: Eq>(
|
pub(super) fn validate_option_progress<T: Eq>(
|
||||||
old: Option<&T>,
|
old: Option<&T>,
|
||||||
next: Option<&T>,
|
next: Option<&T>,
|
||||||
) -> Result<(), RecordTransitionError> {
|
) -> Result<(), RecordTransitionError> {
|
||||||
|
|
@ -625,7 +636,7 @@ fn validate_option_progress<T: Eq>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_process_progress(
|
pub(super) fn validate_process_progress(
|
||||||
old: &PersistedHostProcessOwnership,
|
old: &PersistedHostProcessOwnership,
|
||||||
next: &PersistedHostProcessOwnership,
|
next: &PersistedHostProcessOwnership,
|
||||||
) -> Result<(), RecordTransitionError> {
|
) -> Result<(), RecordTransitionError> {
|
||||||
|
|
@ -661,7 +672,7 @@ fn validate_process_progress(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_existing_process_progress(
|
pub(super) fn validate_existing_process_progress(
|
||||||
old: &PersistedHostProcessOwnership,
|
old: &PersistedHostProcessOwnership,
|
||||||
next: &PersistedHostProcessOwnership,
|
next: &PersistedHostProcessOwnership,
|
||||||
) -> Result<(), RecordTransitionError> {
|
) -> Result<(), RecordTransitionError> {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,11 @@ struct TestTree(PathBuf);
|
||||||
impl TestTree {
|
impl TestTree {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
let sequence = NEXT_TREE.fetch_add(1, Ordering::Relaxed);
|
let sequence = NEXT_TREE.fetch_add(1, Ordering::Relaxed);
|
||||||
let path = std::env::temp_dir().join(format!(
|
let parent = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("../../target/test-artifacts/configuration");
|
||||||
|
fs::create_dir_all(&parent).unwrap();
|
||||||
|
let parent = parent.canonicalize().unwrap();
|
||||||
|
let path = parent.join(format!(
|
||||||
"lazaret-configuration-{}-{sequence}",
|
"lazaret-configuration-{}-{sequence}",
|
||||||
std::process::id()
|
std::process::id()
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -420,6 +420,17 @@ fn require_inputs(inputs: &ColdStartInputs<'_>) -> Result<(), ColdStartError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
include!("cold_start/git_routing.rs");
|
mod git_routing;
|
||||||
include!("cold_start/runtime_staging.rs");
|
mod runtime_staging;
|
||||||
include!("cold_start/tests.rs");
|
|
||||||
|
use git_routing::GitRoutingLayout;
|
||||||
|
#[cfg(test)]
|
||||||
|
use git_routing::parse_global_excludes_path;
|
||||||
|
pub(crate) use runtime_staging::RuntimeStaging;
|
||||||
|
use runtime_staging::{
|
||||||
|
FileIdentity, confined_resolve_flags, hex, host_path, open_or_create_private_directory,
|
||||||
|
remove_staging_directory, require_private_file,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,20 @@
|
||||||
struct GitRoutingLayout {
|
use super::{
|
||||||
|
AtFlags, ColdStartError, ContainerId, File, FileIdentity, HostPath, Mode, OFlags, OsString,
|
||||||
|
OsStringExt, OwnedFd, Path, PathBuf, PipeFlags, Read, ResolveFlags,
|
||||||
|
ResolvedDomainConfiguration, RuntimeGitRoutingAuthority, RuntimeGitRoutingLaunch,
|
||||||
|
TargetCommand, child_broker, confined_resolve_flags, failure, fchmod, git_common_directory,
|
||||||
|
hex, host_path, open_or_create_private_directory, openat2, pipe_with, remove_staging_directory,
|
||||||
|
require_private_file, statat,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(super) struct GitRoutingLayout {
|
||||||
_runtime_directory: RuntimeGitRoutingDirectory,
|
_runtime_directory: RuntimeGitRoutingDirectory,
|
||||||
workspace: File,
|
workspace: File,
|
||||||
workspace_label: PathBuf,
|
workspace_label: PathBuf,
|
||||||
isolated: File,
|
isolated: File,
|
||||||
isolated_label: PathBuf,
|
isolated_label: PathBuf,
|
||||||
mountpoint: File,
|
mountpoint: File,
|
||||||
mountpoint_label: PathBuf,
|
pub(super) mountpoint_label: PathBuf,
|
||||||
git_common: File,
|
git_common: File,
|
||||||
git_common_label: PathBuf,
|
git_common_label: PathBuf,
|
||||||
transient_hooks: File,
|
transient_hooks: File,
|
||||||
|
|
@ -17,7 +26,7 @@ struct GitRoutingLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GitRoutingLayout {
|
impl GitRoutingLayout {
|
||||||
fn create(
|
pub(super) fn create(
|
||||||
configuration: &ResolvedDomainConfiguration,
|
configuration: &ResolvedDomainConfiguration,
|
||||||
container_id: ContainerId,
|
container_id: ContainerId,
|
||||||
invoking_uid: u32,
|
invoking_uid: u32,
|
||||||
|
|
@ -122,7 +131,7 @@ impl GitRoutingLayout {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn launch_authority(
|
pub(super) fn launch_authority(
|
||||||
&self,
|
&self,
|
||||||
configuration: &ResolvedDomainConfiguration,
|
configuration: &ResolvedDomainConfiguration,
|
||||||
) -> Result<RuntimeGitRoutingLaunch, ColdStartError> {
|
) -> Result<RuntimeGitRoutingLaunch, ColdStartError> {
|
||||||
|
|
@ -148,7 +157,7 @@ impl GitRoutingLayout {
|
||||||
.map_err(failure)
|
.map_err(failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mountpoint(&self) -> HostPath {
|
pub(super) fn mountpoint(&self) -> HostPath {
|
||||||
HostPath::new(
|
HostPath::new(
|
||||||
self.mountpoint_label
|
self.mountpoint_label
|
||||||
.as_os_str()
|
.as_os_str()
|
||||||
|
|
@ -157,11 +166,11 @@ impl GitRoutingLayout {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn host_root(&self) -> HostPath {
|
pub(super) fn host_root(&self) -> HostPath {
|
||||||
HostPath::new(self.workspace_label.as_os_str().as_encoded_bytes().to_vec())
|
HostPath::new(self.workspace_label.as_os_str().as_encoded_bytes().to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn isolated_root(&self) -> HostPath {
|
pub(super) fn isolated_root(&self) -> HostPath {
|
||||||
HostPath::new(self.isolated_label.as_os_str().as_encoded_bytes().to_vec())
|
HostPath::new(self.isolated_label.as_os_str().as_encoded_bytes().to_vec())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -212,7 +221,7 @@ fn global_excludes_path(workspace: &Path) -> Result<Option<PathBuf>, ColdStartEr
|
||||||
parse_global_excludes_path(workspace, &output)
|
parse_global_excludes_path(workspace, &output)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_global_excludes_path(
|
pub(super) fn parse_global_excludes_path(
|
||||||
workspace: &Path,
|
workspace: &Path,
|
||||||
output: &[u8],
|
output: &[u8],
|
||||||
) -> Result<Option<PathBuf>, ColdStartError> {
|
) -> Result<Option<PathBuf>, ColdStartError> {
|
||||||
|
|
@ -242,4 +251,3 @@ fn default_global_excludes_path() -> Option<PathBuf> {
|
||||||
})
|
})
|
||||||
.map(|home| home.join("git/ignore"))
|
.map(|home| home.join("git/ignore"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,10 @@
|
||||||
|
use super::{
|
||||||
|
AsFd, AtFlags, ColdStartError, ConfinedPathPlan, ContainerId, File, FileExt, FileType, Mode,
|
||||||
|
MountIdentity, OFlags, OsStr, OsString, OsStringExt, OwnedFd, PathBuf, PermissionsExt, RawDir,
|
||||||
|
RegularFileMailbox, ResolveFlags, Write, failure, fchmod, fstat, mkdirat, mount_identity,
|
||||||
|
openat2, statat, unlinkat,
|
||||||
|
};
|
||||||
|
|
||||||
pub(crate) struct RuntimeStaging {
|
pub(crate) struct RuntimeStaging {
|
||||||
runsc_root: OwnedFd,
|
runsc_root: OwnedFd,
|
||||||
bundles: OwnedFd,
|
bundles: OwnedFd,
|
||||||
|
|
@ -60,7 +67,7 @@ impl RuntimeStaging {
|
||||||
Ok((credentials, requests, results))
|
Ok((credentials, requests, results))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create(
|
pub(crate) fn create(
|
||||||
runtime_domain: &ConfinedPathPlan,
|
runtime_domain: &ConfinedPathPlan,
|
||||||
container_id: ContainerId,
|
container_id: ContainerId,
|
||||||
escalation_client: &File,
|
escalation_client: &File,
|
||||||
|
|
@ -145,7 +152,7 @@ impl RuntimeStaging {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_bundle(&self, bytes: &[u8]) -> Result<(), ColdStartError> {
|
pub(crate) fn write_bundle(&self, bytes: &[u8]) -> Result<(), ColdStartError> {
|
||||||
let descriptor = openat2(
|
let descriptor = openat2(
|
||||||
&self.bundle,
|
&self.bundle,
|
||||||
"config.json",
|
"config.json",
|
||||||
|
|
@ -208,13 +215,13 @@ fn require_executable_regular_file(source: &File, label: &str) -> Result<(), Col
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
struct FileIdentity {
|
pub(super) struct FileIdentity {
|
||||||
device: u64,
|
device: u64,
|
||||||
inode: u64,
|
inode: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileIdentity {
|
impl FileIdentity {
|
||||||
fn capture(descriptor: impl AsFd) -> Result<Self, ColdStartError> {
|
pub(super) fn capture(descriptor: impl AsFd) -> Result<Self, ColdStartError> {
|
||||||
let metadata = fstat(descriptor).map_err(failure)?;
|
let metadata = fstat(descriptor).map_err(failure)?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
device: metadata.st_dev,
|
device: metadata.st_dev,
|
||||||
|
|
@ -250,7 +257,7 @@ impl Drop for RuntimeStaging {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_or_create_private_directory(
|
pub(super) fn open_or_create_private_directory(
|
||||||
parent: impl AsFd,
|
parent: impl AsFd,
|
||||||
name: impl AsRef<std::ffi::OsStr>,
|
name: impl AsRef<std::ffi::OsStr>,
|
||||||
invoking_uid: u32,
|
invoking_uid: u32,
|
||||||
|
|
@ -293,7 +300,7 @@ fn require_private_directory(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn require_private_file(
|
pub(super) fn require_private_file(
|
||||||
descriptor: impl AsFd,
|
descriptor: impl AsFd,
|
||||||
invoking_uid: u32,
|
invoking_uid: u32,
|
||||||
mode: u32,
|
mode: u32,
|
||||||
|
|
@ -311,7 +318,7 @@ fn require_private_file(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_staging_directory(
|
pub(super) fn remove_staging_directory(
|
||||||
parent: impl AsFd,
|
parent: impl AsFd,
|
||||||
name: &std::ffi::OsStr,
|
name: &std::ffi::OsStr,
|
||||||
directory: impl AsFd,
|
directory: impl AsFd,
|
||||||
|
|
@ -369,18 +376,18 @@ fn remove_children(directory: impl AsFd) -> std::io::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confined_resolve_flags() -> ResolveFlags {
|
pub(super) fn confined_resolve_flags() -> ResolveFlags {
|
||||||
ResolveFlags::BENEATH
|
ResolveFlags::BENEATH
|
||||||
| ResolveFlags::NO_MAGICLINKS
|
| ResolveFlags::NO_MAGICLINKS
|
||||||
| ResolveFlags::NO_SYMLINKS
|
| ResolveFlags::NO_SYMLINKS
|
||||||
| ResolveFlags::NO_XDEV
|
| ResolveFlags::NO_XDEV
|
||||||
}
|
}
|
||||||
|
|
||||||
fn host_path(bytes: &[u8]) -> PathBuf {
|
pub(super) fn host_path(bytes: &[u8]) -> PathBuf {
|
||||||
PathBuf::from(OsString::from_vec(bytes.to_vec()))
|
PathBuf::from(OsString::from_vec(bytes.to_vec()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hex(bytes: &[u8]) -> String {
|
pub(super) fn hex(bytes: &[u8]) -> String {
|
||||||
use std::fmt::Write as _;
|
use std::fmt::Write as _;
|
||||||
|
|
||||||
bytes.iter().fold(String::new(), |mut output, byte| {
|
bytes.iter().fold(String::new(), |mut output, byte| {
|
||||||
|
|
@ -388,4 +395,3 @@ fn hex(bytes: &[u8]) -> String {
|
||||||
output
|
output
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,27 @@
|
||||||
#[cfg(test)]
|
use std::fs::File;
|
||||||
mod tests {
|
use std::os::fd::OwnedFd;
|
||||||
use std::fs::File;
|
|
||||||
use std::os::fd::OwnedFd;
|
|
||||||
|
|
||||||
use rustix::pipe::{PipeFlags, pipe_with};
|
use rustix::pipe::{PipeFlags, pipe_with};
|
||||||
|
|
||||||
use super::BootstrapOwnerGuard;
|
use super::BootstrapOwnerGuard;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bootstrap_owner_guard_tracks_the_exact_inherited_pipe() {
|
fn bootstrap_owner_guard_tracks_the_exact_inherited_pipe() {
|
||||||
let (reader, writer) = pipe_with(PipeFlags::CLOEXEC).unwrap();
|
let (reader, writer) = pipe_with(PipeFlags::CLOEXEC).unwrap();
|
||||||
let guard = BootstrapOwnerGuard::from_inherited(reader).unwrap();
|
let guard = BootstrapOwnerGuard::from_inherited(reader).unwrap();
|
||||||
assert!(guard.is_live().unwrap());
|
assert!(guard.is_live().unwrap());
|
||||||
drop(writer);
|
drop(writer);
|
||||||
assert!(!guard.is_live().unwrap());
|
assert!(!guard.is_live().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bootstrap_owner_guard_refuses_non_pipe_authority() {
|
fn bootstrap_owner_guard_refuses_non_pipe_authority() {
|
||||||
let descriptor: OwnedFd = File::open("/dev/null").unwrap().into();
|
let descriptor: OwnedFd = File::open("/dev/null").unwrap().into();
|
||||||
assert!(BootstrapOwnerGuard::from_inherited(descriptor).is_err());
|
assert!(BootstrapOwnerGuard::from_inherited(descriptor).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn repository_relative_global_excludes_are_workspace_relative() {
|
fn repository_relative_global_excludes_are_workspace_relative() {
|
||||||
let root = tempfile::tempdir().unwrap();
|
let root = tempfile::tempdir().unwrap();
|
||||||
let workspace = root.path().join("workspace");
|
let workspace = root.path().join("workspace");
|
||||||
|
|
||||||
|
|
@ -32,5 +30,4 @@ mod tests {
|
||||||
Some(workspace.join("policy/ignore"))
|
Some(workspace.join("policy/ignore"))
|
||||||
);
|
);
|
||||||
assert!(super::parse_global_excludes_path(&workspace, b"bad\0path\n").is_err());
|
assert!(super::parse_global_excludes_path(&workspace, b"bad\0path\n").is_err());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -649,7 +649,26 @@ impl SessionLifecycleAdapter for Arc<SessionRegistry> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
include!("control/execution.rs");
|
mod execution;
|
||||||
include!("control/staging.rs");
|
mod publication;
|
||||||
include!("control/publication.rs");
|
mod staging;
|
||||||
include!("control/tests.rs");
|
|
||||||
|
pub(crate) use execution::AcceptedControlConnection;
|
||||||
|
use execution::{ControlPaths, ExecutionUnavailable, InteractiveExecutionUnavailable};
|
||||||
|
pub use execution::{
|
||||||
|
InteractiveControlSession, InteractiveSessionExecutor, NonInteractiveSessionExecutor,
|
||||||
|
PublishedControlFace, RunscNonInteractiveExecutor,
|
||||||
|
};
|
||||||
|
#[cfg(test)]
|
||||||
|
use execution::{NonInteractiveCommandRunner, hex_lower};
|
||||||
|
#[cfg(test)]
|
||||||
|
use publication::require_peer_uid;
|
||||||
|
use publication::{
|
||||||
|
certificate_digest, enrollment_binding, is_live_session, leaf_parameters, random_signing_key,
|
||||||
|
remove_if_identity_matches, sha256, short_identity,
|
||||||
|
};
|
||||||
|
pub use staging::StagedControlFace;
|
||||||
|
use staging::publication_recovery_ownership;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,26 @@
|
||||||
struct ControlPaths {
|
use super::{
|
||||||
root: PathBuf,
|
Arc, AsFd, CONTROL_SOCKET_NAME, ContainerId, ControlError, File, FileIdentity,
|
||||||
root_file: File,
|
InteractiveSessionBatch, InteractiveSessionEvent, InteractiveSessionRequest, MetadataExt, Mode,
|
||||||
root_identity: FileIdentity,
|
NonInteractiveSessionMessage, NonInteractiveSessionRequest, OFlags, OpenOptions, OsStr,
|
||||||
socket: PathBuf,
|
OsString, OsStringExt, PUBLICATION_IDENTITY_NAME, Path, PathBuf, ProcessExit,
|
||||||
identity: PathBuf,
|
ProtocolPublicationIdentity, Read, RunscCommand, STAGED_CONTROL_SOCKET_NAME,
|
||||||
staged_socket: PathBuf,
|
STAGED_PUBLICATION_IDENTITY_NAME, ServerConfig, SessionExit, SessionId, SessionRegistry,
|
||||||
staged_identity: PathBuf,
|
TargetCommand, TrustAnchor, Write, child_broker, fchmod, fmt, io, openat,
|
||||||
|
remove_if_identity_matches,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(super) struct ControlPaths {
|
||||||
|
pub(super) root: PathBuf,
|
||||||
|
pub(super) root_file: File,
|
||||||
|
pub(super) root_identity: FileIdentity,
|
||||||
|
pub(super) socket: PathBuf,
|
||||||
|
pub(super) identity: PathBuf,
|
||||||
|
pub(super) staged_socket: PathBuf,
|
||||||
|
pub(super) staged_identity: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ControlPaths {
|
impl ControlPaths {
|
||||||
fn open(root: &Path, uid: u32) -> Result<Self, ControlError> {
|
pub(super) fn open(root: &Path, uid: u32) -> Result<Self, ControlError> {
|
||||||
if !root.is_absolute() {
|
if !root.is_absolute() {
|
||||||
return Err(ControlError::InvalidPath(
|
return Err(ControlError::InvalidPath(
|
||||||
"runtime Domain root is not absolute",
|
"runtime Domain root is not absolute",
|
||||||
|
|
@ -43,7 +54,7 @@ impl ControlPaths {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_root(&self, uid: u32) -> Result<(), ControlError> {
|
pub(super) fn validate_root(&self, uid: u32) -> Result<(), ControlError> {
|
||||||
let descriptor = self.root_file.metadata()?;
|
let descriptor = self.root_file.metadata()?;
|
||||||
let current = std::fs::symlink_metadata(&self.root)?;
|
let current = std::fs::symlink_metadata(&self.root)?;
|
||||||
if FileIdentity::from_metadata(&descriptor) != self.root_identity
|
if FileIdentity::from_metadata(&descriptor) != self.root_identity
|
||||||
|
|
@ -57,7 +68,7 @@ impl ControlPaths {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_named_identity(
|
pub(super) fn create_named_identity(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
|
|
@ -90,7 +101,7 @@ impl ControlPaths {
|
||||||
Ok((file, identity))
|
Ok((file, identity))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_identity(
|
pub(super) fn validate_identity(
|
||||||
&self,
|
&self,
|
||||||
file: &File,
|
file: &File,
|
||||||
expected: FileIdentity,
|
expected: FileIdentity,
|
||||||
|
|
@ -100,7 +111,7 @@ impl ControlPaths {
|
||||||
self.validate_identity_at(&self.identity, file, expected, uid, expected_length)
|
self.validate_identity_at(&self.identity, file, expected, uid, expected_length)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_identity_at(
|
pub(super) fn validate_identity_at(
|
||||||
&self,
|
&self,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
file: &File,
|
file: &File,
|
||||||
|
|
@ -131,23 +142,23 @@ impl ControlPaths {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PublishedControlFace {
|
pub struct PublishedControlFace {
|
||||||
listener: std::os::unix::net::UnixListener,
|
pub(super) listener: std::os::unix::net::UnixListener,
|
||||||
paths: ControlPaths,
|
pub(super) paths: ControlPaths,
|
||||||
socket_identity: FileIdentity,
|
pub(super) socket_identity: FileIdentity,
|
||||||
identity_file: File,
|
pub(super) identity_file: File,
|
||||||
identity_file_identity: FileIdentity,
|
pub(super) identity_file_identity: FileIdentity,
|
||||||
publication_identity: ProtocolPublicationIdentity,
|
pub(super) publication_identity: ProtocolPublicationIdentity,
|
||||||
publication_bytes: Vec<u8>,
|
pub(super) publication_bytes: Vec<u8>,
|
||||||
uid: u32,
|
pub(super) uid: u32,
|
||||||
anchor: TrustAnchor,
|
pub(super) anchor: TrustAnchor,
|
||||||
server_config: Arc<ServerConfig>,
|
pub(super) server_config: Arc<ServerConfig>,
|
||||||
registry: Arc<SessionRegistry>,
|
pub(super) registry: Arc<SessionRegistry>,
|
||||||
executor: Arc<dyn NonInteractiveSessionExecutor>,
|
pub(super) executor: Arc<dyn NonInteractiveSessionExecutor>,
|
||||||
interactive_executor: Arc<dyn InteractiveSessionExecutor>,
|
pub(super) interactive_executor: Arc<dyn InteractiveSessionExecutor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct AcceptedControlConnection {
|
pub(crate) struct AcceptedControlConnection {
|
||||||
socket: std::os::unix::net::UnixStream,
|
pub(super) socket: std::os::unix::net::UnixStream,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AcceptedControlConnection {
|
impl AcceptedControlConnection {
|
||||||
|
|
@ -166,7 +177,7 @@ pub trait NonInteractiveSessionExecutor: Send + Sync {
|
||||||
) -> Result<SessionExit, ControlError>;
|
) -> Result<SessionExit, ControlError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ExecutionUnavailable;
|
pub(super) struct ExecutionUnavailable;
|
||||||
|
|
||||||
impl NonInteractiveSessionExecutor for ExecutionUnavailable {
|
impl NonInteractiveSessionExecutor for ExecutionUnavailable {
|
||||||
fn execute(
|
fn execute(
|
||||||
|
|
@ -196,7 +207,7 @@ pub trait InteractiveSessionExecutor: Send + Sync {
|
||||||
) -> Result<Box<dyn InteractiveControlSession>, ControlError>;
|
) -> Result<Box<dyn InteractiveControlSession>, ControlError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InteractiveExecutionUnavailable;
|
pub(super) struct InteractiveExecutionUnavailable;
|
||||||
|
|
||||||
impl InteractiveSessionExecutor for InteractiveExecutionUnavailable {
|
impl InteractiveSessionExecutor for InteractiveExecutionUnavailable {
|
||||||
fn start(
|
fn start(
|
||||||
|
|
@ -208,13 +219,13 @@ impl InteractiveSessionExecutor for InteractiveExecutionUnavailable {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RunscNonInteractiveExecutor {
|
pub struct RunscNonInteractiveExecutor {
|
||||||
program: OsString,
|
pub(super) program: OsString,
|
||||||
global_arguments: Vec<OsString>,
|
pub(super) global_arguments: Vec<OsString>,
|
||||||
container: OsString,
|
pub(super) container: OsString,
|
||||||
runner: Arc<dyn NonInteractiveCommandRunner>,
|
pub(super) runner: Arc<dyn NonInteractiveCommandRunner>,
|
||||||
}
|
}
|
||||||
|
|
||||||
trait NonInteractiveCommandRunner: Send + Sync {
|
pub(super) trait NonInteractiveCommandRunner: Send + Sync {
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
target: &TargetCommand,
|
target: &TargetCommand,
|
||||||
|
|
@ -343,7 +354,7 @@ fn emit_available(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hex_lower(bytes: &[u8]) -> String {
|
pub(super) fn hex_lower(bytes: &[u8]) -> String {
|
||||||
use fmt::Write as _;
|
use fmt::Write as _;
|
||||||
|
|
||||||
bytes.iter().fold(String::new(), |mut output, byte| {
|
bytes.iter().fold(String::new(), |mut output, byte| {
|
||||||
|
|
@ -351,4 +362,3 @@ fn hex_lower(bytes: &[u8]) -> String {
|
||||||
output
|
output
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,20 @@
|
||||||
|
use super::{
|
||||||
|
AcceptedControlConnection, Arc, AsFd, AsRawFd, CONTROL_ALPN, CONTROL_SOCKET_NAME,
|
||||||
|
CertificateDigest, CertificateParams, ClientConfig, ClientConnection, ClientEnrollmentResponse,
|
||||||
|
ClientId, ConfinedPathPlan, ControlClient, ControlError, ControlPaths, ControlRequest,
|
||||||
|
ControlResponse, ControlSessionAdmission, Digest, DistinguishedName, DnType, Duration,
|
||||||
|
ExecutionUnavailable, ExtendedKeyUsagePurpose, File, FileExt, FileIdentity, FileTypeExt,
|
||||||
|
GenerationIdentity, HostCoordinatorDomainStore, InteractiveSessionBatch,
|
||||||
|
InteractiveSessionEvent, InteractiveSessionExecutor, InteractiveSessionMessage, IsCa,
|
||||||
|
KeyUsagePurpose, MAX_CONTROL_BYTES, MAX_ENROLLMENT_BYTES, MetadataExt, Mode,
|
||||||
|
NonInteractiveSessionExecutor, NonInteractiveSessionMessage, OFlags, OffsetDateTime,
|
||||||
|
PUBLICATION_IDENTITY_NAME, Path, PathBuf, PrivateKeyDer, ProtocolPublicationIdentity,
|
||||||
|
ProtocolVersion, PublicationOwnership, PublishedControlFace, Read, ServerConnection,
|
||||||
|
SessionBinding, SessionId, SessionPhase, SessionRegistrationResponse, SessionRegistry, Sha256,
|
||||||
|
SigningKey, StagedControlFace, TlsCredential, TrustAnchor, Write, decode, encode, fmt, io,
|
||||||
|
issue_session_capability, openat, publication_recovery_ownership, socket_peercred,
|
||||||
|
};
|
||||||
|
|
||||||
impl PublishedControlFace {
|
impl PublishedControlFace {
|
||||||
pub(crate) fn recovery_ownership(&self) -> Result<PublicationOwnership, ControlError> {
|
pub(crate) fn recovery_ownership(&self) -> Result<PublicationOwnership, ControlError> {
|
||||||
publication_recovery_ownership(&self.paths)
|
publication_recovery_ownership(&self.paths)
|
||||||
|
|
@ -511,9 +528,7 @@ impl PublishedControlFace {
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Returns framing, size, decoding, or TLS I/O failures.
|
/// Returns framing, size, decoding, or TLS I/O failures.
|
||||||
pub fn receive<
|
pub fn receive<
|
||||||
Response: lazaret_core::records::SchemaType
|
Response: lazaret_core::records::SchemaType + serde::de::DeserializeOwned + serde::Serialize,
|
||||||
+ serde::de::DeserializeOwned
|
|
||||||
+ serde::Serialize,
|
|
||||||
>(
|
>(
|
||||||
stream: &mut impl Read,
|
stream: &mut impl Read,
|
||||||
) -> Result<Response, ControlError> {
|
) -> Result<Response, ControlError> {
|
||||||
|
|
@ -552,7 +567,7 @@ fn remove_exact_if_identity_matches(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_if_identity_matches(path: &Path, expected: FileIdentity) {
|
pub(super) fn remove_if_identity_matches(path: &Path, expected: FileIdentity) {
|
||||||
if std::fs::symlink_metadata(path)
|
if std::fs::symlink_metadata(path)
|
||||||
.ok()
|
.ok()
|
||||||
.is_some_and(|metadata| FileIdentity::from_metadata(&metadata) == expected)
|
.is_some_and(|metadata| FileIdentity::from_metadata(&metadata) == expected)
|
||||||
|
|
@ -632,7 +647,7 @@ fn require_interactive_batch(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn leaf_parameters(
|
pub(super) fn leaf_parameters(
|
||||||
common_name: &str,
|
common_name: &str,
|
||||||
usage: ExtendedKeyUsagePurpose,
|
usage: ExtendedKeyUsagePurpose,
|
||||||
now: OffsetDateTime,
|
now: OffsetDateTime,
|
||||||
|
|
@ -650,13 +665,13 @@ fn leaf_parameters(
|
||||||
Ok(params)
|
Ok(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn random_signing_key() -> Result<SigningKey, ControlError> {
|
pub(super) fn random_signing_key() -> Result<SigningKey, ControlError> {
|
||||||
let mut bytes = [0_u8; 32];
|
let mut bytes = [0_u8; 32];
|
||||||
getrandom::fill(&mut bytes).map_err(|_| ControlError::InvalidCertificateProfile)?;
|
getrandom::fill(&mut bytes).map_err(|_| ControlError::InvalidCertificateProfile)?;
|
||||||
Ok(SigningKey::from_bytes(&bytes))
|
Ok(SigningKey::from_bytes(&bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enrollment_binding(
|
pub(super) fn enrollment_binding(
|
||||||
generation_identity: &GenerationIdentity,
|
generation_identity: &GenerationIdentity,
|
||||||
cli_id: ClientId,
|
cli_id: ClientId,
|
||||||
) -> Result<String, ControlError> {
|
) -> Result<String, ControlError> {
|
||||||
|
|
@ -664,15 +679,15 @@ fn enrollment_binding(
|
||||||
.map_err(ControlError::from)
|
.map_err(ControlError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn certificate_digest(certificate: impl AsRef<[u8]>) -> CertificateDigest {
|
pub(super) fn certificate_digest(certificate: impl AsRef<[u8]>) -> CertificateDigest {
|
||||||
CertificateDigest::new(sha256(certificate.as_ref()))
|
CertificateDigest::new(sha256(certificate.as_ref()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sha256(bytes: &[u8]) -> [u8; 32] {
|
pub(super) fn sha256(bytes: &[u8]) -> [u8; 32] {
|
||||||
Sha256::digest(bytes).into()
|
Sha256::digest(bytes).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn short_identity(bytes: &[u8; 32]) -> String {
|
pub(super) fn short_identity(bytes: &[u8; 32]) -> String {
|
||||||
hex(&bytes[..8])
|
hex(&bytes[..8])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -685,7 +700,7 @@ fn hex(bytes: &[u8]) -> String {
|
||||||
encoded
|
encoded
|
||||||
}
|
}
|
||||||
|
|
||||||
fn require_peer_uid(actual: u32, expected: u32) -> Result<(), ControlError> {
|
pub(super) fn require_peer_uid(actual: u32, expected: u32) -> Result<(), ControlError> {
|
||||||
if actual == expected {
|
if actual == expected {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -693,7 +708,7 @@ fn require_peer_uid(actual: u32, expected: u32) -> Result<(), ControlError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_live_session(phase: SessionPhase) -> bool {
|
pub(super) fn is_live_session(phase: SessionPhase) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
phase,
|
phase,
|
||||||
SessionPhase::Registering
|
SessionPhase::Registering
|
||||||
|
|
@ -704,9 +719,7 @@ fn is_live_session(phase: SessionPhase) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_frame<
|
fn read_frame<
|
||||||
Record: lazaret_core::records::SchemaType
|
Record: lazaret_core::records::SchemaType + serde::de::DeserializeOwned + serde::Serialize,
|
||||||
+ serde::de::DeserializeOwned
|
|
||||||
+ serde::Serialize,
|
|
||||||
>(
|
>(
|
||||||
reader: &mut impl Read,
|
reader: &mut impl Read,
|
||||||
maximum: usize,
|
maximum: usize,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,13 @@
|
||||||
|
use super::{
|
||||||
|
Arc, CONTROL_SOCKET_NAME, ControlError, ControlPaths, DeviceId, ExecutionUnavailable, File,
|
||||||
|
FileIdentity, FileTypeExt, HostPath, InodeId, InteractiveExecutionUnavailable,
|
||||||
|
InteractiveSessionExecutor, MetadataExt, MountIdentity, NonInteractiveSessionExecutor,
|
||||||
|
PUBLICATION_IDENTITY_NAME, Path, PermissionsExt, ProtocolPublicationIdentity,
|
||||||
|
PublicationOwnership, PublishedControlFace, STAGED_CONTROL_SOCKET_NAME,
|
||||||
|
STAGED_PUBLICATION_IDENTITY_NAME, ServerConfig, SessionRegistry, TrustAnchor, encode,
|
||||||
|
mount_identity, remove_if_identity_matches,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct StagedControlFace {
|
pub struct StagedControlFace {
|
||||||
listener: Option<std::os::unix::net::UnixListener>,
|
listener: Option<std::os::unix::net::UnixListener>,
|
||||||
paths: ControlPaths,
|
paths: ControlPaths,
|
||||||
|
|
@ -194,7 +204,7 @@ impl Drop for StagedControlFace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn publication_recovery_ownership(
|
pub(super) fn publication_recovery_ownership(
|
||||||
paths: &ControlPaths,
|
paths: &ControlPaths,
|
||||||
) -> Result<PublicationOwnership, ControlError> {
|
) -> Result<PublicationOwnership, ControlError> {
|
||||||
let socket_path = if paths.staged_socket.exists() {
|
let socket_path = if paths.staged_socket.exists() {
|
||||||
|
|
@ -233,4 +243,3 @@ fn publication_path_identity(
|
||||||
inode: InodeId::new(metadata.ino()),
|
inode: InodeId::new(metadata.ino()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
#[cfg(test)]
|
use std::ffi::OsString;
|
||||||
mod tests {
|
use std::sync::Arc;
|
||||||
use std::ffi::OsString;
|
use std::thread;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
use lazaret_core::identity::{
|
use lazaret_core::identity::{
|
||||||
ConfigurationFingerprint, CoordinatorId, DomainId, GenerationId, SandboxId,
|
ConfigurationFingerprint, CoordinatorId, DomainId, GenerationId, SandboxId,
|
||||||
};
|
};
|
||||||
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, ServerName};
|
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, ServerName};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn generation() -> GenerationIdentity {
|
fn generation() -> GenerationIdentity {
|
||||||
GenerationIdentity {
|
GenerationIdentity {
|
||||||
agent_profile: "trusted".into(),
|
agent_profile: "trusted".into(),
|
||||||
domain_id: DomainId::new([1; 32]),
|
domain_id: DomainId::new([1; 32]),
|
||||||
|
|
@ -20,13 +18,13 @@ mod tests {
|
||||||
coordinator_id: CoordinatorId::new([3; 32]),
|
coordinator_id: CoordinatorId::new([3; 32]),
|
||||||
sandbox_id: SandboxId::new([4; 32]),
|
sandbox_id: SandboxId::new([4; 32]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn client_config_with_certificate(
|
fn client_config_with_certificate(
|
||||||
anchor: &TrustAnchor,
|
anchor: &TrustAnchor,
|
||||||
certificate: CertificateDer<'static>,
|
certificate: CertificateDer<'static>,
|
||||||
private_key: PrivatePkcs8KeyDer<'static>,
|
private_key: PrivatePkcs8KeyDer<'static>,
|
||||||
) -> ClientConfig {
|
) -> ClientConfig {
|
||||||
let mut roots = RootCertStore::empty();
|
let mut roots = RootCertStore::empty();
|
||||||
roots.add(anchor.tls_ca_der.clone()).unwrap();
|
roots.add(anchor.tls_ca_der.clone()).unwrap();
|
||||||
let mut config = ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13])
|
let mut config = ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13])
|
||||||
|
|
@ -36,14 +34,13 @@ mod tests {
|
||||||
config.alpn_protocols = vec![CONTROL_ALPN.to_vec()];
|
config.alpn_protocols = vec![CONTROL_ALPN.to_vec()];
|
||||||
config.resumption = rustls::client::Resumption::disabled();
|
config.resumption = rustls::client::Resumption::disabled();
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handshake_pair(
|
fn handshake_pair(
|
||||||
server_config: ServerConfig,
|
server_config: ServerConfig,
|
||||||
client_config: ClientConfig,
|
client_config: ClientConfig,
|
||||||
) -> (Result<(), rustls::Error>, Result<(), rustls::Error>) {
|
) -> (Result<(), rustls::Error>, Result<(), rustls::Error>) {
|
||||||
let (mut server_socket, mut client_socket) =
|
let (mut server_socket, mut client_socket) = std::os::unix::net::UnixStream::pair().unwrap();
|
||||||
std::os::unix::net::UnixStream::pair().unwrap();
|
|
||||||
let server = thread::spawn(move || {
|
let server = thread::spawn(move || {
|
||||||
let mut connection = ServerConnection::new(Arc::new(server_config)).unwrap();
|
let mut connection = ServerConnection::new(Arc::new(server_config)).unwrap();
|
||||||
while connection.is_handshaking() {
|
while connection.is_handshaking() {
|
||||||
|
|
@ -67,23 +64,23 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
})();
|
})();
|
||||||
(server.join().unwrap(), client)
|
(server.join().unwrap(), client)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn exact_peer_uid_is_required() {
|
fn exact_peer_uid_is_required() {
|
||||||
assert!(require_peer_uid(1_000, 1_000).is_ok());
|
assert!(require_peer_uid(1_000, 1_000).is_ok());
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
require_peer_uid(1_001, 1_000),
|
require_peer_uid(1_001, 1_000),
|
||||||
Err(ControlError::UnauthorizedPeer)
|
Err(ControlError::UnauthorizedPeer)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RecordingRunner {
|
struct RecordingRunner {
|
||||||
observed: Mutex<Option<(OsString, Vec<OsString>)>>,
|
observed: Mutex<Option<(OsString, Vec<OsString>)>>,
|
||||||
exit: ProcessExit,
|
exit: ProcessExit,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NonInteractiveCommandRunner for RecordingRunner {
|
impl NonInteractiveCommandRunner for RecordingRunner {
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
target: &TargetCommand,
|
target: &TargetCommand,
|
||||||
|
|
@ -94,10 +91,10 @@ mod tests {
|
||||||
Some((target.program().to_owned(), target.arguments().to_vec()));
|
Some((target.program().to_owned(), target.arguments().to_vec()));
|
||||||
Ok(self.exit)
|
Ok(self.exit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn runsc_exec_uses_the_command_as_the_first_post_container_argument() {
|
fn runsc_exec_uses_the_command_as_the_first_post_container_argument() {
|
||||||
let runner = Arc::new(RecordingRunner {
|
let runner = Arc::new(RecordingRunner {
|
||||||
observed: Mutex::new(None),
|
observed: Mutex::new(None),
|
||||||
exit: ProcessExit {
|
exit: ProcessExit {
|
||||||
|
|
@ -142,10 +139,10 @@ mod tests {
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rustls_refuses_client_leaf_with_server_role() {
|
fn rustls_refuses_client_leaf_with_server_role() {
|
||||||
let anchor = TrustAnchor::create(generation()).unwrap();
|
let anchor = TrustAnchor::create(generation()).unwrap();
|
||||||
let key = KeyPair::generate_for(&PKCS_ED25519).unwrap();
|
let key = KeyPair::generate_for(&PKCS_ED25519).unwrap();
|
||||||
let certificate = leaf_parameters(
|
let certificate = leaf_parameters(
|
||||||
|
|
@ -163,10 +160,10 @@ mod tests {
|
||||||
);
|
);
|
||||||
let (server, client) = handshake_pair(anchor.server_config().unwrap(), client_config);
|
let (server, client) = handshake_pair(anchor.server_config().unwrap(), client_config);
|
||||||
assert!(server.is_err() || client.is_err());
|
assert!(server.is_err() || client.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rustls_refuses_server_leaf_with_client_role() {
|
fn rustls_refuses_server_leaf_with_client_role() {
|
||||||
let anchor = TrustAnchor::create(generation()).unwrap();
|
let anchor = TrustAnchor::create(generation()).unwrap();
|
||||||
let key = KeyPair::generate_for(&PKCS_ED25519).unwrap();
|
let key = KeyPair::generate_for(&PKCS_ED25519).unwrap();
|
||||||
let certificate = leaf_parameters(
|
let certificate = leaf_parameters(
|
||||||
|
|
@ -177,8 +174,7 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.signed_by(&key, &anchor.tls_ca)
|
.signed_by(&key, &anchor.tls_ca)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let server_config =
|
let server_config = ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13])
|
||||||
ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13])
|
|
||||||
.with_no_client_auth()
|
.with_no_client_auth()
|
||||||
.with_single_cert(
|
.with_single_cert(
|
||||||
vec![certificate.der().clone()],
|
vec![certificate.der().clone()],
|
||||||
|
|
@ -189,5 +185,4 @@ mod tests {
|
||||||
PublishedControlFace::client_config(&anchor.publication_identity(), None).unwrap();
|
PublishedControlFace::client_config(&anchor.publication_identity(), None).unwrap();
|
||||||
let (server, client) = handshake_pair(server_config, client_config);
|
let (server, client) = handshake_pair(server_config, client_config);
|
||||||
assert!(server.is_err() || client.is_err());
|
assert!(server.is_err() || client.is_err());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -986,5 +986,9 @@ impl EscalationCoordinator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
include!("escalation/service.rs");
|
mod local_ownership;
|
||||||
include!("escalation/local_ownership.rs");
|
mod service;
|
||||||
|
|
||||||
|
pub use local_ownership::LocalExecutionOwnership;
|
||||||
|
use local_ownership::{acknowledgment_prefix, validate_executable};
|
||||||
|
pub(crate) use service::{EscalationService, EscalationServiceHandle, EscalationServiceInputs};
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
fn acknowledgment_prefix(request_id: RequestId) -> Vec<u8> {
|
use super::{
|
||||||
|
AsFd, ExecutionOwnershipRecorder, FileType, Mode, OsStrExt, ProcessOwnershipCommitAck,
|
||||||
|
RequestId, StagedProcessOwnership, acknowledgment_file_name, fstat, io,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(super) fn acknowledgment_prefix(request_id: RequestId) -> Vec<u8> {
|
||||||
let example = acknowledgment_file_name(request_id, 0);
|
let example = acknowledgment_file_name(request_id, 0);
|
||||||
let bytes = example.as_bytes();
|
let bytes = example.as_bytes();
|
||||||
bytes[..bytes.len() - 26].to_vec()
|
bytes[..bytes.len() - 26].to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_executable(executable: impl AsFd, invoking_uid: u32) -> io::Result<()> {
|
pub(super) fn validate_executable(executable: impl AsFd, invoking_uid: u32) -> io::Result<()> {
|
||||||
let stat = fstat(executable)?;
|
let stat = fstat(executable)?;
|
||||||
let file_type = FileType::from_raw_mode(stat.st_mode);
|
let file_type = FileType::from_raw_mode(stat.st_mode);
|
||||||
let mode = Mode::from_raw_mode(stat.st_mode);
|
let mode = Mode::from_raw_mode(stat.st_mode);
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue