Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion devolutions-gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ job-queue = { path = "../crates/job-queue" }
job-queue-libsql = { path = "../crates/job-queue-libsql" }
ironrdp-pdu = { version = "0.5", features = ["std"] }
ironrdp-core = { version = "0.1", features = ["std"] }
ironrdp-rdcleanpath = "0.1"
ironrdp-tokio = "0.5"
ironrdp-rdcleanpath = { path = "C:\\dev\\IronRDP\\crates\\ironrdp-rdcleanpath" }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will replace with commit/new version later

ironrdp-connector = { version = "0.5" }
ironrdp-acceptor = { version = "0.5" }
ceviche = "0.6.1"
Expand Down
29 changes: 13 additions & 16 deletions devolutions-gateway/src/api/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,22 +370,19 @@ impl TryFrom<ScannerEvent> for NetworkScanResponse {
protocol,
port,
}) => {
let protocol = match protocol {
None => None,
Some(protocol) => Some(match protocol {
scanner::ServiceType::Rdp => Protocol::Rdp,
scanner::ServiceType::Ard => Protocol::Ard,
scanner::ServiceType::Vnc => Protocol::Vnc,
scanner::ServiceType::Ssh => Protocol::Ssh,
scanner::ServiceType::Sftp => Protocol::Sftp,
scanner::ServiceType::Scp => Protocol::Scp,
scanner::ServiceType::Telnet => Protocol::Telnet,
scanner::ServiceType::Http => Protocol::Http,
scanner::ServiceType::Https => Protocol::Https,
scanner::ServiceType::Ldap => Protocol::Ldap,
scanner::ServiceType::Ldaps => Protocol::Ldaps,
}),
};
let protocol = protocol.map(|protocol| match protocol {
scanner::ServiceType::Rdp => Protocol::Rdp,
scanner::ServiceType::Ard => Protocol::Ard,
scanner::ServiceType::Vnc => Protocol::Vnc,
scanner::ServiceType::Ssh => Protocol::Ssh,
scanner::ServiceType::Sftp => Protocol::Sftp,
scanner::ServiceType::Scp => Protocol::Scp,
scanner::ServiceType::Telnet => Protocol::Telnet,
scanner::ServiceType::Http => Protocol::Http,
scanner::ServiceType::Https => Protocol::Https,
scanner::ServiceType::Ldap => Protocol::Ldap,
scanner::ServiceType::Ldaps => Protocol::Ldaps,
});

let protocol = match protocol {
Some(protocol) => TcpKnockProbe::NamedApplication(protocol),
Expand Down
6 changes: 6 additions & 0 deletions devolutions-gateway/src/credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ pub struct AppCredentialMapping {
#[derive(Debug, Clone)]
pub struct CredentialStoreHandle(Arc<Mutex<CredentialStore>>);

impl Default for CredentialStoreHandle {
fn default() -> Self {
Self::new()
}
}

impl CredentialStoreHandle {
pub fn new() -> Self {
Self(Arc::new(Mutex::new(CredentialStore::new())))
Expand Down
96 changes: 69 additions & 27 deletions devolutions-gateway/src/rd_clean_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,7 @@ async fn read_cleanpath_pdu(mut stream: impl AsyncRead + Unpin + Send) -> io::Re
std::cmp::Ordering::Less => {}
std::cmp::Ordering::Equal => break,
std::cmp::Ordering::Greater => {
return Err(io::Error::new(
ErrorKind::Other,
"no leftover is expected when reading cleanpath PDU",
));
return Err(io::Error::other("no leftover is expected when reading cleanpath PDU"));
}
}
}
Expand Down Expand Up @@ -164,7 +161,7 @@ struct CleanPathResult {
destination: TargetAddr,
server_addr: SocketAddr,
server_stream: tokio_rustls::client::TlsStream<tokio::net::TcpStream>,
x224_rsp: Vec<u8>,
x224_rsp: Option<Vec<u8>>,
}

async fn process_cleanpath(
Expand Down Expand Up @@ -234,31 +231,76 @@ async fn process_cleanpath(
debug!(%selected_target, "Connected to destination server");
span.record("target", selected_target.to_string());

// Send preconnection blob if applicable
if let Some(pcb) = cleanpath_pdu.preconnection_blob {
server_stream.write_all(pcb.as_bytes()).await?;
// Preconnection Blob (PCB) is currently used for Hyper-V VMs almost exclusively in practice.
// However, we still leave space for future extensions of usages of PCB.
//
// Connection sequence with Hyper-V VMs (PCB included and X224 connection request is not present):
// ┌───────────────────────┐ ┌───────────────────────────────────────────────────────────────┐
// │ handled by │ │ handled by IronRDP client │
// │ Gateway │ │ │
// └───────────────────────┘ └───────────────────────────────────────────────────────────────┘
// │ PCB → TLS handshake │ → │ CredSSP → X224 connection request → X224 connection response │
// └───────────────────────┘ └───────────────────────────────────────────────────────────────┘
//
// Connection sequence without Hyper-V VMs (PCB optional):
// ┌───────────────────────────────────────────────────────────────┐ ┌───────────────────────┐
// │ handled by Gateway │ │ handled by IronRDP │
// │ │ │ client │
// └───────────────────────────────────────────────────────────────┘ └───────────────────────┘
// │ PCB → X224 connection request → X224 connection response → TLS| │ → CredSSP → ... │
// └───────────────────────────────────────────────────────────────┘ └───────────────────────┘
//
// Summary:
// - With PCB but not X224 connection request: Gateway handles (1) sending PCB/VmConnectID, (2) TLS handshake, then leaves CredSSP
// and X224 connection request/response to IronRDP client.
// - With PCB and X224 connection request: Gateway handles (1) sending PCB/VmConnectID, (2) X224 connection request, (3) X224 connection response, (4) TLS handshake,
// then leaves CredSSP to IronRDP client.
// - Without PCB: In this case, X224 MUST be present! Gateway handles (1) X224 connection request, (2) X224 connection response, (3) TLS handshake, then leaves CredSSP to IronRDP client.
match (&cleanpath_pdu.preconnection_blob, &cleanpath_pdu.x224_connection_pdu) {
(None, None) => {
return Err(CleanPathError::BadRequest(anyhow::anyhow!(
"RDCleanPath PDU is missing both preconnection blob and X224 connection PDU"
)));
}
(Some(general_pcb), Some(_)) => {
server_stream.write_all(general_pcb.as_bytes()).await?;
Comment thread
irvingoujAtDevolution marked this conversation as resolved.
Outdated
}
(None, Some(_)) => {} // no preconnection blob to send
// This is considered to be the case where the preconnection blob is used for Hyper-V VMs connection
(Some(pcb), None) => {
debug!("Sending preconnection blob to server");
let pcb = ironrdp_pdu::pcb::PreconnectionBlob {
version: ironrdp_pdu::pcb::PcbVersion::V2,
id: 0,
v2_payload: Some(pcb.clone()),
};

let encoded = ironrdp_core::encode_vec(&pcb)
.context("failed to encode preconnection blob")
.map_err(CleanPathError::BadRequest)?;

server_stream.write_all(&encoded).await?;
}
}

// Send X224 connection request
let x224_req = cleanpath_pdu
.x224_connection_pdu
.context("request is missing X224 connection PDU")
.map_err(CleanPathError::BadRequest)?;
server_stream.write_all(x224_req.as_bytes()).await?;
// Send X224 connection request if present
let x224_rsp = if let Some(x224_connection_pdu) = &cleanpath_pdu.x224_connection_pdu {
server_stream.write_all(x224_connection_pdu.as_bytes()).await?;
debug!("X224 connection request sent");

// Receive server X224 connection response
let x224_rsp = read_x224_response(&mut server_stream)
.await
.with_context(|| format!("read X224 response from {selected_target}"))
.map_err(CleanPathError::BadRequest)?;
trace!("Receiving X224 response");

trace!("Receiving X224 response");

let x224_rsp = read_x224_response(&mut server_stream)
.await
.with_context(|| format!("read X224 response from {selected_target}"))
.map_err(CleanPathError::BadRequest)?;
Some(x224_rsp)
} else {
None
};

// Establish TLS connection
trace!("Establishing TLS connection with server");

// Establish TLS connection with server

let server_stream = crate::tls::connect(selected_target.host().to_owned(), server_stream)
.await
.map_err(|source| CleanPathError::TlsHandshake {
Expand Down Expand Up @@ -295,7 +337,7 @@ pub async fn handle(
.await
.context("couldn’t read clean cleanpath PDU")?;

trace!("Processing RDCleanPath");
trace!(RDCleanPath = ?cleanpath_pdu,"Processing RDCleanPath");

let CleanPathResult {
claims,
Expand Down Expand Up @@ -337,7 +379,7 @@ pub async fn handle(
trace!("Sending RDCleanPath response");

let rdcleanpath_rsp = RDCleanPathPdu::new_response(server_addr.to_string(), x224_rsp, x509_chain)
.map_err(|e| anyhow::anyhow!("couldn’t build RDCleanPath response: {e}"))?;
.context("couldn’t build RDCleanPath response")?;

send_clean_path_response(&mut client_stream, &rdcleanpath_rsp).await?;

Expand Down Expand Up @@ -456,7 +498,7 @@ enum WsaError {
WSAESTALE = 10070,
WSAEREMOTE = 10071,
WSASYSNOTREADY = 10091,
WSAVERNOTSUPPORTED = 10092,
WSAVERNOT_SUPPORTED = 10092,
WSANOTINITIALISED = 10093,
WSAEDISCON = 10101,
WSAENOMORE = 10102,
Expand Down
4 changes: 4 additions & 0 deletions devolutions-gateway/src/rdp_pcb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ fn decode_pcb(buf: &[u8]) -> Result<Option<(PreconnectionBlob, usize)>, io::Erro
Ok(pcb) => {
let pdu_size = ironrdp_core::size(&pcb);
let read_len = cursor.pos();
info!(
pdu_size,
read_len, "read preconnection blob (size: {}, read: {})", pdu_size, read_len
);

// NOTE: sanity check (reporting the wrong number will corrupt the communication)
if read_len != pdu_size {
Expand Down
2 changes: 1 addition & 1 deletion devolutions-gateway/src/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pub fn build_server_config(cert_source: CertificateSource) -> anyhow::Result<rus
} => {
let first_certificate = certificates.first().context("empty certificate list")?;

if let Ok(report) = check_certificate_now(&first_certificate) {
if let Ok(report) = check_certificate_now(first_certificate) {
if report.issues.intersects(
CertIssues::MISSING_SERVER_AUTH_EXTENDED_KEY_USAGE | CertIssues::MISSING_SUBJECT_ALT_NAME,
) {
Expand Down
4 changes: 2 additions & 2 deletions jetsocat/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ impl CommonArgs {
let pid = u32::try_from(process_id).context("invalid value for process ID")?;
Some(sysinfo::Pid::from_u32(pid))
} else if c.bool_flag("watch-parent") {
use sysinfo::{ProcessRefreshKind, RefreshKind, System};
use sysinfo::System;

// Find current process' parent process ID
let current_pid =
Expand Down Expand Up @@ -850,7 +850,7 @@ fn clean_old_log_files(logging: &Logging) -> anyhow::Result<()> {
match entry
.metadata()
.and_then(|metadata| metadata.modified())
.and_then(|time| time.elapsed().map_err(|e| io::Error::new(io::ErrorKind::Other, e)))
.and_then(|time| time.elapsed().map_err(io::Error::other))
{
Ok(modified) if modified > MAX_AGE => {
info!("Delete log file");
Expand Down
Loading