Decompose oversized Rust files by responsibility
This commit is contained in:
parent
03bfe92ca0
commit
ee9201b057
1 changed files with 18648 additions and 18513 deletions
372
crates/lazaret-core/src/configuration/loading.rs
Normal file
372
crates/lazaret-core/src/configuration/loading.rs
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
/// Parses the closed trusted-host configuration document without resolving host authority.
|
||||
///
|
||||
/// # Errors
|
||||
/// Unknown, duplicate, aliased, malformed, and unknown-version input is rejected.
|
||||
pub fn parse_trusted_configuration(
|
||||
bytes: &[u8],
|
||||
) -> Result<TrustedHostConfigurationV1, ConfigurationError> {
|
||||
if bytes.len() > MAX_CONFIGURATION_BYTES {
|
||||
return Err(ConfigurationError::new(
|
||||
ConfigurationErrorKind::InvalidConfigurationSource,
|
||||
"configuration exceeds 1,048,576 bytes",
|
||||
));
|
||||
}
|
||||
let text = std::str::from_utf8(bytes).map_err(|_| {
|
||||
ConfigurationError::new(
|
||||
ConfigurationErrorKind::InvalidToml,
|
||||
"configuration is not UTF-8",
|
||||
)
|
||||
})?;
|
||||
let configuration: TrustedHostConfigurationV1 = toml::from_str(text).map_err(|error| {
|
||||
ConfigurationError::new(ConfigurationErrorKind::InvalidToml, error.to_string())
|
||||
})?;
|
||||
validate_structural_configuration(&configuration)?;
|
||||
Ok(configuration)
|
||||
}
|
||||
|
||||
/// Resolves one selected profile into move-owned host authority.
|
||||
///
|
||||
/// # Errors
|
||||
/// Loading fails atomically when source stability, strict syntax, confinement, environment,
|
||||
/// network, volume, executable, or fingerprint validation fails.
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn load_trusted_configuration(
|
||||
request: ConfigurationLoadRequest,
|
||||
) -> Result<ResolvedDomainConfiguration, ConfigurationError> {
|
||||
validate_host_base(&request.home, "HOME")?;
|
||||
validate_host_base(&request.xdg_runtime_dir, "XDG_RUNTIME_DIR")?;
|
||||
validate_profile_name(&request.selected_profile)?;
|
||||
|
||||
let configuration_path = if let Some(explicit) = &request.explicit_configuration_path {
|
||||
let declaration = explicit.as_os_str().to_str().ok_or_else(|| {
|
||||
ConfigurationError::new(
|
||||
ConfigurationErrorKind::InvalidHostInput,
|
||||
"configuration path is not UTF-8",
|
||||
)
|
||||
})?;
|
||||
expand_host_path(declaration, &request.home, None)?
|
||||
} else {
|
||||
config_home(&request).join("lazaret/config.toml")
|
||||
};
|
||||
if request.explicit_configuration_path.is_none() {
|
||||
validate_host_base(&configuration_path, "configuration path")?;
|
||||
}
|
||||
let configuration_canonical = std::fs::canonicalize(&configuration_path).map_err(io_error)?;
|
||||
let configuration_source = ConfinedPathPlan::for_canonical_path(
|
||||
&configuration_canonical,
|
||||
ConfinedPathType::RegularFile,
|
||||
)?;
|
||||
let configuration_bytes = read_stable_configuration(&configuration_source)?;
|
||||
let configuration = parse_trusted_configuration(&configuration_bytes)?;
|
||||
let selected = configuration
|
||||
.profiles()
|
||||
.get(&request.selected_profile)
|
||||
.ok_or_else(|| {
|
||||
ConfigurationError::new(
|
||||
ConfigurationErrorKind::UnknownProfile,
|
||||
request.selected_profile.clone(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let workspace_canonical = std::fs::canonicalize(&request.workspace).map_err(io_error)?;
|
||||
let workspace = confined_existing(&workspace_canonical, ConfinedPathType::Directory, false)?;
|
||||
let workspace_bytes = workspace_canonical.as_os_str().as_bytes();
|
||||
let domain_id = derive_domain_id(&request.selected_profile, workspace_bytes)?;
|
||||
|
||||
let data_root_declaration = configuration.storage().data_root.as_deref();
|
||||
let storage_path = match data_root_declaration {
|
||||
Some(declaration) => expand_host_path(declaration, &request.home, None)?,
|
||||
None => data_home(&request).join("lazaret"),
|
||||
};
|
||||
let runtime_path = request.xdg_runtime_dir.join("lazaret");
|
||||
let invoking_uid = rustix::process::getuid().as_raw();
|
||||
let storage_canonical = ensure_owned_directory(&storage_path, invoking_uid)?;
|
||||
let runtime_canonical = ensure_owned_directory(&runtime_path, invoking_uid)?;
|
||||
let profile_path = storage_canonical
|
||||
.join("profiles")
|
||||
.join(&request.selected_profile);
|
||||
let profile_canonical = ensure_owned_directory(&profile_path, invoking_uid)?;
|
||||
let domain_hex = hex_lower(domain_id.as_bytes());
|
||||
let persistent_domain_canonical = ensure_owned_directory(
|
||||
&storage_canonical.join("domains").join(&domain_hex),
|
||||
invoking_uid,
|
||||
)?;
|
||||
let runtime_domain_canonical = ensure_owned_directory(
|
||||
&runtime_canonical.join("domains").join(&domain_hex),
|
||||
invoking_uid,
|
||||
)?;
|
||||
|
||||
let storage_root = confined_existing(&storage_canonical, ConfinedPathType::Directory, false)?;
|
||||
let runtime_root = confined_existing(&runtime_canonical, ConfinedPathType::Directory, false)?;
|
||||
let profile_root = confined_existing(&profile_canonical, ConfinedPathType::Directory, false)?;
|
||||
let persistent_domain_root = confined_existing(
|
||||
&persistent_domain_canonical,
|
||||
ConfinedPathType::Directory,
|
||||
false,
|
||||
)?;
|
||||
let runtime_domain_root = confined_existing(
|
||||
&runtime_domain_canonical,
|
||||
ConfinedPathType::Directory,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let core_paths = [
|
||||
configuration_canonical.as_path(),
|
||||
workspace_canonical.as_path(),
|
||||
storage_canonical.as_path(),
|
||||
runtime_canonical.as_path(),
|
||||
];
|
||||
let core_authorities = [
|
||||
&configuration_source,
|
||||
&workspace,
|
||||
&storage_root,
|
||||
&runtime_root,
|
||||
];
|
||||
require_pairwise_disjoint(
|
||||
&core_paths,
|
||||
&core_authorities,
|
||||
ConfigurationErrorKind::PathConflict,
|
||||
)?;
|
||||
|
||||
let persistent_dirs = validate_persistent_destinations(
|
||||
&selected.persistent_dirs,
|
||||
request.sandbox_layout.protected_destinations(),
|
||||
)?;
|
||||
let (effective_allow_prefixes, effective_deny_prefixes) =
|
||||
resolve_network(&configuration, selected)?;
|
||||
let network_readiness = resolve_network_readiness(
|
||||
&selected.readiness,
|
||||
&effective_allow_prefixes,
|
||||
&effective_deny_prefixes,
|
||||
)?;
|
||||
let environment_references = resolve_environment_references(selected)?;
|
||||
|
||||
let mut volumes = Vec::with_capacity(selected.volumes.len());
|
||||
let mut resolved_volume_records = Vec::with_capacity(selected.volumes.len());
|
||||
let mut volume_paths: Vec<PathBuf> = Vec::with_capacity(selected.volumes.len());
|
||||
for volume in &selected.volumes {
|
||||
validate_sandbox_path(&volume.container)?;
|
||||
require_strict_child_of_one(&volume.container, &persistent_dirs)?;
|
||||
require_clear_of_protected(
|
||||
&volume.container,
|
||||
request.sandbox_layout.protected_destinations(),
|
||||
)?;
|
||||
let under_profile = volume.host.starts_with("{profile_dir}/");
|
||||
if volume.create && (volume.kind != VolumeKind::Directory || !under_profile) {
|
||||
return Err(ConfigurationError::new(
|
||||
ConfigurationErrorKind::InvalidVolume,
|
||||
"create=true requires a directory strictly beneath {profile_dir}",
|
||||
));
|
||||
}
|
||||
let source_path = expand_host_path(&volume.host, &request.home, Some(&profile_canonical))?;
|
||||
let canonical = if volume.create {
|
||||
ensure_owned_directory(&source_path, invoking_uid)?
|
||||
} else {
|
||||
std::fs::canonicalize(&source_path).map_err(io_error)?
|
||||
};
|
||||
if !volume.create && canonical.as_os_str().as_bytes() != source_path.as_os_str().as_bytes()
|
||||
{
|
||||
return Err(ConfigurationError::new(
|
||||
ConfigurationErrorKind::InvalidVolume,
|
||||
"volume source contains a symlink",
|
||||
));
|
||||
}
|
||||
let expected = match volume.kind {
|
||||
VolumeKind::File => ConfinedPathType::RegularFile,
|
||||
VolumeKind::Directory => ConfinedPathType::Directory,
|
||||
};
|
||||
let source_plan = confined_existing(&canonical, expected, false)?;
|
||||
for (forbidden, forbidden_authority) in core_paths.iter().zip(core_authorities) {
|
||||
if paths_intersect(&canonical, forbidden) {
|
||||
let allowed_profile_child = under_profile
|
||||
&& *forbidden == storage_canonical.as_path()
|
||||
&& is_strict_descendant(&canonical, &profile_canonical);
|
||||
if !allowed_profile_child {
|
||||
return Err(ConfigurationError::new(
|
||||
ConfigurationErrorKind::PathConflict,
|
||||
"volume source intersects a protected host tree",
|
||||
));
|
||||
}
|
||||
}
|
||||
if source_plan.aliases(forbidden_authority) {
|
||||
return Err(ConfigurationError::new(
|
||||
ConfigurationErrorKind::PathConflict,
|
||||
"volume source aliases a protected host authority",
|
||||
));
|
||||
}
|
||||
}
|
||||
if volume_paths
|
||||
.iter()
|
||||
.any(|other| paths_intersect(&canonical, other))
|
||||
|| volumes
|
||||
.iter()
|
||||
.any(|other: &ResolvedVolumeAuthority| source_plan.aliases(&other.source_plan))
|
||||
{
|
||||
return Err(ConfigurationError::new(
|
||||
ConfigurationErrorKind::PathConflict,
|
||||
"volume sources overlap",
|
||||
));
|
||||
}
|
||||
let record = ResolvedVolume {
|
||||
canonical_host_source: host_path(&canonical),
|
||||
container_path: volume.container.clone(),
|
||||
kind: volume.kind,
|
||||
access: volume.access,
|
||||
create: volume.create,
|
||||
};
|
||||
volume_paths.push(canonical);
|
||||
resolved_volume_records.push(record.clone());
|
||||
volumes.push(ResolvedVolumeAuthority {
|
||||
record,
|
||||
source_plan,
|
||||
});
|
||||
}
|
||||
require_pairwise_non_ancestral(
|
||||
selected
|
||||
.volumes
|
||||
.iter()
|
||||
.map(|volume| volume.container.as_str()),
|
||||
ConfigurationErrorKind::InvalidVolume,
|
||||
)?;
|
||||
|
||||
let mut auto_approval_rules = Vec::new();
|
||||
let mut resolved_auto_approval_records = Vec::new();
|
||||
if !selected.escalation.enabled && !selected.escalation.auto_approve.is_empty() {
|
||||
return Err(ConfigurationError::new(
|
||||
ConfigurationErrorKind::InvalidAutoApproval,
|
||||
"disabled escalation profile contains auto-approval rules",
|
||||
));
|
||||
}
|
||||
for rule in &selected.escalation.auto_approve {
|
||||
validate_text(&rule.executable, "auto-approval executable")?;
|
||||
for argument in &rule.args {
|
||||
validate_text(argument, "auto-approval argument")?;
|
||||
}
|
||||
if rule.allow_extra_args && rule.args.is_empty() {
|
||||
return Err(ConfigurationError::new(
|
||||
ConfigurationErrorKind::InvalidAutoApproval,
|
||||
"allow_extra_args requires a nonempty configured prefix",
|
||||
));
|
||||
}
|
||||
let executable_path = expand_host_path(&rule.executable, &request.home, None)?;
|
||||
let canonical = std::fs::canonicalize(&executable_path).map_err(io_error)?;
|
||||
if core_paths
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(volume_paths.iter().map(PathBuf::as_path))
|
||||
.chain([
|
||||
profile_canonical.as_path(),
|
||||
persistent_domain_canonical.as_path(),
|
||||
runtime_domain_canonical.as_path(),
|
||||
])
|
||||
.any(|forbidden| paths_intersect(&canonical, forbidden))
|
||||
{
|
||||
return Err(ConfigurationError::new(
|
||||
ConfigurationErrorKind::InvalidAutoApproval,
|
||||
"auto-approval executable intersects a writable or control tree",
|
||||
));
|
||||
}
|
||||
let executable_plan = confined_existing(&canonical, ConfinedPathType::RegularFile, true)?;
|
||||
if core_authorities
|
||||
.iter()
|
||||
.copied()
|
||||
.chain([&profile_root, &persistent_domain_root, &runtime_domain_root])
|
||||
.any(|forbidden| executable_plan.aliases(forbidden))
|
||||
|| volumes
|
||||
.iter()
|
||||
.any(|volume| executable_plan.aliases(&volume.source_plan))
|
||||
{
|
||||
return Err(ConfigurationError::new(
|
||||
ConfigurationErrorKind::InvalidAutoApproval,
|
||||
"auto-approval executable aliases a writable or control authority",
|
||||
));
|
||||
}
|
||||
let executable = executable_plan.reopen_exact(OFlags::RDONLY | OFlags::NONBLOCK)?;
|
||||
validate_executable(&executable, invoking_uid)?;
|
||||
let record = ResolvedAutoApprovalRule {
|
||||
canonical_executable: host_path(&canonical),
|
||||
args: rule.args.clone(),
|
||||
allow_extra_args: rule.allow_extra_args,
|
||||
};
|
||||
if resolved_auto_approval_records.contains(&record) {
|
||||
return Err(ConfigurationError::new(
|
||||
ConfigurationErrorKind::InvalidAutoApproval,
|
||||
"duplicate normalized auto-approval rule",
|
||||
));
|
||||
}
|
||||
resolved_auto_approval_records.push(record.clone());
|
||||
auto_approval_rules.push(ResolvedAutoApproval {
|
||||
record,
|
||||
executable_plan,
|
||||
invoking_uid,
|
||||
});
|
||||
}
|
||||
|
||||
let configuration_descriptor = configuration_source.open_authority()?;
|
||||
validate_configuration_stat(&rustix::fs::fstat(&configuration_descriptor).map_err(io_error)?)?;
|
||||
for authority in [
|
||||
&workspace,
|
||||
&storage_root,
|
||||
&runtime_root,
|
||||
&profile_root,
|
||||
&persistent_domain_root,
|
||||
&runtime_domain_root,
|
||||
] {
|
||||
drop(authority.open_authority()?);
|
||||
}
|
||||
for volume in &volumes {
|
||||
drop(volume.open_source()?);
|
||||
}
|
||||
for rule in &auto_approval_rules {
|
||||
let executable = rule
|
||||
.executable_plan
|
||||
.reopen_exact(OFlags::RDONLY | OFlags::NONBLOCK)?;
|
||||
validate_executable(&executable, invoking_uid)?;
|
||||
}
|
||||
|
||||
let fingerprint_record = ConfigurationFingerprintRecordV1 {
|
||||
selected_profile: request.selected_profile.clone(),
|
||||
storage_root: host_path(&storage_canonical),
|
||||
runtime_root: host_path(&runtime_canonical),
|
||||
image: selected.image.clone(),
|
||||
isolate_gitignored: selected.isolate_gitignored,
|
||||
persistent_dirs,
|
||||
environment_references,
|
||||
volumes: resolved_volume_records,
|
||||
effective_allow_prefixes: effective_allow_prefixes
|
||||
.iter()
|
||||
.map(|prefix| (prefix.address, prefix.prefix_length))
|
||||
.collect(),
|
||||
effective_deny_prefixes: effective_deny_prefixes
|
||||
.iter()
|
||||
.map(|prefix| (prefix.address, prefix.prefix_length))
|
||||
.collect(),
|
||||
network_readiness,
|
||||
escalation_enabled: selected.escalation.enabled,
|
||||
auto_approval_rules: resolved_auto_approval_records,
|
||||
};
|
||||
let configuration_fingerprint = derive_configuration_fingerprint(&fingerprint_record)?;
|
||||
let ephemeral_environment = resolve_environment_values(
|
||||
&fingerprint_record.environment_references,
|
||||
&request.environment,
|
||||
)?;
|
||||
|
||||
Ok(ResolvedDomainConfiguration {
|
||||
selected_profile: request.selected_profile,
|
||||
domain_id,
|
||||
configuration_fingerprint,
|
||||
fingerprint_record,
|
||||
effective_allow_prefixes,
|
||||
effective_deny_prefixes,
|
||||
ephemeral_environment,
|
||||
configuration_source,
|
||||
workspace,
|
||||
storage_root,
|
||||
runtime_root,
|
||||
profile_root,
|
||||
persistent_domain_root,
|
||||
runtime_domain_root,
|
||||
volumes,
|
||||
auto_approval_rules,
|
||||
})
|
||||
}
|
||||
Loading…
Reference in a new issue