Bind cold start to production authorities

This commit is contained in:
ms2k 2026-07-16 18:39:03 +09:00
commit b2f178d25e
2 changed files with 816 additions and 30 deletions

View file

@ -1,3 +1,4 @@
use std::cell::Cell;
use std::fs::OpenOptions;
use std::sync::{Arc, Mutex};
@ -225,6 +226,36 @@ fn complete_startup(
lifecycle.publish_domain().unwrap();
}
#[test]
fn bootstrap_owner_guard_failure_uses_exact_modeled_disconnect_transition() {
let directory = tempfile::tempdir().unwrap();
let store = open_store(&directory);
let (domain_adapter, session_adapter) = adapters();
let actions = Arc::clone(&domain_adapter.actions);
let mut lifecycle = HostCoordinatorLifecycle::new(&store, domain_adapter, session_adapter);
let checks = Cell::new(0_u8);
let owner = session(10, 1);
let published = lifecycle
.bootstrap_new_domain_while_owner_live(generation(1), owner.clone(), || {
checks.set(checks.get() + 1);
checks.get() < 3
})
.unwrap();
assert!(!published);
assert_eq!(lifecycle.phase(), DomainPhase::Failed);
assert!(!lifecycle.is_published());
assert_eq!(
lifecycle.session_phase(owner.session_id),
Some(SessionPhase::Closed)
);
assert!(
!actions
.lock()
.unwrap()
.contains(&LifecycleAction::PublishDomain)
);
}
#[test]
fn exact_start_join_disconnect_and_teardown_actions_are_ordered() {
let directory = tempfile::tempdir().unwrap();

View file

@ -0,0 +1,185 @@
use std::collections::BTreeMap;
use std::fs::{self, File, OpenOptions};
use std::os::unix::ffi::{OsStrExt, OsStringExt};
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use lazaret_core::configuration::{ConfigurationLoadRequest, load_trusted_configuration};
use lazaret_core::identity::{
ContainerId, CoordinatorId, DeviceId, GenerationId, HostPath, IdMapDigest, InodeId, PolicyId,
PreparedArtifactId, SandboxId, SelectedManifestDigest, SourceIdentity,
};
use lazaret_core::records::{
ArtifactState, Domain, DurableDecision, Generation, IpAddress, PreparedArtifact,
};
use lazaret_host_coordinator::domain_store::HostCoordinatorDomainStore;
use lazaret_host_coordinator::production::{ProductionColdStart, ProductionColdStartInputs};
use lazaret_linux::filesystem::{namespace_identity, try_exclusive_flock};
use lazaret_runtime_supervisor::network::NetworkProbeRequest;
use lazaret_runtime_supervisor::sandbox::{RootProcess, production_sandbox_layout};
use lazaret_storage_catalog::Catalog;
use rustix::process::getuid;
#[test]
fn validated_inputs_stage_one_complete_plan_and_drop_exact_runtime_artifacts() {
let tree = tempfile::tempdir().unwrap();
let configuration = configuration(tree.path());
let persistent_domain = host_path(
configuration
.persistent_domain_root()
.canonical_label()
.as_bytes(),
);
let runtime_domain = host_path(
configuration
.runtime_domain_root()
.canonical_label()
.as_bytes(),
);
let catalog = Catalog::new(
tree.path().join("catalog.redb"),
tree.path().join("catalog.lock"),
);
catalog.initialize().unwrap();
let domain = Domain {
domain_id: configuration.domain_id(),
profile_name: configuration.selected_profile().to_owned(),
canonical_workspace: configuration.workspace().canonical_label().clone(),
};
let lock = OpenOptions::new()
.read(true)
.write(true)
.create_new(true)
.open(persistent_domain.join("domain.lock"))
.unwrap();
let store = Arc::new(
HostCoordinatorDomainStore::open(
try_exclusive_flock(lock).unwrap(),
&persistent_domain.join("domain.redb"),
&domain,
)
.unwrap(),
);
let lower = tree.path().join("prepared-lower");
fs::create_dir(&lower).unwrap();
let metadata = lower.metadata().unwrap();
let artifact = PreparedArtifact {
prepared_artifact_id: PreparedArtifactId::new([8; 32]),
source_identity: SourceIdentity::new(b"synthetic-spike".to_vec()),
selected_manifest_digest: SelectedManifestDigest::new([9; 32]),
policy_id: PolicyId::new([10; 32]),
id_map_digest: IdMapDigest::new([11; 32]),
path: HostPath::new(lower.as_os_str().as_bytes().to_vec()),
device: DeviceId::new(metadata.dev()),
inode: InodeId::new(metadata.ino()),
state: ArtifactState::Available,
};
let generation = Generation {
domain_id: configuration.domain_id(),
generation_id: GenerationId::new(1),
configuration_fingerprint: configuration.configuration_fingerprint(),
coordinator_id: CoordinatorId::new([2; 32]),
network_sidecar_id: lazaret_core::identity::NetworkSidecarId::new([3; 32]),
sandbox_id: SandboxId::new([4; 32]),
durable_decision: DurableDecision::None,
};
let container_id = ContainerId::new([5; 32]);
let escalation_client = File::open("/bin/true").unwrap();
let invalid_escalation_client = File::open("/dev/null").unwrap();
let inputs = |escalation_client| ProductionColdStartInputs {
store: Arc::clone(&store),
catalog: &catalog,
configuration: &configuration,
generation: generation.clone(),
prepared_artifact: &artifact,
root_process: RootProcess::new("/bin/sh", ["-c".into(), "sleep infinity".into()]).unwrap(),
resolver_addresses: vec![IpAddress::V4([10, 0, 2, 3])],
network_probe: NetworkProbeRequest {
domain_id: configuration.domain_id(),
generation_id: GenerationId::new(1),
enforcement_namespace: namespace_identity(Path::new("/proc/self/ns/net")).unwrap(),
denied: Vec::new(),
permitted: Vec::new(),
dns: Vec::new(),
},
reachability: lazaret_core::network::ReachabilityExpectation {
denied_destinations: Vec::new(),
permitted_destinations: Vec::new(),
dns_queries: Vec::new(),
},
container_id,
invoking_uid: getuid().as_raw(),
escalation_client,
};
let staged = ProductionColdStart::stage(inputs(&escalation_client)).unwrap();
let name = hex(container_id.as_bytes());
let bundle = runtime_domain.join("bundles").join(&name);
let control = runtime_domain.join("sandbox-control").join(&name);
assert_private_artifacts(&bundle, &control);
drop(staged);
assert!(!bundle.exists());
assert!(!control.exists());
assert!(ProductionColdStart::stage(inputs(&invalid_escalation_client)).is_err());
assert!(!bundle.exists());
assert!(!control.exists());
}
fn assert_private_artifacts(bundle: &Path, control: &Path) {
let config = bundle.join("config.json");
let escalation_client = control.join("bin/lazaret-escalate");
assert!(config.is_file());
assert!(escalation_client.is_file());
assert!(control.join("credentials").is_dir());
assert_eq!(mode(bundle), 0o700);
assert_eq!(mode(control), 0o700);
assert_eq!(mode(&config), 0o600);
assert_eq!(mode(&escalation_client), 0o700);
}
fn mode(path: &Path) -> u32 {
path.metadata().unwrap().permissions().mode() & 0o777
}
fn configuration(root: &Path) -> lazaret_core::configuration::ResolvedDomainConfiguration {
let configuration_directory = root.join("configuration");
let workspace = root.join("workspace");
let runtime = root.join("runtime");
fs::create_dir(&configuration_directory).unwrap();
fs::create_dir(&workspace).unwrap();
fs::create_dir(&runtime).unwrap();
let configuration_path = configuration_directory.join("lazaret.toml");
fs::write(
&configuration_path,
format!(
"schema = \"lazaret-trusted-host-config-v1\"\n\n[storage]\ndata_root = \"{}\"\n\n[profiles.default]\nimage = \"example.invalid/factory:v1\"\npersistent_dirs = []\n",
root.join("data").display()
),
)
.unwrap();
load_trusted_configuration(ConfigurationLoadRequest {
explicit_configuration_path: Some(configuration_path),
selected_profile: "default".into(),
workspace,
home: root.join("home"),
xdg_config_home: None,
xdg_data_home: None,
xdg_runtime_dir: runtime,
environment: BTreeMap::new(),
sandbox_layout: production_sandbox_layout().unwrap(),
})
.unwrap()
}
fn host_path(bytes: &[u8]) -> PathBuf {
PathBuf::from(std::ffi::OsString::from_vec(bytes.to_vec()))
}
fn hex(bytes: &[u8]) -> String {
use std::fmt::Write as _;
bytes.iter().fold(String::new(), |mut output, byte| {
write!(output, "{byte:02x}").unwrap();
output
})
}