diff --git a/Cargo.lock b/Cargo.lock index 8136ade6a..5d21b6bef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8075,8 +8075,11 @@ dependencies = [ "percent-encoding", "pin-project 1.1.13", "prost 0.13.5", + "rustls-native-certs", + "rustls-pemfile 2.2.0", "socket2 0.5.10", "tokio 1.52.3", + "tokio-rustls", "tokio-stream", "tower 0.4.13", "tower-layer", diff --git a/devolutions-agent/Cargo.toml b/devolutions-agent/Cargo.toml index 2be961bb6..bf7ed414e 100644 --- a/devolutions-agent/Cargo.toml +++ b/devolutions-agent/Cargo.toml @@ -53,7 +53,7 @@ tap = "1.0" tempfile = "3" tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "tls12", "ring"] } tokio-stream = "0.1" -tonic = { version = "0.12", features = ["transport"] } +tonic = { version = "0.12", features = ["transport", "tls-native-roots"] } tracing = "0.1" url = { version = "2.5", features = ["serde"] } x509-parser = "0.16" diff --git a/devolutions-agent/src/psu_agent/mod.rs b/devolutions-agent/src/psu_agent/mod.rs index b1e33dd66..abe4e276b 100644 --- a/devolutions-agent/src/psu_agent/mod.rs +++ b/devolutions-agent/src/psu_agent/mod.rs @@ -145,7 +145,7 @@ impl PsuAgent { } async fn run_single_connection(&self, shutdown_signal: &mut ShutdownSignal, app_token: &str) -> anyhow::Result<()> { - let endpoint = Endpoint::from_shared(self.server_url.clone())?; + let endpoint = psu_endpoint(&self.server_url)?; let channel = endpoint .connect() .await @@ -314,6 +314,11 @@ impl PsuAgent { } } +fn psu_endpoint(server_url: &str) -> Result { + let _ = rustls::crypto::ring::default_provider().install_default(); + Endpoint::new(server_url.to_owned()) +} + pub(crate) fn agent_message(agent_id: &str, connection_id: &str, payload: AgentPayload) -> AgentMessage { AgentMessage { request_id: Uuid::new_v4().simple().to_string(), @@ -432,8 +437,48 @@ async fn get_powershell_version(executable: &str) -> String { #[cfg(test)] mod tests { + use tokio::io::AsyncReadExt as _; + use tokio::net::TcpListener; + use super::*; + async fn first_connection_byte(scheme: &str) -> u8 { + let listener = TcpListener::bind("127.0.0.1:0").await.expect("bind endpoint"); + let url = format!("{scheme}://{}", listener.local_addr().expect("listener address")); + let endpoint = psu_endpoint(&url).expect("create endpoint"); + let connection = tokio::spawn(async move { endpoint.connect().await }); + + let first_byte = tokio::time::timeout(Duration::from_secs(5), async { + let (mut stream, _) = listener.accept().await.expect("accept connection"); + let mut byte = [0]; + stream.read_exact(&mut byte).await.expect("read client preface"); + byte[0] + }) + .await + .expect("client did not connect"); + + connection.abort(); + first_byte + } + + #[tokio::test] + async fn https_endpoint_starts_tls_handshake() { + assert_eq!( + first_connection_byte("https").await, + 0x16, + "HTTPS connection must start with a TLS ClientHello" + ); + } + + #[tokio::test] + async fn http_endpoint_starts_http2_preface() { + assert_eq!( + first_connection_byte("http").await, + b'P', + "HTTP connection must start with the HTTP/2 preface" + ); + } + #[test] fn connect_request_omits_authorization_without_app_token() { let request = connect_request((), None).expect("create request"); diff --git a/package/AgentLinux/README.md b/package/AgentLinux/README.md index a4beb1740..8cdcb0b34 100644 --- a/package/AgentLinux/README.md +++ b/package/AgentLinux/README.md @@ -23,6 +23,8 @@ docker run --rm ` devolutions/devolutions-agent:latest ``` +For HTTPS, set `PSU_SERVER_URL` to an `https://` URL and trust the PSU server certificate in the container's system certificate store. + | Environment variable | Default | | --- | --- | | `PSU_SERVER_URL` | Empty |