diff --git a/Cargo.lock b/Cargo.lock index 249179819..80f88700a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1205,6 +1205,7 @@ dependencies = [ "dunce", "embed-resource", "etherparse", + "expect-test", "futures", "hostname 0.4.1", "http-body-util", @@ -1466,6 +1467,12 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + [[package]] name = "dlib" version = "0.5.2" @@ -1656,6 +1663,16 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "expect-test" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63af43ff4431e848fb47472a920f14fa71c24de13255a5692e93d4e90302acb0" +dependencies = [ + "dissimilar", + "once_cell", +] + [[package]] name = "fallible-iterator" version = "0.2.0" diff --git a/devolutions-gateway/Cargo.toml b/devolutions-gateway/Cargo.toml index c82285bbd..8836e1dbd 100644 --- a/devolutions-gateway/Cargo.toml +++ b/devolutions-gateway/Cargo.toml @@ -130,3 +130,4 @@ devolutions-gateway-generators = { path = "../crates/devolutions-gateway-generat http-body-util = "0.1" tracing-cov-mark = { path = "../crates/tracing-cov-mark" } tracing-subscriber = "0.3" +expect-test = "1.5" diff --git a/devolutions-gateway/src/jmux.rs b/devolutions-gateway/src/jmux.rs index 3854f7be2..969d143c4 100644 --- a/devolutions-gateway/src/jmux.rs +++ b/devolutions-gateway/src/jmux.rs @@ -6,7 +6,7 @@ use crate::token::{JmuxTokenClaims, RecordingPolicy}; use anyhow::Context as _; use devolutions_gateway_task::ChildTask; -use jmux_proxy::JmuxProxy; +use jmux_proxy::{FilteringRule, JmuxConfig, JmuxProxy}; use tap::prelude::*; use tokio::io::{AsyncRead, AsyncWrite}; use tokio::sync::Notify; @@ -18,8 +18,6 @@ pub async fn handle( sessions: SessionMessageSender, subscriber_tx: SubscriberSender, ) -> anyhow::Result<()> { - use jmux_proxy::{FilteringRule, JmuxConfig}; - match claims.jet_rec { RecordingPolicy::None | RecordingPolicy::Stream => (), RecordingPolicy::Proxy => anyhow::bail!("can't meet recording policy"), @@ -31,21 +29,7 @@ pub async fn handle( let main_destination_host = claims.hosts.first().clone(); - let config = JmuxConfig { - filtering: FilteringRule::Any( - claims - .hosts - .into_iter() - .map(|addr| match (addr.host(), addr.port()) { - ("*", 0) => FilteringRule::allow(), - ("*", port) => FilteringRule::port(port), - (host, 0) => FilteringRule::wildcard_host(host.to_owned()), - (host, port) => FilteringRule::wildcard_host(host.to_owned()).and(FilteringRule::port(port)), - }) - .collect(), - ), - }; - + let config = claims_to_jmux_config(&claims); debug!(?config, "JMUX config"); let session_id = claims.jet_aid; @@ -84,3 +68,21 @@ pub async fn handle( res } + +#[doc(hidden)] // Used in tests. +pub fn claims_to_jmux_config(claims: &JmuxTokenClaims) -> JmuxConfig { + JmuxConfig { + filtering: FilteringRule::Any( + claims + .hosts + .iter() + .map(|addr| match (addr.host(), addr.port()) { + ("*", 0) => FilteringRule::allow(), + ("*", port) => FilteringRule::port(port), + (host, 0) => FilteringRule::wildcard_host(host.to_owned()), + (host, port) => FilteringRule::wildcard_host(host.to_owned()).and(FilteringRule::port(port)), + }) + .collect(), + ), + } +} diff --git a/devolutions-gateway/tests/dvls_compatibility.rs b/devolutions-gateway/tests/dvls_compatibility.rs index 08d56dc24..cec9dc763 100644 --- a/devolutions-gateway/tests/dvls_compatibility.rs +++ b/devolutions-gateway/tests/dvls_compatibility.rs @@ -112,6 +112,19 @@ fn now() -> i64 { time::OffsetDateTime::now_utc().unix_timestamp() } +mod as_of_v2025_2_6_0 { + use super::*; + + #[rstest] + #[case::jmux( + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImN0eSI6IkpNVVgifQ.eyJkc3RfYWRkbCI6WyJodHRwczovL2xvY2FsaG9zdDo0NDMiLCJodHRwOi8vd3d3LmxvY2FsaG9zdDo4ODAwIiwiaHR0cHM6Ly93d3cubG9jYWxob3N0OjQ0MyJdLCJkc3RfaHN0IjoiaHR0cDovL2xvY2FsaG9zdDo4ODAwIiwiZXhwIjoxNzUzNjU4NDgxLCJpYXQiOjE3NTM2NTgxODEsImpldF9haWQiOiIyYzNjOGI4ZC0wZThlLTQwMGItYWVmMy1mM2U4ZjFhN2EzOTQiLCJqZXRfYXAiOiJodHRwIiwiamV0X2d3X2lkIjoiZGU0ZDMyODUtMjUzOS00NjhkLThlMmEtMTc1OWVjMDQyYTM3IiwianRpIjoiYTk3NWI4OGMtOGU5My00N2JkLThkNDQtY2QwZGI2YzViNGNmIiwibmJmIjoxNzUzNjU4MTgxfQ.g9yKXuH-A_oRlPaS6xcKddnzQZZ4XTnSFd_pzN-pPzbLAuxOpNyzkhOfSUEkday0Uh3Z2TQ2KxAnkG7zjvO6dKecv4xUamiU8gItuzhgHTzQQBqNsiu-t4rHvG1Ad83cXDzcuGMXiYHAxq4zqPrUN2atzkzXlF6eoG3mNQw8kNGrTCWWyAZgU1_Sjwuyd-MRATNdZt0cy3Awj6dMPCdGR3_oBTnLhPyqIAzfh_56bpUVlayy8u3HBFZo5Wj8uX8dbgN0izna-idvR85rWKqyBLpZUgeEctrk4UnM6Cz9kwCIxtQI5jTmi-U7UIGfggcbmyRWkoWvxr2tnBIPxSZDkA" + )] + fn samples(#[case] sample: &str) { + #[allow(deprecated)] + devolutions_gateway::token::unsafe_debug::dangerous_validate_token(sample, None).unwrap(); + } +} + mod as_of_v2022_3_0_0 { use super::*; use proptest::collection::vec; diff --git a/devolutions-gateway/tests/jmux.rs b/devolutions-gateway/tests/jmux.rs new file mode 100644 index 000000000..634f94d19 --- /dev/null +++ b/devolutions-gateway/tests/jmux.rs @@ -0,0 +1,127 @@ +#![allow(unused_crate_dependencies)] +#![allow(clippy::unwrap_used)] + +use expect_test::{Expect, expect}; + +fn check(sample: &str, expected: Expect) { + #[allow(deprecated)] + let claims = devolutions_gateway::token::unsafe_debug::dangerous_validate_token(sample, None).unwrap(); + + let devolutions_gateway::token::AccessTokenClaims::Jmux(claims) = claims else { + panic!("unexpected token cty") + }; + + let actual = devolutions_gateway::jmux::claims_to_jmux_config(&claims); + + expected.assert_debug_eq(&actual); +} + +#[test] +fn specific_ports() { + check( + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImN0eSI6IkpNVVgifQ.eyJkc3RfYWRkbCI6WyJodHRwczovL2xvY2FsaG9zdDo0NDMiLCJodHRwOi8vd3d3LmxvY2FsaG9zdDo4ODAwIiwiaHR0cHM6Ly93d3cubG9jYWxob3N0OjQ0MyJdLCJkc3RfaHN0IjoiaHR0cDovL2xvY2FsaG9zdDo4ODAwIiwiZXhwIjoxNzUzNjU4NDgxLCJpYXQiOjE3NTM2NTgxODEsImpldF9haWQiOiIyYzNjOGI4ZC0wZThlLTQwMGItYWVmMy1mM2U4ZjFhN2EzOTQiLCJqZXRfYXAiOiJodHRwIiwiamV0X2d3X2lkIjoiZGU0ZDMyODUtMjUzOS00NjhkLThlMmEtMTc1OWVjMDQyYTM3IiwianRpIjoiYTk3NWI4OGMtOGU5My00N2JkLThkNDQtY2QwZGI2YzViNGNmIiwibmJmIjoxNzUzNjU4MTgxfQ.g9yKXuH-A_oRlPaS6xcKddnzQZZ4XTnSFd_pzN-pPzbLAuxOpNyzkhOfSUEkday0Uh3Z2TQ2KxAnkG7zjvO6dKecv4xUamiU8gItuzhgHTzQQBqNsiu-t4rHvG1Ad83cXDzcuGMXiYHAxq4zqPrUN2atzkzXlF6eoG3mNQw8kNGrTCWWyAZgU1_Sjwuyd-MRATNdZt0cy3Awj6dMPCdGR3_oBTnLhPyqIAzfh_56bpUVlayy8u3HBFZo5Wj8uX8dbgN0izna-idvR85rWKqyBLpZUgeEctrk4UnM6Cz9kwCIxtQI5jTmi-U7UIGfggcbmyRWkoWvxr2tnBIPxSZDkA", + expect![[r#" + JmuxConfig { + filtering: Any( + [ + All( + [ + WildcardHost( + "localhost", + ), + Port( + 8800, + ), + ], + ), + All( + [ + WildcardHost( + "localhost", + ), + Port( + 443, + ), + ], + ), + All( + [ + WildcardHost( + "www.localhost", + ), + Port( + 8800, + ), + ], + ), + All( + [ + WildcardHost( + "www.localhost", + ), + Port( + 443, + ), + ], + ), + ], + ), + } + "#]], + ); +} + +#[test] +fn allow_any_port() { + check( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImN0eSI6IkpNVVgifQ.eyJkc3RfYWRkbCI6WyJodHRwOi8vd3d3LmRldm9sdXRpb25zLm5ldDowIl0sImRzdF9oc3QiOiJodHRwOi8vZGV2b2x1dGlvbnMubmV0OjAiLCJleHAiOjE3NTM2NTg0ODEsImlhdCI6MTc1MzY1ODE4MSwiamV0X2FpZCI6IjJjM2M4YjhkLTBlOGUtNDAwYi1hZWYzLWYzZThmMWE3YTM5NCIsImpldF9hcCI6Imh0dHAiLCJqZXRfZ3dfaWQiOiJkZTRkMzI4NS0yNTM5LTQ2OGQtOGUyYS0xNzU5ZWMwNDJhMzciLCJqdGkiOiJhOTc1Yjg4Yy04ZTkzLTQ3YmQtOGQ0NC1jZDBkYjZjNWI0Y2YiLCJuYmYiOjE3NTM2NTgxODF9.SihT5LKgKDKOAVsrpTQ01jC8KkrUuNbU19-rGn8YgV4", + expect![[r#" + JmuxConfig { + filtering: Any( + [ + WildcardHost( + "devolutions.net", + ), + WildcardHost( + "www.devolutions.net", + ), + ], + ), + } + "#]], + ); +} + +#[test] +fn allow_any_host() { + check( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImN0eSI6IkpNVVgifQ.eyJkc3RfaHN0IjoiaHR0cDovLyo6ODAiLCJleHAiOjE3NTM2NTg0ODEsImlhdCI6MTc1MzY1ODE4MSwiamV0X2FpZCI6IjJjM2M4YjhkLTBlOGUtNDAwYi1hZWYzLWYzZThmMWE3YTM5NCIsImpldF9hcCI6Imh0dHAiLCJqZXRfZ3dfaWQiOiJkZTRkMzI4NS0yNTM5LTQ2OGQtOGUyYS0xNzU5ZWMwNDJhMzciLCJqdGkiOiJhOTc1Yjg4Yy04ZTkzLTQ3YmQtOGQ0NC1jZDBkYjZjNWI0Y2YiLCJuYmYiOjE3NTM2NTgxODF9.JqSSKp2w2-dgUn_S3uizvWhS2RUnOvrcZm7YebTjPuc", + expect![[r#" + JmuxConfig { + filtering: Any( + [ + Port( + 80, + ), + ], + ), + } + "#]], + ); +} + +#[test] +fn allow_any_host_and_any_port() { + check( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImN0eSI6IkpNVVgifQ.eyJkc3RfaHN0IjoiaHR0cDovLyo6MCIsImV4cCI6MTc1MzY1ODQ4MSwiaWF0IjoxNzUzNjU4MTgxLCJqZXRfYWlkIjoiMmMzYzhiOGQtMGU4ZS00MDBiLWFlZjMtZjNlOGYxYTdhMzk0IiwiamV0X2FwIjoiaHR0cCIsImpldF9nd19pZCI6ImRlNGQzMjg1LTI1MzktNDY4ZC04ZTJhLTE3NTllYzA0MmEzNyIsImp0aSI6ImE5NzViODhjLThlOTMtNDdiZC04ZDQ0LWNkMGRiNmM1YjRjZiIsIm5iZiI6MTc1MzY1ODE4MX0.tDgAH8uoQSUOJHYnpDoK0Ox2nbPV6alwPjIbMYAullE", + expect![[r#" + JmuxConfig { + filtering: Any( + [ + Allow, + ], + ), + } + "#]], + ); +}