Compose protected runtime files before sandbox handoff

This commit is contained in:
ms2k 2026-07-22 00:40:37 +09:00
commit af9b4de85a
3 changed files with 103 additions and 2 deletions

View file

@ -180,6 +180,10 @@ impl<'a> ColdStart<'a> {
.map_err(|error| failure(format!("Git-routing staging: {error}")))?; .map_err(|error| failure(format!("Git-routing staging: {error}")))?;
let expected_network_namespace = namespace_identity(Path::new(NETWORK_NAMESPACE_PATH)) let expected_network_namespace = namespace_identity(Path::new(NETWORK_NAMESPACE_PATH))
.map_err(|error| failure(format!("network namespace: {error}")))?; .map_err(|error| failure(format!("network namespace: {error}")))?;
let network_policy = derive_ipv4_policy(inputs.configuration)
.map_err(|error| failure(format!("network policy: {error}")))?;
runtime_files::replace_protected_runtime_files(&state_anchor_layout, &network_policy)
.map_err(|error| failure(format!("protected runtime files: {error}")))?;
let request = SandboxBuildRequest { let request = SandboxBuildRequest {
configuration: inputs.configuration, configuration: inputs.configuration,
container_id: inputs.container_id, container_id: inputs.container_id,
@ -198,8 +202,6 @@ impl<'a> ColdStart<'a> {
staging staging
.write_bundle(sandbox_bundle.specification_json()) .write_bundle(sandbox_bundle.specification_json())
.map_err(|error| failure(format!("bundle staging: {error}")))?; .map_err(|error| failure(format!("bundle staging: {error}")))?;
let network_policy = derive_ipv4_policy(inputs.configuration)
.map_err(|error| failure(format!("network policy: {error}")))?;
let ownership = ProcessOwnershipContext { let ownership = ProcessOwnershipContext {
domain_id: inputs.generation.domain_id, domain_id: inputs.generation.domain_id,
generation_id: inputs.generation.generation_id, generation_id: inputs.generation.generation_id,
@ -414,6 +416,7 @@ fn require_inputs(inputs: &ColdStartInputs<'_>) -> Result<(), ColdStartError> {
} }
mod git_routing; mod git_routing;
mod runtime_files;
mod runtime_staging; mod runtime_staging;
use git_routing::GitRoutingLayout; use git_routing::GitRoutingLayout;

View file

@ -0,0 +1,25 @@
use std::net::{Ipv4Addr, Ipv6Addr};
use lazaret_core::network::CanonicalDerivedNetworkPolicy;
use lazaret_core::records::IpAddress;
use lazaret_state_anchor::{ProtectedRuntimeFiles, StateAnchorError, StateAnchorLayout};
pub(super) fn replace_protected_runtime_files(
layout: &StateAnchorLayout<'_>,
policy: &CanonicalDerivedNetworkPolicy,
) -> Result<(), StateAnchorError> {
let resolv_conf = policy
.policy()
.resolvers
.iter()
.map(|resolver| match resolver.address {
IpAddress::V4(address) => format!("nameserver {}\n", Ipv4Addr::from(address)),
IpAddress::V6(address) => format!("nameserver {}\n", Ipv6Addr::from(address)),
})
.collect::<String>();
layout.replace_protected_runtime_files(&ProtectedRuntimeFiles {
resolv_conf: resolv_conf.as_bytes(),
hosts: b"127.0.0.1 localhost\n",
hostname: b"lazaret\n",
})
}

View file

@ -18,6 +18,7 @@ use lazaret_linux::user_namespace::{
REQUIRED_SUBORDINATE_IDS, UserNamespaceIdRange, UserNamespaceMap, REQUIRED_SUBORDINATE_IDS, UserNamespaceIdRange, UserNamespaceMap,
}; };
use lazaret_runtime_supervisor::sandbox::{RootProcess, default_sandbox_layout}; use lazaret_runtime_supervisor::sandbox::{RootProcess, default_sandbox_layout};
use lazaret_state_anchor::StateAnchorLayout;
use lazaret_storage_catalog::Catalog; use lazaret_storage_catalog::Catalog;
use rustix::process::{getgid, getuid}; use rustix::process::{getgid, getuid};
@ -48,6 +49,70 @@ fn non_git_workspace_stages_without_a_routing_runtime() {
drop(staged); drop(staged);
} }
#[test]
fn production_stage_composes_derived_resolver_outside_the_prepared_lower() {
let fixture = ColdStartFixture::new();
let image_resolver = fixture.prepared_lower.join("etc/resolv.conf");
let upper_resolver = fixture
.persistent_domain
.join("state-anchor/upper/etc/resolv.conf");
let empty_runtime_resolver = StateAnchorLayout::create(&fixture.configuration).unwrap();
assert_eq!(
fs::read(empty_runtime_resolver.runtime_files().join("resolv.conf")).unwrap(),
b""
);
drop(empty_runtime_resolver);
let staged = ColdStart::stage(fixture.inputs(&fixture.escalation_client)).unwrap();
assert_eq!(fs::read(&image_resolver).unwrap(), b"image resolver\n");
assert!(!upper_resolver.exists());
assert_eq!(
fs::read(
fixture
.runtime_domain
.join("state-anchor/runtime-files/resolv.conf")
)
.unwrap(),
b"nameserver 10.137.54.3\n"
);
assert_eq!(
fs::read(
fixture
.runtime_domain
.join("state-anchor/runtime-files/hosts")
)
.unwrap(),
b"127.0.0.1 localhost\n"
);
assert_eq!(
fs::read(
fixture
.runtime_domain
.join("state-anchor/runtime-files/hostname")
)
.unwrap(),
b"lazaret\n"
);
let specification: serde_json::Value =
serde_json::from_slice(&fs::read(fixture.bundle().join("config.json")).unwrap()).unwrap();
let resolver_mount = specification["mounts"]
.as_array()
.unwrap()
.iter()
.find(|mount| mount["destination"] == "/etc/resolv.conf")
.unwrap();
assert_eq!(
resolver_mount["source"],
fixture
.runtime_domain
.join("state-anchor/runtime-files/resolv.conf")
.display()
.to_string()
);
drop(staged);
}
#[test] #[test]
fn cleanup_leaves_a_replacement_directory_untouched() { fn cleanup_leaves_a_replacement_directory_untouched() {
let fixture = ColdStartFixture::new(); let fixture = ColdStartFixture::new();
@ -96,7 +161,9 @@ fn replaced_runtime_domain_authority_is_not_used_for_staging() {
struct ColdStartFixture { struct ColdStartFixture {
tree: tempfile::TempDir, tree: tempfile::TempDir,
configuration: lazaret_core::configuration::ResolvedDomainConfiguration, configuration: lazaret_core::configuration::ResolvedDomainConfiguration,
persistent_domain: PathBuf,
runtime_domain: PathBuf, runtime_domain: PathBuf,
prepared_lower: PathBuf,
catalog: Catalog, catalog: Catalog,
store: Arc<HostCoordinatorDomainStore>, store: Arc<HostCoordinatorDomainStore>,
artifact: PreparedArtifact, artifact: PreparedArtifact,
@ -149,6 +216,10 @@ impl ColdStartFixture {
); );
let lower = tree.path().join("prepared-lower"); let lower = tree.path().join("prepared-lower");
fs::create_dir(&lower).unwrap(); fs::create_dir(&lower).unwrap();
fs::create_dir(lower.join("etc")).unwrap();
fs::write(lower.join("etc/resolv.conf"), b"image resolver\n").unwrap();
fs::write(lower.join("etc/hosts"), b"image hosts\n").unwrap();
fs::write(lower.join("etc/hostname"), b"image hostname\n").unwrap();
let metadata = lower.metadata().unwrap(); let metadata = lower.metadata().unwrap();
let artifact_authority = File::open(&lower).unwrap(); let artifact_authority = File::open(&lower).unwrap();
let artifact = PreparedArtifact { let artifact = PreparedArtifact {
@ -174,7 +245,9 @@ impl ColdStartFixture {
Self { Self {
tree, tree,
configuration, configuration,
persistent_domain,
runtime_domain, runtime_domain,
prepared_lower: lower,
catalog, catalog,
store, store,
artifact, artifact,