Decouple client contracts and close private role registries
This commit is contained in:
parent
786049534d
commit
22325aa76a
9 changed files with 1041 additions and 321 deletions
|
|
@ -13,10 +13,10 @@ path = "src/main.rs"
|
|||
clap.workspace = true
|
||||
ed25519-dalek.workspace = true
|
||||
getrandom.workspace = true
|
||||
lazaret-control-client = { version = "=0.0.0", path = "../lazaret-control-client" }
|
||||
lazaret-core = { version = "=0.0.0", path = "../lazaret-core" }
|
||||
lazaret-host-coordinator = { version = "=0.0.0", path = "../lazaret-host-coordinator" }
|
||||
lazaret-domain-state = { version = "=0.0.0", path = "../lazaret-domain-state" }
|
||||
lazaret-linux = { version = "=0.0.0", path = "../lazaret-linux" }
|
||||
lazaret-runtime-supervisor = { version = "=0.0.0", path = "../lazaret-runtime-supervisor" }
|
||||
rustix.workspace = true
|
||||
rustls.workspace = true
|
||||
sha2.workspace = true
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::ffi::OsString;
|
|||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use lazaret_host_coordinator::domain_store::StateResetScopeV1;
|
||||
use lazaret_core::records::StateResetScopeV1;
|
||||
|
||||
use crate::ClientCommand;
|
||||
use crate::state::{StateCommand, StateOperation};
|
||||
|
|
|
|||
|
|
@ -8,19 +8,17 @@ use std::os::unix::fs::MetadataExt;
|
|||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use lazaret_control_client::{ControlClient, ControlClientError};
|
||||
use lazaret_core::configuration::{
|
||||
ConfigurationLoadRequest, ResolvedDomainConfiguration, load_trusted_configuration,
|
||||
};
|
||||
use lazaret_host_coordinator::client_bootstrap::PRIVATE_CLIENT_BOOTSTRAP_ROLE;
|
||||
use lazaret_host_coordinator::control::{ControlError, PublishedControlFace};
|
||||
use lazaret_host_coordinator::waiter::{
|
||||
BOOTSTRAP_WAITER_SOCKET_NAME, BootstrapWaitOutcome, BootstrapWaitRequest, read_frame,
|
||||
write_frame,
|
||||
use lazaret_core::protocol::{
|
||||
BOOTSTRAP_WAITER_SOCKET_NAME, BootstrapWaitOutcome, BootstrapWaitRequest,
|
||||
PRIVATE_CLIENT_BOOTSTRAP_ROLE, read_waiter_frame, write_waiter_frame,
|
||||
};
|
||||
use lazaret_linux::filesystem::try_exclusive_flock;
|
||||
use lazaret_linux::platform::{PlatformObservation, check_platform};
|
||||
use lazaret_linux::process::{BootstrapChild, TargetCommand, child_broker};
|
||||
use lazaret_runtime_supervisor::sandbox::default_sandbox_layout;
|
||||
use rustix::fs::{Mode, OFlags, openat};
|
||||
use rustix::pipe::{PipeFlags, pipe_with};
|
||||
|
||||
|
|
@ -80,7 +78,7 @@ impl CoordinatorClientBackend {
|
|||
|
||||
fn discover(&self) -> Result<Discovery, CoordinatorClientError> {
|
||||
let configuration = self.configuration()?;
|
||||
match PublishedControlFace::discover_confined(
|
||||
match ControlClient::discover_confined(
|
||||
configuration.runtime_domain_root(),
|
||||
rustix::process::getuid().as_raw(),
|
||||
) {
|
||||
|
|
@ -96,7 +94,7 @@ impl CoordinatorClientBackend {
|
|||
authority,
|
||||
))))
|
||||
}
|
||||
Err(ControlError::Io(error)) if error.kind() == io::ErrorKind::NotFound => {
|
||||
Err(ControlClientError::Io(error)) if error.kind() == io::ErrorKind::NotFound => {
|
||||
Ok(Discovery::Absent)
|
||||
}
|
||||
Ok(_) | Err(_) => Ok(Discovery::Stale),
|
||||
|
|
@ -292,17 +290,17 @@ impl CoordinatorClientBackend {
|
|||
.set_read_timeout(Some(observation_remaining(observer)?))
|
||||
.map_err(failure)
|
||||
.map_err(ClientError::Backend)?;
|
||||
write_frame(&mut stream, request).map_err(|error| {
|
||||
write_waiter_frame(&mut stream, request).map_err(|error| {
|
||||
ClientError::Backend(failure(format!("bootstrap waiter request failed: {error}")))
|
||||
})?;
|
||||
let first: BootstrapWaitOutcome = read_frame(&mut stream).map_err(|error| {
|
||||
let first: BootstrapWaitOutcome = read_waiter_frame(&mut stream).map_err(|error| {
|
||||
ClientError::Backend(failure(format!(
|
||||
"bootstrap waiter admission failed: {error}"
|
||||
)))
|
||||
})?;
|
||||
let mut action = observer.waiter(first, false);
|
||||
if action == WaiterAction::AwaitTerminal {
|
||||
action = match read_frame(&mut stream) {
|
||||
action = match read_waiter_frame(&mut stream) {
|
||||
Ok(outcome) => observer.waiter(outcome, true),
|
||||
Err(_) => WaiterAction::RefuseFailed,
|
||||
};
|
||||
|
|
@ -465,7 +463,7 @@ pub(crate) fn load_configuration(
|
|||
xdg_data_home: optional_environment_path("XDG_DATA_HOME"),
|
||||
xdg_runtime_dir: required_environment_path("XDG_RUNTIME_DIR")?,
|
||||
environment: environment(),
|
||||
sandbox_layout: default_sandbox_layout()?,
|
||||
sandbox_layout: lazaret_core::configuration::SandboxLayoutV1::default_layout()?,
|
||||
})?)
|
||||
}
|
||||
|
||||
|
|
@ -558,7 +556,7 @@ mod tests {
|
|||
fs::create_dir(&runtime_path).unwrap();
|
||||
fs::set_permissions(&runtime_path, fs::Permissions::from_mode(0o700)).unwrap();
|
||||
assert!(
|
||||
PublishedControlFace::discover_confined(
|
||||
ControlClient::discover_confined(
|
||||
runtime_configuration.runtime_domain_root(),
|
||||
rustix::process::getuid().as_raw(),
|
||||
)
|
||||
|
|
@ -615,7 +613,7 @@ mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let (authority, socket, observed) = PublishedControlFace::discover_confined(
|
||||
let (authority, socket, observed) = ControlClient::discover_confined(
|
||||
configuration.runtime_domain_root(),
|
||||
rustix::process::getuid().as_raw(),
|
||||
)
|
||||
|
|
@ -690,7 +688,7 @@ mod tests {
|
|||
xdg_data_home: None,
|
||||
xdg_runtime_dir: runtime,
|
||||
environment: BTreeMap::new(),
|
||||
sandbox_layout: default_sandbox_layout().unwrap(),
|
||||
sandbox_layout: lazaret_core::configuration::SandboxLayoutV1::default_layout().unwrap(),
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,12 +123,10 @@ impl<C: ObservationClock> DomainObservation<C> {
|
|||
|
||||
pub(crate) fn waiter(
|
||||
&self,
|
||||
outcome: lazaret_host_coordinator::waiter::BootstrapWaitOutcome,
|
||||
outcome: lazaret_core::protocol::BootstrapWaitOutcome,
|
||||
admitted: bool,
|
||||
) -> WaiterAction {
|
||||
use lazaret_host_coordinator::waiter::BootstrapWaitOutcome::{
|
||||
Failed, Published, Refused, Waiting,
|
||||
};
|
||||
use lazaret_core::protocol::BootstrapWaitOutcome::{Failed, Published, Refused, Waiting};
|
||||
if self.path != ObservationPath::WaitingClient {
|
||||
return WaiterAction::RefuseProtocol;
|
||||
}
|
||||
|
|
@ -177,7 +175,7 @@ impl<C: ObservationClock> DomainObservation<C> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use lazaret_host_coordinator::waiter::BootstrapWaitOutcome;
|
||||
use lazaret_core::protocol::BootstrapWaitOutcome;
|
||||
|
||||
#[derive(Default)]
|
||||
struct FakeClock {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use std::path::PathBuf;
|
|||
use std::sync::Mutex;
|
||||
|
||||
use ed25519_dalek::SigningKey;
|
||||
use lazaret_control_client::{ControlClient, ControlClientError, PendingClientEnrollment};
|
||||
use lazaret_core::identity::{ClientId, SessionAnchorId, SessionId};
|
||||
use lazaret_core::records::{
|
||||
ClientEnrollmentResponse, ControlRequest, ControlResponse, DeveloperDecision,
|
||||
|
|
@ -13,9 +14,6 @@ use lazaret_core::records::{
|
|||
ProtocolPublicationIdentity, ProtocolVersion, SessionBinding, SessionExitOutcome,
|
||||
SessionRegistrationRequest, TlsSubjectId,
|
||||
};
|
||||
use lazaret_host_coordinator::control::{
|
||||
ControlError, PendingClientEnrollment, PublishedControlFace,
|
||||
};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::terminal::{HostTerminal, InteractiveSessionTransport, run_interactive_session};
|
||||
|
|
@ -23,6 +21,7 @@ use crate::terminal::{HostTerminal, InteractiveSessionTransport, run_interactive
|
|||
pub mod cli;
|
||||
pub mod coordinator_backend;
|
||||
mod domain_observation;
|
||||
pub mod private_roles;
|
||||
pub mod state;
|
||||
pub mod terminal;
|
||||
|
||||
|
|
@ -59,7 +58,7 @@ pub enum ClientError<E> {
|
|||
EmptyCommand,
|
||||
Backend(E),
|
||||
Io(io::Error),
|
||||
Control(ControlError),
|
||||
Control(ControlClientError),
|
||||
Protocol(&'static str),
|
||||
}
|
||||
|
||||
|
|
@ -302,15 +301,15 @@ pub fn execute_over_control_face_as<E>(
|
|||
let cli_id = identity.cli_id;
|
||||
let session_id = identity.session_id;
|
||||
let pending = PendingClientEnrollment::create(generation.clone(), cli_id)?;
|
||||
let anonymous = PublishedControlFace::client_config(&endpoint.publication, None)?;
|
||||
let anonymous = ControlClient::config(&endpoint.publication, None)?;
|
||||
let mut enrollment =
|
||||
PublishedControlFace::connect(&endpoint.socket_path, anonymous, &endpoint.publication)?;
|
||||
PublishedControlFace::send(
|
||||
ControlClient::connect(&endpoint.socket_path, anonymous, &endpoint.publication)?;
|
||||
ControlClient::send(
|
||||
&mut enrollment,
|
||||
&ControlRequest::Enroll(pending.request().clone()),
|
||||
)?;
|
||||
let ControlResponse::Enrolled(ClientEnrollmentResponse { credential, .. }) =
|
||||
PublishedControlFace::receive(&mut enrollment)?
|
||||
ControlClient::receive(&mut enrollment)?
|
||||
else {
|
||||
return Err(ClientError::Protocol(
|
||||
"enrollment response was not Enrolled",
|
||||
|
|
@ -331,21 +330,20 @@ pub fn execute_over_control_face_as<E>(
|
|||
session_id,
|
||||
session_anchor_public_key: identity.anchor.verifying_key().to_bytes().to_vec(),
|
||||
};
|
||||
let authenticated = PublishedControlFace::client_config(
|
||||
let authenticated = ControlClient::config(
|
||||
&endpoint.publication,
|
||||
Some((&credential, pending.private_key())),
|
||||
)?;
|
||||
let mut stream =
|
||||
PublishedControlFace::connect(&endpoint.socket_path, authenticated, &endpoint.publication)?;
|
||||
PublishedControlFace::send(
|
||||
ControlClient::connect(&endpoint.socket_path, authenticated, &endpoint.publication)?;
|
||||
ControlClient::send(
|
||||
&mut stream,
|
||||
&ControlRequest::Register(SessionRegistrationRequest {
|
||||
protocol_version: ProtocolVersion::V1,
|
||||
session_binding: binding.clone(),
|
||||
}),
|
||||
)?;
|
||||
let ControlResponse::Registered(registered) = PublishedControlFace::receive(&mut stream)?
|
||||
else {
|
||||
let ControlResponse::Registered(registered) = ControlClient::receive(&mut stream)? else {
|
||||
return Err(ClientError::Protocol(
|
||||
"registration response was not Registered",
|
||||
));
|
||||
|
|
@ -356,7 +354,7 @@ pub fn execute_over_control_face_as<E>(
|
|||
));
|
||||
}
|
||||
endpoint.acknowledge_waiter_transfer()?;
|
||||
PublishedControlFace::send(
|
||||
ControlClient::send(
|
||||
&mut stream,
|
||||
&ControlRequest::Execute(NonInteractiveSessionRequest {
|
||||
protocol_version: ProtocolVersion::V1,
|
||||
|
|
@ -365,8 +363,8 @@ pub fn execute_over_control_face_as<E>(
|
|||
}),
|
||||
)?;
|
||||
loop {
|
||||
let message = PublishedControlFace::receive(&mut stream).map_err(|error| match error {
|
||||
ControlError::Io(_) => ClientError::LostConnection,
|
||||
let message = ControlClient::receive(&mut stream).map_err(|error| match error {
|
||||
ControlClientError::Io(_) => ClientError::LostConnection,
|
||||
other => ClientError::Control(other),
|
||||
})?;
|
||||
match message {
|
||||
|
|
@ -412,15 +410,15 @@ pub fn execute_interactive_over_control_face_as<E, H: HostTerminal>(
|
|||
let cli_id = identity.cli_id;
|
||||
let session_id = identity.session_id;
|
||||
let pending = PendingClientEnrollment::create(generation.clone(), cli_id)?;
|
||||
let anonymous = PublishedControlFace::client_config(&endpoint.publication, None)?;
|
||||
let anonymous = ControlClient::config(&endpoint.publication, None)?;
|
||||
let mut enrollment =
|
||||
PublishedControlFace::connect(&endpoint.socket_path, anonymous, &endpoint.publication)?;
|
||||
PublishedControlFace::send(
|
||||
ControlClient::connect(&endpoint.socket_path, anonymous, &endpoint.publication)?;
|
||||
ControlClient::send(
|
||||
&mut enrollment,
|
||||
&ControlRequest::Enroll(pending.request().clone()),
|
||||
)?;
|
||||
let ControlResponse::Enrolled(ClientEnrollmentResponse { credential, .. }) =
|
||||
PublishedControlFace::receive(&mut enrollment)?
|
||||
ControlClient::receive(&mut enrollment)?
|
||||
else {
|
||||
return Err(ClientError::Protocol(
|
||||
"enrollment response was not Enrolled",
|
||||
|
|
@ -440,21 +438,20 @@ pub fn execute_interactive_over_control_face_as<E, H: HostTerminal>(
|
|||
session_id,
|
||||
session_anchor_public_key: identity.anchor.verifying_key().to_bytes().to_vec(),
|
||||
};
|
||||
let authenticated = PublishedControlFace::client_config(
|
||||
let authenticated = ControlClient::config(
|
||||
&endpoint.publication,
|
||||
Some((&credential, pending.private_key())),
|
||||
)?;
|
||||
let mut stream =
|
||||
PublishedControlFace::connect(&endpoint.socket_path, authenticated, &endpoint.publication)?;
|
||||
PublishedControlFace::send(
|
||||
ControlClient::connect(&endpoint.socket_path, authenticated, &endpoint.publication)?;
|
||||
ControlClient::send(
|
||||
&mut stream,
|
||||
&ControlRequest::Register(SessionRegistrationRequest {
|
||||
protocol_version: ProtocolVersion::V1,
|
||||
session_binding: binding.clone(),
|
||||
}),
|
||||
)?;
|
||||
let ControlResponse::Registered(registered) = PublishedControlFace::receive(&mut stream)?
|
||||
else {
|
||||
let ControlResponse::Registered(registered) = ControlClient::receive(&mut stream)? else {
|
||||
return Err(ClientError::Protocol(
|
||||
"registration response was not Registered",
|
||||
));
|
||||
|
|
@ -481,7 +478,7 @@ impl InteractiveSessionTransport for ControlFaceInteractiveTransport {
|
|||
&mut self,
|
||||
request: InteractiveSessionRequest,
|
||||
) -> Result<InteractiveSessionBatch, ClientError<Self::Error>> {
|
||||
PublishedControlFace::send(
|
||||
ControlClient::send(
|
||||
&mut self.stream,
|
||||
&ControlRequest::ExecuteInteractive(request),
|
||||
)?;
|
||||
|
|
@ -492,7 +489,7 @@ impl InteractiveSessionTransport for ControlFaceInteractiveTransport {
|
|||
&mut self,
|
||||
event: InteractiveSessionEvent,
|
||||
) -> Result<InteractiveSessionBatch, ClientError<Self::Error>> {
|
||||
PublishedControlFace::send(&mut self.stream, &event).map_err(map_control_connection)?;
|
||||
ControlClient::send(&mut self.stream, &event).map_err(map_control_connection)?;
|
||||
receive_interactive_batch(&mut self.stream)
|
||||
}
|
||||
}
|
||||
|
|
@ -500,12 +497,12 @@ impl InteractiveSessionTransport for ControlFaceInteractiveTransport {
|
|||
fn receive_interactive_batch(
|
||||
stream: &mut impl std::io::Read,
|
||||
) -> Result<InteractiveSessionBatch, ClientError<std::convert::Infallible>> {
|
||||
PublishedControlFace::receive(stream).map_err(map_control_connection)
|
||||
ControlClient::receive(stream).map_err(map_control_connection)
|
||||
}
|
||||
|
||||
fn map_control_connection(error: ControlError) -> ClientError<std::convert::Infallible> {
|
||||
fn map_control_connection(error: ControlClientError) -> ClientError<std::convert::Infallible> {
|
||||
match error {
|
||||
ControlError::Io(_) => ClientError::LostConnection,
|
||||
ControlClientError::Io(_) => ClientError::LostConnection,
|
||||
other => ClientError::Control(other),
|
||||
}
|
||||
}
|
||||
|
|
@ -547,8 +544,8 @@ impl<E> From<io::Error> for ClientError<E> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<E> From<ControlError> for ClientError<E> {
|
||||
fn from(error: ControlError) -> Self {
|
||||
impl<E> From<ControlClientError> for ClientError<E> {
|
||||
fn from(error: ControlClientError) -> Self {
|
||||
Self::Control(error)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
fn main() {
|
||||
let arguments = std::env::args_os().skip(1).collect::<Vec<_>>();
|
||||
if let Some(result) = lazaret_linux::process::dispatch_private_process_role(&arguments) {
|
||||
if let Err(error) = result {
|
||||
eprintln!("private process role failed: {error}");
|
||||
if let Some(outcome) = lazaret_client::private_roles::REGISTRY.dispatch(&arguments) {
|
||||
if let Err(error) = outcome.result {
|
||||
eprintln!("private role {} failed: {error}", outcome.name);
|
||||
std::process::exit(1);
|
||||
}
|
||||
return;
|
||||
|
|
|
|||
19
crates/lazaret-client/src/private_roles.rs
Normal file
19
crates/lazaret-client/src/private_roles.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use lazaret_linux::private_role::{PrivateRole, PrivateRoleRegistry};
|
||||
use lazaret_linux::process::{PRIVATE_PROCESS_ROLE, dispatch_private_process_role};
|
||||
|
||||
static ROLES: [PrivateRole; 1] = [PrivateRole::new(
|
||||
PRIVATE_PROCESS_ROLE,
|
||||
dispatch_private_process_role,
|
||||
)];
|
||||
|
||||
pub static REGISTRY: PrivateRoleRegistry = PrivateRoleRegistry::new(&ROLES);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn client_declares_only_process_acquisition() {
|
||||
assert_eq!(REGISTRY.names(), [PRIVATE_PROCESS_ROLE]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
use std::io::{self, Write};
|
||||
use std::os::fd::AsFd;
|
||||
|
||||
use lazaret_core::records::Domain;
|
||||
use lazaret_host_coordinator::domain_store::{HostCoordinatorDomainStore, StateResetScopeV1};
|
||||
use lazaret_host_coordinator::state_maintenance::{
|
||||
use lazaret_core::records::{Domain, StateResetScopeV1};
|
||||
use lazaret_domain_state::domain_store::HostCoordinatorDomainStore;
|
||||
use lazaret_domain_state::state_maintenance::{
|
||||
DomainStateReport, StateAnchorEntryKind, inspect_absent_domain_state, inspect_domain_state,
|
||||
reset_domain_state,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,164 +0,0 @@
|
|||
use std::fs::OpenOptions;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use lazaret_client::{ControlEndpoint, execute_over_control_face};
|
||||
use lazaret_core::identity::{
|
||||
ConfigurationFingerprint, CoordinatorId, DomainId, GenerationId, HostPath, NetworkSidecarId,
|
||||
SandboxId,
|
||||
};
|
||||
use lazaret_core::records::{
|
||||
Domain, DurableDecision, Generation, GenerationIdentity, NonInteractiveOutput,
|
||||
NonInteractiveSessionMessage, NonInteractiveSessionRequest, SessionExit,
|
||||
};
|
||||
use lazaret_host_coordinator::control::{
|
||||
ControlError, NonInteractiveSessionExecutor, PublishedControlFace, TrustAnchor,
|
||||
};
|
||||
use lazaret_host_coordinator::domain_store::{DomainStoreMutation, HostCoordinatorDomainStore};
|
||||
use lazaret_linux::filesystem::try_exclusive_flock;
|
||||
|
||||
struct TestDirectory(PathBuf);
|
||||
|
||||
impl TestDirectory {
|
||||
fn new() -> Self {
|
||||
let path = std::env::temp_dir().join(format!(
|
||||
"lazaret-client-control-{}-{}",
|
||||
std::process::id(),
|
||||
random_suffix()
|
||||
));
|
||||
std::fs::create_dir(&path).unwrap();
|
||||
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o700)).unwrap();
|
||||
Self(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestDirectory {
|
||||
fn drop(&mut self) {
|
||||
std::fs::remove_dir_all(&self.0).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
struct Executor;
|
||||
|
||||
impl NonInteractiveSessionExecutor for Executor {
|
||||
fn execute(
|
||||
&self,
|
||||
request: &NonInteractiveSessionRequest,
|
||||
emit: &mut dyn FnMut(NonInteractiveSessionMessage) -> Result<(), ControlError>,
|
||||
) -> Result<SessionExit, ControlError> {
|
||||
assert_eq!(request.argv, [b"tool".to_vec(), b"argument".to_vec()]);
|
||||
emit(NonInteractiveSessionMessage::Stdout(NonInteractiveOutput {
|
||||
session_id: request.session_id,
|
||||
bytes: b"first".to_vec(),
|
||||
}))?;
|
||||
emit(NonInteractiveSessionMessage::Stderr(NonInteractiveOutput {
|
||||
session_id: request.session_id,
|
||||
bytes: b"second".to_vec(),
|
||||
}))?;
|
||||
Ok(SessionExit::exited(request.session_id, 19))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_enrolls_registers_forwards_both_streams_and_returns_guest_status() {
|
||||
let directory = TestDirectory::new();
|
||||
let identity = generation_identity();
|
||||
let store = open_store(&directory, &identity);
|
||||
let face = Arc::new(
|
||||
PublishedControlFace::publish_with_executor(
|
||||
&directory.0,
|
||||
rustix::process::getuid().as_raw(),
|
||||
TrustAnchor::create(identity).unwrap(),
|
||||
store,
|
||||
Arc::new(Executor),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
let endpoint = ControlEndpoint::new(
|
||||
face.socket_path().to_path_buf(),
|
||||
face.publication_identity().clone(),
|
||||
);
|
||||
let server = {
|
||||
let face = Arc::clone(&face);
|
||||
std::thread::spawn(move || {
|
||||
face.serve_next().unwrap();
|
||||
face.serve_next().unwrap();
|
||||
})
|
||||
};
|
||||
let mut stdout = Vec::new();
|
||||
let mut stderr = Vec::new();
|
||||
|
||||
let status = execute_over_control_face(
|
||||
&endpoint,
|
||||
&[b"tool".to_vec(), b"argument".to_vec()],
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
server.join().unwrap();
|
||||
assert_eq!(status, 19);
|
||||
assert_eq!(stdout, b"first");
|
||||
assert_eq!(stderr, b"second");
|
||||
}
|
||||
|
||||
fn generation_identity() -> GenerationIdentity {
|
||||
GenerationIdentity {
|
||||
agent_profile: "trusted".into(),
|
||||
domain_id: DomainId::new([1; 32]),
|
||||
generation_id: GenerationId::new(1),
|
||||
configuration_fingerprint: ConfigurationFingerprint::new([2; 32]),
|
||||
coordinator_id: CoordinatorId::new([3; 32]),
|
||||
sandbox_id: SandboxId::new([4; 32]),
|
||||
}
|
||||
}
|
||||
|
||||
fn open_store(
|
||||
directory: &TestDirectory,
|
||||
identity: &GenerationIdentity,
|
||||
) -> Arc<HostCoordinatorDomainStore> {
|
||||
let lock = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(directory.0.join("domain.lock"))
|
||||
.unwrap();
|
||||
let domain = Domain {
|
||||
domain_id: identity.domain_id,
|
||||
profile_name: identity.agent_profile.clone(),
|
||||
canonical_workspace: HostPath::new(b"/workspace/project".to_vec()),
|
||||
};
|
||||
let store = Arc::new(
|
||||
HostCoordinatorDomainStore::open(
|
||||
try_exclusive_flock(lock).unwrap(),
|
||||
&directory.0.join("domain.redb"),
|
||||
&domain,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
for durable_decision in [
|
||||
DurableDecision::None,
|
||||
DurableDecision::Preparing,
|
||||
DurableDecision::Published,
|
||||
] {
|
||||
store
|
||||
.apply(&[DomainStoreMutation::PutGeneration(Generation {
|
||||
domain_id: identity.domain_id,
|
||||
generation_id: identity.generation_id,
|
||||
configuration_fingerprint: identity.configuration_fingerprint,
|
||||
coordinator_id: identity.coordinator_id,
|
||||
network_sidecar_id: NetworkSidecarId::new([5; 32]),
|
||||
sandbox_id: identity.sandbox_id,
|
||||
durable_decision,
|
||||
})])
|
||||
.unwrap();
|
||||
}
|
||||
store
|
||||
}
|
||||
|
||||
fn random_suffix() -> u64 {
|
||||
let mut bytes = [0_u8; 8];
|
||||
getrandom::fill(&mut bytes).unwrap();
|
||||
u64::from_ne_bytes(bytes)
|
||||
}
|
||||
Loading…
Reference in a new issue