Skip to content

Commit 8645d07

Browse files
author
Mathias Chunnoo
committed
Apply insecure proxy option to websockets
If the insecure option is set on a websocket proxy, it will use a `rustls` or `native-tls` connector, depending on which features are enabled, that allows insecure TLS connections. If neither of those features are enabled, it will fail to create an insecure connection.
1 parent bc84a17 commit 8645d07

File tree

4 files changed

+141
-6
lines changed

4 files changed

+141
-6
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ lol_html = "1.2.1"
4545
mime_guess = "2.0.4"
4646
minify-html = "0.15.0"
4747
minify-js = "0.5.6" # stick with 0.5.x as 0.6 seems to create broken JS
48+
native-tls = { version = "0.2", default-features = false, optional = true }
4849
notify = "8"
4950
notify-debouncer-full = "0.5"
5051
once_cell = "1"
@@ -54,6 +55,7 @@ rand = "0.9.0"
5455
regex = "1"
5556
remove_dir_all = "1"
5657
reqwest = { version = "0.12", default-features = false, features = ["stream", "trust-dns"] }
58+
rustls = { version = "0.23", default-features = false, optional = true }
5759
schemars = { version = "0.8", features = ["derive"] }
5860
seahash = { version = "4", features = ["use_std"] }
5961
semver = "1"
@@ -103,6 +105,7 @@ rustls = [
103105
"reqwest/rustls-tls-native-roots",
104106
"tokio-tungstenite/rustls",
105107
"tokio-tungstenite/rustls-tls-native-roots",
108+
"dep:rustls",
106109
]
107110

