Remove phase vocabulary from Lazaret interfaces
This commit is contained in:
parent
3f9b1b3543
commit
e7dd95aea6
1 changed files with 547 additions and 579 deletions
299
crates/lazaret-host-coordinator/tests/cold_start.rs
Normal file
299
crates/lazaret-host-coordinator/tests/cold_start.rs
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
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::cold_start::{ColdStart, ColdStartInputs};
|
||||
use lazaret_host_coordinator::domain_store::HostCoordinatorDomainStore;
|
||||
use lazaret_linux::filesystem::{namespace_identity, try_exclusive_flock};
|
||||
use lazaret_runtime_supervisor::network::NetworkProbeRequest;
|
||||
use lazaret_runtime_supervisor::sandbox::{RootProcess, default_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 fixture = ColdStartFixture::new();
|
||||
let staged = ColdStart::stage(fixture.inputs(&fixture.escalation_client)).unwrap();
|
||||
let bundle = fixture.bundle();
|
||||
let control = fixture.control();
|
||||
let overlay = fixture.overlay();
|
||||
assert_private_artifacts(&bundle, &control);
|
||||
assert!(overlay.is_dir());
|
||||
drop(staged);
|
||||
assert!(!bundle.exists());
|
||||
assert!(!control.exists());
|
||||
assert!(!overlay.exists());
|
||||
assert!(ColdStart::stage(fixture.inputs(&fixture.invalid_escalation_client)).is_err());
|
||||
assert!(!bundle.exists());
|
||||
assert!(!control.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_git_workspace_stages_without_a_routing_runtime() {
|
||||
let fixture = ColdStartFixture::new();
|
||||
|
||||
let staged = ColdStart::stage(fixture.inputs(&fixture.escalation_client)).unwrap();
|
||||
assert!(!fixture.runtime_domain.join("git-routing").exists());
|
||||
drop(staged);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cleanup_leaves_a_replacement_directory_untouched() {
|
||||
let fixture = ColdStartFixture::new();
|
||||
let staged = ColdStart::stage(fixture.inputs(&fixture.escalation_client)).unwrap();
|
||||
let bundle = fixture.bundle();
|
||||
let moved = bundle.with_extension("moved");
|
||||
fs::rename(&bundle, &moved).unwrap();
|
||||
fs::create_dir(&bundle).unwrap();
|
||||
fs::write(bundle.join("replacement"), b"trusted replacement").unwrap();
|
||||
drop(staged);
|
||||
assert_eq!(
|
||||
fs::read(bundle.join("replacement")).unwrap(),
|
||||
b"trusted replacement"
|
||||
);
|
||||
assert!(moved.is_dir());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixed_runtime_parents_refuse_symlinks_and_wrong_modes() {
|
||||
let fixture = ColdStartFixture::new();
|
||||
let bundles = fixture.runtime_domain.join("bundles");
|
||||
fs::create_dir(&bundles).unwrap();
|
||||
fs::set_permissions(&bundles, fs::Permissions::from_mode(0o755)).unwrap();
|
||||
assert!(ColdStart::stage(fixture.inputs(&fixture.escalation_client)).is_err());
|
||||
|
||||
fs::remove_dir(&bundles).unwrap();
|
||||
let controls = fixture.runtime_domain.join("sandbox-control");
|
||||
let redirect = fixture.tree.path().join("redirect");
|
||||
fs::create_dir(&redirect).unwrap();
|
||||
std::os::unix::fs::symlink(&redirect, &controls).unwrap();
|
||||
assert!(ColdStart::stage(fixture.inputs(&fixture.escalation_client)).is_err());
|
||||
assert!(redirect.read_dir().unwrap().next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaced_runtime_domain_authority_is_not_used_for_staging() {
|
||||
let fixture = ColdStartFixture::new();
|
||||
let moved = fixture.runtime_domain.with_extension("moved");
|
||||
fs::rename(&fixture.runtime_domain, &moved).unwrap();
|
||||
fs::create_dir(&fixture.runtime_domain).unwrap();
|
||||
fs::set_permissions(&fixture.runtime_domain, fs::Permissions::from_mode(0o700)).unwrap();
|
||||
assert!(ColdStart::stage(fixture.inputs(&fixture.escalation_client)).is_err());
|
||||
assert!(fixture.runtime_domain.read_dir().unwrap().next().is_none());
|
||||
}
|
||||
|
||||
struct ColdStartFixture {
|
||||
tree: tempfile::TempDir,
|
||||
configuration: lazaret_core::configuration::ResolvedDomainConfiguration,
|
||||
runtime_domain: PathBuf,
|
||||
catalog: Catalog,
|
||||
store: Arc<HostCoordinatorDomainStore>,
|
||||
artifact: PreparedArtifact,
|
||||
artifact_authority: File,
|
||||
generation: Generation,
|
||||
container_id: ContainerId,
|
||||
escalation_client: File,
|
||||
invalid_escalation_client: File,
|
||||
}
|
||||
|
||||
impl ColdStartFixture {
|
||||
fn new() -> Self {
|
||||
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_authority = File::open(&lower).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,
|
||||
};
|
||||
Self {
|
||||
tree,
|
||||
configuration,
|
||||
runtime_domain,
|
||||
catalog,
|
||||
store,
|
||||
artifact,
|
||||
artifact_authority,
|
||||
generation,
|
||||
container_id: ContainerId::new([5; 32]),
|
||||
escalation_client: File::open("/bin/true").unwrap(),
|
||||
invalid_escalation_client: File::open("/dev/null").unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn inputs<'a>(&'a self, escalation_client: &'a File) -> ColdStartInputs<'a> {
|
||||
ColdStartInputs {
|
||||
store: Arc::clone(&self.store),
|
||||
catalog: &self.catalog,
|
||||
configuration: &self.configuration,
|
||||
generation: self.generation.clone(),
|
||||
prepared_artifact: &self.artifact,
|
||||
prepared_artifact_authority: &self.artifact_authority,
|
||||
prepared_artifact_label: Path::new(
|
||||
std::str::from_utf8(self.artifact.path.as_bytes()).unwrap(),
|
||||
),
|
||||
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: self.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: self.container_id,
|
||||
invoking_uid: getuid().as_raw(),
|
||||
escalation_client,
|
||||
sandbox_init: escalation_client,
|
||||
}
|
||||
}
|
||||
|
||||
fn bundle(&self) -> PathBuf {
|
||||
self.runtime_domain
|
||||
.join("bundles")
|
||||
.join(hex(self.container_id.as_bytes()))
|
||||
}
|
||||
|
||||
fn control(&self) -> PathBuf {
|
||||
self.runtime_domain
|
||||
.join("sandbox-control")
|
||||
.join(hex(self.container_id.as_bytes()))
|
||||
}
|
||||
|
||||
fn overlay(&self) -> PathBuf {
|
||||
self.runtime_domain
|
||||
.join("runsc-overlay")
|
||||
.join(hex(self.container_id.as_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
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[network]\ndefault_allow = [\"192.0.2.10/32\"]\n\n[profiles.default]\nimage = \"example.invalid/factory:v1\"\npersistent_dirs = []\n\n[profiles.default.readiness]\ndenied = [{{ address = \"10.20.30.40\", port = 443 }}]\npermitted = [{{ address = \"192.0.2.10\", port = 443 }}]\ndns = [{{ resolver = \"198.51.100.53\", query = \"example.invalid\" }}]\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: default_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