Bind cold start to production authorities
This commit is contained in:
parent
a9c9cf7ab3
commit
b2f178d25e
2 changed files with 816 additions and 30 deletions
|
|
@ -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();
|
||||
|
|
|
|||
185
crates/lazaret-host-coordinator/tests/production.rs
Normal file
185
crates/lazaret-host-coordinator/tests/production.rs
Normal 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
|
||||
})
|
||||
}
|
||||
Loading…
Reference in a new issue