108111
rustls-aws-lc = [
@@ -120,10 +123,11 @@ native-tls = [
120123
"axum-server/tls-openssl",
121124
"reqwest/native-tls",
122125
"tokio-tungstenite/native-tls",
126+
"dep:native-tls",
123127
]
124128

125129
# enable the update check on startup
126130
update_check = ["crates_io_api"]
127131

128132
# enable vendoring on crates supporting that
129-
vendored = ["openssl?/vendored"]
133+
vendored = ["openssl?/vendored", "native-tls?/vendored"]

src/proxy.rs

Lines changed: 125 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ use bytes::BytesMut;
1414
use futures_util::{sink::SinkExt, stream::StreamExt, TryStreamExt};
1515
use http::{header::HOST, HeaderMap};
1616
use std::sync::Arc;
17+
use tokio::net::TcpStream;
1718
use tokio_tungstenite::{
1819
connect_async,
19-
tungstenite::{protocol::CloseFrame, Message as MsgTng},
20+
tungstenite::{self, protocol::CloseFrame, Message as MsgTng},
21+
MaybeTlsStream, WebSocketStream,
2022
};
2123
use tower_http::trace::TraceLayer;
2224

@@ -144,6 +146,121 @@ fn make_outbound_request(
144146
Ok(request)
145147
}
146148

149+
/// Attempts to create a websocket connection.
150+
/// If `insecure` is true, it will allow websocket connections over insecure TLS.
151+
/// Will fail if `insecure` is true and none of the `native-tls` or `rustls` features are enabled.
152+
async fn connect_webscoket(
153+
outbound_request: http::Request<()>,
154+
insecure: bool,
155+
) -> anyhow::Result<(
156+
WebSocketStream<MaybeTlsStream<TcpStream>>,
157+
tungstenite::handshake::client::Response,
158+
)> {
159+
if insecure {
160+
#[cfg(any(feature = "native-tls", feature = "rustls"))]
161+
{
162+
use tokio_tungstenite::connect_async_tls_with_config;
163+
connect_async_tls_with_config(outbound_request, None, false, make_insecure_connector())
164+
.await
165+
.map_err(|err| {
166+
anyhow::anyhow!("error establishing insecure WebSocket connection: {err}")
167+
})
168+
}
169+
#[cfg(not(any(feature = "native-tls", feature = "rustls")))]
170+
{
171+
Err(anyhow::anyhow!(
172+
"Insecure WebSockets requires the `native-tls` or `rustls` to be feature enabled."
173+
))
174+
}
175+
} else {
176+
connect_async(outbound_request)
177+
.await
178+
.map_err(|err| anyhow::anyhow!("error establishing secure WebSocket connection: {err}"))
179+
}
180+
}
181+
182+
/// Create a connector which does not verify TLS certificates.
183+
/// Defaults to a `rustls` connector if both the `rustls` and `native-tls` features are enabled.
184+
#[cfg(any(feature = "native-tls", feature = "rustls"))]
185+
fn make_insecure_connector() -> Option<tokio_tungstenite::Connector> {
186+
#[cfg(feature = "rustls")]
187+
{
188+
use rustls::{
189+
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
190+
pki_types::{CertificateDer, ServerName, UnixTime},
191+
ClientConfig, DigitallySignedStruct, SignatureScheme,
192+
};
193+
194+
/// A `rustls` certificate verifier that allows insecure certificates.
195+
#[derive(Debug)]
196+
struct NoCertVerification;
197+
198+
impl ServerCertVerifier for NoCertVerification {
199+
fn verify_server_cert(
200+
&self,
201+
_: &CertificateDer<'_>,
202+
_: &[CertificateDer<'_>],
203+
_: &ServerName<'_>,
204+
_: &[u8],
205+
_: UnixTime,
206+
) -> Result<ServerCertVerified, rustls::Error> {
207+
Ok(ServerCertVerified::assertion())
208+
}
209+
210+
fn verify_tls12_signature(
211+
&self,
212+
_: &[u8],
213+
_: &CertificateDer<'_>,
214+
_: &DigitallySignedStruct,
215+
) -> Result<HandshakeSignatureValid, rustls::Error> {
216+
Ok(HandshakeSignatureValid::assertion())
217+
}
218+
219+
fn verify_tls13_signature(
220+
&self,
221+
_: &[u8],
222+
_: &CertificateDer<'_>,
223+
_: &DigitallySignedStruct,
224+
) -> Result<HandshakeSignatureValid, rustls::Error> {
225+
Ok(HandshakeSignatureValid::assertion())
226+
}
227+
228+
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
229+
vec![
230+
SignatureScheme::ED25519,
231+
SignatureScheme::ECDSA_NISTP256_SHA256,
232+
SignatureScheme::ECDSA_NISTP384_SHA384,
233+
SignatureScheme::ECDSA_NISTP521_SHA512,
234+
SignatureScheme::RSA_PSS_SHA256,
235+
SignatureScheme::RSA_PSS_SHA384,
236+
SignatureScheme::RSA_PSS_SHA512,
237+
SignatureScheme::ED448,
238+
]
239+
}
240+
}
241+
242+
Some(tokio_tungstenite::Connector::Rustls(std::sync::Arc::new(
243+
ClientConfig::builder()
244+
.dangerous()
245+
.with_custom_certificate_verifier(Arc::new(NoCertVerification {}))
246+
.with_no_client_auth(),
247+
)))
248+
}
249+
#[cfg(all(feature = "native-tls", not(feature = "rustls")))]
250+
{
251+
match native_tls::TlsConnector::builder()
252+
.danger_accept_invalid_certs(true)
253+
.build()
254+
{
255+
Ok(connector) => Some(tokio_tungstenite::Connector::NativeTls(connector)),
256+
Err(err) => {
257+
tracing::error!(error = ?err, "error building native TLS connector");
258+
None
259+
}
260+
}
261+
}
262+
}
263+
147264
impl ProxyHandlerHttp {
148265
/// Construct a new instance.
149266
pub fn new(
@@ -243,6 +360,8 @@ pub struct ProxyHandlerWebSocket {
243360
rewrite: Option<String>,
244361
/// The headers to inject with the request
245362
request_headers: HeaderMap,
363+
/// Allow insecure TLS websocket connections.
364+
insecure: bool,
246365
}
247366

248367
impl ProxyHandlerWebSocket {
@@ -252,12 +371,14 @@ impl ProxyHandlerWebSocket {
252371
backend: Uri,
253372
headers: HeaderMap,
254373
rewrite: Option<String>,
374+
insecure: bool,
255375
) -> Arc<Self> {
256376
Arc::new(Self {
257377
proto,
258378
backend,
259379
rewrite,
260380
request_headers: headers,
381+
insecure,
261382
})
262383
}
263384

@@ -337,14 +458,15 @@ impl ProxyHandlerWebSocket {
337458
}
338459
};
339460

340-
// Establish WS connection to backend.
341-
let (backend, _res) = match connect_async(outbound_request).await {
461+
// Try to astablish a websocket connection to the backend and handle potential errors
462+
let (backend, _res) = match connect_webscoket(outbound_request, self.insecure).await {
342463
Ok(backend) => backend,
343464
Err(err) => {
344465
tracing::error!(error = ?err, "error establishing WebSocket connection to backend {:?} for proxy", &outbound_uri);
345466
return;
346467
}
347468
};
469+
348470
let (mut backend_sink, mut backend_stream) = backend.split();
349471
let (mut frontend_sink, mut frontend_stream) = ws.split();
350472

src/serve/proxy.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,24 @@ impl ProxyBuilder {
4545
.to_string();
4646

4747
if ws {
48+
let insecure = opts.insecure;
4849
let handler = ProxyHandlerWebSocket::new(
4950
proto,
5051
backend.clone(),
5152
request_headers.clone(),
5253
rewrite,
54+
insecure,
5355
);
5456
tracing::info!(
55-
"{}proxying websocket {} -> {}",
57+
"{}proxying websocket {} -> {} {}",
5658
SERVER,
5759
handler.path(),
58-
&backend
60+
&backend,
61+
if insecure {
62+
format!("; {DANGER}️ insecure TLS")
63+
} else {
64+
Default::default()
65+
}
5966
);
6067
self.router = handler.register(self.router);
6168
Ok(self)

0 commit comments

Comments
 (0)