Skip to content

Commit

Permalink
feat(dgw): WebSocket-TLS endpoint (/jet/tls) (#400)
Browse files Browse the repository at this point in the history
Issue: DGW-83
  • Loading branch information
CBenoit authored Mar 14, 2023
1 parent 265f0db commit 46368f6
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 96 deletions.
2 changes: 1 addition & 1 deletion devolutions-gateway/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ pub mod registry;
pub mod service;
pub mod session;
pub mod subscriber;
pub mod tcp;
pub mod token;
pub mod transport;
pub mod utils;
pub mod websocket_client;
pub mod websocket_forward;

pub mod tls_sanity {
use anyhow::Context as _;
Expand Down
3 changes: 1 addition & 2 deletions devolutions-gateway/src/rdp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ use crate::utils::{self, TargetAddr};
use anyhow::Context;
use bytes::BytesMut;
use nonempty::NonEmpty;
use sspi::credssp;
use sspi::AuthIdentity;
use sspi::{credssp, AuthIdentity};
use std::io;
use std::net::SocketAddr;
use std::sync::Arc;
Expand Down
5 changes: 4 additions & 1 deletion devolutions-gateway/src/rdp_extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,10 @@ async fn process_cleanpath(
let mut server_transport = {
// Establish TLS connection with server

let dns_name = "stub_string".try_into().unwrap();
let dns_name = destination
.host()
.try_into()
.context("Invalid DNS name in selected target")?;

// TODO: optimize client config creation
//
Expand Down
89 changes: 0 additions & 89 deletions devolutions-gateway/src/tcp.rs

This file was deleted.

92 changes: 89 additions & 3 deletions devolutions-gateway/src/websocket_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,19 @@ impl WebsocketService {
)
.await
.map_err(|err| io::Error::new(ErrorKind::Other, format!("Handle TCP error - {err:#}")))
} else if req.method() == Method::GET && req_uri.starts_with("/jet/tls") {
info!("{} {}", req.method(), req_uri);
handle_tls(
req,
client_addr,
self.conf.clone(),
&self.token_cache,
&self.jrl,
self.sessions.clone(),
self.subscriber_tx.clone(),
)
.await
.map_err(|err| io::Error::new(ErrorKind::Other, format!("Handle TLS error - {err:#}")))
} else {
saphir::server::inject_raw_with_peer_addr(req, Some(client_addr))
.await
Expand Down Expand Up @@ -462,7 +475,8 @@ fn process_req(req: &Request<Body>) -> Response<Body> {
Author: Ran Benita<bluetech> (ran234@gmail.com)
*/

use base64::{engine::general_purpose::STANDARD, Engine as _};
use base64::engine::general_purpose::STANDARD;
use base64::Engine as _;

fn convert_key(input: &[u8]) -> String {
const WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
Expand Down Expand Up @@ -643,7 +657,7 @@ async fn handle_tcp(
anyhow::bail!("missing authorization"); // AUTHORIZATION
};

let claims = crate::tcp::authorize(client_addr, token, &conf, token_cache, jrl)?; // FORBIDDEN
let claims = crate::websocket_forward::authorize(client_addr, token, &conf, token_cache, jrl)?; // FORBIDDEN

if let Some(upgrade_val) = req.headers().get("upgrade").and_then(|v| v.to_str().ok()) {
if upgrade_val != "websocket" {
Expand All @@ -656,7 +670,16 @@ async fn handle_tcp(
tokio::spawn(async move {
let fut = async {
let stream = upgrade_websocket(&mut req).await?;
crate::tcp::handle(stream, client_addr, conf, claims, sessions, subscriber_tx).await
crate::websocket_forward::PlainForward::builder()
.client_addr(client_addr)
.client_stream(stream)
.conf(conf)
.claims(claims)
.sessions(sessions)
.subscriber_tx(subscriber_tx)
.build()
.run()
.await
}
.instrument(info_span!("tcp", client = %client_addr));

Expand All @@ -669,6 +692,69 @@ async fn handle_tcp(
Ok(rsp)
}

async fn handle_tls(
mut req: Request<Body>,
client_addr: SocketAddr,
conf: Arc<Conf>,
token_cache: &TokenCache,
jrl: &CurrentJrl,
sessions: SessionManagerHandle,
subscriber_tx: SubscriberSender,
) -> anyhow::Result<Response<Body>> {
use crate::http::middlewares::auth::{parse_auth_header, AuthHeaderType};

let token = if let Some(authorization_value) = req.headers().get(header::AUTHORIZATION) {
let authorization_value = authorization_value.to_str().context("bad authorization header value")?; // BAD REQUEST
match parse_auth_header(authorization_value) {
Some((AuthHeaderType::Bearer, token)) => token,
_ => anyhow::bail!("bad authorization header value"), // BAD REQUEST
}
} else if let Some(token) = req.uri().query().and_then(|q| {
q.split('&')
.filter_map(|segment| segment.split_once('='))
.find_map(|(key, val)| key.eq("token").then_some(val))
}) {
token
} else {
anyhow::bail!("missing authorization"); // AUTHORIZATION
};

let claims = crate::websocket_forward::authorize(client_addr, token, &conf, token_cache, jrl)?; // FORBIDDEN

if let Some(upgrade_val) = req.headers().get("upgrade").and_then(|v| v.to_str().ok()) {
if upgrade_val != "websocket" {
anyhow::bail!("unexpected upgrade header value: {}", upgrade_val) // BAD REQUEST
}
}

let rsp = process_req(&req);

tokio::spawn(async move {
let fut = async {
let stream = upgrade_websocket(&mut req).await?;
crate::websocket_forward::PlainForward::builder()
.client_addr(client_addr)
.client_stream(stream)
.conf(conf)
.claims(claims)
.sessions(sessions)
.subscriber_tx(subscriber_tx)
.with_tls(true)
.build()
.run()
.await
}
.instrument(info_span!("tls", client = %client_addr));

match fut.await {
Ok(()) => {}
Err(error) => error!(client = %client_addr, error = format!("{error:#}"), "WebSocket-TLS failure"),
}
});

Ok(rsp)
}

type WebsocketTransport = transport::WebSocketStream<tokio_tungstenite::WebSocketStream<hyper::upgrade::Upgraded>>;

async fn upgrade_websocket(req: &mut Request<Body>) -> anyhow::Result<WebsocketTransport> {
Expand Down
Loading

0 comments on commit 46368f6

Please sign in to comment.