Confine runtime staging to retained authority
This commit is contained in:
parent
b2f178d25e
commit
fc5b2b6ca0
1 changed files with 434 additions and 173 deletions
|
|
@ -23,108 +23,198 @@ 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);
|
||||
let fixture = ProductionFixture::new();
|
||||
let staged = ProductionColdStart::stage(fixture.inputs(&fixture.escalation_client)).unwrap();
|
||||
let bundle = fixture.bundle();
|
||||
let control = fixture.control();
|
||||
assert_private_artifacts(&bundle, &control);
|
||||
drop(staged);
|
||||
assert!(!bundle.exists());
|
||||
assert!(!control.exists());
|
||||
assert!(ProductionColdStart::stage(inputs(&invalid_escalation_client)).is_err());
|
||||
assert!(
|
||||
ProductionColdStart::stage(fixture.inputs(&fixture.invalid_escalation_client)).is_err()
|
||||
);
|
||||
assert!(!bundle.exists());
|
||||
assert!(!control.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cleanup_leaves_a_replacement_directory_untouched() {
|
||||
let fixture = ProductionFixture::new();
|
||||
let staged = ProductionColdStart::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 = ProductionFixture::new();
|
||||
let bundles = fixture.runtime_domain.join("bundles");
|
||||
fs::create_dir(&bundles).unwrap();
|
||||
fs::set_permissions(&bundles, fs::Permissions::from_mode(0o755)).unwrap();
|
||||
assert!(ProductionColdStart::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!(ProductionColdStart::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 = ProductionFixture::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!(ProductionColdStart::stage(fixture.inputs(&fixture.escalation_client)).is_err());
|
||||
assert!(fixture.runtime_domain.read_dir().unwrap().next().is_none());
|
||||
}
|
||||
|
||||
struct ProductionFixture {
|
||||
tree: tempfile::TempDir,
|
||||
configuration: lazaret_core::configuration::ResolvedDomainConfiguration,
|
||||
runtime_domain: PathBuf,
|
||||
catalog: Catalog,
|
||||
store: Arc<HostCoordinatorDomainStore>,
|
||||
artifact: PreparedArtifact,
|
||||
generation: Generation,
|
||||
container_id: ContainerId,
|
||||
escalation_client: File,
|
||||
invalid_escalation_client: File,
|
||||
}
|
||||
|
||||
impl ProductionFixture {
|
||||
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 = 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,
|
||||
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) -> ProductionColdStartInputs<'a> {
|
||||
ProductionColdStartInputs {
|
||||
store: Arc::clone(&self.store),
|
||||
catalog: &self.catalog,
|
||||
configuration: &self.configuration,
|
||||
generation: self.generation.clone(),
|
||||
prepared_artifact: &self.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: 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,
|
||||
}
|
||||
}
|
||||
|
||||
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 assert_private_artifacts(bundle: &Path, control: &Path) {
|
||||
let config = bundle.join("config.json");
|
||||
let escalation_client = control.join("bin/lazaret-escalate");
|
||||
|
|
|
|||
Loading…
Reference in a new issue