Implement PTY-native session terminal control
This commit is contained in:
parent
058e58e816
commit
8cef037fa3
2 changed files with 1488 additions and 382 deletions
|
|
@ -13,8 +13,10 @@ use lazaret_core::identity::{
|
|||
};
|
||||
use lazaret_core::records::{
|
||||
ClientEnrollmentResponse, ControlRequest, ControlResponse, Domain, DurableDecision, Generation,
|
||||
GenerationIdentity, ProtocolVersion, Session, SessionBinding, SessionPhase,
|
||||
SessionRegistrationRequest, SessionRegistrationResponse, TlsCredential,
|
||||
GenerationIdentity, InteractiveSessionBatch, InteractiveSessionMessage,
|
||||
InteractiveSessionRequest, ProtocolVersion, Session, SessionBinding, SessionPhase,
|
||||
SessionRegistrationRequest, SessionRegistrationResponse, SessionTerminalMessage,
|
||||
TerminalResize, TlsCredential,
|
||||
};
|
||||
use lazaret_host_coordinator::control::{
|
||||
CONTROL_ALPN, ControlError, PendingClientEnrollment, PublishedControlFace, SessionRegistry,
|
||||
|
|
@ -477,6 +479,47 @@ fn real_uds_tls_enrollment_and_registration_preserve_exact_profiles_and_session_
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v1_interactive_client_receives_an_explicit_upgrade_refusal() {
|
||||
let directory = TestDirectory::new("interactive-v1-refusal");
|
||||
let identity = generation_identity(7);
|
||||
let (face, _) = publish(&directory, &identity);
|
||||
let client = enroll(&face, ClientId::new(bytes(33)));
|
||||
let binding = registration_binding(&identity, ClientId::new(bytes(33)), 34, 35);
|
||||
let session_id = binding.session_id;
|
||||
let (mut stream, serving, _) = open_registration(&face, &client, binding);
|
||||
let initial_size = TerminalResize {
|
||||
session_id,
|
||||
rows: 24.try_into().unwrap(),
|
||||
columns: 80.try_into().unwrap(),
|
||||
pixel_width: 0,
|
||||
pixel_height: 0,
|
||||
};
|
||||
|
||||
PublishedControlFace::send(
|
||||
&mut stream,
|
||||
&ControlRequest::ExecuteInteractive(InteractiveSessionRequest {
|
||||
protocol_version: ProtocolVersion::V1,
|
||||
session_id,
|
||||
argv: vec![b"sh".to_vec()],
|
||||
initial_size,
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
let refusal: InteractiveSessionBatch = PublishedControlFace::receive(&mut stream).unwrap();
|
||||
assert_eq!(refusal.session_id, session_id);
|
||||
assert!(matches!(
|
||||
refusal.messages.as_slice(),
|
||||
[
|
||||
InteractiveSessionMessage::Terminal(SessionTerminalMessage::Output(output)),
|
||||
InteractiveSessionMessage::Terminal(SessionTerminalMessage::Exit(exit))
|
||||
] if output.bytes == b"Lazaret interactive protocol upgrade required\r\n"
|
||||
&& exit.outcome() == lazaret_core::records::SessionExitOutcome::RuntimeLost
|
||||
));
|
||||
drop(stream);
|
||||
serving.join().unwrap().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_session_cli_and_anchor_identities_are_refused_while_registered() {
|
||||
let directory = TestDirectory::new("duplicates");
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ use lazaret_core::identity::{
|
|||
use lazaret_core::network::derive_ipv4_policy;
|
||||
use lazaret_core::records::{
|
||||
Domain, DurableDecision, Generation, HostProcessOwnershipSlot, ParticipantRole,
|
||||
ParticipantState, ProcessOwnershipCommitAck, Session, SessionPhase, SessionTerminalEvent,
|
||||
SessionTerminalMessage, TerminalResize, TerminalSignal, TerminalSignalKind,
|
||||
ParticipantState, ProcessOwnershipCommitAck, Session, SessionPhase, SessionTerminalEventV2,
|
||||
SessionTerminalMessage, TerminalBreak, TerminalInput, TerminalModesV2, TerminalResize,
|
||||
};
|
||||
use lazaret_host_coordinator::domain_store::{DomainStoreMutation, HostCoordinatorDomainStore};
|
||||
use lazaret_host_coordinator::git_routing::GitRoutingCoordinator;
|
||||
|
|
@ -363,6 +363,14 @@ fn run_session_terminal_matrix() -> Result<(), MatrixError> {
|
|||
environment: fixture.configuration.ephemeral_environment().clone(),
|
||||
internal_pid_file: runtime_domain.join("session-internal.pid"),
|
||||
argv: vec![b"/bin/sh".to_vec(), b"-c".to_vec(), script.into_bytes()],
|
||||
initial_size: TerminalResize {
|
||||
session_id,
|
||||
rows: std::num::NonZeroU16::new(24).unwrap(),
|
||||
columns: std::num::NonZeroU16::new(80).unwrap(),
|
||||
pixel_width: 0,
|
||||
pixel_height: 0,
|
||||
},
|
||||
initial_modes: standard_terminal_modes(),
|
||||
};
|
||||
let mut engine = SessionTerminalEngine::new(
|
||||
Arc::clone(&fixture.store),
|
||||
|
|
@ -401,7 +409,7 @@ fn run_session_terminal_matrix() -> Result<(), MatrixError> {
|
|||
engine
|
||||
.handle(
|
||||
GenerationId::new(7),
|
||||
SessionTerminalEvent::Resize(TerminalResize {
|
||||
SessionTerminalEventV2::Resize(TerminalResize {
|
||||
session_id,
|
||||
rows: std::num::NonZeroU16::new(41).unwrap(),
|
||||
columns: std::num::NonZeroU16::new(121).unwrap(),
|
||||
|
|
@ -410,18 +418,23 @@ fn run_session_terminal_matrix() -> Result<(), MatrixError> {
|
|||
}),
|
||||
)
|
||||
.map_err(failure)?;
|
||||
for signal in [
|
||||
TerminalSignalKind::Interrupt,
|
||||
TerminalSignalKind::Quit,
|
||||
TerminalSignalKind::Suspend,
|
||||
] {
|
||||
for byte in [3, 28, 26] {
|
||||
engine
|
||||
.handle(
|
||||
GenerationId::new(7),
|
||||
SessionTerminalEvent::Signal(TerminalSignal { session_id, signal }),
|
||||
SessionTerminalEventV2::Input(TerminalInput {
|
||||
session_id,
|
||||
bytes: vec![byte],
|
||||
}),
|
||||
)
|
||||
.map_err(failure)?;
|
||||
}
|
||||
engine
|
||||
.handle(
|
||||
GenerationId::new(7),
|
||||
SessionTerminalEventV2::Break(TerminalBreak { session_id }),
|
||||
)
|
||||
.map_err(failure)?;
|
||||
wait_for_signal_lines(&mut engine, session_id, &signals, 4)?;
|
||||
if second_signals.exists() {
|
||||
return Err(failure("terminal signals crossed Session IDs"));
|
||||
|
|
@ -593,6 +606,35 @@ fn wait_for_path(path: &Path) -> Result<(), MatrixError> {
|
|||
Err(failure("Session Terminal guest did not become ready"))
|
||||
}
|
||||
|
||||
fn standard_terminal_modes() -> TerminalModesV2 {
|
||||
TerminalModesV2 {
|
||||
input_modes: 1280,
|
||||
output_modes: 5,
|
||||
control_modes: 983_231,
|
||||
local_modes: 35_387,
|
||||
line_discipline: 0,
|
||||
input_speed: 38_400,
|
||||
output_speed: 38_400,
|
||||
interrupt: 3,
|
||||
quit: 28,
|
||||
erase: 127,
|
||||
kill: 21,
|
||||
eof: 4,
|
||||
time: 0,
|
||||
minimum: 1,
|
||||
switch: 0,
|
||||
start: 17,
|
||||
stop: 19,
|
||||
suspend: 26,
|
||||
end_of_line: 0,
|
||||
reprint: 18,
|
||||
discard: 15,
|
||||
word_erase: 23,
|
||||
literal_next: 22,
|
||||
end_of_line_2: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_for_signal_lines(
|
||||
engine: &mut SessionTerminalEngine,
|
||||
session_id: SessionId,
|
||||
|
|
|
|||
Loading…
Reference in a new issue